Merge branch 'hierachial_rewrite_improved_meshing' into mc_1215
This commit is contained in:
259
src/main/java/me/cortex/voxy/client/GPUSelectorWindows2.java
Normal file
259
src/main/java/me/cortex/voxy/client/GPUSelectorWindows2.java
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
package me.cortex.voxy.client;
|
||||||
|
|
||||||
|
import me.cortex.voxy.common.Logger;
|
||||||
|
import org.lwjgl.system.JNI;
|
||||||
|
import org.lwjgl.system.MemoryStack;
|
||||||
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
import org.lwjgl.system.windows.GDI32;
|
||||||
|
import org.lwjgl.system.windows.Kernel32;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static org.lwjgl.system.APIUtil.apiGetFunctionAddressOptional;
|
||||||
|
|
||||||
|
public class GPUSelectorWindows2 {
|
||||||
|
private static final long D3DKMTSetProperties = apiGetFunctionAddressOptional(GDI32.getLibrary(), "D3DKMTSetProperties");
|
||||||
|
private static final long D3DKMTEnumAdapters2 = apiGetFunctionAddressOptional(GDI32.getLibrary(), "D3DKMTEnumAdapters2");
|
||||||
|
private static final long D3DKMTCloseAdapter = apiGetFunctionAddressOptional(GDI32.getLibrary(), "D3DKMTCloseAdapter");
|
||||||
|
private static final long D3DKMTQueryAdapterInfo = apiGetFunctionAddressOptional(GDI32.getLibrary(), "D3DKMTQueryAdapterInfo");
|
||||||
|
|
||||||
|
private static int setPCIProperties(int type, int vendor, int device, int subSys) {
|
||||||
|
try (var stack = MemoryStack.stackPush()) {
|
||||||
|
var buff = stack.calloc(0x10).order(ByteOrder.nativeOrder());
|
||||||
|
buff.putInt(0, vendor);
|
||||||
|
buff.putInt(4, device);
|
||||||
|
buff.putInt(8, subSys);
|
||||||
|
buff.putInt(12, 0);
|
||||||
|
return setProperties(type, buff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int setProperties(int type, ByteBuffer payload) {
|
||||||
|
if (D3DKMTSetProperties == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
try (var stack = MemoryStack.stackPush()) {
|
||||||
|
var buff = stack.calloc(0x18).order(ByteOrder.nativeOrder());
|
||||||
|
buff.putInt(0, type);
|
||||||
|
buff.putInt(4, payload.remaining());
|
||||||
|
buff.putLong(16, MemoryUtil.memAddress(payload));
|
||||||
|
return JNI.callPI(MemoryUtil.memAddress(buff), D3DKMTSetProperties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int query(int handle, int type, ByteBuffer payload) {
|
||||||
|
if (D3DKMTQueryAdapterInfo == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
try (var stack = MemoryStack.stackPush()) {
|
||||||
|
var buff = stack.calloc(0x14).order(ByteOrder.nativeOrder());
|
||||||
|
buff.putInt(0, handle);
|
||||||
|
buff.putInt(4, type);
|
||||||
|
buff.putLong(8, MemoryUtil.memAddress(payload));
|
||||||
|
buff.putInt(16, payload.remaining());
|
||||||
|
return JNI.callPI(MemoryUtil.memAddress(buff), D3DKMTQueryAdapterInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int closeHandle(int handle) {
|
||||||
|
if (D3DKMTCloseAdapter == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
try (var stack = MemoryStack.stackPush()) {
|
||||||
|
var buff = stack.calloc(0x4).order(ByteOrder.nativeOrder());
|
||||||
|
buff.putInt(0, handle);
|
||||||
|
return JNI.callPI(MemoryUtil.memAddress(buff), D3DKMTCloseAdapter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int queryAdapterType(int handle, int[] out) {
|
||||||
|
int ret;
|
||||||
|
try (var stack = MemoryStack.stackPush()) {
|
||||||
|
var buff = stack.calloc(0x4).order(ByteOrder.nativeOrder());
|
||||||
|
//KMTQAITYPE_ADAPTERTYPE
|
||||||
|
if ((ret=query(handle, 15, buff))<0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
out[0] = buff.getInt(0);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record AdapterInfo(int type, long luid, int vendor, int device, int subSystem) {
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String LUID = Integer.toHexString((int) ((luid>>>32)&0xFFFFFFFFL))+"-"+Integer.toHexString((int) (luid&0xFFFFFFFFL));
|
||||||
|
return "{type=%s, luid=%s, vendor=%s, device=%s, subSys=%s}".formatted(Integer.toString(type),LUID, Integer.toHexString(vendor), Integer.toHexString(device), Integer.toHexString(subSystem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private record PCIDeviceId(int vendor, int device, int subVendor, int subSystem, int revision, int busType) {}
|
||||||
|
private static int queryPCIAddress(int handle, int index, PCIDeviceId[] deviceOut) {
|
||||||
|
int ret = 0;
|
||||||
|
try (var stack = MemoryStack.stackPush()) {
|
||||||
|
var buff = stack.calloc(4*7).order(ByteOrder.nativeOrder());
|
||||||
|
buff.putInt(0, index);
|
||||||
|
//KMTQAITYPE_PHYSICALADAPTERDEVICEIDS
|
||||||
|
if ((ret = query(handle, 31, buff)) < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
deviceOut[0] = new PCIDeviceId(buff.getInt(4),buff.getInt(8),buff.getInt(12),buff.getInt(16),buff.getInt(20),buff.getInt(24));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int enumAdapters(Consumer<AdapterInfo> consumer) {
|
||||||
|
if (D3DKMTEnumAdapters2 == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = 0;
|
||||||
|
try (var stack = MemoryStack.stackPush()) {
|
||||||
|
var query = stack.calloc(0x10).order(ByteOrder.nativeOrder());
|
||||||
|
if ((ret = JNI.callPI(MemoryUtil.memAddress(query), D3DKMTEnumAdapters2)) < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
int adapterCount = query.getInt(0);
|
||||||
|
var adapterList = stack.calloc(0x14 * adapterCount).order(ByteOrder.nativeOrder());
|
||||||
|
query.putLong(8, MemoryUtil.memAddress(adapterList));
|
||||||
|
if ((ret = JNI.callPI(MemoryUtil.memAddress(query), D3DKMTEnumAdapters2)) < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
adapterCount = query.getInt(0);
|
||||||
|
for (int adapterIndex = 0; adapterIndex < adapterCount; adapterIndex++) {
|
||||||
|
var adapter = adapterList.slice(adapterIndex*0x14, 0x14).order(ByteOrder.nativeOrder());
|
||||||
|
//We only care about these 2
|
||||||
|
int handle = adapter.getInt(0);
|
||||||
|
long luid = adapter.getLong(4);
|
||||||
|
|
||||||
|
int[] type = new int[1];
|
||||||
|
if ((ret = queryAdapterType(handle, type))<0) {
|
||||||
|
Logger.error("Query type error: " + ret);
|
||||||
|
//We errored
|
||||||
|
if (closeHandle(handle) < 0) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
PCIDeviceId[] out = new PCIDeviceId[1];
|
||||||
|
//Get the root adapter device
|
||||||
|
if ((ret = queryPCIAddress(handle, 0, out)) < 0) {
|
||||||
|
Logger.error("Query pci error: " + ret);
|
||||||
|
//We errored
|
||||||
|
if (closeHandle(handle) < 0) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int subSys = (out[0].subSystem<<16)|out[0].subVendor;//It seems the pci subsystem address is a joined integer
|
||||||
|
consumer.accept(new AdapterInfo(type[0], luid, out[0].vendor, out[0].device, subSys));
|
||||||
|
|
||||||
|
if (closeHandle(handle) < 0) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//=======================================================================================
|
||||||
|
|
||||||
|
private static final int[] HDC_STUB = { 0x48, 0x83, 0xC1, 0x0C, 0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x48, 0x89, 0x01, 0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2F, 0x51, 0xFF, 0xD0, 0x59, 0x8B, 0x41, 0x08, 0x89, 0x41, 0xFC, 0x48, 0x31, 0xC0, 0x89, 0x41, 0x08, 0xC3 };
|
||||||
|
private static void insertLong(long l, byte[] out, int offset) {
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
out[i+offset] = (byte)(l & 0xFF);
|
||||||
|
l >>= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static byte[] createFinishedHDCStub(long luid, long D3DKMTOpenAdapterFromLuid) {
|
||||||
|
byte[] stub = new byte[HDC_STUB.length];
|
||||||
|
for (int i = 0; i < stub.length; i++) {
|
||||||
|
stub[i] = (byte) HDC_STUB[i];
|
||||||
|
}
|
||||||
|
insertLong(luid, stub, 6);
|
||||||
|
insertLong(D3DKMTOpenAdapterFromLuid, stub, 19);
|
||||||
|
return stub;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final long D3DKMTOpenAdapterFromLuid = apiGetFunctionAddressOptional(GDI32.getLibrary(), "D3DKMTOpenAdapterFromLuid");
|
||||||
|
private static final long D3DKMTOpenAdapterFromHdc = apiGetFunctionAddressOptional(GDI32.getLibrary(), "D3DKMTOpenAdapterFromHdc");
|
||||||
|
private static final long VirtualProtect = apiGetFunctionAddressOptional(Kernel32.getLibrary(), "VirtualProtect");
|
||||||
|
|
||||||
|
private static void VirtualProtect(long addr, long size) {
|
||||||
|
try (var stack = MemoryStack.stackPush()) {
|
||||||
|
var oldProtection = stack.calloc(4);
|
||||||
|
JNI.callPPPPI(addr, size, 0x40/*PAGE_EXECUTE_READWRITE*/, MemoryUtil.memAddress(oldProtection), VirtualProtect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static void memcpy(long ptr, byte[] data) {
|
||||||
|
for (int i = 0; i < data.length; i++) {
|
||||||
|
MemoryUtil.memPutByte(ptr + i, data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void installHDCStub(long adapterLuid) {
|
||||||
|
if (D3DKMTOpenAdapterFromHdc == 0 || VirtualProtect == 0 || D3DKMTOpenAdapterFromLuid == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Logger.info("AdapterLuid callback at: " + Long.toHexString(D3DKMTOpenAdapterFromLuid));
|
||||||
|
var stub = createFinishedHDCStub(adapterLuid, D3DKMTOpenAdapterFromLuid);
|
||||||
|
|
||||||
|
VirtualProtect(D3DKMTOpenAdapterFromHdc, stub.length);
|
||||||
|
memcpy(D3DKMTOpenAdapterFromHdc, stub);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void installQueryStub() {
|
||||||
|
if (D3DKMTQueryAdapterInfo == 0 || VirtualProtect == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VirtualProtect(D3DKMTQueryAdapterInfo, 0x10);
|
||||||
|
|
||||||
|
var fixAndRetStub = new byte[] { 0x48, (byte) 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, 0x48, (byte) 0x8B, 0x0D, 0x15, 0x00, 0x00, 0x00, 0x48, (byte) 0x89, 0x08, 0x48, (byte) 0x8B, 0x0D, 0x13, 0x00, 0x00, 0x00, 0x48, (byte) 0x89, 0x48, 0x08, 0x48, 0x31, (byte) 0xC0, 0x48, (byte) 0xF7, (byte) 0xD0, (byte) 0xC3, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0};
|
||||||
|
long stubPtr = MemoryUtil.nmemAlloc(fixAndRetStub.length);
|
||||||
|
VirtualProtect(stubPtr, fixAndRetStub.length);
|
||||||
|
|
||||||
|
|
||||||
|
insertLong(D3DKMTQueryAdapterInfo, fixAndRetStub, 2);
|
||||||
|
insertLong((MemoryUtil.memGetLong(D3DKMTQueryAdapterInfo)), fixAndRetStub, 38);
|
||||||
|
insertLong((MemoryUtil.memGetLong(D3DKMTQueryAdapterInfo+8)), fixAndRetStub, 38+8);
|
||||||
|
|
||||||
|
memcpy(stubPtr, fixAndRetStub);
|
||||||
|
Logger.info("Restore stub at: " + Long.toHexString(stubPtr));
|
||||||
|
|
||||||
|
|
||||||
|
var jmpStub = new byte[]{ 0x48, (byte) 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xFF, (byte) 0xE0};
|
||||||
|
insertLong(stubPtr, jmpStub, 2);
|
||||||
|
|
||||||
|
memcpy(D3DKMTQueryAdapterInfo, jmpStub);
|
||||||
|
Logger.info("D3DKMTQueryAdapterInfo at: " + Long.toHexString(D3DKMTQueryAdapterInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void doSelector(int index) {
|
||||||
|
List<AdapterInfo> adapters = new ArrayList<>();
|
||||||
|
//Must support rendering and must not be software renderer
|
||||||
|
if (enumAdapters(adapter->{if ((adapter.type&5)==1) adapters.add(adapter); }) < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (var adapter : adapters) {
|
||||||
|
Logger.error(adapter.toString());
|
||||||
|
}
|
||||||
|
var adapter = adapters.get(index);
|
||||||
|
|
||||||
|
installHDCStub(adapter.luid);
|
||||||
|
installQueryStub();
|
||||||
|
|
||||||
|
setPCIProperties(1/*USER*/, adapter.vendor, adapter.device, adapter.subSystem);
|
||||||
|
setPCIProperties(2/*USER GLOBAL*/, adapter.vendor, adapter.device, adapter.subSystem);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,16 @@ package me.cortex.voxy.client;
|
|||||||
|
|
||||||
import me.cortex.voxy.client.config.VoxyConfig;
|
import me.cortex.voxy.client.config.VoxyConfig;
|
||||||
import me.cortex.voxy.client.saver.ContextSelectionSystem;
|
import me.cortex.voxy.client.saver.ContextSelectionSystem;
|
||||||
|
import me.cortex.voxy.common.util.Pair;
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
import me.cortex.voxy.commonImpl.IVoxyWorld;
|
import me.cortex.voxy.commonImpl.IVoxyWorld;
|
||||||
import me.cortex.voxy.commonImpl.ImportManager;
|
import me.cortex.voxy.commonImpl.ImportManager;
|
||||||
import me.cortex.voxy.commonImpl.VoxyInstance;
|
import me.cortex.voxy.commonImpl.VoxyInstance;
|
||||||
import net.minecraft.client.world.ClientWorld;
|
import net.minecraft.client.world.ClientWorld;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||||
|
|
||||||
public class VoxyClientInstance extends VoxyInstance {
|
public class VoxyClientInstance extends VoxyInstance {
|
||||||
private static final ContextSelectionSystem SELECTOR = new ContextSelectionSystem();
|
private static final ContextSelectionSystem SELECTOR = new ContextSelectionSystem();
|
||||||
|
|
||||||
@@ -25,6 +29,7 @@ public class VoxyClientInstance extends VoxyInstance {
|
|||||||
if (vworld == null) {
|
if (vworld == null) {
|
||||||
vworld = this.createWorld(SELECTOR.getBestSelectionOrCreate(world).createSectionStorageBackend());
|
vworld = this.createWorld(SELECTOR.getBestSelectionOrCreate(world).createSectionStorageBackend());
|
||||||
((IVoxyWorld)world).setWorldEngine(vworld);
|
((IVoxyWorld)world).setWorldEngine(vworld);
|
||||||
|
//testDbPerformance2(vworld);
|
||||||
} else {
|
} else {
|
||||||
if (!this.activeWorlds.contains(vworld)) {
|
if (!this.activeWorlds.contains(vworld)) {
|
||||||
throw new IllegalStateException("World referenced does not exist in instance");
|
throw new IllegalStateException("World referenced does not exist in instance");
|
||||||
@@ -32,4 +37,64 @@ public class VoxyClientInstance extends VoxyInstance {
|
|||||||
}
|
}
|
||||||
return vworld;
|
return vworld;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static void testDbPerformance(WorldEngine engine) {
|
||||||
|
Random r = new Random(123456);
|
||||||
|
r.nextLong();
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
int c = 0;
|
||||||
|
long tA = 0;
|
||||||
|
long tR = 0;
|
||||||
|
for (int i = 0; i < 1_000_000; i++) {
|
||||||
|
if (i == 20_000) {
|
||||||
|
c = 0;
|
||||||
|
start = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
c++;
|
||||||
|
int x = (r.nextInt(256*2+2)-256);//-32
|
||||||
|
int z = (r.nextInt(256*2+2)-256);//-32
|
||||||
|
int y = r.nextInt(2)-1;
|
||||||
|
int lvl = 0;//r.nextInt(5);
|
||||||
|
long t = System.nanoTime();
|
||||||
|
var sec = engine.acquire(WorldEngine.getWorldSectionId(lvl, x>>lvl, y>>lvl, z>>lvl));
|
||||||
|
tA += System.nanoTime()-t;
|
||||||
|
t = System.nanoTime();
|
||||||
|
sec.release();
|
||||||
|
tR += System.nanoTime()-t;
|
||||||
|
}
|
||||||
|
long delta = System.currentTimeMillis() - start;
|
||||||
|
System.out.println("Total "+delta+"ms " + ((double)delta/c) + "ms average tA: " + tA + " tR: " + tR);
|
||||||
|
}
|
||||||
|
private static void testDbPerformance2(WorldEngine engine) {
|
||||||
|
Random r = new Random(123456);
|
||||||
|
r.nextLong();
|
||||||
|
ConcurrentLinkedDeque<Long> queue = new ConcurrentLinkedDeque<>();
|
||||||
|
var ser = engine.instanceIn.getThreadPool().createServiceNoCleanup("aa", 1, ()-> () ->{
|
||||||
|
var sec = engine.acquire(queue.poll());
|
||||||
|
sec.release();
|
||||||
|
});
|
||||||
|
int priming = 1_000_000;
|
||||||
|
for (int i = 0; i < 2_000_000+priming; i++) {
|
||||||
|
int x = (r.nextInt(256*2+2)-256)>>2;//-32
|
||||||
|
int z = (r.nextInt(256*2+2)-256)>>2;//-32
|
||||||
|
int y = r.nextInt(2)-1;
|
||||||
|
int lvl = 0;//r.nextInt(5);
|
||||||
|
queue.add(WorldEngine.getWorldSectionId(lvl, x>>lvl, y>>lvl, z>>lvl));
|
||||||
|
}
|
||||||
|
for (int i = 0; i < priming; i++) {
|
||||||
|
ser.execute();
|
||||||
|
}
|
||||||
|
ser.blockTillEmpty();
|
||||||
|
int c = queue.size();
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
for (int i = 0; i < c; i++) {
|
||||||
|
ser.execute();
|
||||||
|
}
|
||||||
|
ser.blockTillEmpty();
|
||||||
|
long delta = System.currentTimeMillis() - start;
|
||||||
|
ser.shutdown();
|
||||||
|
System.out.println("Total "+delta+"ms " + ((double)delta/c) + "ms average total, avg wrt threads: " + (((double)delta/c)*engine.instanceIn.getThreadPool().getThreadCount()) + "ms");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,12 +63,8 @@ public class VoxyCommands {
|
|||||||
((IGetVoxyRenderSystem)wr).shutdownRenderer();
|
((IGetVoxyRenderSystem)wr).shutdownRenderer();
|
||||||
}
|
}
|
||||||
var w = ((IVoxyWorld)MinecraftClient.getInstance().world);
|
var w = ((IVoxyWorld)MinecraftClient.getInstance().world);
|
||||||
if (w != null) {
|
if (w != null) w.shutdownEngine();
|
||||||
if (w.getWorldEngine() != null) {
|
|
||||||
instance.stopWorld(w.getWorldEngine());
|
|
||||||
}
|
|
||||||
w.setWorldEngine(null);
|
|
||||||
}
|
|
||||||
VoxyCommon.shutdownInstance();
|
VoxyCommon.shutdownInstance();
|
||||||
VoxyCommon.createInstance();
|
VoxyCommon.createInstance();
|
||||||
if (wr!=null) {
|
if (wr!=null) {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package me.cortex.voxy.client.config;
|
|||||||
import com.google.gson.FieldNamingPolicy;
|
import com.google.gson.FieldNamingPolicy;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import me.cortex.voxy.client.saver.ContextSelectionSystem;
|
|
||||||
import me.cortex.voxy.common.Logger;
|
import me.cortex.voxy.common.Logger;
|
||||||
import net.fabricmc.loader.api.FabricLoader;
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
|
||||||
@@ -24,7 +23,7 @@ public class VoxyConfig {
|
|||||||
public boolean enabled = true;
|
public boolean enabled = true;
|
||||||
public boolean enableRendering = true;
|
public boolean enableRendering = true;
|
||||||
public boolean ingestEnabled = true;
|
public boolean ingestEnabled = true;
|
||||||
//public int renderDistance = 128;
|
public int sectionRenderDistance = 16;
|
||||||
public int serviceThreads = Math.max(Runtime.getRuntime().availableProcessors()/2, 1);
|
public int serviceThreads = Math.max(Runtime.getRuntime().availableProcessors()/2, 1);
|
||||||
public float subDivisionSize = 128;
|
public float subDivisionSize = 128;
|
||||||
public int secondaryLruCacheSize = 1024;
|
public int secondaryLruCacheSize = 1024;
|
||||||
|
|||||||
@@ -46,13 +46,7 @@ public class VoxyConfigScreenFactory implements ModMenuApi {
|
|||||||
}
|
}
|
||||||
//Shutdown world
|
//Shutdown world
|
||||||
if (world != null && ON_SAVE_RELOAD_ALL) {
|
if (world != null && ON_SAVE_RELOAD_ALL) {
|
||||||
//This is a hack inserted for the client world thing
|
world.shutdownEngine();
|
||||||
//TODO: FIXME: MAKE BETTER
|
|
||||||
var engine = world.getWorldEngine();
|
|
||||||
if (engine != null) {
|
|
||||||
VoxyCommon.getInstance().stopWorld(engine);
|
|
||||||
}
|
|
||||||
world.setWorldEngine(null);
|
|
||||||
}
|
}
|
||||||
//Shutdown instance
|
//Shutdown instance
|
||||||
if (ON_SAVE_RELOAD_ALL) {
|
if (ON_SAVE_RELOAD_ALL) {
|
||||||
@@ -112,6 +106,20 @@ public class VoxyConfigScreenFactory implements ModMenuApi {
|
|||||||
.setDefaultValue((int) DEFAULT.subDivisionSize)
|
.setDefaultValue((int) DEFAULT.subDivisionSize)
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
|
category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.general.renderDistance"), config.sectionRenderDistance, 2, 64)
|
||||||
|
.setTooltip(Text.translatable("voxy.config.general.renderDistance.tooltip"))
|
||||||
|
.setSaveConsumer(val -> {
|
||||||
|
if (config.sectionRenderDistance != val) {
|
||||||
|
config.sectionRenderDistance = val;
|
||||||
|
var wrenderer = ((IGetVoxyRenderSystem) (MinecraftClient.getInstance().worldRenderer));
|
||||||
|
if (wrenderer != null && wrenderer.getVoxyRenderSystem() != null) {
|
||||||
|
wrenderer.getVoxyRenderSystem().setRenderDistance(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setDefaultValue(DEFAULT.sectionRenderDistance)
|
||||||
|
.build());
|
||||||
|
|
||||||
//category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.general.lruCacheSize"), config.secondaryLruCacheSize, 16, 1<<13)
|
//category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.general.lruCacheSize"), config.secondaryLruCacheSize, 16, 1<<13)
|
||||||
// .setTooltip(Text.translatable("voxy.config.general.lruCacheSize.tooltip"))
|
// .setTooltip(Text.translatable("voxy.config.general.lruCacheSize.tooltip"))
|
||||||
// .setSaveConsumer(val ->{if (config.secondaryLruCacheSize != val) reload(); config.secondaryLruCacheSize = val;})
|
// .setSaveConsumer(val ->{if (config.secondaryLruCacheSize != val) reload(); config.secondaryLruCacheSize = val;})
|
||||||
|
|||||||
@@ -1,45 +1,18 @@
|
|||||||
package me.cortex.voxy.client.core;
|
package me.cortex.voxy.client.core;
|
||||||
|
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
|
||||||
import me.cortex.voxy.client.config.VoxyConfig;
|
import me.cortex.voxy.client.config.VoxyConfig;
|
||||||
import me.cortex.voxy.client.core.gl.Capabilities;
|
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
|
||||||
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
|
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
|
||||||
import me.cortex.voxy.client.core.rendering.*;
|
import me.cortex.voxy.client.core.rendering.building.RenderDataFactory45;
|
||||||
import me.cortex.voxy.client.core.rendering.building.RenderDataFactory4;
|
|
||||||
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
|
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
|
||||||
import me.cortex.voxy.client.core.rendering.post.PostProcessing;
|
|
||||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
|
||||||
import me.cortex.voxy.client.core.util.IrisUtil;
|
|
||||||
import me.cortex.voxy.client.saver.ContextSelectionSystem;
|
import me.cortex.voxy.client.saver.ContextSelectionSystem;
|
||||||
import me.cortex.voxy.client.taskbar.Taskbar;
|
|
||||||
import me.cortex.voxy.common.Logger;
|
import me.cortex.voxy.common.Logger;
|
||||||
import me.cortex.voxy.common.util.MemoryBuffer;
|
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
import me.cortex.voxy.commonImpl.importers.WorldImporter;
|
|
||||||
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
||||||
import me.cortex.voxy.common.world.WorldSection;
|
import me.cortex.voxy.common.world.WorldSection;
|
||||||
import me.cortex.voxy.common.world.other.Mapper;
|
import me.cortex.voxy.common.world.other.Mapper;
|
||||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
|
||||||
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.gui.hud.ClientBossBar;
|
|
||||||
import net.minecraft.client.render.Camera;
|
|
||||||
import net.minecraft.client.render.Frustum;
|
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
|
||||||
import net.minecraft.entity.boss.BossBar;
|
|
||||||
import net.minecraft.text.Text;
|
|
||||||
import net.minecraft.util.math.MathHelper;
|
|
||||||
import net.minecraft.world.World;
|
|
||||||
import net.minecraft.world.chunk.WorldChunk;
|
|
||||||
import org.joml.Matrix4f;
|
|
||||||
import org.lwjgl.opengl.GL11;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
import static org.lwjgl.opengl.GL30C.*;
|
import static org.lwjgl.opengl.GL30C.*;
|
||||||
|
|
||||||
@@ -162,147 +135,5 @@ public class VoxelCore {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void testMeshingPerformance() {
|
|
||||||
var modelService = new ModelBakerySubsystem(this.world.getMapper());
|
|
||||||
var factory = new RenderDataFactory4(this.world, modelService.factory, false);
|
|
||||||
|
|
||||||
List<WorldSection> sections = new ArrayList<>();
|
|
||||||
|
|
||||||
System.out.println("Loading sections");
|
|
||||||
for (int x = -17; x <= 17; x++) {
|
|
||||||
for (int z = -17; z <= 17; z++) {
|
|
||||||
for (int y = -1; y <= 4; y++) {
|
|
||||||
var section = this.world.acquire(0, x, y, z);
|
|
||||||
|
|
||||||
int nonAir = 0;
|
|
||||||
for (long state : section.copyData()) {
|
|
||||||
nonAir += Mapper.isAir(state)?0:1;
|
|
||||||
modelService.requestBlockBake(Mapper.getBlockId(state));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nonAir > 500 && Math.abs(x) <= 16 && Math.abs(z) <= 16) {
|
|
||||||
sections.add(section);
|
|
||||||
} else {
|
|
||||||
section.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println("Baking models");
|
|
||||||
{
|
|
||||||
//Bake everything
|
|
||||||
while (!modelService.areQueuesEmpty()) {
|
|
||||||
modelService.tick();
|
|
||||||
glFinish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println("Ready!");
|
|
||||||
|
|
||||||
{
|
|
||||||
int iteration = 0;
|
|
||||||
while (true) {
|
|
||||||
long start = System.currentTimeMillis();
|
|
||||||
for (var section : sections) {
|
|
||||||
var mesh = factory.generateMesh(section);
|
|
||||||
|
|
||||||
mesh.free();
|
|
||||||
}
|
|
||||||
long delta = System.currentTimeMillis() - start;
|
|
||||||
System.out.println("Iteration: " + (iteration++) + " took " + delta + "ms, for an average of " + ((float)delta/sections.size()) + "ms per section");
|
|
||||||
//System.out.println("Quad count: " + factory.quadCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void testDbPerformance() {
|
|
||||||
Random r = new Random(123456);
|
|
||||||
r.nextLong();
|
|
||||||
long start = System.currentTimeMillis();
|
|
||||||
int c = 0;
|
|
||||||
for (int i = 0; i < 500_000; i++) {
|
|
||||||
if (i == 20_000) {
|
|
||||||
c = 0;
|
|
||||||
start = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
c++;
|
|
||||||
int x = (r.nextInt(256*2+2)-256)>>1;//-32
|
|
||||||
int z = (r.nextInt(256*2+2)-256)>>1;//-32
|
|
||||||
int y = 0;
|
|
||||||
int lvl = 0;//r.nextInt(5);
|
|
||||||
this.world.acquire(WorldEngine.getWorldSectionId(lvl, x>>lvl, y>>lvl, z>>lvl)).release();
|
|
||||||
}
|
|
||||||
long delta = System.currentTimeMillis() - start;
|
|
||||||
System.out.println("Total "+delta+"ms " + ((double)delta/c) + "ms average" );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void testFullMesh() {
|
|
||||||
var modelService = new ModelBakerySubsystem(this.world.getMapper());
|
|
||||||
var completedCounter = new AtomicInteger();
|
|
||||||
var generationService = new RenderGenerationService(this.world, modelService, this.serviceThreadPool, a-> {completedCounter.incrementAndGet(); a.free();}, false);
|
|
||||||
|
|
||||||
|
|
||||||
var r = new Random(12345);
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 10_000; i++) {
|
|
||||||
int x = (r.nextInt(256*2+2)-256)>>1;//-32
|
|
||||||
int z = (r.nextInt(256*2+2)-256)>>1;//-32
|
|
||||||
int y = r.nextInt(10)-2;
|
|
||||||
int lvl = 0;//r.nextInt(5);
|
|
||||||
long key = WorldEngine.getWorldSectionId(lvl, x>>lvl, y>>lvl, z>>lvl);
|
|
||||||
generationService.enqueueTask(key);
|
|
||||||
}
|
|
||||||
int i = 0;
|
|
||||||
while (true) {
|
|
||||||
modelService.tick();
|
|
||||||
if (i++%5000==0)
|
|
||||||
System.out.println(completedCounter.get());
|
|
||||||
glFinish();
|
|
||||||
List<String> a = new ArrayList<>();
|
|
||||||
generationService.addDebugData(a);
|
|
||||||
if (a.getFirst().endsWith(" 0")) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println("Running benchmark");
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
completedCounter.set(0);
|
|
||||||
long start = System.currentTimeMillis();
|
|
||||||
int C = 200_000;
|
|
||||||
for (int i = 0; i < C; i++) {
|
|
||||||
int x = (r.nextInt(256 * 2 + 2) - 256) >> 1;//-32
|
|
||||||
int z = (r.nextInt(256 * 2 + 2) - 256) >> 1;//-32
|
|
||||||
int y = r.nextInt(10) - 2;
|
|
||||||
int lvl = 0;//r.nextInt(5);
|
|
||||||
long key = WorldEngine.getWorldSectionId(lvl, x >> lvl, y >> lvl, z >> lvl);
|
|
||||||
generationService.enqueueTask(key);
|
|
||||||
}
|
|
||||||
//int i = 0;
|
|
||||||
while (true) {
|
|
||||||
//if (i++%5000==0)
|
|
||||||
// System.out.println(completedCounter.get());
|
|
||||||
modelService.tick();
|
|
||||||
glFinish();
|
|
||||||
List<String> a = new ArrayList<>();
|
|
||||||
generationService.addDebugData(a);
|
|
||||||
if (a.getFirst().endsWith(" 0")) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
long delta = (System.currentTimeMillis()-start);
|
|
||||||
System.out.println("Time "+delta+"ms count: " + completedCounter.get() + " avg per mesh: " + ((double)delta/completedCounter.get()) + "ms");
|
|
||||||
if (false)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
generationService.shutdown();
|
|
||||||
modelService.shutdown();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package me.cortex.voxy.client.core.rendering;
|
||||||
|
|
||||||
|
import me.cortex.voxy.client.core.util.RingTracker;
|
||||||
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
|
|
||||||
|
import java.util.function.LongConsumer;
|
||||||
|
|
||||||
|
public class RenderDistanceTracker {
|
||||||
|
private static final int CHECK_DISTANCE_BLOCKS = 128;
|
||||||
|
private final LongConsumer addTopLevelNode;
|
||||||
|
private final LongConsumer removeTopLevelNode;
|
||||||
|
private final int processRate;
|
||||||
|
private final int minSec;
|
||||||
|
private final int maxSec;
|
||||||
|
private RingTracker tracker;
|
||||||
|
private int renderDistance;
|
||||||
|
private double posX;
|
||||||
|
private double posZ;
|
||||||
|
public RenderDistanceTracker(int rate, int minSec, int maxSec, LongConsumer addTopLevelNode, LongConsumer removeTopLevelNode) {
|
||||||
|
this.addTopLevelNode = addTopLevelNode;
|
||||||
|
this.removeTopLevelNode = removeTopLevelNode;
|
||||||
|
this.renderDistance = 2;
|
||||||
|
this.tracker = new RingTracker(this.renderDistance, 0, 0, true);
|
||||||
|
this.processRate = rate;
|
||||||
|
this.minSec = minSec;
|
||||||
|
this.maxSec = maxSec;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRenderDistance(int renderDistance) {
|
||||||
|
this.tracker.unload();
|
||||||
|
this.tracker.process(Integer.MAX_VALUE, this::add, this::rem);
|
||||||
|
this.renderDistance = renderDistance;
|
||||||
|
this.tracker = new RingTracker(renderDistance, ((int)this.posX)>>9, ((int)this.posZ)>>9, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCenterAndProcess(double x, double z) {
|
||||||
|
double dx = this.posX-x;
|
||||||
|
double dz = this.posZ-z;
|
||||||
|
if (CHECK_DISTANCE_BLOCKS*CHECK_DISTANCE_BLOCKS<dx*dx+dz*dz) {
|
||||||
|
this.posX = x;
|
||||||
|
this.posZ = z;
|
||||||
|
this.tracker.moveCenter(((int)x)>>9, ((int)z)>>9);
|
||||||
|
}
|
||||||
|
this.tracker.process(this.processRate, this::add, this::rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add(int x, int z) {
|
||||||
|
for (int y = this.minSec; y <= this.maxSec; y++) {
|
||||||
|
this.addTopLevelNode.accept(WorldEngine.getWorldSectionId(4, x, y, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rem(int x, int z) {
|
||||||
|
for (int y = this.minSec; y <= this.maxSec; y++) {
|
||||||
|
this.removeTopLevelNode.accept(WorldEngine.getWorldSectionId(4, x, y, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package me.cortex.voxy.client.core.rendering;
|
package me.cortex.voxy.client.core.rendering;
|
||||||
|
|
||||||
|
import io.netty.util.internal.MathUtil;
|
||||||
|
import me.cortex.voxy.client.core.gl.Capabilities;
|
||||||
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
|
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
|
||||||
import me.cortex.voxy.client.core.model.ModelStore;
|
import me.cortex.voxy.client.core.model.ModelStore;
|
||||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
||||||
@@ -52,7 +54,7 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
|
|||||||
|
|
||||||
//Max sections: ~500k
|
//Max sections: ~500k
|
||||||
//Max geometry: 1 gb
|
//Max geometry: 1 gb
|
||||||
this.sectionRenderer = (T) createSectionRenderer(this.modelService.getStore(),1<<20, (1L<<32)-1024);
|
this.sectionRenderer = (T) createSectionRenderer(this.modelService.getStore(),1<<20, Math.min((1L<<(64-Long.numberOfLeadingZeros(Capabilities.INSTANCE.ssboMaxSize-1)))<<1, 1L<<32)-1024/*(1L<<32)-1024*/);
|
||||||
Logger.info("Using renderer: " + this.sectionRenderer.getClass().getSimpleName());
|
Logger.info("Using renderer: " + this.sectionRenderer.getClass().getSimpleName());
|
||||||
|
|
||||||
//Do something incredibly hacky, we dont need to keep the reference to this around, so just connect and discard
|
//Do something incredibly hacky, we dont need to keep the reference to this around, so just connect and discard
|
||||||
@@ -84,18 +86,15 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
|
|||||||
world.getMapper().setBiomeCallback(this.modelService::addBiome);
|
world.getMapper().setBiomeCallback(this.modelService::addBiome);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int q = -60;
|
public void addTopLevelNode(long pos) {
|
||||||
|
this.nodeManager.insertTopLevelNode(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeTopLevelNode(long pos) {
|
||||||
|
this.nodeManager.removeTopLevelNode(pos);
|
||||||
|
}
|
||||||
|
|
||||||
public void setup(Camera camera) {
|
public void setup(Camera camera) {
|
||||||
final int W = 32;
|
|
||||||
final int H = 2;
|
|
||||||
boolean SIDED = false;
|
|
||||||
for (int i = 0; i<64 && q<((W*2+1)*(W*2+1)*H)&&q++>=0;i++) {
|
|
||||||
this.nodeManager.insertTopLevelNode(WorldEngine.getWorldSectionId(4, (q%(W*2+1))-(SIDED?0:W), ((q/(W*2+1))/(W*2+1))-1, ((q/(W*2+1))%(W*2+1))-(SIDED?0:W)));
|
|
||||||
}
|
|
||||||
if (q==((W*2+1)*(W*2+1)*H)) {
|
|
||||||
q++;
|
|
||||||
Logger.info("Finished loading render distance");
|
|
||||||
}
|
|
||||||
this.modelService.tick();
|
this.modelService.tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,6 +173,7 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
|
|||||||
this.nodeCleaner.free();
|
this.nodeCleaner.free();
|
||||||
//Release all the unprocessed built geometry
|
//Release all the unprocessed built geometry
|
||||||
this.geometryUpdateQueue.clear(BuiltSection::free);
|
this.geometryUpdateQueue.clear(BuiltSection::free);
|
||||||
|
this.sectionUpdateQueue.clear(WorldSection::release);//Release anything thats in the queue
|
||||||
}
|
}
|
||||||
|
|
||||||
public Viewport<?> getViewport() {
|
public Viewport<?> getViewport() {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import java.util.function.LongConsumer;
|
|||||||
import static me.cortex.voxy.common.world.WorldEngine.UPDATE_TYPE_BLOCK_BIT;
|
import static me.cortex.voxy.common.world.WorldEngine.UPDATE_TYPE_BLOCK_BIT;
|
||||||
|
|
||||||
public class SectionUpdateRouter implements ISectionWatcher {
|
public class SectionUpdateRouter implements ISectionWatcher {
|
||||||
private static final int SLICES = 1<<3;
|
private static final int SLICES = 1<<8;
|
||||||
public interface IChildUpdate {void accept(WorldSection section);}
|
public interface IChildUpdate {void accept(WorldSection section);}
|
||||||
|
|
||||||
private final Long2ByteOpenHashMap[] slices = new Long2ByteOpenHashMap[SLICES];
|
private final Long2ByteOpenHashMap[] slices = new Long2ByteOpenHashMap[SLICES];
|
||||||
|
|||||||
@@ -5,14 +5,21 @@ import me.cortex.voxy.client.config.VoxyConfig;
|
|||||||
import me.cortex.voxy.client.core.gl.Capabilities;
|
import me.cortex.voxy.client.core.gl.Capabilities;
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||||
import me.cortex.voxy.client.core.model.ColourDepthTextureData;
|
import me.cortex.voxy.client.core.model.ColourDepthTextureData;
|
||||||
|
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
|
||||||
import me.cortex.voxy.client.core.model.ModelTextureBakery;
|
import me.cortex.voxy.client.core.model.ModelTextureBakery;
|
||||||
|
import me.cortex.voxy.client.core.rendering.building.RenderDataFactory45;
|
||||||
|
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
|
||||||
import me.cortex.voxy.client.core.rendering.post.PostProcessing;
|
import me.cortex.voxy.client.core.rendering.post.PostProcessing;
|
||||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
||||||
import me.cortex.voxy.client.core.rendering.util.RawDownloadStream;
|
import me.cortex.voxy.client.core.rendering.util.RawDownloadStream;
|
||||||
import me.cortex.voxy.client.core.util.IrisUtil;
|
import me.cortex.voxy.client.core.util.IrisUtil;
|
||||||
|
import me.cortex.voxy.client.core.util.RingTracker;
|
||||||
import me.cortex.voxy.common.Logger;
|
import me.cortex.voxy.common.Logger;
|
||||||
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
|
import me.cortex.voxy.common.world.WorldSection;
|
||||||
|
import me.cortex.voxy.common.world.other.Mapper;
|
||||||
|
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||||
import net.minecraft.block.Blocks;
|
import net.minecraft.block.Blocks;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
import net.minecraft.client.render.Camera;
|
import net.minecraft.client.render.Camera;
|
||||||
@@ -22,25 +29,45 @@ import org.joml.Matrix4f;
|
|||||||
import org.lwjgl.opengl.GL11;
|
import org.lwjgl.opengl.GL11;
|
||||||
import org.lwjgl.system.MemoryUtil;
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.GL11C.glFinish;
|
||||||
import static org.lwjgl.opengl.GL30C.GL_DRAW_FRAMEBUFFER_BINDING;
|
import static org.lwjgl.opengl.GL30C.GL_DRAW_FRAMEBUFFER_BINDING;
|
||||||
|
|
||||||
public class VoxyRenderSystem {
|
public class VoxyRenderSystem {
|
||||||
private final RenderService renderer;
|
private final RenderService renderer;
|
||||||
private final PostProcessing postProcessing;
|
private final PostProcessing postProcessing;
|
||||||
|
private final WorldEngine worldIn;
|
||||||
|
private final RenderDistanceTracker renderDistanceTracker;
|
||||||
|
|
||||||
public VoxyRenderSystem(WorldEngine world, ServiceThreadPool threadPool) {
|
public VoxyRenderSystem(WorldEngine world, ServiceThreadPool threadPool) {
|
||||||
//Trigger the shared index buffer loading
|
//Trigger the shared index buffer loading
|
||||||
SharedIndexBuffer.INSTANCE.id();
|
SharedIndexBuffer.INSTANCE.id();
|
||||||
Capabilities.init();//Ensure clinit is called
|
Capabilities.init();//Ensure clinit is called
|
||||||
|
|
||||||
|
this.worldIn = world;
|
||||||
this.renderer = new RenderService(world, threadPool);
|
this.renderer = new RenderService(world, threadPool);
|
||||||
this.postProcessing = new PostProcessing();
|
this.postProcessing = new PostProcessing();
|
||||||
|
|
||||||
|
this.renderDistanceTracker = new RenderDistanceTracker(10,
|
||||||
|
MinecraftClient.getInstance().world.getBottomSectionCoord()>>5,
|
||||||
|
(MinecraftClient.getInstance().world.getTopSectionCoord()-1)>>5,
|
||||||
|
this.renderer::addTopLevelNode,
|
||||||
|
this.renderer::removeTopLevelNode);
|
||||||
|
|
||||||
|
this.renderDistanceTracker.setRenderDistance(VoxyConfig.CONFIG.sectionRenderDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRenderDistance(int renderDistance) {
|
||||||
|
this.renderDistanceTracker.setRenderDistance(renderDistance);
|
||||||
|
}
|
||||||
|
|
||||||
public void renderSetup(Frustum frustum, Camera camera) {
|
public void renderSetup(Frustum frustum, Camera camera) {
|
||||||
|
this.renderDistanceTracker.setCenterAndProcess(camera.getBlockPos().getX(), camera.getBlockPos().getZ());
|
||||||
|
|
||||||
this.renderer.setup(camera);
|
this.renderer.setup(camera);
|
||||||
PrintfDebugUtil.tick();
|
PrintfDebugUtil.tick();
|
||||||
}
|
}
|
||||||
@@ -173,4 +200,129 @@ public class VoxyRenderSystem {
|
|||||||
Logger.info("Shutting down post processor");
|
Logger.info("Shutting down post processor");
|
||||||
if (this.postProcessing!=null){try {this.postProcessing.shutdown();} catch (Exception e) {Logger.error("Error shutting down post processor", e);}}
|
if (this.postProcessing!=null){try {this.postProcessing.shutdown();} catch (Exception e) {Logger.error("Error shutting down post processor", e);}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void testMeshingPerformance() {
|
||||||
|
var modelService = new ModelBakerySubsystem(this.worldIn.getMapper());
|
||||||
|
var factory = new RenderDataFactory45(this.worldIn, modelService.factory, false);
|
||||||
|
|
||||||
|
List<WorldSection> sections = new ArrayList<>();
|
||||||
|
|
||||||
|
System.out.println("Loading sections");
|
||||||
|
for (int x = -17; x <= 17; x++) {
|
||||||
|
for (int z = -17; z <= 17; z++) {
|
||||||
|
for (int y = -1; y <= 4; y++) {
|
||||||
|
var section = this.worldIn.acquire(0, x, y, z);
|
||||||
|
|
||||||
|
int nonAir = 0;
|
||||||
|
for (long state : section.copyData()) {
|
||||||
|
nonAir += Mapper.isAir(state)?0:1;
|
||||||
|
modelService.requestBlockBake(Mapper.getBlockId(state));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nonAir > 500 && Math.abs(x) <= 16 && Math.abs(z) <= 16) {
|
||||||
|
sections.add(section);
|
||||||
|
} else {
|
||||||
|
section.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("Baking models");
|
||||||
|
{
|
||||||
|
//Bake everything
|
||||||
|
while (!modelService.areQueuesEmpty()) {
|
||||||
|
modelService.tick();
|
||||||
|
glFinish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("Ready!");
|
||||||
|
|
||||||
|
{
|
||||||
|
int iteration = 0;
|
||||||
|
while (true) {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
for (var section : sections) {
|
||||||
|
var mesh = factory.generateMesh(section);
|
||||||
|
|
||||||
|
mesh.free();
|
||||||
|
}
|
||||||
|
long delta = System.currentTimeMillis() - start;
|
||||||
|
System.out.println("Iteration: " + (iteration++) + " took " + delta + "ms, for an average of " + ((float)delta/sections.size()) + "ms per section");
|
||||||
|
//System.out.println("Quad count: " + factory.quadCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testFullMesh() {
|
||||||
|
var modelService = new ModelBakerySubsystem(this.worldIn.getMapper());
|
||||||
|
var completedCounter = new AtomicInteger();
|
||||||
|
var generationService = new RenderGenerationService(this.worldIn, modelService, VoxyCommon.getInstance().getThreadPool(), a-> {completedCounter.incrementAndGet(); a.free();}, false);
|
||||||
|
|
||||||
|
|
||||||
|
var r = new Random(12345);
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 10_000; i++) {
|
||||||
|
int x = (r.nextInt(256*2+2)-256)>>1;//-32
|
||||||
|
int z = (r.nextInt(256*2+2)-256)>>1;//-32
|
||||||
|
int y = r.nextInt(10)-2;
|
||||||
|
int lvl = 0;//r.nextInt(5);
|
||||||
|
long key = WorldEngine.getWorldSectionId(lvl, x>>lvl, y>>lvl, z>>lvl);
|
||||||
|
generationService.enqueueTask(key);
|
||||||
|
}
|
||||||
|
int i = 0;
|
||||||
|
while (true) {
|
||||||
|
modelService.tick();
|
||||||
|
if (i++%5000==0)
|
||||||
|
System.out.println(completedCounter.get());
|
||||||
|
glFinish();
|
||||||
|
List<String> a = new ArrayList<>();
|
||||||
|
generationService.addDebugData(a);
|
||||||
|
if (a.getFirst().endsWith(" 0")) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("Running benchmark");
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
completedCounter.set(0);
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
int C = 200_000;
|
||||||
|
for (int i = 0; i < C; i++) {
|
||||||
|
int x = (r.nextInt(256 * 2 + 2) - 256) >> 1;//-32
|
||||||
|
int z = (r.nextInt(256 * 2 + 2) - 256) >> 1;//-32
|
||||||
|
int y = r.nextInt(10) - 2;
|
||||||
|
int lvl = 0;//r.nextInt(5);
|
||||||
|
long key = WorldEngine.getWorldSectionId(lvl, x >> lvl, y >> lvl, z >> lvl);
|
||||||
|
generationService.enqueueTask(key);
|
||||||
|
}
|
||||||
|
//int i = 0;
|
||||||
|
while (true) {
|
||||||
|
//if (i++%5000==0)
|
||||||
|
// System.out.println(completedCounter.get());
|
||||||
|
modelService.tick();
|
||||||
|
glFinish();
|
||||||
|
List<String> a = new ArrayList<>();
|
||||||
|
generationService.addDebugData(a);
|
||||||
|
if (a.getFirst().endsWith(" 0")) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
long delta = (System.currentTimeMillis()-start);
|
||||||
|
System.out.println("Time "+delta+"ms count: " + completedCounter.get() + " avg per mesh: " + ((double)delta/completedCounter.get()) + "ms");
|
||||||
|
if (false)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
generationService.shutdown();
|
||||||
|
modelService.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,670 +0,0 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.building;
|
|
||||||
|
|
||||||
import me.cortex.voxy.client.core.gl.Capabilities;
|
|
||||||
import me.cortex.voxy.client.core.model.ModelFactory;
|
|
||||||
import me.cortex.voxy.client.core.model.ModelQueries;
|
|
||||||
import me.cortex.voxy.client.core.util.Mesher2D;
|
|
||||||
import me.cortex.voxy.client.core.util.ScanMesher2D;
|
|
||||||
import me.cortex.voxy.common.util.MemoryBuffer;
|
|
||||||
import me.cortex.voxy.common.util.UnsafeUtil;
|
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
|
||||||
import me.cortex.voxy.common.world.WorldSection;
|
|
||||||
import me.cortex.voxy.common.world.other.Mapper;
|
|
||||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
|
||||||
import org.lwjgl.system.MemoryUtil;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
|
|
||||||
public class RenderDataFactory4 {
|
|
||||||
private static final boolean VERIFY_MESHING = VoxyCommon.isVerificationFlagOn("verifyMeshing");
|
|
||||||
|
|
||||||
private final WorldEngine world;
|
|
||||||
private final ModelFactory modelMan;
|
|
||||||
|
|
||||||
//private final long[] sectionData = new long[32*32*32*2];
|
|
||||||
private final long[] sectionData = new long[32*32*32*2];
|
|
||||||
|
|
||||||
private final int[] opaqueMasks = new int[32*32];
|
|
||||||
|
|
||||||
//TODO: emit directly to memory buffer instead of long arrays
|
|
||||||
|
|
||||||
//Each axis gets a max quad count of 2^16 (65536 quads) since that is the max the basic geometry manager can handle
|
|
||||||
private final MemoryBuffer directionalQuadBuffer = new MemoryBuffer(6*(8*(1<<16)));
|
|
||||||
private final long directionalQuadBufferPtr = this.directionalQuadBuffer.address;
|
|
||||||
private final int[] directionalQuadCounters = new int[6];//Maybe change to short? (or long /w raw pointers)
|
|
||||||
|
|
||||||
|
|
||||||
private int minX;
|
|
||||||
private int minY;
|
|
||||||
private int minZ;
|
|
||||||
private int maxX;
|
|
||||||
private int maxY;
|
|
||||||
private int maxZ;
|
|
||||||
|
|
||||||
private int quadCount = 0;
|
|
||||||
|
|
||||||
|
|
||||||
//Wont work for double sided quads
|
|
||||||
private final class Mesher extends ScanMesher2D {
|
|
||||||
public int auxiliaryPosition = 0;
|
|
||||||
public boolean doAuxiliaryFaceOffset = true;
|
|
||||||
public int axis = 0;
|
|
||||||
|
|
||||||
//Note x, z are in top right
|
|
||||||
@Override
|
|
||||||
protected void emitQuad(int x, int z, int length, int width, long data) {
|
|
||||||
RenderDataFactory4.this.quadCount++;
|
|
||||||
|
|
||||||
if (VERIFY_MESHING) {
|
|
||||||
if (length<1||length>16) {
|
|
||||||
throw new IllegalStateException("length out of bounds: " + length);
|
|
||||||
}
|
|
||||||
if (width<1||width>16) {
|
|
||||||
throw new IllegalStateException("width out of bounds: " + width);
|
|
||||||
}
|
|
||||||
if (x<0||x>31) {
|
|
||||||
throw new IllegalStateException("x out of bounds: " + x);
|
|
||||||
}
|
|
||||||
if (z<0||z>31) {
|
|
||||||
throw new IllegalStateException("z out of bounds: " + z);
|
|
||||||
}
|
|
||||||
if (x-(length-1)<0 || z-(width-1)<0) {
|
|
||||||
throw new IllegalStateException("dim out of bounds: " + (x-(length-1))+", " + (z-(width-1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
x -= length-1;
|
|
||||||
z -= width-1;
|
|
||||||
|
|
||||||
if (this.axis == 2) {
|
|
||||||
//Need to swizzle the data if on x axis
|
|
||||||
int tmp = x;
|
|
||||||
x = z;
|
|
||||||
z = tmp;
|
|
||||||
|
|
||||||
tmp = length;
|
|
||||||
length = width;
|
|
||||||
width = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Lower 26 bits can be auxiliary data since that is where quad position information goes;
|
|
||||||
int auxData = (int) (data&((1<<26)-1));
|
|
||||||
int faceSide = auxData&1;
|
|
||||||
data &= ~(data&((1<<26)-1));
|
|
||||||
|
|
||||||
final int axis = this.axis;
|
|
||||||
int face = (axis<<1)|faceSide;
|
|
||||||
int encodedPosition = face;
|
|
||||||
|
|
||||||
//Shift up if is negative axis
|
|
||||||
int auxPos = this.auxiliaryPosition;
|
|
||||||
auxPos += this.doAuxiliaryFaceOffset?(1-faceSide):0;//Shift
|
|
||||||
|
|
||||||
if (VERIFY_MESHING) {
|
|
||||||
if (auxPos > 31) {
|
|
||||||
throw new IllegalStateException("OOB face: " + auxPos + ", " + faceSide);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
encodedPosition |= ((width - 1) << 7) | ((length - 1) << 3);
|
|
||||||
|
|
||||||
encodedPosition |= x << (axis==2?16:21);
|
|
||||||
encodedPosition |= z << (axis==1?16:11);
|
|
||||||
encodedPosition |= auxPos << (axis==0?16:(axis==1?11:21));
|
|
||||||
|
|
||||||
long quad = data | encodedPosition;
|
|
||||||
|
|
||||||
|
|
||||||
MemoryUtil.memPutLong(RenderDataFactory4.this.directionalQuadBufferPtr + (RenderDataFactory4.this.directionalQuadCounters[face]++)*8L + face*8L*(1<<16), quad);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private final Mesher blockMesher = new Mesher();
|
|
||||||
|
|
||||||
public RenderDataFactory4(WorldEngine world, ModelFactory modelManager, boolean emitMeshlets) {
|
|
||||||
this.world = world;
|
|
||||||
this.modelMan = modelManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//TODO: MAKE a render cache that caches each WorldSection directional face generation, cause then can just pull that directly
|
|
||||||
// instead of needing to regen the entire thing
|
|
||||||
|
|
||||||
|
|
||||||
//Ok so the idea for fluid rendering is to make it use a seperate mesher and use a different code path for it
|
|
||||||
// since fluid states are explicitly overlays over the base block
|
|
||||||
// can do funny stuff like double rendering
|
|
||||||
|
|
||||||
private static final boolean USE_UINT64 = Capabilities.INSTANCE.INT64_t;
|
|
||||||
public static final int QUADS_PER_MESHLET = 14;
|
|
||||||
private static void writePos(long ptr, long pos) {
|
|
||||||
if (USE_UINT64) {
|
|
||||||
MemoryUtil.memPutLong(ptr, pos);
|
|
||||||
} else {
|
|
||||||
MemoryUtil.memPutInt(ptr, (int) (pos>>32));
|
|
||||||
MemoryUtil.memPutInt(ptr + 4, (int)pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void prepareSectionData() {
|
|
||||||
final var sectionData = this.sectionData;
|
|
||||||
int opaque = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < 32*32*32;) {
|
|
||||||
long block = sectionData[i + 32 * 32 * 32];//Get the block mapping
|
|
||||||
|
|
||||||
int modelId = this.modelMan.getModelId(Mapper.getBlockId(block));
|
|
||||||
long modelMetadata = this.modelMan.getModelMetadataFromClientId(modelId);
|
|
||||||
|
|
||||||
sectionData[i * 2] = modelId | ((long) (Mapper.getLightId(block)) << 16) | (ModelQueries.isBiomeColoured(modelMetadata)?(((long) (Mapper.getBiomeId(block))) << 24):0);
|
|
||||||
sectionData[i * 2 + 1] = modelMetadata;
|
|
||||||
|
|
||||||
boolean isFullyOpaque = ModelQueries.isFullyOpaque(modelMetadata);
|
|
||||||
opaque |= (isFullyOpaque ? 1:0) << (i & 31);
|
|
||||||
|
|
||||||
//TODO: here also do bitmask of what neighboring sections are needed to compute (may be getting rid of this in future)
|
|
||||||
|
|
||||||
//Do increment here
|
|
||||||
i++;
|
|
||||||
|
|
||||||
if ((i & 31) == 0) {
|
|
||||||
this.opaqueMasks[(i >> 5) - 1] = opaque;
|
|
||||||
opaque = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void generateYZFaces() {
|
|
||||||
for (int axis = 0; axis < 2; axis++) {
|
|
||||||
this.blockMesher.axis = axis;
|
|
||||||
for (int layer = 0; layer < 31; layer++) {
|
|
||||||
this.blockMesher.auxiliaryPosition = layer;
|
|
||||||
for (int other = 0; other < 32; other++) {//TODO: need to do the faces that border sections
|
|
||||||
int pidx = axis==0 ?(layer*32+other):(other*32+layer);
|
|
||||||
int skipAmount = axis==0?32:1;
|
|
||||||
|
|
||||||
int current = this.opaqueMasks[pidx];
|
|
||||||
int next = this.opaqueMasks[pidx + skipAmount];
|
|
||||||
|
|
||||||
int msk = current ^ next;
|
|
||||||
if (msk == 0) {
|
|
||||||
this.blockMesher.skip(32);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: For boarder sections, should NOT EMIT neighbors faces
|
|
||||||
int faceForwardMsk = msk & current;
|
|
||||||
int cIdx = -1;
|
|
||||||
while (msk != 0) {
|
|
||||||
int index = Integer.numberOfTrailingZeros(msk);//Is also the x-axis index
|
|
||||||
int delta = index - cIdx - 1;
|
|
||||||
cIdx = index; //index--;
|
|
||||||
if (delta != 0) this.blockMesher.skip(delta);
|
|
||||||
msk &= ~Integer.lowestOneBit(msk);
|
|
||||||
|
|
||||||
int facingForward = ((faceForwardMsk >> index) & 1);
|
|
||||||
|
|
||||||
{
|
|
||||||
int idx = index + (pidx*32);
|
|
||||||
|
|
||||||
//TODO: swap this out for something not getting the next entry
|
|
||||||
long A = this.sectionData[idx * 2];
|
|
||||||
long B = this.sectionData[(idx + skipAmount * 32) * 2];
|
|
||||||
|
|
||||||
//Flip data with respect to facing direction
|
|
||||||
long selfModel = facingForward == 1 ? A : B;
|
|
||||||
long nextModel = facingForward == 1 ? B : A;
|
|
||||||
|
|
||||||
//Example thing thats just wrong but as example
|
|
||||||
this.blockMesher.putNext(((long) facingForward) |//Facing
|
|
||||||
((selfModel & 0xFFFF) << 26) | //ModelId
|
|
||||||
(((nextModel>>16)&0xFF) << 55) |//Lighting
|
|
||||||
((selfModel&(0x1FFL<<24))<<(46-24))//biomeId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.blockMesher.endRow();
|
|
||||||
}
|
|
||||||
this.blockMesher.finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (true) {
|
|
||||||
this.blockMesher.doAuxiliaryFaceOffset = false;
|
|
||||||
//Hacky generate section side faces (without check neighbor section)
|
|
||||||
for (int side = 0; side < 2; side++) {
|
|
||||||
int layer = side == 0 ? 0 : 31;
|
|
||||||
this.blockMesher.auxiliaryPosition = layer;
|
|
||||||
for (int other = 0; other < 32; other++) {
|
|
||||||
int pidx = axis == 0 ? (layer * 32 + other) : (other * 32 + layer);
|
|
||||||
int msk = this.opaqueMasks[pidx];
|
|
||||||
if (msk == 0) {
|
|
||||||
this.blockMesher.skip(32);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cIdx = -1;
|
|
||||||
while (msk != 0) {
|
|
||||||
int index = Integer.numberOfTrailingZeros(msk);//Is also the x-axis index
|
|
||||||
int delta = index - cIdx - 1;
|
|
||||||
cIdx = index; //index--;
|
|
||||||
if (delta != 0) this.blockMesher.skip(delta);
|
|
||||||
msk &= ~Integer.lowestOneBit(msk);
|
|
||||||
|
|
||||||
{
|
|
||||||
int idx = index + (pidx * 32);
|
|
||||||
|
|
||||||
//TODO: swap this out for something not getting the next entry
|
|
||||||
long A = this.sectionData[idx * 2];
|
|
||||||
|
|
||||||
//Example thing thats just wrong but as example
|
|
||||||
this.blockMesher.putNext((long) (side == 0 ? 0L : 1L) |
|
|
||||||
((A & 0xFFFFL) << 26) |
|
|
||||||
(((0xFFL) & 0xFF) << 55) |
|
|
||||||
((A&(0x1FFL<<24))<<(46-24))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.blockMesher.endRow();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.blockMesher.finish();
|
|
||||||
}
|
|
||||||
this.blockMesher.doAuxiliaryFaceOffset = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private final Mesher[] xAxisMeshers = new Mesher[32];
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 32; i++) {
|
|
||||||
var mesher = new Mesher();
|
|
||||||
mesher.auxiliaryPosition = i;
|
|
||||||
mesher.axis = 2;//X axis
|
|
||||||
this.xAxisMeshers[i] = mesher;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final long X_I_MSK = 0x4210842108421L;
|
|
||||||
private void generateXFaces() {
|
|
||||||
for (int y = 0; y < 32; y++) {
|
|
||||||
long sumA = 0;
|
|
||||||
long sumB = 0;
|
|
||||||
long sumC = 0;
|
|
||||||
int partialHasCount = -1;
|
|
||||||
int msk = 0;
|
|
||||||
for (int z = 0; z < 32; z++) {
|
|
||||||
int lMsk = this.opaqueMasks[y*32+z];
|
|
||||||
msk = (lMsk^(lMsk>>>1));
|
|
||||||
msk &= -1>>>1;//Remove top bit as we dont actually know/have the data for that slice
|
|
||||||
|
|
||||||
//Always increment cause can do funny trick (i.e. -1 on skip amount)
|
|
||||||
sumA += X_I_MSK;
|
|
||||||
sumB += X_I_MSK;
|
|
||||||
sumC += X_I_MSK;
|
|
||||||
|
|
||||||
partialHasCount &= ~msk;
|
|
||||||
|
|
||||||
if (z == 30 && partialHasCount != 0) {//Hackfix for incremental count overflow issue
|
|
||||||
int cmsk = partialHasCount;
|
|
||||||
while (cmsk!=0) {
|
|
||||||
int index = Integer.numberOfTrailingZeros(cmsk);
|
|
||||||
cmsk &= ~Integer.lowestOneBit(cmsk);
|
|
||||||
//TODO: fixme! check this is correct or if should be 30
|
|
||||||
this.xAxisMeshers[index].skip(31);
|
|
||||||
}
|
|
||||||
//Clear the sum
|
|
||||||
sumA &= ~(Long.expand(Integer.toUnsignedLong(partialHasCount), X_I_MSK)*0x1F);
|
|
||||||
sumB &= ~(Long.expand(Integer.toUnsignedLong(partialHasCount)>>11, X_I_MSK)*0x1F);
|
|
||||||
sumC &= ~(Long.expand(Integer.toUnsignedLong(partialHasCount)>>22, X_I_MSK)*0x1F);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msk == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
{//Dont need this as can just increment everything then -1 in mask
|
|
||||||
//Compute and increment skips for indexes
|
|
||||||
long imsk = Integer.toUnsignedLong(~msk);// we only want to increment where there isnt a face
|
|
||||||
sumA += Long.expand(imsk, X_I_MSK);
|
|
||||||
sumB += Long.expand(imsk>>11, X_I_MSK);
|
|
||||||
sumC += Long.expand(imsk>>22, X_I_MSK);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
int faceForwardMsk = msk&lMsk;
|
|
||||||
int iter = msk;
|
|
||||||
while (iter!=0) {
|
|
||||||
int index = Integer.numberOfTrailingZeros(iter);
|
|
||||||
iter &= ~Integer.lowestOneBit(iter);
|
|
||||||
|
|
||||||
var mesher = this.xAxisMeshers[index];
|
|
||||||
|
|
||||||
int skipCount;//Compute the skip count
|
|
||||||
{//TODO: Branch-less
|
|
||||||
//Compute skip and clear
|
|
||||||
if (index<11) {
|
|
||||||
skipCount = (int) (sumA>>(index*5));
|
|
||||||
sumA &= ~(0x1FL<<(index*5));
|
|
||||||
} else if (index<22) {
|
|
||||||
skipCount = (int) (sumB>>((index-11)*5));
|
|
||||||
sumB &= ~(0x1FL<<((index-11)*5));
|
|
||||||
} else {
|
|
||||||
skipCount = (int) (sumC>>((index-22)*5));
|
|
||||||
sumC &= ~(0x1FL<<((index-22)*5));
|
|
||||||
}
|
|
||||||
skipCount &= 0x1F;
|
|
||||||
skipCount--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skipCount != 0) {
|
|
||||||
mesher.skip(skipCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
int facingForward = ((faceForwardMsk>>index)&1);
|
|
||||||
{
|
|
||||||
int idx = index + (z * 32) + (y * 32 * 32);
|
|
||||||
//TODO: swap this out for something not getting the next entry
|
|
||||||
long A = this.sectionData[idx * 2];
|
|
||||||
long B = this.sectionData[(idx + 1) * 2];
|
|
||||||
|
|
||||||
//Flip data with respect to facing direction
|
|
||||||
long selfModel = facingForward==1?A:B;
|
|
||||||
long nextModel = facingForward==1?B:A;
|
|
||||||
|
|
||||||
//Example thing thats just wrong but as example
|
|
||||||
mesher.putNext(((long) facingForward) |//Facing
|
|
||||||
((selfModel & 0xFFFF) << 26) | //ModelId
|
|
||||||
(((nextModel>>16)&0xFF) << 55) |//Lighting
|
|
||||||
((selfModel&(0x1FFL<<24))<<(46-24))//biomeId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Need to skip the remaining entries in the skip array
|
|
||||||
{
|
|
||||||
msk = ~msk;//Invert the mask as we only need to set stuff that isnt 0
|
|
||||||
while (msk!=0) {
|
|
||||||
int index = Integer.numberOfTrailingZeros(msk);
|
|
||||||
msk &= ~Integer.lowestOneBit(msk);
|
|
||||||
int skipCount;
|
|
||||||
if (index < 11) {
|
|
||||||
skipCount = (int) (sumA>>(index*5));
|
|
||||||
} else if (index<22) {
|
|
||||||
skipCount = (int) (sumB>>((index-11)*5));
|
|
||||||
} else {
|
|
||||||
skipCount = (int) (sumC>>((index-22)*5));
|
|
||||||
}
|
|
||||||
skipCount &= 0x1F;
|
|
||||||
|
|
||||||
if (skipCount != 0) {
|
|
||||||
this.xAxisMeshers[index].skip(skipCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Generate the side faces, hackily, using 0 and 1 mesher
|
|
||||||
if (true) {
|
|
||||||
var ma = this.xAxisMeshers[0];
|
|
||||||
var mb = this.xAxisMeshers[31];
|
|
||||||
ma.finish();
|
|
||||||
mb.finish();
|
|
||||||
ma.doAuxiliaryFaceOffset = false;
|
|
||||||
mb.doAuxiliaryFaceOffset = false;
|
|
||||||
for (int y = 0; y < 32; y++) {
|
|
||||||
int skipA = 0;
|
|
||||||
int skipB = 0;
|
|
||||||
for (int z = 0; z < 32; z++) {
|
|
||||||
int i = y*32+z;
|
|
||||||
int msk = this.opaqueMasks[i];
|
|
||||||
if ((msk & 1) != 0) {
|
|
||||||
ma.skip(skipA); skipA = 0;
|
|
||||||
|
|
||||||
long A = this.sectionData[(i<<5) * 2];
|
|
||||||
|
|
||||||
ma.putNext(0L | ((A&0xFFFF)<<26) | (((0xFFL)&0xFF)<<55)|((A&(0x1FFL<<24))<<(46-24)));
|
|
||||||
} else {skipA++;}
|
|
||||||
|
|
||||||
if ((msk & (1<<31)) != 0) {
|
|
||||||
mb.skip(skipB); skipB = 0;
|
|
||||||
|
|
||||||
long A = this.sectionData[(i*32+31) * 2];
|
|
||||||
|
|
||||||
mb.putNext(1L | ((A&0xFFFF)<<26) | (((0xFFL)&0xFF)<<55)|((A&(0x1FFL<<24))<<(46-24)));
|
|
||||||
} else {skipB++;}
|
|
||||||
}
|
|
||||||
ma.skip(skipA);
|
|
||||||
mb.skip(skipB);
|
|
||||||
}
|
|
||||||
ma.finish();
|
|
||||||
mb.finish();
|
|
||||||
ma.doAuxiliaryFaceOffset = true;
|
|
||||||
mb.doAuxiliaryFaceOffset = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var mesher : this.xAxisMeshers) {
|
|
||||||
mesher.finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
private static long createQuad() {
|
|
||||||
((long)clientModelId) | (((long) Mapper.getLightId(ModelQueries.faceUsesSelfLighting(metadata, face)?self:facingState))<<16) | ((((long) Mapper.getBiomeId(self))<<24) * (ModelQueries.isBiomeColoured(metadata)?1:0)) | otherFlags
|
|
||||||
|
|
||||||
|
|
||||||
long data = Integer.toUnsignedLong(array[i*3+1]);
|
|
||||||
data |= ((long) array[i*3+2])<<32;
|
|
||||||
long encodedQuad = Integer.toUnsignedLong(QuadEncoder.encodePosition(face, otherAxis, quad)) | ((data&0xFFFF)<<26) | (((data>>16)&0xFF)<<55) | (((data>>24)&0x1FF)<<46);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
//section is already acquired and gets released by the parent
|
|
||||||
public BuiltSection generateMesh(WorldSection section) {
|
|
||||||
//Copy section data to end of array so that can mutate array while reading safely
|
|
||||||
section.copyDataTo(this.sectionData, 32*32*32);
|
|
||||||
|
|
||||||
this.quadCount = 0;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
Arrays.fill(this.directionalQuadCounters, (short) 0);
|
|
||||||
|
|
||||||
|
|
||||||
this.world.acquire(section.lvl, section.x+1, section.y, section.z).release();
|
|
||||||
this.world.acquire(section.lvl, section.x-1, section.y, section.z).release();
|
|
||||||
|
|
||||||
this.world.acquire(section.lvl, section.x, section.y+1, section.z).release();
|
|
||||||
this.world.acquire(section.lvl, section.x, section.y-1, section.z).release();
|
|
||||||
|
|
||||||
this.world.acquire(section.lvl, section.x, section.y, section.z+1).release();
|
|
||||||
this.world.acquire(section.lvl, section.x, section.y, section.z-1).release();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Prepare everything
|
|
||||||
this.prepareSectionData();
|
|
||||||
|
|
||||||
|
|
||||||
this.generateYZFaces();
|
|
||||||
this.generateXFaces();
|
|
||||||
|
|
||||||
|
|
||||||
//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
|
|
||||||
|
|
||||||
if (this.quadCount == 0) {
|
|
||||||
return BuiltSection.emptyWithChildren(section.key, section.getNonEmptyChildren());
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: FIXME AND OPTIMIZE, get rid of the stupid quad collector bullshit
|
|
||||||
|
|
||||||
int[] offsets = new int[8];
|
|
||||||
var buff = new MemoryBuffer(this.quadCount * 8L);
|
|
||||||
long ptr = buff.address;
|
|
||||||
int coff = 0;
|
|
||||||
|
|
||||||
for (int face = 0; face < 6; face++) {
|
|
||||||
offsets[face + 2] = coff;
|
|
||||||
int size = this.directionalQuadCounters[face];
|
|
||||||
UnsafeUtil.memcpy(this.directionalQuadBufferPtr + (face*(8*(1<<16))), ptr + coff*8L, (size* 8L));
|
|
||||||
coff += size;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int aabb = 0;
|
|
||||||
aabb |= 0;
|
|
||||||
aabb |= 0<<5;
|
|
||||||
aabb |= 0<<10;
|
|
||||||
aabb |= (31)<<15;
|
|
||||||
aabb |= (31)<<20;
|
|
||||||
aabb |= (31)<<25;
|
|
||||||
|
|
||||||
return new BuiltSection(section.key, section.getNonEmptyChildren(), aabb, buff, offsets);
|
|
||||||
|
|
||||||
/*
|
|
||||||
buff = new MemoryBuffer(bufferSize * 8L);
|
|
||||||
long ptr = buff.address;
|
|
||||||
int coff = 0;
|
|
||||||
|
|
||||||
//Ordering is: translucent, double sided quads, directional quads
|
|
||||||
offsets[0] = coff;
|
|
||||||
int size = this.translucentQuadCollector.size();
|
|
||||||
LongArrayList arrayList = this.translucentQuadCollector;
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
long data = arrayList.getLong(i);
|
|
||||||
MemoryUtil.memPutLong(ptr + ((coff++) * 8L), data);
|
|
||||||
}
|
|
||||||
|
|
||||||
offsets[1] = coff;
|
|
||||||
size = this.doubleSidedQuadCollector.size();
|
|
||||||
arrayList = this.doubleSidedQuadCollector;
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
long data = arrayList.getLong(i);
|
|
||||||
MemoryUtil.memPutLong(ptr + ((coff++) * 8L), data);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int face = 0; face < 6; face++) {
|
|
||||||
offsets[face + 2] = coff;
|
|
||||||
final LongArrayList faceArray = this.directionalQuadCollectors[face];
|
|
||||||
size = faceArray.size();
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
long data = faceArray.getLong(i);
|
|
||||||
MemoryUtil.memPutLong(ptr + ((coff++) * 8L), data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
return new BuiltSection(section.key, section.getNonEmptyChildren(), aabb, buff, offsets);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
public void free() {
|
|
||||||
this.directionalQuadBuffer.free();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Returns true if a face was placed
|
|
||||||
private boolean putFluidFaceIfCan(Mesher2D mesher, int face, int opposingFace, long self, long metadata, int selfClientModelId, int selfBlockId, long facingState, long facingMetadata, int facingClientModelId, int a, int b) {
|
|
||||||
int selfFluidClientId = this.modelMan.getFluidClientStateId(selfClientModelId);
|
|
||||||
long selfFluidMetadata = this.modelMan.getModelMetadataFromClientId(selfFluidClientId);
|
|
||||||
|
|
||||||
int facingFluidClientId = -1;
|
|
||||||
if (ModelQueries.containsFluid(facingMetadata)) {
|
|
||||||
facingFluidClientId = this.modelMan.getFluidClientStateId(facingClientModelId);
|
|
||||||
}
|
|
||||||
|
|
||||||
//If both of the states are the same, then dont render the fluid face
|
|
||||||
if (selfFluidClientId == facingFluidClientId) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (facingFluidClientId != -1) {
|
|
||||||
//TODO: OPTIMIZE
|
|
||||||
if (this.world.getMapper().getBlockStateFromBlockId(selfBlockId).getBlock() == this.world.getMapper().getBlockStateFromBlockId(Mapper.getBlockId(facingState)).getBlock()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (ModelQueries.faceOccludes(facingMetadata, opposingFace)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//if the model has a fluid state but is not a liquid need to see if the solid state had a face rendered and that face is occluding, if so, dont render the fluid state face
|
|
||||||
if ((!ModelQueries.isFluid(metadata)) && ModelQueries.faceOccludes(metadata, face)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//TODO:FIXME SOMEHOW THIS IS CRITICAL!!!!!!!!!!!!!!!!!!
|
|
||||||
// so there is one more issue need to be fixed, if water is layered ontop of eachother, the side faces depend on the water state ontop
|
|
||||||
// this has been hackfixed in the model texture bakery but a proper solution that doesnt explode the sides of the water textures needs to be done
|
|
||||||
// the issue is that the fluid rendering depends on the up state aswell not just the face state which is really really painful to account for
|
|
||||||
// e.g the sides of a full water is 8 high or something, not the full block height, this results in a gap between water layers
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
long otherFlags = 0;
|
|
||||||
otherFlags |= ModelQueries.isTranslucent(selfFluidMetadata)?1L<<33:0;
|
|
||||||
otherFlags |= ModelQueries.isDoubleSided(selfFluidMetadata)?1L<<34:0;
|
|
||||||
mesher.put(a, b, ((long)selfFluidClientId) | (((long) Mapper.getLightId(ModelQueries.faceUsesSelfLighting(selfFluidMetadata, face)?self:facingState))<<16) | ((((long) Mapper.getBiomeId(self))<<24) * (ModelQueries.isBiomeColoured(selfFluidMetadata)?1:0)) | otherFlags);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Returns true if a face was placed
|
|
||||||
private boolean putFaceIfCan(Mesher2D mesher, int face, int opposingFace, long metadata, int clientModelId, int selfBiome, long facingModelId, long facingMetadata, int selfLight, int facingLight, int a, int b) {
|
|
||||||
if (ModelQueries.cullsSame(metadata) && clientModelId == facingModelId) {
|
|
||||||
//If we are facing a block, and we are both the same state, dont render that face
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//If face can be occluded and is occluded from the facing block, then dont render the face
|
|
||||||
if (ModelQueries.faceCanBeOccluded(metadata, face) && ModelQueries.faceOccludes(facingMetadata, opposingFace)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
long otherFlags = 0;
|
|
||||||
otherFlags |= ModelQueries.isTranslucent(metadata)?1L<<33:0;
|
|
||||||
otherFlags |= ModelQueries.isDoubleSided(metadata)?1L<<34:0;
|
|
||||||
mesher.put(a, b, ((long)clientModelId) | (((long) Mapper.getLightId(ModelQueries.faceUsesSelfLighting(metadata, face)?selfLight:facingLight))<<16) | (ModelQueries.isBiomeColoured(metadata)?(((long) Mapper.getBiomeId(selfBiome))<<24):0) | otherFlags);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getMeshletHoldingCount(int quads, int quadsPerMeshlet, int meshletSize) {
|
|
||||||
return ((quads+(quadsPerMeshlet-1))/quadsPerMeshlet)*meshletSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int alignUp(int n, int alignment) {
|
|
||||||
return (n + alignment - 1) & -alignment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,6 +20,14 @@ import java.util.Map;
|
|||||||
public class RenderDataFactory45 {
|
public class RenderDataFactory45 {
|
||||||
private static final boolean VERIFY_MESHING = VoxyCommon.isVerificationFlagOn("verifyMeshing");
|
private static final boolean VERIFY_MESHING = VoxyCommon.isVerificationFlagOn("verifyMeshing");
|
||||||
|
|
||||||
|
//TODO: MAKE a render cache that caches each WorldSection directional face generation, cause then can just pull that directly
|
||||||
|
// instead of needing to regen the entire thing
|
||||||
|
|
||||||
|
//Ok so the idea for fluid rendering is to make it use a seperate mesher and use a different code path for it
|
||||||
|
// since fluid states are explicitly overlays over the base block
|
||||||
|
// can do funny stuff like double rendering
|
||||||
|
|
||||||
|
|
||||||
private final WorldEngine world;
|
private final WorldEngine world;
|
||||||
private final ModelFactory modelMan;
|
private final ModelFactory modelMan;
|
||||||
|
|
||||||
@@ -121,8 +129,11 @@ public class RenderDataFactory45 {
|
|||||||
long quad = data | Integer.toUnsignedLong(encodedPosition);
|
long quad = data | Integer.toUnsignedLong(encodedPosition);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
MemoryUtil.memPutLong(RenderDataFactory45.this.directionalQuadBufferPtr + (RenderDataFactory45.this.directionalQuadCounters[face]++)*8L + face*8L*(1<<16), quad);
|
MemoryUtil.memPutLong(RenderDataFactory45.this.directionalQuadBufferPtr + (RenderDataFactory45.this.directionalQuadCounters[face]++)*8L + face*8L*(1<<16), quad);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Update AABB bounds
|
//Update AABB bounds
|
||||||
if (axis == 0) {//Y
|
if (axis == 0) {//Y
|
||||||
RenderDataFactory45.this.minY = Math.min(RenderDataFactory45.this.minY, auxPos);
|
RenderDataFactory45.this.minY = Math.min(RenderDataFactory45.this.minY, auxPos);
|
||||||
@@ -157,6 +168,7 @@ public class RenderDataFactory45 {
|
|||||||
|
|
||||||
|
|
||||||
private final Mesher blockMesher = new Mesher();
|
private final Mesher blockMesher = new Mesher();
|
||||||
|
private final Mesher seondaryblockMesher = new Mesher();//Used for dual non-opaque geometry
|
||||||
|
|
||||||
public RenderDataFactory45(WorldEngine world, ModelFactory modelManager, boolean emitMeshlets) {
|
public RenderDataFactory45(WorldEngine world, ModelFactory modelManager, boolean emitMeshlets) {
|
||||||
this.world = world;
|
this.world = world;
|
||||||
@@ -277,7 +289,8 @@ public class RenderDataFactory45 {
|
|||||||
private void generateYZOpaqueInnerGeometry(int axis) {
|
private void generateYZOpaqueInnerGeometry(int axis) {
|
||||||
for (int layer = 0; layer < 31; layer++) {
|
for (int layer = 0; layer < 31; layer++) {
|
||||||
this.blockMesher.auxiliaryPosition = layer;
|
this.blockMesher.auxiliaryPosition = layer;
|
||||||
for (int other = 0; other < 32; other++) {//TODO: need to do the faces that border sections
|
int cSkip = 0;
|
||||||
|
for (int other = 0; other < 32; other++) {
|
||||||
int pidx = axis==0 ?(layer*32+other):(other*32+layer);
|
int pidx = axis==0 ?(layer*32+other):(other*32+layer);
|
||||||
int skipAmount = axis==0?32:1;
|
int skipAmount = axis==0?32:1;
|
||||||
|
|
||||||
@@ -286,10 +299,13 @@ public class RenderDataFactory45 {
|
|||||||
|
|
||||||
int msk = current ^ next;
|
int msk = current ^ next;
|
||||||
if (msk == 0) {
|
if (msk == 0) {
|
||||||
this.blockMesher.skip(32);
|
cSkip += 32;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.blockMesher.skip(cSkip);
|
||||||
|
cSkip = 0;
|
||||||
|
|
||||||
//TODO: For boarder sections, should NOT EMIT neighbors faces
|
//TODO: For boarder sections, should NOT EMIT neighbors faces
|
||||||
int faceForwardMsk = msk & current;
|
int faceForwardMsk = msk & current;
|
||||||
int cIdx = -1;
|
int cIdx = -1;
|
||||||
@@ -321,6 +337,7 @@ public class RenderDataFactory45 {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.blockMesher.endRow();
|
this.blockMesher.endRow();
|
||||||
}
|
}
|
||||||
this.blockMesher.finish();
|
this.blockMesher.finish();
|
||||||
@@ -333,14 +350,18 @@ public class RenderDataFactory45 {
|
|||||||
for (int side = 0; side < 2; side++) {//-, +
|
for (int side = 0; side < 2; side++) {//-, +
|
||||||
int layer = side == 0 ? 0 : 31;
|
int layer = side == 0 ? 0 : 31;
|
||||||
this.blockMesher.auxiliaryPosition = layer;
|
this.blockMesher.auxiliaryPosition = layer;
|
||||||
|
int cSkips = 0;
|
||||||
for (int other = 0; other < 32; other++) {
|
for (int other = 0; other < 32; other++) {
|
||||||
int pidx = axis == 0 ? (layer * 32 + other) : (other * 32 + layer);
|
int pidx = axis == 0 ? (layer * 32 + other) : (other * 32 + layer);
|
||||||
int msk = this.opaqueMasks[pidx];
|
int msk = this.opaqueMasks[pidx];
|
||||||
if (msk == 0) {
|
if (msk == 0) {
|
||||||
this.blockMesher.skip(32);
|
cSkips += 32;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.blockMesher.skip(cSkips);
|
||||||
|
cSkips = 0;
|
||||||
|
|
||||||
int cIdx = -1;
|
int cIdx = -1;
|
||||||
while (msk != 0) {
|
while (msk != 0) {
|
||||||
int index = Integer.numberOfTrailingZeros(msk);//Is also the x-axis index
|
int index = Integer.numberOfTrailingZeros(msk);//Is also the x-axis index
|
||||||
@@ -384,6 +405,73 @@ public class RenderDataFactory45 {
|
|||||||
this.blockMesher.doAuxiliaryFaceOffset = true;
|
this.blockMesher.doAuxiliaryFaceOffset = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void generateYZNonOpaqueInnerGeometry(int axis) {
|
||||||
|
//Note: think is ok to just reuse.. blockMesher
|
||||||
|
this.seondaryblockMesher.doAuxiliaryFaceOffset = false;
|
||||||
|
this.blockMesher.axis = axis;
|
||||||
|
this.seondaryblockMesher.axis = axis;
|
||||||
|
for (int layer = 0; layer < 32; layer++) {//(should be 1->31, then have outer face mesher)
|
||||||
|
this.blockMesher.auxiliaryPosition = layer;
|
||||||
|
this.seondaryblockMesher.auxiliaryPosition = layer;
|
||||||
|
int cSkip = 0;
|
||||||
|
for (int other = 0; other < 32; other++) {//TODO: need to do the faces that border sections
|
||||||
|
int pidx = axis == 0 ? (layer * 32 + other) : (other * 32 + layer);
|
||||||
|
|
||||||
|
int msk = this.nonOpaqueMasks[pidx];
|
||||||
|
|
||||||
|
if (msk == 0) {
|
||||||
|
cSkip += 32;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.blockMesher.skip(cSkip);
|
||||||
|
this.seondaryblockMesher.skip(cSkip);
|
||||||
|
cSkip = 0;
|
||||||
|
|
||||||
|
int cIdx = -1;
|
||||||
|
while (msk != 0) {
|
||||||
|
int index = Integer.numberOfTrailingZeros(msk);//Is also the x-axis index
|
||||||
|
int delta = index - cIdx - 1;
|
||||||
|
cIdx = index; //index--;
|
||||||
|
if (delta != 0) {
|
||||||
|
this.blockMesher.skip(delta);
|
||||||
|
this.seondaryblockMesher.skip(delta);
|
||||||
|
}
|
||||||
|
msk &= ~Integer.lowestOneBit(msk);
|
||||||
|
|
||||||
|
{
|
||||||
|
int idx = index + (pidx * 32);
|
||||||
|
|
||||||
|
//TODO: swap this out for something not getting the next entry
|
||||||
|
long A = this.sectionData[idx * 2];
|
||||||
|
long B = this.sectionData[idx * 2+1];
|
||||||
|
if (ModelQueries.isTranslucent(B)) {
|
||||||
|
this.blockMesher.putNext(0);
|
||||||
|
this.seondaryblockMesher.putNext(0);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Example thing thats just wrong but as example
|
||||||
|
this.blockMesher.putNext((long) (false ? 0L : 1L) |
|
||||||
|
((A & 0xFFFFL) << 26) |
|
||||||
|
(((0xFFL) & 0xFF) << 55) |
|
||||||
|
((A&(0x1FFL<<24))<<(46-24))
|
||||||
|
);
|
||||||
|
this.seondaryblockMesher.putNext((long) (true ? 0L : 1L) |
|
||||||
|
((A & 0xFFFFL) << 26) |
|
||||||
|
(((0xFFL) & 0xFF) << 55) |
|
||||||
|
((A&(0x1FFL<<24))<<(46-24))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.blockMesher.endRow();
|
||||||
|
this.seondaryblockMesher.endRow();
|
||||||
|
}
|
||||||
|
this.blockMesher.finish();
|
||||||
|
this.seondaryblockMesher.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void generateYZFaces() {
|
private void generateYZFaces() {
|
||||||
for (int axis = 0; axis < 2; axis++) {//Y then Z
|
for (int axis = 0; axis < 2; axis++) {//Y then Z
|
||||||
this.blockMesher.axis = axis;
|
this.blockMesher.axis = axis;
|
||||||
@@ -391,54 +479,7 @@ public class RenderDataFactory45 {
|
|||||||
this.generateYZOpaqueInnerGeometry(axis);
|
this.generateYZOpaqueInnerGeometry(axis);
|
||||||
this.generateYZOpaqueOuterGeometry(axis);
|
this.generateYZOpaqueOuterGeometry(axis);
|
||||||
|
|
||||||
|
//this.generateYZNonOpaqueInnerGeometry(axis);
|
||||||
if (false) {//Non fully opaque geometry
|
|
||||||
//Note: think is ok to just reuse.. blockMesher
|
|
||||||
this.blockMesher.axis = axis;
|
|
||||||
for (int layer = 0; layer < 32; layer++) {
|
|
||||||
this.blockMesher.auxiliaryPosition = layer;
|
|
||||||
for (int other = 0; other < 32; other++) {//TODO: need to do the faces that border sections
|
|
||||||
int pidx = axis == 0 ? (layer * 32 + other) : (other * 32 + layer);
|
|
||||||
|
|
||||||
int msk = this.nonOpaqueMasks[pidx];
|
|
||||||
|
|
||||||
if (msk == 0) {
|
|
||||||
this.blockMesher.skip(32);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int cIdx = -1;
|
|
||||||
while (msk != 0) {
|
|
||||||
int index = Integer.numberOfTrailingZeros(msk);//Is also the x-axis index
|
|
||||||
int delta = index - cIdx - 1;
|
|
||||||
cIdx = index; //index--;
|
|
||||||
if (delta != 0) this.blockMesher.skip(delta);
|
|
||||||
msk &= ~Integer.lowestOneBit(msk);
|
|
||||||
|
|
||||||
{
|
|
||||||
int idx = index + (pidx * 32);
|
|
||||||
|
|
||||||
//TODO: swap this out for something not getting the next entry
|
|
||||||
long A = this.sectionData[idx * 2];
|
|
||||||
long B = this.sectionData[idx * 2+1];
|
|
||||||
if (ModelQueries.isFluid(B)) {
|
|
||||||
this.blockMesher.putNext(0);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
//Example thing thats just wrong but as example
|
|
||||||
this.blockMesher.putNext((long) (false ? 0L : 1L) |
|
|
||||||
((A & 0xFFFFL) << 26) |
|
|
||||||
(((0xFFL) & 0xFF) << 55) |
|
|
||||||
((A&(0x1FFL<<24))<<(46-24))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.blockMesher.endRow();
|
|
||||||
}
|
|
||||||
this.blockMesher.finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,6 +674,11 @@ public class RenderDataFactory45 {
|
|||||||
mb.doAuxiliaryFaceOffset = true;
|
mb.doAuxiliaryFaceOffset = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void generateXNonOpaqueInnerGeometry() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void generateXFaces() {
|
private void generateXFaces() {
|
||||||
this.generateXOpaqueInnerGeometry();
|
this.generateXOpaqueInnerGeometry();
|
||||||
this.generateXOuterOpaqueGeometry();
|
this.generateXOuterOpaqueGeometry();
|
||||||
@@ -668,6 +714,8 @@ public class RenderDataFactory45 {
|
|||||||
{//Reset all the block meshes
|
{//Reset all the block meshes
|
||||||
this.blockMesher.reset();
|
this.blockMesher.reset();
|
||||||
this.blockMesher.doAuxiliaryFaceOffset = true;
|
this.blockMesher.doAuxiliaryFaceOffset = true;
|
||||||
|
this.seondaryblockMesher.reset();
|
||||||
|
this.seondaryblockMesher.doAuxiliaryFaceOffset = true;
|
||||||
for (var mesher : this.xAxisMeshers) {
|
for (var mesher : this.xAxisMeshers) {
|
||||||
mesher.reset();
|
mesher.reset();
|
||||||
mesher.doAuxiliaryFaceOffset = true;
|
mesher.doAuxiliaryFaceOffset = true;
|
||||||
|
|||||||
@@ -1,725 +0,0 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.building;
|
|
||||||
|
|
||||||
import me.cortex.voxy.client.core.gl.Capabilities;
|
|
||||||
import me.cortex.voxy.client.core.model.ModelFactory;
|
|
||||||
import me.cortex.voxy.client.core.model.ModelQueries;
|
|
||||||
import me.cortex.voxy.client.core.util.Mesher2D;
|
|
||||||
import me.cortex.voxy.client.core.util.ScanMesher2D;
|
|
||||||
import me.cortex.voxy.common.util.MemoryBuffer;
|
|
||||||
import me.cortex.voxy.common.util.UnsafeUtil;
|
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
|
||||||
import me.cortex.voxy.common.world.WorldSection;
|
|
||||||
import me.cortex.voxy.common.world.other.Mapper;
|
|
||||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
|
||||||
import org.lwjgl.system.MemoryUtil;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
|
|
||||||
public class RenderDataFactory5 {
|
|
||||||
private static final boolean VERIFY_MESHING = VoxyCommon.isVerificationFlagOn("verifyMeshing");
|
|
||||||
|
|
||||||
private final WorldEngine world;
|
|
||||||
private final ModelFactory modelMan;
|
|
||||||
|
|
||||||
//private final long[] sectionData = new long[32*32*32*2];
|
|
||||||
private final long[] sectionData = new long[32*32*32*2];
|
|
||||||
|
|
||||||
private final int[] opaqueMasks = new int[32*32];
|
|
||||||
private final int[] nonOpaqueMasks = new int[32*32];
|
|
||||||
|
|
||||||
|
|
||||||
//TODO: emit directly to memory buffer instead of long arrays
|
|
||||||
|
|
||||||
//Each axis gets a max quad count of 2^16 (65536 quads) since that is the max the basic geometry manager can handle
|
|
||||||
private final MemoryBuffer directionalQuadBuffer = new MemoryBuffer(6*(8*(1<<16)));
|
|
||||||
private final long directionalQuadBufferPtr = this.directionalQuadBuffer.address;
|
|
||||||
private final int[] directionalQuadCounters = new int[6];//Maybe change to short? (or long /w raw pointers)
|
|
||||||
|
|
||||||
|
|
||||||
private int minX;
|
|
||||||
private int minY;
|
|
||||||
private int minZ;
|
|
||||||
private int maxX;
|
|
||||||
private int maxY;
|
|
||||||
private int maxZ;
|
|
||||||
|
|
||||||
private int quadCount = 0;
|
|
||||||
|
|
||||||
|
|
||||||
//Wont work for double sided quads
|
|
||||||
private final class Mesher extends ScanMesher2D {
|
|
||||||
public int auxiliaryPosition = 0;
|
|
||||||
public boolean doAuxiliaryFaceOffset = true;
|
|
||||||
public int axis = 0;
|
|
||||||
|
|
||||||
//Note x, z are in top right
|
|
||||||
@Override
|
|
||||||
protected void emitQuad(int x, int z, int length, int width, long data) {
|
|
||||||
RenderDataFactory5.this.quadCount++;
|
|
||||||
|
|
||||||
if (VERIFY_MESHING) {
|
|
||||||
if (length<1||length>16) {
|
|
||||||
throw new IllegalStateException("length out of bounds: " + length);
|
|
||||||
}
|
|
||||||
if (width<1||width>16) {
|
|
||||||
throw new IllegalStateException("width out of bounds: " + width);
|
|
||||||
}
|
|
||||||
if (x<0||x>31) {
|
|
||||||
throw new IllegalStateException("x out of bounds: " + x);
|
|
||||||
}
|
|
||||||
if (z<0||z>31) {
|
|
||||||
throw new IllegalStateException("z out of bounds: " + z);
|
|
||||||
}
|
|
||||||
if (x-(length-1)<0 || z-(width-1)<0) {
|
|
||||||
throw new IllegalStateException("dim out of bounds: " + (x-(length-1))+", " + (z-(width-1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
x -= length-1;
|
|
||||||
z -= width-1;
|
|
||||||
|
|
||||||
if (this.axis == 2) {
|
|
||||||
//Need to swizzle the data if on x axis
|
|
||||||
int tmp = x;
|
|
||||||
x = z;
|
|
||||||
z = tmp;
|
|
||||||
|
|
||||||
tmp = length;
|
|
||||||
length = width;
|
|
||||||
width = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Lower 26 bits can be auxiliary data since that is where quad position information goes;
|
|
||||||
int auxData = (int) (data&((1<<26)-1));
|
|
||||||
int faceSide = auxData&1;
|
|
||||||
data &= ~(data&((1<<26)-1));
|
|
||||||
|
|
||||||
final int axis = this.axis;
|
|
||||||
int face = (axis<<1)|faceSide;
|
|
||||||
int encodedPosition = face;
|
|
||||||
|
|
||||||
//Shift up if is negative axis
|
|
||||||
int auxPos = this.auxiliaryPosition;
|
|
||||||
auxPos += this.doAuxiliaryFaceOffset?(1-faceSide):0;//Shift
|
|
||||||
|
|
||||||
if (VERIFY_MESHING) {
|
|
||||||
if (auxPos > 31) {
|
|
||||||
throw new IllegalStateException("OOB face: " + auxPos + ", " + faceSide);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
encodedPosition |= ((width - 1) << 7) | ((length - 1) << 3);
|
|
||||||
|
|
||||||
encodedPosition |= x << (axis==2?16:21);
|
|
||||||
encodedPosition |= z << (axis==1?16:11);
|
|
||||||
encodedPosition |= auxPos << (axis==0?16:(axis==1?11:21));
|
|
||||||
|
|
||||||
long quad = data | encodedPosition;
|
|
||||||
|
|
||||||
|
|
||||||
MemoryUtil.memPutLong(RenderDataFactory5.this.directionalQuadBufferPtr + (RenderDataFactory5.this.directionalQuadCounters[face]++)*8L + face*8L*(1<<16), quad);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private final Mesher blockMesher = new Mesher();
|
|
||||||
private final Mesher partiallyOpaqueMesher = new Mesher();
|
|
||||||
|
|
||||||
public RenderDataFactory5(WorldEngine world, ModelFactory modelManager, boolean emitMeshlets) {
|
|
||||||
this.world = world;
|
|
||||||
this.modelMan = modelManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//TODO: MAKE a render cache that caches each WorldSection directional face generation, cause then can just pull that directly
|
|
||||||
// instead of needing to regen the entire thing
|
|
||||||
|
|
||||||
|
|
||||||
//Ok so the idea for fluid rendering is to make it use a seperate mesher and use a different code path for it
|
|
||||||
// since fluid states are explicitly overlays over the base block
|
|
||||||
// can do funny stuff like double rendering
|
|
||||||
|
|
||||||
private static final boolean USE_UINT64 = Capabilities.INSTANCE.INT64_t;
|
|
||||||
public static final int QUADS_PER_MESHLET = 14;
|
|
||||||
private static void writePos(long ptr, long pos) {
|
|
||||||
if (USE_UINT64) {
|
|
||||||
MemoryUtil.memPutLong(ptr, pos);
|
|
||||||
} else {
|
|
||||||
MemoryUtil.memPutInt(ptr, (int) (pos>>32));
|
|
||||||
MemoryUtil.memPutInt(ptr + 4, (int)pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void prepareSectionData() {
|
|
||||||
final var sectionData = this.sectionData;
|
|
||||||
int opaque = 0;
|
|
||||||
int notEmpty = 0;
|
|
||||||
|
|
||||||
int neighborAcquireMsk = 0;
|
|
||||||
for (int i = 0; i < 32*32*32;) {
|
|
||||||
long block = sectionData[i + 32 * 32 * 32];//Get the block mapping
|
|
||||||
|
|
||||||
int modelId = this.modelMan.getModelId(Mapper.getBlockId(block));
|
|
||||||
long modelMetadata = this.modelMan.getModelMetadataFromClientId(modelId);
|
|
||||||
|
|
||||||
sectionData[i * 2] = modelId | ((long) (Mapper.getLightId(block)) << 16) | (ModelQueries.isBiomeColoured(modelMetadata)?(((long) (Mapper.getBiomeId(block))) << 24):0);
|
|
||||||
sectionData[i * 2 + 1] = modelMetadata;
|
|
||||||
|
|
||||||
boolean isFullyOpaque = ModelQueries.isFullyOpaque(modelMetadata);
|
|
||||||
opaque |= (isFullyOpaque ? 1:0) << (i & 31);
|
|
||||||
notEmpty |= (modelId!=0 ? 1:0) << (i & 31);
|
|
||||||
|
|
||||||
//TODO: here also do bitmask of what neighboring sections are needed to compute (may be getting rid of this in future)
|
|
||||||
|
|
||||||
//Do increment here
|
|
||||||
i++;
|
|
||||||
|
|
||||||
if ((i & 31) == 0) {
|
|
||||||
this.opaqueMasks[(i >> 5) - 1] = opaque;
|
|
||||||
this.nonOpaqueMasks[(i >> 5) - 1] = notEmpty^opaque;
|
|
||||||
opaque = 0;
|
|
||||||
notEmpty = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void generateYZFaces() {
|
|
||||||
for (int axis = 0; axis < 2; axis++) {
|
|
||||||
this.blockMesher.axis = axis;
|
|
||||||
for (int layer = 0; layer < 31; layer++) {
|
|
||||||
this.blockMesher.auxiliaryPosition = layer;
|
|
||||||
for (int other = 0; other < 32; other++) {//TODO: need to do the faces that border sections
|
|
||||||
int pidx = axis==0 ?(layer*32+other):(other*32+layer);
|
|
||||||
int skipAmount = axis==0?32:1;
|
|
||||||
|
|
||||||
int current = this.opaqueMasks[pidx];
|
|
||||||
int next = this.opaqueMasks[pidx + skipAmount];
|
|
||||||
|
|
||||||
int msk = current ^ next;
|
|
||||||
if (msk == 0) {
|
|
||||||
this.blockMesher.skip(32);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: For boarder sections, should NOT EMIT neighbors faces
|
|
||||||
int faceForwardMsk = msk & current;
|
|
||||||
int cIdx = -1;
|
|
||||||
while (msk != 0) {
|
|
||||||
int index = Integer.numberOfTrailingZeros(msk);//Is also the x-axis index
|
|
||||||
int delta = index - cIdx - 1;
|
|
||||||
cIdx = index; //index--;
|
|
||||||
if (delta != 0) this.blockMesher.skip(delta);
|
|
||||||
msk &= ~Integer.lowestOneBit(msk);
|
|
||||||
|
|
||||||
int facingForward = ((faceForwardMsk >> index) & 1);
|
|
||||||
|
|
||||||
{
|
|
||||||
int idx = index + (pidx*32);
|
|
||||||
|
|
||||||
//TODO: swap this out for something not getting the next entry
|
|
||||||
long A = this.sectionData[idx * 2];
|
|
||||||
long B = this.sectionData[(idx + skipAmount * 32) * 2];
|
|
||||||
|
|
||||||
//Flip data with respect to facing direction
|
|
||||||
long selfModel = facingForward == 1 ? A : B;
|
|
||||||
long nextModel = facingForward == 1 ? B : A;
|
|
||||||
|
|
||||||
//Example thing thats just wrong but as example
|
|
||||||
this.blockMesher.putNext(((long) facingForward) |//Facing
|
|
||||||
((selfModel & 0xFFFF) << 26) | //ModelId
|
|
||||||
(((nextModel>>16)&0xFF) << 55) |//Lighting
|
|
||||||
((selfModel&(0x1FFL<<24))<<(46-24))//biomeId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.blockMesher.endRow();
|
|
||||||
}
|
|
||||||
this.blockMesher.finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (true) {
|
|
||||||
this.blockMesher.doAuxiliaryFaceOffset = false;
|
|
||||||
//Hacky generate section side faces (without check neighbor section)
|
|
||||||
for (int side = 0; side < 2; side++) {
|
|
||||||
int layer = side == 0 ? 0 : 31;
|
|
||||||
this.blockMesher.auxiliaryPosition = layer;
|
|
||||||
for (int other = 0; other < 32; other++) {
|
|
||||||
int pidx = axis == 0 ? (layer * 32 + other) : (other * 32 + layer);
|
|
||||||
int msk = this.opaqueMasks[pidx];
|
|
||||||
if (msk == 0) {
|
|
||||||
this.blockMesher.skip(32);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cIdx = -1;
|
|
||||||
while (msk != 0) {
|
|
||||||
int index = Integer.numberOfTrailingZeros(msk);//Is also the x-axis index
|
|
||||||
int delta = index - cIdx - 1;
|
|
||||||
cIdx = index; //index--;
|
|
||||||
if (delta != 0) this.blockMesher.skip(delta);
|
|
||||||
msk &= ~Integer.lowestOneBit(msk);
|
|
||||||
|
|
||||||
{
|
|
||||||
int idx = index + (pidx * 32);
|
|
||||||
|
|
||||||
//TODO: swap this out for something not getting the next entry
|
|
||||||
long A = this.sectionData[idx * 2];
|
|
||||||
|
|
||||||
//Example thing thats just wrong but as example
|
|
||||||
this.blockMesher.putNext((long) (side == 0 ? 0L : 1L) |
|
|
||||||
((A & 0xFFFFL) << 26) |
|
|
||||||
(((0xFFL) & 0xFF) << 55) |
|
|
||||||
((A&(0x1FFL<<24))<<(46-24))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.blockMesher.endRow();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.blockMesher.finish();
|
|
||||||
}
|
|
||||||
this.blockMesher.doAuxiliaryFaceOffset = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
{//Non fully opaque geometry
|
|
||||||
//Note: think is ok to just reuse.. blockMesher
|
|
||||||
this.blockMesher.axis = axis;
|
|
||||||
for (int layer = 0; layer < 32; layer++) {
|
|
||||||
this.blockMesher.auxiliaryPosition = layer;
|
|
||||||
for (int other = 0; other < 32; other++) {//TODO: need to do the faces that border sections
|
|
||||||
int pidx = axis == 0 ? (layer * 32 + other) : (other * 32 + layer);
|
|
||||||
|
|
||||||
int msk = this.nonOpaqueMasks[pidx];
|
|
||||||
|
|
||||||
if (msk == 0) {
|
|
||||||
this.blockMesher.skip(32);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int cIdx = -1;
|
|
||||||
while (msk != 0) {
|
|
||||||
int index = Integer.numberOfTrailingZeros(msk);//Is also the x-axis index
|
|
||||||
int delta = index - cIdx - 1;
|
|
||||||
cIdx = index; //index--;
|
|
||||||
if (delta != 0) this.blockMesher.skip(delta);
|
|
||||||
msk &= ~Integer.lowestOneBit(msk);
|
|
||||||
|
|
||||||
{
|
|
||||||
int idx = index + (pidx * 32);
|
|
||||||
|
|
||||||
//TODO: swap this out for something not getting the next entry
|
|
||||||
long A = this.sectionData[idx * 2];
|
|
||||||
|
|
||||||
//Example thing thats just wrong but as example
|
|
||||||
this.blockMesher.putNext((long) (false ? 0L : 1L) |
|
|
||||||
((A & 0xFFFFL) << 26) |
|
|
||||||
(((0xFFL) & 0xFF) << 55) |
|
|
||||||
((A&(0x1FFL<<24))<<(46-24))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.blockMesher.endRow();
|
|
||||||
}
|
|
||||||
this.blockMesher.finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private final Mesher[] xAxisMeshers = new Mesher[32];
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 32; i++) {
|
|
||||||
var mesher = new Mesher();
|
|
||||||
mesher.auxiliaryPosition = i;
|
|
||||||
mesher.axis = 2;//X axis
|
|
||||||
this.xAxisMeshers[i] = mesher;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final long X_I_MSK = 0x4210842108421L;
|
|
||||||
private void generateXFaces() {
|
|
||||||
for (int y = 0; y < 32; y++) {
|
|
||||||
long sumA = 0;
|
|
||||||
long sumB = 0;
|
|
||||||
long sumC = 0;
|
|
||||||
int partialHasCount = -1;
|
|
||||||
int msk = 0;
|
|
||||||
for (int z = 0; z < 32; z++) {
|
|
||||||
int lMsk = this.opaqueMasks[y*32+z];
|
|
||||||
msk = (lMsk^(lMsk>>>1));
|
|
||||||
msk &= -1>>>1;//Remove top bit as we dont actually know/have the data for that slice
|
|
||||||
|
|
||||||
//Always increment cause can do funny trick (i.e. -1 on skip amount)
|
|
||||||
sumA += X_I_MSK;
|
|
||||||
sumB += X_I_MSK;
|
|
||||||
sumC += X_I_MSK;
|
|
||||||
|
|
||||||
partialHasCount &= ~msk;
|
|
||||||
|
|
||||||
if (z == 30 && partialHasCount != 0) {//Hackfix for incremental count overflow issue
|
|
||||||
int cmsk = partialHasCount;
|
|
||||||
while (cmsk!=0) {
|
|
||||||
int index = Integer.numberOfTrailingZeros(cmsk);
|
|
||||||
cmsk &= ~Integer.lowestOneBit(cmsk);
|
|
||||||
//TODO: fixme! check this is correct or if should be 30
|
|
||||||
this.xAxisMeshers[index].skip(31);
|
|
||||||
}
|
|
||||||
//Clear the sum
|
|
||||||
sumA &= ~(Long.expand(Integer.toUnsignedLong(partialHasCount), X_I_MSK)*0x1F);
|
|
||||||
sumB &= ~(Long.expand(Integer.toUnsignedLong(partialHasCount)>>11, X_I_MSK)*0x1F);
|
|
||||||
sumC &= ~(Long.expand(Integer.toUnsignedLong(partialHasCount)>>22, X_I_MSK)*0x1F);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msk == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
{//Dont need this as can just increment everything then -1 in mask
|
|
||||||
//Compute and increment skips for indexes
|
|
||||||
long imsk = Integer.toUnsignedLong(~msk);// we only want to increment where there isnt a face
|
|
||||||
sumA += Long.expand(imsk, X_I_MSK);
|
|
||||||
sumB += Long.expand(imsk>>11, X_I_MSK);
|
|
||||||
sumC += Long.expand(imsk>>22, X_I_MSK);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
int faceForwardMsk = msk&lMsk;
|
|
||||||
int iter = msk;
|
|
||||||
while (iter!=0) {
|
|
||||||
int index = Integer.numberOfTrailingZeros(iter);
|
|
||||||
iter &= ~Integer.lowestOneBit(iter);
|
|
||||||
|
|
||||||
var mesher = this.xAxisMeshers[index];
|
|
||||||
|
|
||||||
int skipCount;//Compute the skip count
|
|
||||||
{//TODO: Branch-less
|
|
||||||
//Compute skip and clear
|
|
||||||
if (index<11) {
|
|
||||||
skipCount = (int) (sumA>>(index*5));
|
|
||||||
sumA &= ~(0x1FL<<(index*5));
|
|
||||||
} else if (index<22) {
|
|
||||||
skipCount = (int) (sumB>>((index-11)*5));
|
|
||||||
sumB &= ~(0x1FL<<((index-11)*5));
|
|
||||||
} else {
|
|
||||||
skipCount = (int) (sumC>>((index-22)*5));
|
|
||||||
sumC &= ~(0x1FL<<((index-22)*5));
|
|
||||||
}
|
|
||||||
skipCount &= 0x1F;
|
|
||||||
skipCount--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skipCount != 0) {
|
|
||||||
mesher.skip(skipCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
int facingForward = ((faceForwardMsk>>index)&1);
|
|
||||||
{
|
|
||||||
int idx = index + (z * 32) + (y * 32 * 32);
|
|
||||||
//TODO: swap this out for something not getting the next entry
|
|
||||||
long A = this.sectionData[idx * 2];
|
|
||||||
long B = this.sectionData[(idx + 1) * 2];
|
|
||||||
|
|
||||||
//Flip data with respect to facing direction
|
|
||||||
long selfModel = facingForward==1?A:B;
|
|
||||||
long nextModel = facingForward==1?B:A;
|
|
||||||
|
|
||||||
//Example thing thats just wrong but as example
|
|
||||||
mesher.putNext(((long) facingForward) |//Facing
|
|
||||||
((selfModel & 0xFFFF) << 26) | //ModelId
|
|
||||||
(((nextModel>>16)&0xFF) << 55) |//Lighting
|
|
||||||
((selfModel&(0x1FFL<<24))<<(46-24))//biomeId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Need to skip the remaining entries in the skip array
|
|
||||||
{
|
|
||||||
msk = ~msk;//Invert the mask as we only need to set stuff that isnt 0
|
|
||||||
while (msk!=0) {
|
|
||||||
int index = Integer.numberOfTrailingZeros(msk);
|
|
||||||
msk &= ~Integer.lowestOneBit(msk);
|
|
||||||
int skipCount;
|
|
||||||
if (index < 11) {
|
|
||||||
skipCount = (int) (sumA>>(index*5));
|
|
||||||
} else if (index<22) {
|
|
||||||
skipCount = (int) (sumB>>((index-11)*5));
|
|
||||||
} else {
|
|
||||||
skipCount = (int) (sumC>>((index-22)*5));
|
|
||||||
}
|
|
||||||
skipCount &= 0x1F;
|
|
||||||
|
|
||||||
if (skipCount != 0) {
|
|
||||||
this.xAxisMeshers[index].skip(skipCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Generate the side faces, hackily, using 0 and 1 mesher
|
|
||||||
if (true) {
|
|
||||||
var ma = this.xAxisMeshers[0];
|
|
||||||
var mb = this.xAxisMeshers[31];
|
|
||||||
ma.finish();
|
|
||||||
mb.finish();
|
|
||||||
ma.doAuxiliaryFaceOffset = false;
|
|
||||||
mb.doAuxiliaryFaceOffset = false;
|
|
||||||
for (int y = 0; y < 32; y++) {
|
|
||||||
int skipA = 0;
|
|
||||||
int skipB = 0;
|
|
||||||
for (int z = 0; z < 32; z++) {
|
|
||||||
int i = y*32+z;
|
|
||||||
int msk = this.opaqueMasks[i];
|
|
||||||
if ((msk & 1) != 0) {
|
|
||||||
ma.skip(skipA); skipA = 0;
|
|
||||||
|
|
||||||
long A = this.sectionData[(i<<5) * 2];
|
|
||||||
|
|
||||||
ma.putNext(0L | ((A&0xFFFF)<<26) | (((0xFFL)&0xFF)<<55)|((A&(0x1FFL<<24))<<(46-24)));
|
|
||||||
} else {skipA++;}
|
|
||||||
|
|
||||||
if ((msk & (1<<31)) != 0) {
|
|
||||||
mb.skip(skipB); skipB = 0;
|
|
||||||
|
|
||||||
long A = this.sectionData[(i*32+31) * 2];
|
|
||||||
|
|
||||||
mb.putNext(1L | ((A&0xFFFF)<<26) | (((0xFFL)&0xFF)<<55)|((A&(0x1FFL<<24))<<(46-24)));
|
|
||||||
} else {skipB++;}
|
|
||||||
}
|
|
||||||
ma.skip(skipA);
|
|
||||||
mb.skip(skipB);
|
|
||||||
}
|
|
||||||
ma.finish();
|
|
||||||
mb.finish();
|
|
||||||
ma.doAuxiliaryFaceOffset = true;
|
|
||||||
mb.doAuxiliaryFaceOffset = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var mesher : this.xAxisMeshers) {
|
|
||||||
mesher.finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
private static long createQuad() {
|
|
||||||
((long)clientModelId) | (((long) Mapper.getLightId(ModelQueries.faceUsesSelfLighting(metadata, face)?self:facingState))<<16) | ((((long) Mapper.getBiomeId(self))<<24) * (ModelQueries.isBiomeColoured(metadata)?1:0)) | otherFlags
|
|
||||||
|
|
||||||
|
|
||||||
long data = Integer.toUnsignedLong(array[i*3+1]);
|
|
||||||
data |= ((long) array[i*3+2])<<32;
|
|
||||||
long encodedQuad = Integer.toUnsignedLong(QuadEncoder.encodePosition(face, otherAxis, quad)) | ((data&0xFFFF)<<26) | (((data>>16)&0xFF)<<55) | (((data>>24)&0x1FF)<<46);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
//section is already acquired and gets released by the parent
|
|
||||||
public BuiltSection generateMesh(WorldSection section) {
|
|
||||||
//Copy section data to end of array so that can mutate array while reading safely
|
|
||||||
section.copyDataTo(this.sectionData, 32*32*32);
|
|
||||||
|
|
||||||
this.quadCount = 0;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
Arrays.fill(this.directionalQuadCounters, (short) 0);
|
|
||||||
|
|
||||||
|
|
||||||
this.world.acquire(section.lvl, section.x+1, section.y, section.z).release();
|
|
||||||
this.world.acquire(section.lvl, section.x-1, section.y, section.z).release();
|
|
||||||
|
|
||||||
this.world.acquire(section.lvl, section.x, section.y+1, section.z).release();
|
|
||||||
this.world.acquire(section.lvl, section.x, section.y-1, section.z).release();
|
|
||||||
|
|
||||||
this.world.acquire(section.lvl, section.x, section.y, section.z+1).release();
|
|
||||||
this.world.acquire(section.lvl, section.x, section.y, section.z-1).release();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Prepare everything
|
|
||||||
this.prepareSectionData();
|
|
||||||
|
|
||||||
|
|
||||||
this.generateYZFaces();
|
|
||||||
this.generateXFaces();
|
|
||||||
|
|
||||||
|
|
||||||
//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
|
|
||||||
|
|
||||||
if (this.quadCount == 0) {
|
|
||||||
return BuiltSection.emptyWithChildren(section.key, section.getNonEmptyChildren());
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: FIXME AND OPTIMIZE, get rid of the stupid quad collector bullshit
|
|
||||||
|
|
||||||
int[] offsets = new int[8];
|
|
||||||
var buff = new MemoryBuffer(this.quadCount * 8L);
|
|
||||||
long ptr = buff.address;
|
|
||||||
int coff = 0;
|
|
||||||
|
|
||||||
for (int face = 0; face < 6; face++) {
|
|
||||||
offsets[face + 2] = coff;
|
|
||||||
int size = this.directionalQuadCounters[face];
|
|
||||||
UnsafeUtil.memcpy(this.directionalQuadBufferPtr + (face*(8*(1<<16))), ptr + coff*8L, (size* 8L));
|
|
||||||
coff += size;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int aabb = 0;
|
|
||||||
aabb |= 0;
|
|
||||||
aabb |= 0<<5;
|
|
||||||
aabb |= 0<<10;
|
|
||||||
aabb |= (31)<<15;
|
|
||||||
aabb |= (31)<<20;
|
|
||||||
aabb |= (31)<<25;
|
|
||||||
|
|
||||||
return new BuiltSection(section.key, section.getNonEmptyChildren(), aabb, buff, offsets);
|
|
||||||
|
|
||||||
/*
|
|
||||||
buff = new MemoryBuffer(bufferSize * 8L);
|
|
||||||
long ptr = buff.address;
|
|
||||||
int coff = 0;
|
|
||||||
|
|
||||||
//Ordering is: translucent, double sided quads, directional quads
|
|
||||||
offsets[0] = coff;
|
|
||||||
int size = this.translucentQuadCollector.size();
|
|
||||||
LongArrayList arrayList = this.translucentQuadCollector;
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
long data = arrayList.getLong(i);
|
|
||||||
MemoryUtil.memPutLong(ptr + ((coff++) * 8L), data);
|
|
||||||
}
|
|
||||||
|
|
||||||
offsets[1] = coff;
|
|
||||||
size = this.doubleSidedQuadCollector.size();
|
|
||||||
arrayList = this.doubleSidedQuadCollector;
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
long data = arrayList.getLong(i);
|
|
||||||
MemoryUtil.memPutLong(ptr + ((coff++) * 8L), data);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int face = 0; face < 6; face++) {
|
|
||||||
offsets[face + 2] = coff;
|
|
||||||
final LongArrayList faceArray = this.directionalQuadCollectors[face];
|
|
||||||
size = faceArray.size();
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
long data = faceArray.getLong(i);
|
|
||||||
MemoryUtil.memPutLong(ptr + ((coff++) * 8L), data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
return new BuiltSection(section.key, section.getNonEmptyChildren(), aabb, buff, offsets);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
public void free() {
|
|
||||||
this.directionalQuadBuffer.free();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Returns true if a face was placed
|
|
||||||
private boolean putFluidFaceIfCan(Mesher2D mesher, int face, int opposingFace, long self, long metadata, int selfClientModelId, int selfBlockId, long facingState, long facingMetadata, int facingClientModelId, int a, int b) {
|
|
||||||
int selfFluidClientId = this.modelMan.getFluidClientStateId(selfClientModelId);
|
|
||||||
long selfFluidMetadata = this.modelMan.getModelMetadataFromClientId(selfFluidClientId);
|
|
||||||
|
|
||||||
int facingFluidClientId = -1;
|
|
||||||
if (ModelQueries.containsFluid(facingMetadata)) {
|
|
||||||
facingFluidClientId = this.modelMan.getFluidClientStateId(facingClientModelId);
|
|
||||||
}
|
|
||||||
|
|
||||||
//If both of the states are the same, then dont render the fluid face
|
|
||||||
if (selfFluidClientId == facingFluidClientId) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (facingFluidClientId != -1) {
|
|
||||||
//TODO: OPTIMIZE
|
|
||||||
if (this.world.getMapper().getBlockStateFromBlockId(selfBlockId).getBlock() == this.world.getMapper().getBlockStateFromBlockId(Mapper.getBlockId(facingState)).getBlock()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (ModelQueries.faceOccludes(facingMetadata, opposingFace)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//if the model has a fluid state but is not a liquid need to see if the solid state had a face rendered and that face is occluding, if so, dont render the fluid state face
|
|
||||||
if ((!ModelQueries.isFluid(metadata)) && ModelQueries.faceOccludes(metadata, face)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//TODO:FIXME SOMEHOW THIS IS CRITICAL!!!!!!!!!!!!!!!!!!
|
|
||||||
// so there is one more issue need to be fixed, if water is layered ontop of eachother, the side faces depend on the water state ontop
|
|
||||||
// this has been hackfixed in the model texture bakery but a proper solution that doesnt explode the sides of the water textures needs to be done
|
|
||||||
// the issue is that the fluid rendering depends on the up state aswell not just the face state which is really really painful to account for
|
|
||||||
// e.g the sides of a full water is 8 high or something, not the full block height, this results in a gap between water layers
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
long otherFlags = 0;
|
|
||||||
otherFlags |= ModelQueries.isTranslucent(selfFluidMetadata)?1L<<33:0;
|
|
||||||
otherFlags |= ModelQueries.isDoubleSided(selfFluidMetadata)?1L<<34:0;
|
|
||||||
mesher.put(a, b, ((long)selfFluidClientId) | (((long) Mapper.getLightId(ModelQueries.faceUsesSelfLighting(selfFluidMetadata, face)?self:facingState))<<16) | ((((long) Mapper.getBiomeId(self))<<24) * (ModelQueries.isBiomeColoured(selfFluidMetadata)?1:0)) | otherFlags);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Returns true if a face was placed
|
|
||||||
private boolean putFaceIfCan(Mesher2D mesher, int face, int opposingFace, long metadata, int clientModelId, int selfBiome, long facingModelId, long facingMetadata, int selfLight, int facingLight, int a, int b) {
|
|
||||||
if (ModelQueries.cullsSame(metadata) && clientModelId == facingModelId) {
|
|
||||||
//If we are facing a block, and we are both the same state, dont render that face
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//If face can be occluded and is occluded from the facing block, then dont render the face
|
|
||||||
if (ModelQueries.faceCanBeOccluded(metadata, face) && ModelQueries.faceOccludes(facingMetadata, opposingFace)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
long otherFlags = 0;
|
|
||||||
otherFlags |= ModelQueries.isTranslucent(metadata)?1L<<33:0;
|
|
||||||
otherFlags |= ModelQueries.isDoubleSided(metadata)?1L<<34:0;
|
|
||||||
mesher.put(a, b, ((long)clientModelId) | (((long) Mapper.getLightId(ModelQueries.faceUsesSelfLighting(metadata, face)?selfLight:facingLight))<<16) | (ModelQueries.isBiomeColoured(metadata)?(((long) Mapper.getBiomeId(selfBiome))<<24):0) | otherFlags);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getMeshletHoldingCount(int quads, int quadsPerMeshlet, int meshletSize) {
|
|
||||||
return ((quads+(quadsPerMeshlet-1))/quadsPerMeshlet)*meshletSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int alignUp(int n, int alignment) {
|
|
||||||
return (n + alignment - 1) & -alignment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -216,8 +216,9 @@ public class HierarchicalOcclusionTraverser {
|
|||||||
//TODO: Move the first queue to a persistent list so its not updated every frame
|
//TODO: Move the first queue to a persistent list so its not updated every frame
|
||||||
|
|
||||||
ptr = UploadStream.INSTANCE.upload(this.scratchQueueA, 0, 4L*initialQueueSize);
|
ptr = UploadStream.INSTANCE.upload(this.scratchQueueA, 0, 4L*initialQueueSize);
|
||||||
for (int i = 0; i < initialQueueSize; i++) {
|
int i = 0;
|
||||||
MemoryUtil.memPutInt(ptr + 4L*i, this.nodeManager.getTopLevelNodeIds().getInt(i));
|
for (int node : this.nodeManager.getTopLevelNodeIds()) {
|
||||||
|
MemoryUtil.memPutInt(ptr + 4L*(i++), node);
|
||||||
}
|
}
|
||||||
|
|
||||||
UploadStream.INSTANCE.commit();
|
UploadStream.INSTANCE.commit();
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.hierachical;
|
package me.cortex.voxy.client.core.rendering.hierachical;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||||
@@ -87,7 +86,7 @@ public class NodeManager {
|
|||||||
private final Long2IntOpenHashMap activeSectionMap = new Long2IntOpenHashMap();
|
private final Long2IntOpenHashMap activeSectionMap = new Long2IntOpenHashMap();
|
||||||
private final NodeStore nodeData;
|
private final NodeStore nodeData;
|
||||||
public final int maxNodeCount;
|
public final int maxNodeCount;
|
||||||
private final IntArrayList topLevelNodeIds = new IntArrayList();
|
private final IntOpenHashSet topLevelNodeIds = new IntOpenHashSet();
|
||||||
private final LongOpenHashSet topLevelNodes = new LongOpenHashSet();
|
private final LongOpenHashSet topLevelNodes = new LongOpenHashSet();
|
||||||
private int activeNodeRequestCount;
|
private int activeNodeRequestCount;
|
||||||
|
|
||||||
@@ -133,24 +132,26 @@ public class NodeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void removeTopLevelNode(long pos) {
|
public void removeTopLevelNode(long pos) {
|
||||||
|
if (!this.topLevelNodes.remove(pos)) {
|
||||||
|
throw new IllegalStateException("Position not in top level map");
|
||||||
|
}
|
||||||
int nodeId = this.activeSectionMap.get(pos);
|
int nodeId = this.activeSectionMap.get(pos);
|
||||||
if (nodeId == -1) {
|
if (nodeId == -1) {
|
||||||
Logger.error("Tried removing top level pos " + WorldEngine.pprintPos(pos) + " but it was not in active map, discarding!");
|
throw new IllegalStateException("Tried removing top level pos " + WorldEngine.pprintPos(pos) + " but it was not in active map, discarding!");
|
||||||
return;
|
}
|
||||||
|
if ((nodeId&NODE_TYPE_MSK)!=NODE_TYPE_REQUEST) {
|
||||||
|
int id = nodeId&NODE_ID_MSK;
|
||||||
|
if (!this.topLevelNodeIds.remove(id)) {
|
||||||
|
throw new IllegalStateException("Node id was not in top level node ids: " + nodeId + " pos: " + WorldEngine.pprintPos(pos));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//TODO: assert is top level node
|
|
||||||
|
|
||||||
//TODO:FIXME augment topLevelNodeIds with a hashmap from node id to array index
|
|
||||||
// OR!! just ensure the list is always ordered?? maybe? idk i think hashmap is best
|
|
||||||
// since the array list might get shuffled as nodes are removed
|
|
||||||
// since need to move the entry at the end of the array to fill a hole made
|
|
||||||
|
|
||||||
// remove from topLevelNodes aswell
|
|
||||||
|
|
||||||
|
//Remove the entire thing
|
||||||
|
this.recurseRemoveNode(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
IntArrayList getTopLevelNodeIds() {
|
IntOpenHashSet getTopLevelNodeIds() {
|
||||||
return this.topLevelNodeIds;
|
return this.topLevelNodeIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -599,6 +600,35 @@ public class NodeManager {
|
|||||||
this._recurseRemoveNode(pos, false);
|
this._recurseRemoveNode(pos, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void _removeRequest(int reqId, NodeChildRequest req, long pos) {
|
||||||
|
//Delete all the request data
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
if ((req.getMsk()&(1<<i))==0) continue;
|
||||||
|
|
||||||
|
int mesh = req.getChildMesh(i);
|
||||||
|
if (mesh != EMPTY_GEOMETRY_ID && mesh != NULL_GEOMETRY_ID)
|
||||||
|
this.geometryManager.removeSection(mesh);
|
||||||
|
|
||||||
|
//Unwatch the request position
|
||||||
|
long childPos = makeChildPos(pos, i);
|
||||||
|
//Remove from section tracker
|
||||||
|
int cId = this.activeSectionMap.remove(childPos);
|
||||||
|
if (cId == -1) {
|
||||||
|
throw new IllegalStateException("Child not in activeMap");
|
||||||
|
}
|
||||||
|
if ((cId&NODE_TYPE_MSK) != NODE_TYPE_REQUEST || (cId&REQUEST_TYPE_MSK) != REQUEST_TYPE_CHILD || (cId&NODE_ID_MSK) != reqId) {
|
||||||
|
throw new IllegalStateException("Invalid child active state map: " + cId);
|
||||||
|
}
|
||||||
|
if (!this.watcher.unwatch(childPos, WorldEngine.UPDATE_FLAGS)) {
|
||||||
|
throw new IllegalStateException("Pos was not being watched");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.childRequests.release(reqId);//Release the request
|
||||||
|
this.activeNodeRequestCount--;
|
||||||
|
}
|
||||||
|
|
||||||
//Recursivly fully removes all nodes and children
|
//Recursivly fully removes all nodes and children
|
||||||
private void _recurseRemoveNode(long pos, boolean onlyRemoveChildren) {
|
private void _recurseRemoveNode(long pos, boolean onlyRemoveChildren) {
|
||||||
//NOTE: this also removes from the section map
|
//NOTE: this also removes from the section map
|
||||||
@@ -612,8 +642,8 @@ public class NodeManager {
|
|||||||
throw new IllegalStateException("Cannot remove pos that doesnt exist");
|
throw new IllegalStateException("Cannot remove pos that doesnt exist");
|
||||||
}
|
}
|
||||||
int type = nodeId&NODE_TYPE_MSK;
|
int type = nodeId&NODE_TYPE_MSK;
|
||||||
nodeId &= NODE_ID_MSK;
|
|
||||||
if (type == NODE_TYPE_INNER || type == NODE_TYPE_LEAF) {
|
if (type == NODE_TYPE_INNER || type == NODE_TYPE_LEAF) {
|
||||||
|
nodeId &= NODE_ID_MSK;
|
||||||
if (!this.nodeData.nodeExists(nodeId)) {
|
if (!this.nodeData.nodeExists(nodeId)) {
|
||||||
throw new IllegalStateException("Node exists in section map but not in nodeData");
|
throw new IllegalStateException("Node exists in section map but not in nodeData");
|
||||||
}
|
}
|
||||||
@@ -628,34 +658,8 @@ public class NodeManager {
|
|||||||
var req = this.childRequests.get(reqId);
|
var req = this.childRequests.get(reqId);
|
||||||
childExistence ^= req.getMsk();
|
childExistence ^= req.getMsk();
|
||||||
|
|
||||||
//TODO FINISH
|
this._removeRequest(reqId, req, pos);
|
||||||
|
|
||||||
//Delete all the request data
|
|
||||||
for (int i = 0; i < 8; i++) {
|
|
||||||
if ((req.getMsk()&(1<<i))==0) continue;
|
|
||||||
|
|
||||||
int mesh = req.getChildMesh(i);
|
|
||||||
if (mesh != EMPTY_GEOMETRY_ID && mesh != NULL_GEOMETRY_ID)
|
|
||||||
this.geometryManager.removeSection(mesh);
|
|
||||||
|
|
||||||
//Unwatch the request position
|
|
||||||
long childPos = makeChildPos(pos, i);
|
|
||||||
//Remove from section tracker
|
|
||||||
int cId = this.activeSectionMap.remove(childPos);
|
|
||||||
if (cId == -1) {
|
|
||||||
throw new IllegalStateException("Child not in activeMap");
|
|
||||||
}
|
|
||||||
if ((cId&NODE_TYPE_MSK) != NODE_TYPE_REQUEST || (cId&REQUEST_TYPE_MSK) != REQUEST_TYPE_CHILD || (cId&NODE_ID_MSK) != reqId) {
|
|
||||||
throw new IllegalStateException("Invalid child active state map: " + cId);
|
|
||||||
}
|
|
||||||
if (!this.watcher.unwatch(childPos, WorldEngine.UPDATE_FLAGS)) {
|
|
||||||
throw new IllegalStateException("Pos was not being watched");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
this.childRequests.release(reqId);//Release the request
|
|
||||||
this.activeNodeRequestCount--;
|
|
||||||
if (onlyRemoveChildren) {
|
if (onlyRemoveChildren) {
|
||||||
this.nodeData.unmarkRequestInFlight(nodeId);
|
this.nodeData.unmarkRequestInFlight(nodeId);
|
||||||
this.nodeData.setNodeRequest(nodeId, NULL_REQUEST_ID);
|
this.nodeData.setNodeRequest(nodeId, NULL_REQUEST_ID);
|
||||||
@@ -745,10 +749,33 @@ public class NodeManager {
|
|||||||
//TODO: probably need this.clearId(nodeId);
|
//TODO: probably need this.clearId(nodeId);
|
||||||
this.invalidateNode(nodeId);
|
this.invalidateNode(nodeId);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (type == NODE_TYPE_REQUEST) {
|
||||||
|
if (!this.watcher.unwatch(pos, WorldEngine.UPDATE_FLAGS)) {
|
||||||
|
throw new IllegalStateException("Pos was not being watched");
|
||||||
|
}
|
||||||
|
if ((nodeId&REQUEST_TYPE_MSK) == REQUEST_TYPE_SINGLE) {
|
||||||
|
nodeId &= NODE_ID_MSK;
|
||||||
|
|
||||||
Logger.error("UNFINISHED OPERATION TODO: FIXME3");
|
var req = this.singleRequests.get(nodeId);
|
||||||
//NOTE: There are request type singles and request type child!!!!
|
if (req.getPosition() != pos)
|
||||||
|
throw new IllegalStateException();
|
||||||
|
|
||||||
|
this.singleRequests.release(nodeId);
|
||||||
|
if (req.hasMeshSet()) {
|
||||||
|
int mesh = req.getMesh();
|
||||||
|
if (mesh != EMPTY_GEOMETRY_ID && mesh != NULL_GEOMETRY_ID)
|
||||||
|
this.geometryManager.removeSection(mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
nodeId &= NODE_ID_MSK;
|
||||||
|
var req = this.childRequests.get(nodeId);
|
||||||
|
if (req.getPosition() != pos)
|
||||||
|
throw new IllegalStateException();
|
||||||
|
this._removeRequest(nodeId, req, pos);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,4 +42,7 @@ class SingleNodeRequest {
|
|||||||
public boolean hasChildExistenceSet() {
|
public boolean hasChildExistenceSet() {
|
||||||
return (this.setMsk&2)!=0;
|
return (this.setMsk&2)!=0;
|
||||||
}
|
}
|
||||||
|
public boolean hasMeshSet() {
|
||||||
|
return (this.setMsk&1)!=0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ package me.cortex.voxy.client.core.rendering.hierachical;
|
|||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.*;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2IntFunction;
|
|
||||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
|
||||||
import me.cortex.voxy.client.core.rendering.ISectionWatcher;
|
import me.cortex.voxy.client.core.rendering.ISectionWatcher;
|
||||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
||||||
import me.cortex.voxy.client.core.rendering.section.AbstractSectionGeometryManager;
|
import me.cortex.voxy.client.core.rendering.section.AbstractSectionGeometryManager;
|
||||||
@@ -236,6 +234,10 @@ public class TestNodeManager {
|
|||||||
public void verifyIntegrity() {
|
public void verifyIntegrity() {
|
||||||
this.nodeManager.verifyIntegrity(this.watcher.updateTypes.keySet(), this.cleaner.active);
|
this.nodeManager.verifyIntegrity(this.watcher.updateTypes.keySet(), this.cleaner.active);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void remTopPos(long pos) {
|
||||||
|
this.nodeManager.removeTopLevelNode(pos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void fillInALl(TestBase test, long pos, Long2IntFunction converter) {
|
private static void fillInALl(TestBase test, long pos, Long2IntFunction converter) {
|
||||||
@@ -274,8 +276,8 @@ public class TestNodeManager {
|
|||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
Logger.INSERT_CLASS = false;
|
Logger.INSERT_CLASS = false;
|
||||||
int ITER_COUNT = 50_000;
|
int ITER_COUNT = 5_000;
|
||||||
int INNER_ITER_COUNT = 500_000;
|
int INNER_ITER_COUNT = 100_000;
|
||||||
boolean GEO_REM = true;
|
boolean GEO_REM = true;
|
||||||
|
|
||||||
AtomicInteger finished = new AtomicInteger();
|
AtomicInteger finished = new AtomicInteger();
|
||||||
@@ -299,13 +301,14 @@ public class TestNodeManager {
|
|||||||
}
|
}
|
||||||
System.out.println("Finished " + finished.get() + " iterations out of " + ITER_COUNT);
|
System.out.println("Finished " + finished.get() + " iterations out of " + ITER_COUNT);
|
||||||
}
|
}
|
||||||
private static long rPos(Random r) {
|
private static long rPos(Random r, LongList tops) {
|
||||||
int lvl = r.nextInt(5);
|
int lvl = r.nextInt(5);
|
||||||
|
long top = tops.getLong(r.nextInt(tops.size()));
|
||||||
if (lvl==4) {
|
if (lvl==4) {
|
||||||
return WorldEngine.getWorldSectionId(4,0,0,0);
|
return top;
|
||||||
}
|
}
|
||||||
int bound = 16>>lvl;
|
int bound = 16>>lvl;
|
||||||
return WorldEngine.getWorldSectionId(lvl, r.nextInt(bound), r.nextInt(bound), r.nextInt(bound));
|
return WorldEngine.getWorldSectionId(lvl, r.nextInt(bound)+(WorldEngine.getX(top)<<4), r.nextInt(bound)+(WorldEngine.getY(top)<<4), r.nextInt(bound)+(WorldEngine.getZ(top)<<4));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean runTest(int ITERS, int testIdx, Set<List<StackTraceElement>> traces, boolean geoRemoval) {
|
private static boolean runTest(int ITERS, int testIdx, Set<List<StackTraceElement>> traces, boolean geoRemoval) {
|
||||||
@@ -314,14 +317,30 @@ public class TestNodeManager {
|
|||||||
Random r = new Random(testIdx * 1234L);
|
Random r = new Random(testIdx * 1234L);
|
||||||
try {
|
try {
|
||||||
var test = new TestBase();
|
var test = new TestBase();
|
||||||
|
LongList tops = new LongArrayList();
|
||||||
//Fuzzy bruteforce everything
|
//Fuzzy bruteforce everything
|
||||||
test.putTopPos(POS_A);
|
test.putTopPos(POS_A);
|
||||||
|
tops.add(POS_A);
|
||||||
for (int i = 0; i < ITERS; i++) {
|
for (int i = 0; i < ITERS; i++) {
|
||||||
long pos = rPos(r);
|
long pos = rPos(r, tops);
|
||||||
int op = r.nextInt(4);
|
int op = r.nextInt(5);
|
||||||
int extra = r.nextInt(256);
|
int extra = r.nextInt(256);
|
||||||
boolean hasGeometry = r.nextBoolean();
|
boolean hasGeometry = r.nextBoolean();
|
||||||
if (op == 0) {
|
boolean addRemTLN = r.nextInt(512) == 0;
|
||||||
|
boolean extraBool = r.nextBoolean();
|
||||||
|
if (op == 0 && addRemTLN) {
|
||||||
|
pos = WorldEngine.getWorldSectionId(4, r.nextInt(5)-2, r.nextInt(5)-2, r.nextInt(5)-2);
|
||||||
|
boolean cont = tops.contains(pos);
|
||||||
|
if (cont&&extraBool&&tops.size()>1) {
|
||||||
|
extraBool = true;
|
||||||
|
test.remTopPos(pos);
|
||||||
|
tops.rem(pos);
|
||||||
|
} else if (!cont) {
|
||||||
|
extraBool = false;
|
||||||
|
test.putTopPos(pos);
|
||||||
|
tops.add(pos);
|
||||||
|
}
|
||||||
|
} else if (op == 0) {
|
||||||
test.request(pos);
|
test.request(pos);
|
||||||
}
|
}
|
||||||
if (op == 1) {
|
if (op == 1) {
|
||||||
@@ -336,8 +355,20 @@ public class TestNodeManager {
|
|||||||
test.printNodeChanges();
|
test.printNodeChanges();
|
||||||
test.verifyIntegrity();
|
test.verifyIntegrity();
|
||||||
}
|
}
|
||||||
test.childUpdate(POS_A, 0);
|
for (long top : tops) {
|
||||||
test.meshUpdate(POS_A, 0, 0);
|
test.remTopPos(top);
|
||||||
|
}
|
||||||
|
test.printNodeChanges();
|
||||||
|
test.verifyIntegrity();
|
||||||
|
if (test.nodeManager.getCurrentMaxNodeId() != -1) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
if (!test.cleaner.active.isEmpty()) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
if (!test.watcher.updateTypes.isEmpty()) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
if (test.geometryManager.memoryInUse != 0) {
|
if (test.geometryManager.memoryInUse != 0) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
|||||||
204
src/main/java/me/cortex/voxy/client/core/util/RingTracker.java
Normal file
204
src/main/java/me/cortex/voxy/client/core/util/RingTracker.java
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
package me.cortex.voxy.client.core.util;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
|
||||||
|
import me.cortex.voxy.common.Logger;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
//Tracks a ring and load/unload positions
|
||||||
|
// can process N of these load/unload positions
|
||||||
|
public class RingTracker {
|
||||||
|
//TODO: replace with custom map that removes elements if its mapped to 0
|
||||||
|
private final Long2ByteOpenHashMap operations = new Long2ByteOpenHashMap(1<<13);
|
||||||
|
private final int[] boundDist;
|
||||||
|
private final int radius;
|
||||||
|
private int centerX;
|
||||||
|
private int centerZ;
|
||||||
|
|
||||||
|
public RingTracker(int radius, int centerX, int centerZ, boolean fill) {
|
||||||
|
this.centerX = centerX;
|
||||||
|
this.centerZ = centerZ;
|
||||||
|
this.radius = radius;
|
||||||
|
this.boundDist = generateBoundingHalfCircleDistance(radius);
|
||||||
|
if (fill) {
|
||||||
|
this.fillRing(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long pack(int x, int z) {
|
||||||
|
return Integer.toUnsignedLong(x)|(Integer.toUnsignedLong(z)<<32);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillRing(boolean load) {
|
||||||
|
for (int i = 0; i <= this.radius*2; i++) {
|
||||||
|
int x = this.centerX + i - this.radius;
|
||||||
|
int d = this.boundDist[i];
|
||||||
|
for (int z = this.centerZ-d; z <= this.centerZ+d; z++) {
|
||||||
|
int res = this.operations.addTo(pack(x, z), (byte) (load?1:-1));
|
||||||
|
if ((load&&0<res)||(((!load)&&res<0))) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unload() {
|
||||||
|
this.fillRing(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Moves the center from old to new and updates the operations map
|
||||||
|
public void moveCenter(int x, int z) {
|
||||||
|
//TODO, if the new center is greater than radius from current, unload all current and load all at new
|
||||||
|
if (this.radius+1<Math.abs(x-this.centerX) || this.radius+1<Math.abs(z-this.centerZ)) {
|
||||||
|
this.fillRing(false);
|
||||||
|
this.centerX = x;
|
||||||
|
this.centerZ = z;
|
||||||
|
this.fillRing(true);
|
||||||
|
} else {
|
||||||
|
if (x != this.centerX) {
|
||||||
|
moveX(x - this.centerX);
|
||||||
|
}
|
||||||
|
if (z != this.centerZ) {
|
||||||
|
moveZ(z - this.centerZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveZ(int delta) {
|
||||||
|
if (delta == 0) return;
|
||||||
|
//Since +- 1 is the most common operation, fastpath it
|
||||||
|
if (delta == -1 || delta == 1) {
|
||||||
|
for (int i = 0; i <= this.radius * 2; i++) {
|
||||||
|
int x = this.centerX + i - this.radius;
|
||||||
|
//Multiply by the delta since its +-1 it also then makes it the correct orientation
|
||||||
|
int d = this.boundDist[i]*delta;
|
||||||
|
int pz = this.centerZ+d+delta;//Point to add (we need to offset by 1 in the mov direction)
|
||||||
|
int nz = this.centerZ-d;//Point to rem
|
||||||
|
if (0<this.operations.addTo(pack(x, pz), (byte) 1))//Load point
|
||||||
|
throw new IllegalStateException("x: "+x+", z: "+pz+" state: "+this.operations.get(pack(x, pz)));
|
||||||
|
if (this.operations.addTo(pack(x, nz), (byte) -1)<0)//Unload point
|
||||||
|
throw new IllegalStateException("x: "+x+", z: "+nz+" state: "+this.operations.get(pack(x, nz)));
|
||||||
|
}
|
||||||
|
this.centerZ += delta;
|
||||||
|
} else {
|
||||||
|
int sDelta = Integer.signum(delta);
|
||||||
|
for (int i = 0; i <= this.radius * 2; i++) {
|
||||||
|
int x = this.centerX + i - this.radius;
|
||||||
|
//Multiply by the delta since its +-1 it also then makes it the correct orientation
|
||||||
|
int d = this.boundDist[i]*sDelta;
|
||||||
|
int pz = this.centerZ+d;//Point to add (we need to offset by 1 in the mov direction)
|
||||||
|
for (int z = pz + (sDelta<0?delta:1); z <= pz + (sDelta<0?-1:delta); z++) {
|
||||||
|
if (0<this.operations.addTo(pack(x, z), (byte) 1))//Load point
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
int nz = this.centerZ-d;//Point to rem
|
||||||
|
for (int z = nz + (sDelta<0?(delta+1):0); z < nz + (sDelta<0?1:delta); z++) {
|
||||||
|
if (this.operations.addTo(pack(x, z), (byte) -1)<0)//Unload point
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.centerZ += delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveX(int delta) {
|
||||||
|
if (delta == 0) return;
|
||||||
|
//Since +- 1 is the most common operation, fastpath it
|
||||||
|
if (delta == -1 || delta == 1) {
|
||||||
|
for (int i = 0; i <= this.radius * 2; i++) {
|
||||||
|
int z = this.centerZ + i - this.radius;
|
||||||
|
//Multiply by the delta since its +-1 it also then makes it the correct orientation
|
||||||
|
int d = this.boundDist[i]*delta;
|
||||||
|
int px = this.centerX+d+delta;//Point to add (we need to offset by 1 in the mov direction)
|
||||||
|
int nx = this.centerX-d;//Point to rem
|
||||||
|
if (0<this.operations.addTo(pack(px, z), (byte) 1))//Load point
|
||||||
|
throw new IllegalStateException();
|
||||||
|
if (this.operations.addTo(pack(nx, z), (byte) -1)<0)//Unload point
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
this.centerX += delta;
|
||||||
|
} else {
|
||||||
|
int sDelta = Integer.signum(delta);
|
||||||
|
for (int i = 0; i <= this.radius * 2; i++) {
|
||||||
|
int z = this.centerZ + i - this.radius;
|
||||||
|
//Multiply by the delta since its +-1 it also then makes it the correct orientation
|
||||||
|
int d = this.boundDist[i]*sDelta;
|
||||||
|
int px = this.centerX+d;//Point to add (we need to offset by 1 in the mov direction)
|
||||||
|
for (int x = px + (sDelta<0?delta:1); x <= px + (sDelta<0?-1:delta); x++) {
|
||||||
|
if (0<this.operations.addTo(pack(x, z), (byte) 1))//Load point
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
int nx = this.centerX-d;//Point to rem
|
||||||
|
for (int x = nx + (sDelta<0?(delta+1):0); x < nx + (sDelta<0?1:delta); x++) {
|
||||||
|
if (this.operations.addTo(pack(x, z), (byte) -1)<0)//Unload point
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.centerX += delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IUpdateConsumer {
|
||||||
|
void accept(int x, int z);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Processes N operations from the operations map
|
||||||
|
public int process(int N, IUpdateConsumer onAdd, IUpdateConsumer onRemove) {
|
||||||
|
if (this.operations.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
var iter = this.operations.long2ByteEntrySet().fastIterator();
|
||||||
|
int i = 0;
|
||||||
|
while (iter.hasNext() && N--!=0) {
|
||||||
|
var entry = iter.next();
|
||||||
|
if (entry.getByteValue()==0) {
|
||||||
|
iter.remove(); N++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
byte op = entry.getByteValue();
|
||||||
|
if (op != 1 && op != -1) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
boolean isAdd = op == 1;
|
||||||
|
long pos = entry.getLongKey();
|
||||||
|
int x = (int) (pos&0xFFFFFFFFL);
|
||||||
|
int z = (int) ((pos>>>32)&0xFFFFFFFFL);
|
||||||
|
if (isAdd) {
|
||||||
|
onAdd.accept(x, z);
|
||||||
|
} else {
|
||||||
|
onRemove.accept(x, z);
|
||||||
|
}
|
||||||
|
iter.remove();
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int[] generateBoundingHalfCircleDistance(int radius) {
|
||||||
|
var ret = new int[radius*2+1];
|
||||||
|
for (int i = -radius; i <= radius; i++) {
|
||||||
|
ret[i+radius] = (int)Math.sqrt(radius*radius - i*i);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
for (int j = 0; j < 50; j++) {
|
||||||
|
Random r = new Random((j+18723)*1234);
|
||||||
|
var tracker = new RingTracker(r.nextInt(100)+1, 0, 0, true);
|
||||||
|
int R = r.nextInt(500);
|
||||||
|
for (int i = 0; i < 50_000; i++) {
|
||||||
|
int x = r.nextInt(R*2+1)-R;
|
||||||
|
int z = r.nextInt(R*2+1)-R;
|
||||||
|
tracker.moveCenter(x, z);
|
||||||
|
}
|
||||||
|
tracker.fillRing(false);
|
||||||
|
tracker.process(64, (x,z)->{
|
||||||
|
Logger.info("Add:", x,",",z);
|
||||||
|
}, (x,z)->{
|
||||||
|
Logger.info("Remove:", x,",",z);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,7 +65,7 @@ public class RingUtil {
|
|||||||
public static int[] generatingBoundingCorner2D(int radius) {
|
public static int[] generatingBoundingCorner2D(int radius) {
|
||||||
IntOpenHashSet points = new IntOpenHashSet();
|
IntOpenHashSet points = new IntOpenHashSet();
|
||||||
//Do 2 pass (x and y) to generate and cover all points
|
//Do 2 pass (x and y) to generate and cover all points
|
||||||
for (int i = 0; i <= radius; i++) {
|
for (int i = 1; i <= radius; i++) {
|
||||||
int other = (int) Math.floor(Math.sqrt(radius*radius - i*i));
|
int other = (int) Math.floor(Math.sqrt(radius*radius - i*i));
|
||||||
//add points (x,other) and (other,x) as it covers the full spectrum
|
//add points (x,other) and (other,x) as it covers the full spectrum
|
||||||
points.add((i<<16)|other);
|
points.add((i<<16)|other);
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public abstract class ScanMesher2D {
|
|||||||
//TODO: replace with much better method, TODO: check this is right!!
|
//TODO: replace with much better method, TODO: check this is right!!
|
||||||
this.putNext(0);
|
this.putNext(0);
|
||||||
if (1<count) {
|
if (1<count) {
|
||||||
this.emitRanged(((1 << (count - 1)) - 1) << (this.currentIndex & 31));
|
this.emitRanged(((1 << (Math.min(count, 32) - 1)) - 1) << (this.currentIndex & 31));
|
||||||
this.currentIndex += count - 1;
|
this.currentIndex += count - 1;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package me.cortex.voxy.client.mixin.minecraft;
|
||||||
|
|
||||||
|
import me.cortex.voxy.client.GPUSelectorWindows2;
|
||||||
|
import net.minecraft.client.WindowEventHandler;
|
||||||
|
import net.minecraft.client.WindowSettings;
|
||||||
|
import net.minecraft.client.util.MonitorTracker;
|
||||||
|
import net.minecraft.client.util.Window;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(Window.class)
|
||||||
|
public class MixinWindow {
|
||||||
|
@Inject(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/Window;throwOnGlError()V"))
|
||||||
|
private void injectInitWindow(WindowEventHandler eventHandler, MonitorTracker monitorTracker, WindowSettings settings, String fullscreenVideoMode, String title, CallbackInfo ci) {
|
||||||
|
var prop = System.getProperty("voxy.forceGpuSelectionIndex", "NO");
|
||||||
|
if (!prop.equals("NO")) {
|
||||||
|
GPUSelectorWindows2.doSelector(Integer.parseInt(prop));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,11 +52,7 @@ public abstract class MixinWorldRenderer implements IGetVoxyRenderSystem {
|
|||||||
this.shutdownRenderer();
|
this.shutdownRenderer();
|
||||||
|
|
||||||
if (this.world != null) {
|
if (this.world != null) {
|
||||||
var engine = ((IVoxyWorld)this.world).getWorldEngine();
|
((IVoxyWorld)this.world).shutdownEngine();
|
||||||
if (engine != null) {
|
|
||||||
VoxyCommon.getInstance().stopWorld(engine);
|
|
||||||
}
|
|
||||||
((IVoxyWorld)this.world).setWorldEngine(null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,13 @@ public class Logger {
|
|||||||
String error = (INSERT_CLASS?("["+stackEntry.getClassName()+"]: "):"") + Stream.of(args).map(Logger::objToString).collect(Collectors.joining(" "));
|
String error = (INSERT_CLASS?("["+stackEntry.getClassName()+"]: "):"") + Stream.of(args).map(Logger::objToString).collect(Collectors.joining(" "));
|
||||||
LOGGER.error(error, throwable);
|
LOGGER.error(error, throwable);
|
||||||
if (VoxyCommon.IS_IN_MINECRAFT && !VoxyCommon.IS_DEDICATED_SERVER) {
|
if (VoxyCommon.IS_IN_MINECRAFT && !VoxyCommon.IS_DEDICATED_SERVER) {
|
||||||
MinecraftClient.getInstance().executeSync(()->{var player = MinecraftClient.getInstance().player; if (player != null) player.sendMessage(Text.literal(error), true);});
|
var instance = MinecraftClient.getInstance();
|
||||||
|
if (instance != null) {
|
||||||
|
instance.executeSync(() -> {
|
||||||
|
var player = MinecraftClient.getInstance().player;
|
||||||
|
if (player != null) player.sendMessage(Text.literal(error), true);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package me.cortex.voxy.common.config;
|
|||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.function.LongConsumer;
|
||||||
|
|
||||||
public interface IMappingStorage {
|
public interface IMappingStorage {
|
||||||
|
void iterateStoredSectionPositions(LongConsumer consumer);
|
||||||
void putIdMapping(int id, ByteBuffer data);
|
void putIdMapping(int id, ByteBuffer data);
|
||||||
Int2ObjectOpenHashMap<byte[]> getIdMappingsData();
|
Int2ObjectOpenHashMap<byte[]> getIdMappingsData();
|
||||||
void flush();
|
void flush();
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ import me.cortex.voxy.common.config.storage.StorageConfig;
|
|||||||
import me.cortex.voxy.common.util.ThreadLocalMemoryBuffer;
|
import me.cortex.voxy.common.util.ThreadLocalMemoryBuffer;
|
||||||
import me.cortex.voxy.common.world.SaveLoadSystem;
|
import me.cortex.voxy.common.world.SaveLoadSystem;
|
||||||
import me.cortex.voxy.common.world.SaveLoadSystem2;
|
import me.cortex.voxy.common.world.SaveLoadSystem2;
|
||||||
|
import me.cortex.voxy.common.world.SaveLoadSystem3;
|
||||||
import me.cortex.voxy.common.world.WorldSection;
|
import me.cortex.voxy.common.world.WorldSection;
|
||||||
import me.cortex.voxy.common.world.other.Mapper;
|
import me.cortex.voxy.common.world.other.Mapper;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.function.LongConsumer;
|
||||||
|
|
||||||
public class SectionSerializationStorage extends SectionStorage {
|
public class SectionSerializationStorage extends SectionStorage {
|
||||||
private final StorageBackend backend;
|
private final StorageBackend backend;
|
||||||
@@ -26,7 +28,7 @@ public class SectionSerializationStorage extends SectionStorage {
|
|||||||
public int loadSection(WorldSection into) {
|
public int loadSection(WorldSection into) {
|
||||||
var data = this.backend.getSectionData(into.key, MEMORY_CACHE.get().createUntrackedUnfreeableReference());
|
var data = this.backend.getSectionData(into.key, MEMORY_CACHE.get().createUntrackedUnfreeableReference());
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
if (!SaveLoadSystem.deserialize(into, data)) {
|
if (!SaveLoadSystem3.deserialize(into, data)) {
|
||||||
this.backend.deleteSectionData(into.key);
|
this.backend.deleteSectionData(into.key);
|
||||||
//TODO: regenerate the section from children
|
//TODO: regenerate the section from children
|
||||||
Arrays.fill(into._unsafeGetRawDataArray(), Mapper.AIR);
|
Arrays.fill(into._unsafeGetRawDataArray(), Mapper.AIR);
|
||||||
@@ -46,7 +48,7 @@ public class SectionSerializationStorage extends SectionStorage {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void saveSection(WorldSection section) {
|
public void saveSection(WorldSection section) {
|
||||||
var saveData = SaveLoadSystem.serialize(section);
|
var saveData = SaveLoadSystem3.serialize(section);
|
||||||
this.backend.setSectionData(section.key, saveData);
|
this.backend.setSectionData(section.key, saveData);
|
||||||
saveData.free();
|
saveData.free();
|
||||||
}
|
}
|
||||||
@@ -71,6 +73,11 @@ public class SectionSerializationStorage extends SectionStorage {
|
|||||||
this.backend.close();
|
this.backend.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void iterateStoredSectionPositions(LongConsumer consumer) {
|
||||||
|
this.backend.iterateStoredSectionPositions(consumer);
|
||||||
|
}
|
||||||
|
|
||||||
public static class Config extends SectionStorageConfig {
|
public static class Config extends SectionStorageConfig {
|
||||||
public StorageConfig storage;
|
public StorageConfig storage;
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import java.util.List;
|
|||||||
import java.util.function.LongConsumer;
|
import java.util.function.LongConsumer;
|
||||||
|
|
||||||
public abstract class StorageBackend implements IMappingStorage {
|
public abstract class StorageBackend implements IMappingStorage {
|
||||||
public abstract void iterateStoredSectionPositions(LongConsumer consumer);
|
|
||||||
|
|
||||||
//Implementation may use the scratch buffer as the return value, it MUST NOT free the scratch buffer
|
//Implementation may use the scratch buffer as the return value, it MUST NOT free the scratch buffer
|
||||||
public abstract MemoryBuffer getSectionData(long key, MemoryBuffer scratch);
|
public abstract MemoryBuffer getSectionData(long key, MemoryBuffer scratch);
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ public class FragmentedStorageBackendAdaptor extends StorageBackend {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void iterateStoredSectionPositions(LongConsumer consumer) {
|
public void iterateStoredSectionPositions(LongConsumer consumer) {
|
||||||
throw new IllegalStateException("Not yet implemented");
|
for (var backend : this.backends) {
|
||||||
|
backend.iterateStoredSectionPositions(consumer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: reencode the key to be shifted one less OR
|
//TODO: reencode the key to be shifted one less OR
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public class RocksDBStorageBackend extends StorageBackend {
|
|||||||
private final ColumnFamilyHandle worldSections;
|
private final ColumnFamilyHandle worldSections;
|
||||||
private final ColumnFamilyHandle idMappings;
|
private final ColumnFamilyHandle idMappings;
|
||||||
private final ReadOptions sectionReadOps;
|
private final ReadOptions sectionReadOps;
|
||||||
|
private final WriteOptions sectionWriteOps;
|
||||||
|
|
||||||
//NOTE: closes in order
|
//NOTE: closes in order
|
||||||
private final List<AbstractImmutableNativeReference> closeList = new ArrayList<>();
|
private final List<AbstractImmutableNativeReference> closeList = new ArrayList<>();
|
||||||
@@ -49,7 +50,9 @@ public class RocksDBStorageBackend extends StorageBackend {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions().optimizeUniversalStyleCompaction();
|
final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions()
|
||||||
|
.optimizeUniversalStyleCompaction()
|
||||||
|
.optimizeForPointLookup(128);
|
||||||
|
|
||||||
final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
|
final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
|
||||||
new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts),
|
new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts),
|
||||||
@@ -59,7 +62,8 @@ public class RocksDBStorageBackend extends StorageBackend {
|
|||||||
|
|
||||||
final DBOptions options = new DBOptions()
|
final DBOptions options = new DBOptions()
|
||||||
.setCreateIfMissing(true)
|
.setCreateIfMissing(true)
|
||||||
.setCreateMissingColumnFamilies(true);
|
.setCreateMissingColumnFamilies(true)
|
||||||
|
.setMaxTotalWalSize(1024*1024*512);//512 mb max WAL size
|
||||||
|
|
||||||
List<ColumnFamilyHandle> handles = new ArrayList<>();
|
List<ColumnFamilyHandle> handles = new ArrayList<>();
|
||||||
|
|
||||||
@@ -69,12 +73,14 @@ public class RocksDBStorageBackend extends StorageBackend {
|
|||||||
handles);
|
handles);
|
||||||
|
|
||||||
this.sectionReadOps = new ReadOptions();
|
this.sectionReadOps = new ReadOptions();
|
||||||
|
this.sectionWriteOps = new WriteOptions();
|
||||||
|
|
||||||
this.closeList.addAll(handles);
|
this.closeList.addAll(handles);
|
||||||
this.closeList.add(this.db);
|
this.closeList.add(this.db);
|
||||||
this.closeList.add(options);
|
this.closeList.add(options);
|
||||||
this.closeList.add(cfOpts);
|
this.closeList.add(cfOpts);
|
||||||
this.closeList.add(this.sectionReadOps);
|
this.closeList.add(this.sectionReadOps);
|
||||||
|
this.closeList.add(this.sectionWriteOps);
|
||||||
|
|
||||||
this.worldSections = handles.get(1);
|
this.worldSections = handles.get(1);
|
||||||
this.idMappings = handles.get(2);
|
this.idMappings = handles.get(2);
|
||||||
@@ -87,7 +93,19 @@ public class RocksDBStorageBackend extends StorageBackend {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void iterateStoredSectionPositions(LongConsumer consumer) {
|
public void iterateStoredSectionPositions(LongConsumer consumer) {
|
||||||
throw new IllegalStateException("Not yet implemented");
|
try (var stack = MemoryStack.stackPush()) {
|
||||||
|
ByteBuffer keyBuff = stack.calloc(8);
|
||||||
|
long keyBuffPtr = MemoryUtil.memAddress(keyBuff);
|
||||||
|
var iter = this.db.newIterator(this.worldSections, this.sectionReadOps);
|
||||||
|
iter.seekToFirst();
|
||||||
|
while (iter.isValid()) {
|
||||||
|
iter.key(keyBuff);
|
||||||
|
long key = Long.reverseBytes(MemoryUtil.memGetLong(keyBuffPtr));
|
||||||
|
consumer.accept(key);
|
||||||
|
iter.next();
|
||||||
|
}
|
||||||
|
iter.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -117,10 +135,10 @@ public class RocksDBStorageBackend extends StorageBackend {
|
|||||||
//TODO: FIXME, use the ByteBuffer variant
|
//TODO: FIXME, use the ByteBuffer variant
|
||||||
@Override
|
@Override
|
||||||
public void setSectionData(long key, MemoryBuffer data) {
|
public void setSectionData(long key, MemoryBuffer data) {
|
||||||
try {
|
try (var stack = MemoryStack.stackPush()) {
|
||||||
var buffer = new byte[(int) data.size];
|
var keyBuff = stack.calloc(8);
|
||||||
UnsafeUtil.memcpy(data.address, buffer);
|
MemoryUtil.memPutLong(MemoryUtil.memAddress(keyBuff), Long.reverseBytes(key));
|
||||||
this.db.put(this.worldSections, longToBytes(key), buffer);
|
this.db.put(this.worldSections, this.sectionWriteOps, keyBuff, data.asByteBuffer());
|
||||||
} catch (RocksDBException e) {
|
} catch (RocksDBException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ public class ThreadLocalMemoryBuffer {
|
|||||||
this.threadLocal = ThreadLocal.withInitial(()->createMemoryBuffer(size));
|
this.threadLocal = ThreadLocal.withInitial(()->createMemoryBuffer(size));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static MemoryBuffer create(long size) {
|
||||||
|
return createMemoryBuffer(size);
|
||||||
|
}
|
||||||
|
|
||||||
public MemoryBuffer get() {
|
public MemoryBuffer get() {
|
||||||
return this.threadLocal.get();
|
return this.threadLocal.get();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,22 +30,22 @@ public class SaveLoadSystem {
|
|||||||
return x|(y<<10)|(z<<5);
|
return x|(y<<10)|(z<<5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record SerializationCache(long[] blockStateCache, short[] compressedCache, long[] lutCache, Long2ShortOpenHashMap lutMapCache) {
|
||||||
private static final ThreadLocal<short[]> SHORT_CACHE = ThreadLocal.withInitial(()->new short[32*32*32]);
|
public SerializationCache() {
|
||||||
private static final ThreadLocal<long[]> LONG_CACHE = ThreadLocal.withInitial(()->new long[32*32*32]);
|
this(new long[WorldSection.SECTION_VOLUME], new short[WorldSection.SECTION_VOLUME], new long[WorldSection.SECTION_VOLUME], new Long2ShortOpenHashMap(512));
|
||||||
private static final ThreadLocal<Long2ShortOpenHashMap> OTHER_THING_CACHE = ThreadLocal.withInitial(()-> {
|
this.lutMapCache.defaultReturnValue((short) -1);
|
||||||
var thing = new Long2ShortOpenHashMap(512);
|
}
|
||||||
thing.defaultReturnValue((short) -1);
|
}
|
||||||
return thing;
|
private static final ThreadLocal<SerializationCache> CACHE = ThreadLocal.withInitial(SerializationCache::new);
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
//TODO: Cache like long2short and the short and other data to stop allocs
|
//TODO: Cache like long2short and the short and other data to stop allocs
|
||||||
public static MemoryBuffer serialize(WorldSection section) {
|
public static MemoryBuffer serialize(WorldSection section) {
|
||||||
var data = section.copyData();
|
var cache = CACHE.get();
|
||||||
var compressed = SHORT_CACHE.get();
|
var data = cache.blockStateCache;
|
||||||
Long2ShortOpenHashMap LUT = OTHER_THING_CACHE.get();LUT.clear();
|
section.copyDataTo(data);
|
||||||
long[] lutValues = LONG_CACHE.get();//If there are more than this many states in a section... im concerned
|
var compressed = cache.compressedCache;
|
||||||
|
Long2ShortOpenHashMap LUT = cache.lutMapCache; LUT.clear();
|
||||||
|
long[] lutValues = cache.lutCache;//If there are more than this many states in a section... im concerned
|
||||||
short lutIndex = 0;
|
short lutIndex = 0;
|
||||||
long pHash = 99;
|
long pHash = 99;
|
||||||
for (int i = 0; i < data.length; i++) {
|
for (int i = 0; i < data.length; i++) {
|
||||||
@@ -103,7 +103,7 @@ public class SaveLoadSystem {
|
|||||||
throw new IllegalStateException("lutLen impossibly large, max size should be 32768 but got size " + lutLen);
|
throw new IllegalStateException("lutLen impossibly large, max size should be 32768 but got size " + lutLen);
|
||||||
}
|
}
|
||||||
//TODO: cache this in a thread local
|
//TODO: cache this in a thread local
|
||||||
long[] lut = LONG_CACHE.get();
|
long[] lut = CACHE.get().lutCache;
|
||||||
long hash = 0;
|
long hash = 0;
|
||||||
if (VERIFY_HASH_ON_LOAD) {
|
if (VERIFY_HASH_ON_LOAD) {
|
||||||
hash = key ^ (lutLen * 1293481298141L);
|
hash = key ^ (lutLen * 1293481298141L);
|
||||||
|
|||||||
106
src/main/java/me/cortex/voxy/common/world/SaveLoadSystem3.java
Normal file
106
src/main/java/me/cortex/voxy/common/world/SaveLoadSystem3.java
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package me.cortex.voxy.common.world;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ShortOpenHashMap;
|
||||||
|
import me.cortex.voxy.common.Logger;
|
||||||
|
import me.cortex.voxy.common.util.MemoryBuffer;
|
||||||
|
import me.cortex.voxy.common.util.ThreadLocalMemoryBuffer;
|
||||||
|
import me.cortex.voxy.common.util.UnsafeUtil;
|
||||||
|
import me.cortex.voxy.common.world.other.Mapper;
|
||||||
|
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||||
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
|
public class SaveLoadSystem3 {
|
||||||
|
private record SerializationCache(long[] blockStateCache, Long2ShortOpenHashMap lutMapCache, MemoryBuffer memoryBuffer) {
|
||||||
|
public SerializationCache() {
|
||||||
|
this(new long[WorldSection.SECTION_VOLUME],
|
||||||
|
new Long2ShortOpenHashMap(512),
|
||||||
|
ThreadLocalMemoryBuffer.create(WorldSection.SECTION_VOLUME*2+WorldSection.SECTION_VOLUME*8+1024));
|
||||||
|
this.lutMapCache.defaultReturnValue((short) -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static int lin2z(int i) {//y,z,x
|
||||||
|
int x = i&0x1F;
|
||||||
|
int y = (i>>10)&0x1F;
|
||||||
|
int z = (i>>5)&0x1F;
|
||||||
|
return Integer.expand(x,0b1001001001001)|Integer.expand(y,0b10010010010010)|Integer.expand(z,0b100100100100100);
|
||||||
|
|
||||||
|
//zyxzyxzyxzyxzyx
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int z2lin(int i) {
|
||||||
|
int x = Integer.compress(i, 0b1001001001001);
|
||||||
|
int y = Integer.compress(i, 0b10010010010010);
|
||||||
|
int z = Integer.compress(i, 0b100100100100100);
|
||||||
|
return x|(y<<10)|(z<<5);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final ThreadLocal<SerializationCache> CACHE = ThreadLocal.withInitial(SerializationCache::new);
|
||||||
|
|
||||||
|
//TODO: Cache like long2short and the short and other data to stop allocs
|
||||||
|
public static MemoryBuffer serialize(WorldSection section) {
|
||||||
|
var cache = CACHE.get();
|
||||||
|
var data = cache.blockStateCache;
|
||||||
|
section.copyDataTo(data);
|
||||||
|
|
||||||
|
Long2ShortOpenHashMap LUT = cache.lutMapCache; LUT.clear();
|
||||||
|
|
||||||
|
MemoryBuffer buffer = cache.memoryBuffer().createUntrackedUnfreeableReference();
|
||||||
|
long ptr = buffer.address;
|
||||||
|
|
||||||
|
MemoryUtil.memPutLong(ptr, section.key); ptr += 8;
|
||||||
|
long metadataPtr = ptr; ptr += 8;
|
||||||
|
|
||||||
|
long blockPtr = ptr; ptr += WorldSection.SECTION_VOLUME*2;
|
||||||
|
for (int i = 0; i < WorldSection.SECTION_VOLUME; i++) {
|
||||||
|
long block = data[i];
|
||||||
|
short mapping = LUT.putIfAbsent(block, (short) LUT.size());
|
||||||
|
if (mapping == -1) {
|
||||||
|
mapping = (short) (LUT.size()-1);
|
||||||
|
MemoryUtil.memPutLong(ptr, block); ptr+=8;
|
||||||
|
}
|
||||||
|
MemoryUtil.memPutShort(blockPtr, mapping); blockPtr+=2;
|
||||||
|
}
|
||||||
|
if (LUT.size() >= 1<<16) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
long metadata = 0;
|
||||||
|
metadata |= Integer.toUnsignedLong(LUT.size());//Bottom 2 bytes
|
||||||
|
metadata |= Byte.toUnsignedLong(section.getNonEmptyChildren())<<16;//Next byte
|
||||||
|
//5 bytes free
|
||||||
|
|
||||||
|
MemoryUtil.memPutLong(metadataPtr, metadata);
|
||||||
|
//TODO: do hash
|
||||||
|
|
||||||
|
//TODO: rework the storage system to not need to do useless copies like this (this is an issue for serialization, deserialization has solved this already)
|
||||||
|
return buffer.subSize(ptr-buffer.address).copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean deserialize(WorldSection section, MemoryBuffer data) {
|
||||||
|
long ptr = data.address;
|
||||||
|
long key = MemoryUtil.memGetLong(ptr); ptr += 8;
|
||||||
|
|
||||||
|
if (section.key != key) {
|
||||||
|
//throw new IllegalStateException("Decompressed section not the same as requested. got: " + key + " expected: " + section.key);
|
||||||
|
Logger.error("Decompressed section not the same as requested. got: " + key + " expected: " + section.key);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
long metadata = MemoryUtil.memGetLong(ptr); ptr += 8;
|
||||||
|
section.nonEmptyChildren = (byte) ((metadata>>>16)&0xFF);
|
||||||
|
|
||||||
|
int nonEmptyBlockCount = 0;
|
||||||
|
long lutBasePtr = ptr + WorldSection.SECTION_VOLUME*2;
|
||||||
|
var blockData = section.data;
|
||||||
|
for (int i = 0; i < WorldSection.SECTION_VOLUME; i++) {
|
||||||
|
short lutId = MemoryUtil.memGetShort(ptr); ptr+=2;
|
||||||
|
long blockId = MemoryUtil.memGetLong(lutBasePtr+Short.toUnsignedLong(lutId)*8L);
|
||||||
|
nonEmptyBlockCount += Mapper.isAir(blockId)?0:1;
|
||||||
|
blockData[i] = blockId;
|
||||||
|
}
|
||||||
|
section.nonEmptyBlockCount = nonEmptyBlockCount;
|
||||||
|
ptr = lutBasePtr + (metadata&0xFFFF)*8L;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ import me.cortex.voxy.common.config.section.SectionStorage;
|
|||||||
import me.cortex.voxy.common.util.TrackedObject;
|
import me.cortex.voxy.common.util.TrackedObject;
|
||||||
import me.cortex.voxy.common.voxelization.VoxelizedSection;
|
import me.cortex.voxy.common.voxelization.VoxelizedSection;
|
||||||
import me.cortex.voxy.common.world.other.Mapper;
|
import me.cortex.voxy.common.world.other.Mapper;
|
||||||
|
import me.cortex.voxy.commonImpl.VoxyInstance;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
public class WorldEngine {
|
public class WorldEngine {
|
||||||
@@ -24,8 +26,7 @@ public class WorldEngine {
|
|||||||
private final ActiveSectionTracker sectionTracker;
|
private final ActiveSectionTracker sectionTracker;
|
||||||
private ISectionChangeCallback dirtyCallback;
|
private ISectionChangeCallback dirtyCallback;
|
||||||
private ISectionSaveCallback saveCallback;
|
private ISectionSaveCallback saveCallback;
|
||||||
private final int maxMipLevels;
|
volatile boolean isLive = true;
|
||||||
private volatile boolean isLive = true;
|
|
||||||
|
|
||||||
public void setDirtyCallback(ISectionChangeCallback callback) {
|
public void setDirtyCallback(ISectionChangeCallback callback) {
|
||||||
this.dirtyCallback = callback;
|
this.dirtyCallback = callback;
|
||||||
@@ -37,12 +38,16 @@ public class WorldEngine {
|
|||||||
|
|
||||||
public Mapper getMapper() {return this.mapper;}
|
public Mapper getMapper() {return this.mapper;}
|
||||||
public boolean isLive() {return this.isLive;}
|
public boolean isLive() {return this.isLive;}
|
||||||
|
|
||||||
|
public final @Nullable VoxyInstance instanceIn;
|
||||||
|
|
||||||
public WorldEngine(SectionStorage storage, int cacheCount) {
|
public WorldEngine(SectionStorage storage, int cacheCount) {
|
||||||
this(storage, MAX_LOD_LAYER+1, cacheCount);//The +1 is because its from 1 not from 0
|
this(storage, cacheCount, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private WorldEngine(SectionStorage storage, int maxMipLayers, int cacheCount) {
|
public WorldEngine(SectionStorage storage, int cacheCount, @Nullable VoxyInstance instance) {
|
||||||
this.maxMipLevels = maxMipLayers;
|
this.instanceIn = instance;
|
||||||
|
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
this.mapper = new Mapper(this.storage);
|
this.mapper = new Mapper(this.storage);
|
||||||
//5 cache size bits means that the section tracker has 32 separate maps that it uses
|
//5 cache size bits means that the section tracker has 32 separate maps that it uses
|
||||||
@@ -101,6 +106,9 @@ public class WorldEngine {
|
|||||||
|
|
||||||
public void markDirty(WorldSection section, int changeState) {
|
public void markDirty(WorldSection section, int changeState) {
|
||||||
if (!this.isLive) throw new IllegalStateException("World is not live");
|
if (!this.isLive) throw new IllegalStateException("World is not live");
|
||||||
|
if (section.tracker != this.sectionTracker) {
|
||||||
|
throw new IllegalStateException("Section is not from here");
|
||||||
|
}
|
||||||
if (this.dirtyCallback != null) {
|
if (this.dirtyCallback != null) {
|
||||||
this.dirtyCallback.accept(section, changeState);
|
this.dirtyCallback.accept(section, changeState);
|
||||||
}
|
}
|
||||||
@@ -109,89 +117,6 @@ public class WorldEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//TODO: move this to auxilery class so that it can take into account larger than 4 mip levels
|
|
||||||
//Executes an update to the world and automatically updates all the parent mip layers up to level 4 (e.g. where 1 chunk section is 1 block big)
|
|
||||||
|
|
||||||
//NOTE: THIS RUNS ON THE THREAD IT WAS EXECUTED ON, when this method exits, the calling method may assume that VoxelizedSection is no longer needed
|
|
||||||
public void insertUpdate(VoxelizedSection section) {//TODO: add a bitset of levels to update and if it should force update
|
|
||||||
if (!this.isLive) throw new IllegalStateException("World is not live");
|
|
||||||
boolean shouldCheckEmptiness = false;
|
|
||||||
WorldSection previousSection = null;
|
|
||||||
|
|
||||||
for (int lvl = 0; lvl < this.maxMipLevels; lvl++) {
|
|
||||||
var worldSection = this.acquire(lvl, section.x >> (lvl + 1), section.y >> (lvl + 1), section.z >> (lvl + 1));
|
|
||||||
|
|
||||||
int emptinessStateChange = 0;
|
|
||||||
//Propagate the child existence state of the previous iteration to this section
|
|
||||||
if (lvl != 0 && shouldCheckEmptiness) {
|
|
||||||
emptinessStateChange = worldSection.updateEmptyChildState(previousSection);
|
|
||||||
//We kept the previous section acquired, so we need to release it
|
|
||||||
previousSection.release();
|
|
||||||
previousSection = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int msk = (1<<(lvl+1))-1;
|
|
||||||
int bx = (section.x&msk)<<(4-lvl);
|
|
||||||
int by = (section.y&msk)<<(4-lvl);
|
|
||||||
int bz = (section.z&msk)<<(4-lvl);
|
|
||||||
|
|
||||||
int nonAirCountDelta = 0;
|
|
||||||
boolean didStateChange = false;
|
|
||||||
|
|
||||||
|
|
||||||
{//Do a bunch of funny math
|
|
||||||
int baseVIdx = VoxelizedSection.getBaseIndexForLevel(lvl);
|
|
||||||
int baseSec = bx | (bz << 5) | (by << 10);
|
|
||||||
int secMsk = 0xF >> lvl;
|
|
||||||
secMsk |= (secMsk << 5) | (secMsk << 10);
|
|
||||||
var secD = worldSection.data;
|
|
||||||
for (int i = 0; i <= 0xFFF >> (lvl * 3); i++) {
|
|
||||||
int secIdx = Integer.expand(i, secMsk)+baseSec;
|
|
||||||
long newId = section.section[baseVIdx+i];
|
|
||||||
long oldId = secD[secIdx]; secD[secIdx] = newId;
|
|
||||||
nonAirCountDelta += Mapper.isAir(oldId) == Mapper.isAir(newId) ? 0 : (Mapper.isAir(newId) ? -1 : 1);
|
|
||||||
didStateChange |= newId != oldId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nonAirCountDelta != 0) {
|
|
||||||
worldSection.addNonEmptyBlockCount(nonAirCountDelta);
|
|
||||||
if (lvl == 0) {
|
|
||||||
emptinessStateChange = worldSection.updateLvl0State() ? 2 : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (didStateChange||(emptinessStateChange!=0)) {
|
|
||||||
this.markDirty(worldSection, (didStateChange?UPDATE_TYPE_BLOCK_BIT:0)|(emptinessStateChange!=0?UPDATE_TYPE_CHILD_EXISTENCE_BIT:0));
|
|
||||||
}
|
|
||||||
|
|
||||||
//Need to release the section after using it
|
|
||||||
if (didStateChange||(emptinessStateChange==2)) {
|
|
||||||
if (emptinessStateChange==2) {
|
|
||||||
//Major state emptiness change, bubble up
|
|
||||||
shouldCheckEmptiness = true;
|
|
||||||
//Dont release the section, it will be released on the next loop
|
|
||||||
previousSection = worldSection;
|
|
||||||
} else {
|
|
||||||
//Propagate up without state change
|
|
||||||
shouldCheckEmptiness = false;
|
|
||||||
previousSection = null;
|
|
||||||
worldSection.release();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//If nothing changed just need to release, dont need to update parent mips
|
|
||||||
worldSection.release();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (previousSection != null) {
|
|
||||||
previousSection.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addDebugData(List<String> debug) {
|
public void addDebugData(List<String> debug) {
|
||||||
debug.add("ACC/SCC: " + this.sectionTracker.getLoadedCacheCount()+"/"+this.sectionTracker.getSecondaryCacheSize());//Active cache count, Secondary cache counts
|
debug.add("ACC/SCC: " + this.sectionTracker.getLoadedCacheCount()+"/"+this.sectionTracker.getSecondaryCacheSize());//Active cache count, Secondary cache counts
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public final class WorldSection {
|
|||||||
volatile int nonEmptyBlockCount = 0;
|
volatile int nonEmptyBlockCount = 0;
|
||||||
volatile byte nonEmptyChildren;
|
volatile byte nonEmptyChildren;
|
||||||
|
|
||||||
private final ActiveSectionTracker tracker;
|
final ActiveSectionTracker tracker;
|
||||||
public final AtomicBoolean inSaveQueue = new AtomicBoolean();
|
public final AtomicBoolean inSaveQueue = new AtomicBoolean();
|
||||||
|
|
||||||
//When the first bit is set it means its loaded
|
//When the first bit is set it means its loaded
|
||||||
|
|||||||
90
src/main/java/me/cortex/voxy/common/world/WorldUpdater.java
Normal file
90
src/main/java/me/cortex/voxy/common/world/WorldUpdater.java
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package me.cortex.voxy.common.world;
|
||||||
|
|
||||||
|
import me.cortex.voxy.common.voxelization.VoxelizedSection;
|
||||||
|
import me.cortex.voxy.common.world.other.Mapper;
|
||||||
|
|
||||||
|
import static me.cortex.voxy.common.world.WorldEngine.*;
|
||||||
|
|
||||||
|
public class WorldUpdater {
|
||||||
|
//TODO: move this to auxilery class so that it can take into account larger than 4 mip levels
|
||||||
|
//Executes an update to the world and automatically updates all the parent mip layers up to level 4 (e.g. where 1 chunk section is 1 block big)
|
||||||
|
|
||||||
|
//NOTE: THIS RUNS ON THE THREAD IT WAS EXECUTED ON, when this method exits, the calling method may assume that VoxelizedSection is no longer needed
|
||||||
|
public static void insertUpdate(WorldEngine into, VoxelizedSection section) {//TODO: add a bitset of levels to update and if it should force update
|
||||||
|
if (!into.isLive) throw new IllegalStateException("World is not live");
|
||||||
|
boolean shouldCheckEmptiness = false;
|
||||||
|
WorldSection previousSection = null;
|
||||||
|
|
||||||
|
for (int lvl = 0; lvl < MAX_LOD_LAYER+1; lvl++) {
|
||||||
|
var worldSection = into.acquire(lvl, section.x >> (lvl + 1), section.y >> (lvl + 1), section.z >> (lvl + 1));
|
||||||
|
|
||||||
|
int emptinessStateChange = 0;
|
||||||
|
//Propagate the child existence state of the previous iteration to this section
|
||||||
|
if (lvl != 0 && shouldCheckEmptiness) {
|
||||||
|
emptinessStateChange = worldSection.updateEmptyChildState(previousSection);
|
||||||
|
//We kept the previous section acquired, so we need to release it
|
||||||
|
previousSection.release();
|
||||||
|
previousSection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int msk = (1<<(lvl+1))-1;
|
||||||
|
int bx = (section.x&msk)<<(4-lvl);
|
||||||
|
int by = (section.y&msk)<<(4-lvl);
|
||||||
|
int bz = (section.z&msk)<<(4-lvl);
|
||||||
|
|
||||||
|
int nonAirCountDelta = 0;
|
||||||
|
boolean didStateChange = false;
|
||||||
|
|
||||||
|
|
||||||
|
{//Do a bunch of funny math
|
||||||
|
int baseVIdx = VoxelizedSection.getBaseIndexForLevel(lvl);
|
||||||
|
int baseSec = bx | (bz << 5) | (by << 10);
|
||||||
|
int secMsk = 0xF >> lvl;
|
||||||
|
secMsk |= (secMsk << 5) | (secMsk << 10);
|
||||||
|
var secD = worldSection.data;
|
||||||
|
for (int i = 0; i <= 0xFFF >> (lvl * 3); i++) {
|
||||||
|
int secIdx = Integer.expand(i, secMsk)+baseSec;
|
||||||
|
long newId = section.section[baseVIdx+i];
|
||||||
|
long oldId = secD[secIdx]; secD[secIdx] = newId;
|
||||||
|
nonAirCountDelta += Mapper.isAir(oldId) == Mapper.isAir(newId) ? 0 : (Mapper.isAir(newId) ? -1 : 1);
|
||||||
|
didStateChange |= newId != oldId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nonAirCountDelta != 0) {
|
||||||
|
worldSection.addNonEmptyBlockCount(nonAirCountDelta);
|
||||||
|
if (lvl == 0) {
|
||||||
|
emptinessStateChange = worldSection.updateLvl0State() ? 2 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (didStateChange||(emptinessStateChange!=0)) {
|
||||||
|
into.markDirty(worldSection, (didStateChange?UPDATE_TYPE_BLOCK_BIT:0)|(emptinessStateChange!=0?UPDATE_TYPE_CHILD_EXISTENCE_BIT:0));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Need to release the section after using it
|
||||||
|
if (didStateChange||(emptinessStateChange==2)) {
|
||||||
|
if (emptinessStateChange==2) {
|
||||||
|
//Major state emptiness change, bubble up
|
||||||
|
shouldCheckEmptiness = true;
|
||||||
|
//Dont release the section, it will be released on the next loop
|
||||||
|
previousSection = worldSection;
|
||||||
|
} else {
|
||||||
|
//Propagate up without state change
|
||||||
|
shouldCheckEmptiness = false;
|
||||||
|
previousSection = null;
|
||||||
|
worldSection.release();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//If nothing changed just need to release, dont need to update parent mips
|
||||||
|
worldSection.release();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousSection != null) {
|
||||||
|
previousSection.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,12 +7,14 @@ import me.cortex.voxy.common.voxelization.WorldConversionFactory;
|
|||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
import me.cortex.voxy.common.thread.ServiceSlice;
|
import me.cortex.voxy.common.thread.ServiceSlice;
|
||||||
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
||||||
|
import me.cortex.voxy.common.world.WorldUpdater;
|
||||||
import me.cortex.voxy.commonImpl.IVoxyWorld;
|
import me.cortex.voxy.commonImpl.IVoxyWorld;
|
||||||
import net.minecraft.util.math.ChunkSectionPos;
|
import net.minecraft.util.math.ChunkSectionPos;
|
||||||
import net.minecraft.world.LightType;
|
import net.minecraft.world.LightType;
|
||||||
import net.minecraft.world.chunk.ChunkNibbleArray;
|
import net.minecraft.world.chunk.ChunkNibbleArray;
|
||||||
import net.minecraft.world.chunk.ChunkSection;
|
import net.minecraft.world.chunk.ChunkSection;
|
||||||
import net.minecraft.world.chunk.WorldChunk;
|
import net.minecraft.world.chunk.WorldChunk;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||||
|
|
||||||
@@ -32,46 +34,51 @@ public class VoxelIngestService {
|
|||||||
var vs = SECTION_CACHE.get().setPosition(task.cx, task.cy, task.cz);
|
var vs = SECTION_CACHE.get().setPosition(task.cx, task.cy, task.cz);
|
||||||
|
|
||||||
if (section.isEmpty() && task.blockLight==null && task.skyLight==null) {//If the chunk section has lighting data, propagate it
|
if (section.isEmpty() && task.blockLight==null && task.skyLight==null) {//If the chunk section has lighting data, propagate it
|
||||||
task.world.insertUpdate(vs.zero());
|
WorldUpdater.insertUpdate(task.world, vs.zero());
|
||||||
} else {
|
} else {
|
||||||
ILightingSupplier supplier = (x,y,z) -> (byte) 0;
|
|
||||||
var sla = task.skyLight;
|
|
||||||
var bla = task.blockLight;
|
|
||||||
boolean sl = sla != null && !sla.isUninitialized();
|
|
||||||
boolean bl = bla != null && !bla.isUninitialized();
|
|
||||||
if (sl || bl) {
|
|
||||||
if (sl && bl) {
|
|
||||||
supplier = (x,y,z)-> {
|
|
||||||
int block = Math.min(15,bla.get(x, y, z));
|
|
||||||
int sky = Math.min(15,sla.get(x, y, z));
|
|
||||||
return (byte) (sky|(block<<4));
|
|
||||||
};
|
|
||||||
} else if (bl) {
|
|
||||||
supplier = (x,y,z)-> {
|
|
||||||
int block = Math.min(15,bla.get(x, y, z));
|
|
||||||
int sky = 0;
|
|
||||||
return (byte) (sky|(block<<4));
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
supplier = (x,y,z)-> {
|
|
||||||
int block = 0;
|
|
||||||
int sky = Math.min(15,sla.get(x, y, z));
|
|
||||||
return (byte) (sky|(block<<4));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
VoxelizedSection csec = WorldConversionFactory.convert(
|
VoxelizedSection csec = WorldConversionFactory.convert(
|
||||||
SECTION_CACHE.get(),
|
SECTION_CACHE.get(),
|
||||||
task.world.getMapper(),
|
task.world.getMapper(),
|
||||||
section.getBlockStateContainer(),
|
section.getBlockStateContainer(),
|
||||||
section.getBiomeContainer(),
|
section.getBiomeContainer(),
|
||||||
supplier
|
getLightingSupplier(task)
|
||||||
);
|
);
|
||||||
WorldConversionFactory.mipSection(csec, task.world.getMapper());
|
WorldConversionFactory.mipSection(csec, task.world.getMapper());
|
||||||
task.world.insertUpdate(csec);
|
WorldUpdater.insertUpdate(task.world, csec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private static ILightingSupplier getLightingSupplier(IngestSection task) {
|
||||||
|
ILightingSupplier supplier = (x,y,z) -> (byte) 0;
|
||||||
|
var sla = task.skyLight;
|
||||||
|
var bla = task.blockLight;
|
||||||
|
boolean sl = sla != null && !sla.isUninitialized();
|
||||||
|
boolean bl = bla != null && !bla.isUninitialized();
|
||||||
|
if (sl || bl) {
|
||||||
|
if (sl && bl) {
|
||||||
|
supplier = (x,y,z)-> {
|
||||||
|
int block = Math.min(15,bla.get(x, y, z));
|
||||||
|
int sky = Math.min(15,sla.get(x, y, z));
|
||||||
|
return (byte) (sky|(block<<4));
|
||||||
|
};
|
||||||
|
} else if (bl) {
|
||||||
|
supplier = (x,y,z)-> {
|
||||||
|
int block = Math.min(15,bla.get(x, y, z));
|
||||||
|
int sky = 0;
|
||||||
|
return (byte) (sky|(block<<4));
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
supplier = (x,y,z)-> {
|
||||||
|
int block = 0;
|
||||||
|
int sky = Math.min(15,sla.get(x, y, z));
|
||||||
|
return (byte) (sky|(block<<4));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return supplier;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean shouldIngestSection(ChunkSection section, int cx, int cy, int cz) {
|
private static boolean shouldIngestSection(ChunkSection section, int cx, int cy, int cz) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ import me.cortex.voxy.common.world.WorldEngine;
|
|||||||
public interface IVoxyWorld {
|
public interface IVoxyWorld {
|
||||||
WorldEngine getWorldEngine();
|
WorldEngine getWorldEngine();
|
||||||
void setWorldEngine(WorldEngine engine);
|
void setWorldEngine(WorldEngine engine);
|
||||||
|
void shutdownEngine();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ public class VoxyInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected WorldEngine createWorld(SectionStorage storage) {
|
protected WorldEngine createWorld(SectionStorage storage) {
|
||||||
var world = new WorldEngine(storage, 2048);
|
var world = new WorldEngine(storage, 2048, this);
|
||||||
world.setSaveCallback(this.savingService::enqueueSave);
|
world.setSaveCallback(this.savingService::enqueueSave);
|
||||||
this.activeWorlds.add(world);
|
this.activeWorlds.add(world);
|
||||||
return world;
|
return world;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import me.cortex.voxy.common.util.Pair;
|
|||||||
import me.cortex.voxy.common.voxelization.VoxelizedSection;
|
import me.cortex.voxy.common.voxelization.VoxelizedSection;
|
||||||
import me.cortex.voxy.common.voxelization.WorldConversionFactory;
|
import me.cortex.voxy.common.voxelization.WorldConversionFactory;
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
|
import me.cortex.voxy.common.world.WorldUpdater;
|
||||||
import me.cortex.voxy.common.world.other.Mapper;
|
import me.cortex.voxy.common.world.other.Mapper;
|
||||||
import me.cortex.voxy.common.world.service.SectionSavingService;
|
import me.cortex.voxy.common.world.service.SectionSavingService;
|
||||||
import net.minecraft.block.Block;
|
import net.minecraft.block.Block;
|
||||||
@@ -231,6 +232,9 @@ public class DHImporter implements IDataImporter {
|
|||||||
Logger.warn("Could not find block state with data", encEntry.substring(b));
|
Logger.warn("Could not find block state with data", encEntry.substring(b));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (block == Blocks.AIR) {
|
||||||
|
Logger.warn("Could not find block entry with id:", bId);
|
||||||
|
}
|
||||||
blockId = this.engine.getMapper().getIdForBlockState(state);
|
blockId = this.engine.getMapper().getIdForBlockState(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,6 +299,7 @@ public class DHImporter implements IDataImporter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((x+1)%16==0) {
|
if ((x+1)%16==0) {
|
||||||
for (int sz = 0; sz < 4; sz++) {
|
for (int sz = 0; sz < 4; sz++) {
|
||||||
for (int sy = 0; sy < this.worldHeightSections; sy++) {
|
for (int sy = 0; sy < this.worldHeightSections; sy++) {
|
||||||
@@ -302,7 +307,7 @@ public class DHImporter implements IDataImporter {
|
|||||||
WorldConversionFactory.mipSection(section, this.engine.getMapper());
|
WorldConversionFactory.mipSection(section, this.engine.getMapper());
|
||||||
|
|
||||||
section.setPosition(X*4+(x>>4), sy+(this.bottomOfWorld>>4), (Z*4)+sz);
|
section.setPosition(X*4+(x>>4), sy+(this.bottomOfWorld>>4), (Z*4)+sz);
|
||||||
this.engine.insertUpdate(section);
|
WorldUpdater.insertUpdate(this.engine, section);
|
||||||
}
|
}
|
||||||
|
|
||||||
int count = this.processedChunks.incrementAndGet();
|
int count = this.processedChunks.incrementAndGet();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import me.cortex.voxy.common.voxelization.WorldConversionFactory;
|
|||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
import me.cortex.voxy.common.thread.ServiceSlice;
|
import me.cortex.voxy.common.thread.ServiceSlice;
|
||||||
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
||||||
|
import me.cortex.voxy.common.world.WorldUpdater;
|
||||||
import me.cortex.voxy.common.world.service.SectionSavingService;
|
import me.cortex.voxy.common.world.service.SectionSavingService;
|
||||||
import net.minecraft.block.Block;
|
import net.minecraft.block.Block;
|
||||||
import net.minecraft.block.BlockState;
|
import net.minecraft.block.BlockState;
|
||||||
@@ -473,7 +474,6 @@ public class WorldImporter implements IDataImporter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
WorldConversionFactory.mipSection(csec, this.world.getMapper());
|
WorldConversionFactory.mipSection(csec, this.world.getMapper());
|
||||||
|
WorldUpdater.insertUpdate(this.world, csec);
|
||||||
this.world.insertUpdate(csec);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,4 +25,12 @@ public class MixinWorld implements IVoxyWorld {
|
|||||||
}
|
}
|
||||||
this.voxyWorld = engine;
|
this.voxyWorld = engine;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdownEngine() {
|
||||||
|
if (this.voxyWorld != null && this.voxyWorld.instanceIn != null) {
|
||||||
|
this.voxyWorld.instanceIn.stopWorld(this.voxyWorld);
|
||||||
|
this.setWorldEngine(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
"voxy.config.title": "Voxy config",
|
"voxy.config.title": "Voxy config",
|
||||||
|
|
||||||
"voxy.config.general": "General",
|
"voxy.config.general": "General",
|
||||||
"voxy.config.threads": "Threads",
|
|
||||||
"voxy.config.storage": "Storage",
|
|
||||||
|
|
||||||
"voxy.config.general.enabled": "Enable Voxy",
|
"voxy.config.general.enabled": "Enable Voxy",
|
||||||
"voxy.config.general.enabled.tooltip": "Fully enables or disables voxy",
|
"voxy.config.general.enabled.tooltip": "Fully enables or disables voxy",
|
||||||
@@ -18,5 +16,8 @@
|
|||||||
"voxy.config.general.serviceThreads.tooltip": "Number of threads the ServiceThreadPool can use",
|
"voxy.config.general.serviceThreads.tooltip": "Number of threads the ServiceThreadPool can use",
|
||||||
|
|
||||||
"voxy.config.general.subDivisionSize": "Pixels^2 of subdivision size",
|
"voxy.config.general.subDivisionSize": "Pixels^2 of subdivision size",
|
||||||
"voxy.config.general.subDivisionSize.tooltip": "Maximum size in pixels (squared) of screenspace AABB before subdiving to smaller LoDs (Smaller being higher quality)"
|
"voxy.config.general.subDivisionSize.tooltip": "Maximum size in pixels (squared) of screenspace AABB before subdiving to smaller LoDs (Smaller being higher quality)",
|
||||||
|
|
||||||
|
"voxy.config.general.renderDistance": "Render distance",
|
||||||
|
"voxy.config.general.renderDistance.tooltip": "Render distance of voxy, each unit is equivalent to 32 chunks in vanilla (i.e. to get to vanilla render distance multiply by 32)"
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
#line 1
|
|
||||||
//TODO: FIXME: this isnt actually correct cause depending on the face (i think) it could be 1/64 th of a position off
|
//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
|
// but im going to assume that since we are dealing with huge render distances, this shouldent matter that much
|
||||||
float extractFaceIndentation(uint faceData) {
|
float extractFaceIndentation(uint faceData) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#line 1
|
|
||||||
#ifdef GL_ARB_gpu_shader_int64
|
#ifdef GL_ARB_gpu_shader_int64
|
||||||
#define Quad uint64_t
|
#define Quad uint64_t
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#line 1
|
|
||||||
uint extractDetail(SectionMeta section) {
|
uint extractDetail(SectionMeta section) {
|
||||||
return section.posA>>28;
|
return section.posA>>28;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"minecraft.MixinDebugHud",
|
"minecraft.MixinDebugHud",
|
||||||
"minecraft.MixinMinecraftClient",
|
"minecraft.MixinMinecraftClient",
|
||||||
"minecraft.MixinThreadExecutor",
|
"minecraft.MixinThreadExecutor",
|
||||||
|
"minecraft.MixinWindow",
|
||||||
"minecraft.MixinWorldRenderer",
|
"minecraft.MixinWorldRenderer",
|
||||||
"sodium.MixinDefaultChunkRenderer",
|
"sodium.MixinDefaultChunkRenderer",
|
||||||
"sodium.MixinRenderSectionManager"
|
"sodium.MixinRenderSectionManager"
|
||||||
|
|||||||
Reference in New Issue
Block a user