50 Commits

Author SHA1 Message Date
mcrcortex
2327c6baf8 thing 2025-06-18 08:53:32 +10:00
mcrcortex
144faf5b21 move frex check loop 2025-06-17 12:52:55 +10:00
mcrcortex
dc6dd4bb11 remove pow for now 2025-06-16 23:40:10 +10:00
mcrcortex
84482e8998 Make the request queue dynamically capped, greatly increasing snappyness 2025-06-16 23:39:36 +10:00
mcrcortex
072ece7a3d add max 2025-06-16 21:36:39 +10:00
mcrcortex
22d557ed01 am stupid 2025-06-16 21:33:48 +10:00
mcrcortex
b454d54a99 it works 2025-06-16 20:45:49 +10:00
mcrcortex
341119386a poc 2025-06-16 20:17:44 +10:00
mcrcortex
1575d7319c big progress in fixing culling 2025-06-16 20:03:28 +10:00
mcrcortex
950e92d7c7 h 2025-06-16 20:03:12 +10:00
mcrcortex
0e98f52580 improved logger 2025-06-16 19:28:32 +10:00
mcrcortex
caf2703102 small thing 2025-06-16 12:41:29 +10:00
mcrcortex
b79923de3d Remove setup 2025-06-16 12:22:26 +10:00
mcrcortex
ee6d171ef6 Note 2025-06-16 12:21:37 +10:00
mcrcortex
1c30198347 Remove an info log 2025-06-14 13:54:31 +10:00
mcrcortex
4ed9199e1c attempt improvments is not :( 2025-06-14 00:20:23 +10:00
mcrcortex
2027cb064c works 2025-06-14 00:14:18 +10:00
mcrcortex
a00eec69b7 a 2025-06-13 23:19:23 +10:00
mcrcortex
84c07c4115 tweeked fences? 2025-06-10 20:45:39 +10:00
mcrcortex
6398164d42 attempt to improve the data preperation of render factory 2025-06-10 20:37:34 +10:00
mcrcortex
fa42ad5a03 remove copies 2025-06-10 19:25:56 +10:00
mcrcortex
22553eb1f9 attempted to improve mipping by computing dx,dy accross entire uv instead of local uv 2025-06-10 13:08:26 +10:00
mcrcortex
cb599eea0b misc fixes 2025-06-09 20:47:40 +10:00
mcrcortex
f73413e7c0 Dont deadlock 2025-06-09 20:26:19 +10:00
mcrcortex
5b752d3f87 remove unused 2025-06-09 19:25:48 +10:00
mcrcortex
4f37d3b597 remove old memory patch 2025-06-07 20:32:42 +10:00
mcrcortex
21b497d2d4 force gl finish 2025-06-07 19:29:56 +10:00
mcrcortex
3bfc0c266d change to stable hash 2025-06-07 16:10:46 +10:00
mcrcortex
f252fa3a7a Make into error 2025-06-07 14:41:18 +10:00
mcrcortex
66266fb426 Note 2025-06-07 14:38:02 +10:00
mcrcortex
225e2d9d1a is ment to be * not + idiot 2025-06-07 14:37:02 +10:00
mcrcortex
3b4aa75890 am dumb stupid or also dumb, how this did not cause multiple things to immediatly explode 18 times, have no idea 2025-06-07 14:04:45 +10:00
mcrcortex
0c1917d56e hopefully fix issues 2025-06-07 12:02:30 +10:00
mcrcortex
35850082d5 Shuffled around shaders 2025-06-06 17:00:25 +10:00
mcrcortex
d24b719a93 Move tests 2025-06-06 12:02:54 +10:00
mcrcortex
a0c33a439b subtract 1gb from memory limit instead of 512 mb 2025-06-04 16:21:14 +10:00
mcrcortex
6bbd2c521a Use representitive fragment when possible 2025-06-03 22:14:46 +10:00
mcrcortex
7575c35b02 tweek build script 2025-06-03 16:18:31 +10:00
mcrcortex
f78a8df275 fix possible issue of deadlock with geometry cleaner 2025-06-03 01:02:57 +10:00
mcrcortex
8462dde374 use provoking vertex 2025-06-03 00:08:01 +10:00
mcrcortex
075e8f2897 attempt to improve rocksdb options 2025-06-02 22:12:29 +10:00
mcrcortex
204989b909 tweeks + version number in config 2025-06-02 21:28:15 +10:00
mcrcortex
c023e3b4f2 Fix face baking not being flipped, hopefully 2025-06-02 12:46:38 +10:00
mcrcortex
e7c4d6f132 more state checking 2025-06-02 12:23:06 +10:00
mcrcortex
9d0cf33a45 Attempted fix for world timeout 2025-06-02 11:47:12 +10:00
mcrcortex
34c5c71d77 change to logger 2025-05-30 20:48:37 +10:00
mcrcortex
03bede4067 more logic errors fixed 2025-05-29 23:32:33 +10:00
mcrcortex
f624f85698 nvm am just stupid 2025-05-29 23:29:44 +10:00
mcrcortex
985fa4b53c apparently . is converted into a folder and becomes .\ instead of .voxy 2025-05-29 23:26:53 +10:00
mcrcortex
6c6c08d188 better debug logging + fix tracking crash 2025-05-29 23:13:20 +10:00
52 changed files with 503 additions and 879 deletions

View File

@@ -188,7 +188,9 @@ processIncludeJars {
}
remapJar {
delete getDestinationDirectory().get()
doFirst {
delete fileTree(getDestinationDirectory().get())
}
def hash = gitCommitHash();
if (!hash.equals("<UnknownCommit>")) {
@@ -218,7 +220,7 @@ dependencies {
include(runtimeOnly "org.lwjgl:lwjgl-zstd:$lwjglVersion:natives-linux")
include(implementation 'redis.clients:jedis:5.1.0')
include(implementation('org.rocksdb:rocksdbjni:8.10.0'))
include(implementation('org.rocksdb:rocksdbjni:10.2.1'))
include(implementation 'org.apache.commons:commons-pool2:2.12.0')
include(implementation 'org.lz4:lz4-java:1.8.0')
include(implementation('org.tukaani:xz:1.10'))

View File

@@ -29,9 +29,7 @@ public class TimingStatistics {
throw new IllegalStateException();
}
this.running = true;
VarHandle.fullFence();
this.timestamp = System.nanoTime();
VarHandle.fullFence();
}
public void stop() {
@@ -39,9 +37,7 @@ public class TimingStatistics {
throw new IllegalStateException();
}
this.running = false;
VarHandle.fullFence();
this.runtime += System.nanoTime() - this.timestamp;
VarHandle.fullFence();
}
public void subtract(TimeSampler sampler) {

View File

@@ -29,6 +29,11 @@ public class VoxyClientInstance extends VoxyInstance {
private final Path basePath = getBasePath();
public VoxyClientInstance() {
super(VoxyConfig.CONFIG.serviceThreads);
try {
Files.createDirectories(this.basePath);
} catch (Exception e) {
throw new RuntimeException(e);
}
this.storageConfig = getCreateStorageConfig(this.basePath);
}
@@ -75,26 +80,25 @@ public class VoxyClientInstance extends VoxyInstance {
try {
config = Serialization.GSON.fromJson(Files.readString(json), Config.class);
if (config == null) {
throw new IllegalStateException("Config deserialization null, reverting to default");
}
Logger.error("Config deserialization null, reverting to default");
} else {
if (config.sectionStorageConfig == null) {
throw new IllegalStateException("Config section storage null, reverting to default");
Logger.error("Config section storage null, reverting to default");
config = null;
}
}
} catch (Exception e) {
Logger.error("Failed to load the storage configuration file, resetting it to default, this will probably break your save if you used a custom storage config", e);
}
}
try {
if (config == null) {
config = DEFAULT_STORAGE_CONFIG;
}
try {
Files.writeString(json, Serialization.GSON.toJson(config));
} catch (IOException e) {
throw new RuntimeException(e);
}
} catch (Exception e) {
throw new RuntimeException("Failed to deserialize the default config, aborting!", e);
throw new RuntimeException("Failed write the config, aborting!", e);
}
if (config == null) {
throw new IllegalStateException("Config is still null\n");
@@ -103,6 +107,7 @@ public class VoxyClientInstance extends VoxyInstance {
}
private static class Config {
public int version = 1;
public SectionStorageConfig sectionStorageConfig;
}
private static final Config DEFAULT_STORAGE_CONFIG;
@@ -152,96 +157,4 @@ public class VoxyClientInstance extends VoxyInstance {
}
return basePath.toAbsolutePath();
}
/*
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");
}
private void verifyTopNodeChildren(int X, int Y, int Z) {
var world = this.getOrMakeRenderWorld(MinecraftClient.getInstance().world);
for (int lvl = 0; lvl < 5; lvl++) {
for (int y = (Y<<5)>>lvl; y < ((Y+1)<<5)>>lvl; y++) {
for (int x = (X<<5)>>lvl; x < ((X+1)<<5)>>lvl; x++) {
for (int z = (Z<<5)>>lvl; z < ((Z+1)<<5)>>lvl; z++) {
if (lvl == 0) {
var own = world.acquire(lvl, x, y, z);
if ((own.getNonEmptyChildren() != 0) ^ (own.getNonEmptyBlockCount() != 0)) {
Logger.error("Lvl 0 node not marked correctly " + WorldEngine.pprintPos(own.key));
}
own.release();
} else {
byte msk = 0;
for (int child = 0; child < 8; child++) {
var section = world.acquire(lvl-1, (child&1)+(x<<1), ((child>>2)&1)+(y<<1), ((child>>1)&1)+(z<<1));
msk |= (byte) (section.getNonEmptyBlockCount()!=0?(1<<child):0);
section.release();
}
var own = world.acquire(lvl, x, y, z);
if (own.getNonEmptyChildren() != msk) {
Logger.error("Section empty child mask not correct " + WorldEngine.pprintPos(own.key) + " got: " + String.format("%8s", Integer.toBinaryString(Byte.toUnsignedInt(own.getNonEmptyChildren()))).replace(' ', '0') + " expected: " + String.format("%8s", Integer.toBinaryString(Byte.toUnsignedInt(msk))).replace(' ', '0'));
}
own.release();
}
}
}
}
}
}
*/
}

View File

@@ -28,7 +28,7 @@ public class VoxyConfig implements OptionStorage<VoxyConfig> {
public boolean enableRendering = true;
public boolean ingestEnabled = true;
public int sectionRenderDistance = 16;
public int serviceThreads = Math.max(CpuLayout.CORES.length/2, 1);
public int serviceThreads = (int) Math.max(CpuLayout.CORES.length/1.5, 1);
public float subDivisionSize = 64;
public boolean renderVanillaFog = false;
public boolean renderStatistics = false;

View File

@@ -61,6 +61,14 @@ public class VoxyRenderSystem {
public final ChunkBoundRenderer chunkBoundRenderer;
public VoxyRenderSystem(WorldEngine world, ServiceThreadPool threadPool) {
//Keep the world loaded, NOTE: this is done FIRST, to keep and ensure that even if the rest of loading takes more
// than timeout, we keep the world acquired
world.acquireRef();
try {
//wait for opengl to be finished, this should hopefully ensure all memory allocations are free
glFinish();
glFinish();
//Trigger the shared index buffer loading
SharedIndexBuffer.INSTANCE.id();
Capabilities.init();//Ensure clinit is called
@@ -68,8 +76,8 @@ public class VoxyRenderSystem {
this.worldIn = world;
this.renderer = new RenderService(world, threadPool);
this.postProcessing = new PostProcessing();
int minSec = MinecraftClient.getInstance().world.getBottomSectionCoord()>>5;
int maxSec = (MinecraftClient.getInstance().world.getTopSectionCoord()-1)>>5;
int minSec = MinecraftClient.getInstance().world.getBottomSectionCoord() >> 5;
int maxSec = (MinecraftClient.getInstance().world.getTopSectionCoord() - 1) >> 5;
//Do some very cheeky stuff for MiB
if (false) {
@@ -86,9 +94,10 @@ public class VoxyRenderSystem {
this.renderDistanceTracker.setRenderDistance(VoxyConfig.CONFIG.sectionRenderDistance);
this.chunkBoundRenderer = new ChunkBoundRenderer();
//Keep the world loaded
this.worldIn.acquireRef();
} catch (RuntimeException e) {
world.releaseRef();//If something goes wrong, we must release the world first
throw e;
}
}
public void setRenderDistance(int renderDistance) {
@@ -96,40 +105,6 @@ public class VoxyRenderSystem {
}
//private static final ModelTextureBakery mtb = new ModelTextureBakery(16, 16);
//private static final RawDownloadStream downstream = new RawDownloadStream(1<<20);
public void renderSetup(Frustum frustum, Camera camera) {
TimingStatistics.resetSamplers();
/*
if (false) {
int allocation = downstream.download(2 * 4 * 6 * 16 * 16, ptr -> {
ColourDepthTextureData[] textureData = new ColourDepthTextureData[6];
final int FACE_SIZE = 16 * 16;
for (int face = 0; face < 6; face++) {
long faceDataPtr = ptr + (FACE_SIZE * 4) * face * 2;
int[] colour = new int[FACE_SIZE];
int[] depth = new int[FACE_SIZE];
//Copy out colour
for (int i = 0; i < FACE_SIZE; i++) {
//De-interpolate results
colour[i] = MemoryUtil.memGetInt(faceDataPtr + (i * 4 * 2));
depth[i] = MemoryUtil.memGetInt(faceDataPtr + (i * 4 * 2) + 4);
}
textureData[face] = new ColourDepthTextureData(colour, depth, 16, 16);
}
if (textureData[0].colour()[0] == 0) {
int a = 0;
}
});
mtb.renderFacesToStream(Blocks.AIR.getDefaultState(), 123456, false, downstream.getBufferId(), allocation);
downstream.submit();
downstream.tick();
}*/
}
private void autoBalanceSubDivSize() {
//only increase quality while there are very few mesh queues, this stops,
// e.g. while flying and is rendering alot of low quality chunks
@@ -172,6 +147,9 @@ public class VoxyRenderSystem {
if (IrisUtil.irisShadowActive()) {
return;
}
TimingStatistics.resetSamplers();
//Do some very cheeky stuff for MiB
if (false) {
int sector = (((int)Math.floor(cameraX)>>4)+512)>>10;
@@ -218,7 +196,7 @@ public class VoxyRenderSystem {
this.postProcessing.setup(viewport.width, viewport.height, boundFB);
TimingStatistics.F.stop();
this.renderer.renderFarAwayOpaque(viewport, this.chunkBoundRenderer.getDepthBoundTexture(), startTime);
this.renderer.renderFarAwayOpaque(viewport, this.chunkBoundRenderer.getDepthBoundTexture());
TimingStatistics.F.start();
@@ -296,129 +274,4 @@ public class VoxyRenderSystem {
//Release hold on the world
this.worldIn.releaseRef();
}
private void testMeshingPerformance() {
var modelService = new ModelBakerySubsystem(this.worldIn.getMapper());
var factory = new RenderDataFactory(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(5_000_000);
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(), false);
generationService.setResultConsumer(a-> {completedCounter.incrementAndGet(); a.free();});
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(5_000_000);
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(5_000_000);
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();
}
}

View File

@@ -13,6 +13,7 @@ public class Capabilities {
public static final Capabilities INSTANCE = new Capabilities();
public final boolean repFragTest;
public final boolean meshShaders;
public final boolean INT64_t;
public final long ssboMaxSize;
@@ -28,7 +29,8 @@ public class Capabilities {
var cap = GL.getCapabilities();
this.compute = cap.glDispatchComputeIndirect != 0;
this.indirectParameters = cap.glMultiDrawElementsIndirectCountARB != 0;
this.meshShaders = cap.GL_NV_mesh_shader && cap.GL_NV_representative_fragment_test;
this.repFragTest = cap.GL_NV_representative_fragment_test;
this.meshShaders = cap.GL_NV_mesh_shader;
this.canQueryGpuMemory = cap.GL_NVX_gpu_memory_info;
//this.INT64_t = cap.GL_ARB_gpu_shader_int64 || cap.GL_AMD_gpu_shader_int64;
//The only reliable way to test for int64 support is to try compile a shader

View File

@@ -175,7 +175,7 @@ public class Shader extends TrackedObject {
String log = GL20C.glGetShaderInfoLog(shader);
if (!log.isEmpty()) {
System.err.println(log);
Logger.warn(log);
}
int result = GL20C.glGetShaderi(shader, GL20C.GL_COMPILE_STATUS);

View File

@@ -74,8 +74,6 @@ public class ModelBakerySubsystem {
}
}*/
//TimingStatistics.modelProcess.start();
long start = System.nanoTime();
VarHandle.fullFence();
if (this.blockIdCount.get() != 0) {
long budget = Math.min(totalBudget-150_000, totalBudget-(this.factory.resultJobs.size()*10_000L))-150_000;
@@ -100,7 +98,7 @@ public class ModelBakerySubsystem {
this.factory.tick();
start = System.nanoTime();
long start = System.nanoTime();
while (!this.factory.resultJobs.isEmpty()) {
this.factory.resultJobs.poll().run();
if (totalBudget<(System.nanoTime()-start))

View File

@@ -8,6 +8,7 @@ import me.cortex.voxy.client.core.gl.Capabilities;
import me.cortex.voxy.client.core.model.bakery.ModelTextureBakery;
import me.cortex.voxy.client.core.rendering.util.RawDownloadStream;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.util.MemoryBuffer;
import me.cortex.voxy.common.world.other.Mapper;
import net.minecraft.block.Block;
@@ -490,7 +491,7 @@ public class ModelFactory {
throw new IllegalStateException("Biome was put in an id that was not null");
}
if (oldBiome == biome) {
System.err.println("Biome added was a duplicate");
Logger.error("Biome added was a duplicate");
}
int i = 0;

View File

@@ -221,6 +221,12 @@ public class ModelTextureBakery {
var mat = new Matrix4f();
for (int i = 0; i < VIEWS.length; i++) {
if (i==1||i==2||i==4) {
glCullFace(GL_FRONT);
} else {
glCullFace(GL_BACK);
}
glViewport((i % 3) * this.width, (i / 3) * this.height, this.width, this.height);
//The projection matrix
@@ -239,6 +245,12 @@ public class ModelTextureBakery {
var mat = new Matrix4f();
for (int i = 0; i < VIEWS.length; i++) {
if (i==1||i==2||i==4) {
glCullFace(GL_FRONT);
} else {
glCullFace(GL_BACK);
}
this.vc.reset();
this.bakeFluidState(state, layer, i);
if (this.vc.isEmpty()) continue;
@@ -264,6 +276,12 @@ public class ModelTextureBakery {
var mat = new Matrix4f();
for (int i = 0; i < VIEWS.length; i++) {
if (i==1||i==2||i==4) {
glCullFace(GL_FRONT);
} else {
glCullFace(GL_BACK);
}
glViewport((i % 3) * this.width, (i / 3) * this.height, this.width, this.height);
//The projection matrix
@@ -303,23 +321,24 @@ public class ModelTextureBakery {
static {
//TODO: FIXME: need to bake in the correct orientation, HOWEVER some orientations require a flipped winding order!!!!
//the face/direction is the face (e.g. down is the down face)
addView(0, -90,0, 0, 0);//Direction.DOWN
addView(1, 90,0, 0, 0b100);//Direction.UP
addView(0, -90,0, 0, false);//Direction.DOWN
addView(1, 90,0, 0, false);//Direction.UP
addView(2, 0,180, 0, true);//Direction.NORTH
addView(3, 0,0, 0, false);//Direction.SOUTH
//TODO: check these arnt the wrong way round
addView(4, 0,90, 270, false);//Direction.EAST
addView(5, 0,270, 270, false);//Direction.WEST
addView(2, 0,180, 0, 0b001);//Direction.NORTH
addView(3, 0,0, 0, 0);//Direction.SOUTH
addView(4, 0,90, 270, 0b100);//Direction.WEST
addView(5, 0,270, 270, 0);//Direction.EAST
}
private static void addView(int i, float pitch, float yaw, float rotation, boolean flipX) {
private static void addView(int i, float pitch, float yaw, float rotation, int flip) {
var stack = new MatrixStack();
stack.translate(0.5f,0.5f,0.5f);
stack.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(rotation));
stack.multiply(RotationAxis.POSITIVE_X.rotationDegrees(pitch));
stack.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw));
stack.multiplyPositionMatrix(new Matrix4f().scale(1-2*(flip&1), 1-(flip&2), 1-((flip>>1)&2)));
stack.translate(-0.5f,-0.5f,-0.5f);
VIEWS[i] = new Matrix4f(stack.peek().getPositionMatrix());
}

View File

@@ -50,13 +50,13 @@ public class RenderService<T extends AbstractSectionRenderer<J, Q>, J extends Vi
//Limit to available dedicated memory if possible
if (Capabilities.INSTANCE.canQueryGpuMemory) {
//512mb less than avalible,
long limit = Capabilities.INSTANCE.getFreeDedicatedGpuMemory() - 512*1024*1024;
long limit = Capabilities.INSTANCE.getFreeDedicatedGpuMemory() - 1024*1024*1024;
// Give a minimum of 512 mb requirement
limit = Math.max(512*1024*1024, limit);
geometryCapacity = Math.min(geometryCapacity, limit);
}
//geometryCapacity = 1<<24;
//geometryCapacity = 1<<28;
//geometryCapacity = 1<<30;//1GB test
return geometryCapacity;
}
@@ -84,7 +84,7 @@ public class RenderService<T extends AbstractSectionRenderer<J, Q>, J extends Vi
this.nodeManager = new AsyncNodeManager(1<<21, this.geometryData, this.renderGen);
this.nodeCleaner = new NodeCleaner(this.nodeManager);
this.traversal = new HierarchicalOcclusionTraverser(this.nodeManager, this.nodeCleaner);
this.traversal = new HierarchicalOcclusionTraverser(this.nodeManager, this.nodeCleaner, this.renderGen);
world.setDirtyCallback(this.nodeManager::worldEvent);
@@ -106,7 +106,19 @@ public class RenderService<T extends AbstractSectionRenderer<J, Q>, J extends Vi
this.modelService.tick(budget);
}
public void renderFarAwayOpaque(J viewport, GlTexture depthBoundTexture, long frameStart) {
private boolean frexStillHasWork() {
if (!VoxyClient.isFrexActive()) {
return false;
}
//If frex is running we must tick everything to ensure correctness
UploadStream.INSTANCE.tick();
//Done here as is allows less gl state resetup
this.modelService.tick(100_000_000);
glFinish();
return this.nodeManager.hasWork() || this.renderGen.getTaskCount()!=0 || !this.modelService.areQueuesEmpty();
}
public void renderFarAwayOpaque(J viewport, GlTexture depthBoundTexture) {
//LightMapHelper.tickLightmap();
//Render previous geometry with the abstract renderer
@@ -121,6 +133,13 @@ public class RenderService<T extends AbstractSectionRenderer<J, Q>, J extends Vi
this.sectionRenderer.renderOpaque(viewport, depthBoundTexture);
TimingStatistics.G.stop();
{
int depthBuffer = glGetFramebufferAttachmentParameteri(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
//Compute the mip chain
viewport.hiZBuffer.buildMipChain(depthBuffer, viewport.width, viewport.height);
}
do {
//NOTE: need to do the upload and download tick here, after the section renderer renders the world, to ensure "stable"
// sections
@@ -157,23 +176,11 @@ public class RenderService<T extends AbstractSectionRenderer<J, Q>, J extends Vi
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT | GL_PIXEL_BUFFER_BARRIER_BIT);
int depthBuffer = glGetFramebufferAttachmentParameteri(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
//if (depthBuffer == 0) {
// depthBuffer = glGetFramebufferAttachmentParameteri(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
//}
TimingStatistics.I.start();
this.traversal.doTraversal(viewport, depthBuffer);
this.traversal.doTraversal(viewport);
TimingStatistics.I.stop();
if (VoxyClient.isFrexActive()) {//If frex is running we must tick everything to ensure correctness
UploadStream.INSTANCE.tick();
//Done here as is allows less gl state resetup
this.tickModelService(100_000_000);
glFinish();
}
} while (VoxyClient.isFrexActive() && (this.nodeManager.hasWork() || this.renderGen.getTaskCount()!=0 || !this.modelService.areQueuesEmpty()));
} while (this.frexStillHasWork());
TimingStatistics.H.start();

View File

@@ -207,17 +207,17 @@ public class RenderDataFactory {
return quadData;
}
private int prepareSectionData() {
private int prepareSectionData(final long[] rawSectionData) {
final var sectionData = this.sectionData;
final var rawModelIds = this.modelMan._unsafeRawAccess();
int opaque = 0;
int notEmpty = 0;
int pureFluid = 0;
int partialFluid = 0;
long opaque = 0;
long notEmpty = 0;
long pureFluid = 0;
long partialFluid = 0;
int neighborAcquireMsk = 0;//-+x, -+y, -+Z
int neighborAcquireMsk = 0;//-+x, -+z, -+y
for (int i = 0; i < 32*32*32;) {
long block = sectionData[i + 32 * 32 * 32];//Get the block mapping
long block = rawSectionData[i];//Get the block mapping
if (Mapper.isAir(block)) {//If it is air, just emit lighting
sectionData[i * 2] = (block&(0xFFL<<56))>>1;
sectionData[i * 2 + 1] = 0;
@@ -231,7 +231,7 @@ public class RenderDataFactory {
sectionData[i * 2] = packPartialQuadData(modelId, block, modelMetadata);
sectionData[i * 2 + 1] = modelMetadata;
int msk = 1 << (i & 31);
long msk = 1L << (i & 63);
opaque |= ModelQueries.isFullyOpaque(modelMetadata) ? msk : 0;
notEmpty |= modelId != 0 ? msk : 0;
pureFluid |= ModelQueries.isFluid(modelMetadata) ? msk : 0;
@@ -241,21 +241,28 @@ public class RenderDataFactory {
//Do increment here
i++;
if ((i & 31) == 0 && notEmpty != 0) {
this.opaqueMasks[(i >> 5) - 1] = opaque;
this.nonOpaqueMasks[(i >> 5) - 1] = (notEmpty^opaque)&~pureFluid;
this.fluidMasks[(i >> 5) - 1] = pureFluid|partialFluid;
if ((i & 63) == 0 && notEmpty != 0) {
long nonOpaque = (notEmpty^opaque)&~pureFluid;
long fluid = pureFluid|partialFluid;
this.opaqueMasks[(i >> 5) - 2] = (int) opaque;
this.opaqueMasks[(i >> 5) - 1] = (int) (opaque>>>32);
this.nonOpaqueMasks[(i >> 5) - 2] = (int) nonOpaque;
this.nonOpaqueMasks[(i >> 5) - 1] = (int) (nonOpaque>>>32);
this.fluidMasks[(i >> 5) - 2] = (int) fluid;
this.fluidMasks[(i >> 5) - 1] = (int) (fluid>>>32);
int packedEmpty = (int) ((notEmpty>>>32)|notEmpty);
int neighborMsk = 0;
//-+x
neighborMsk |= notEmpty&1;//-x
neighborMsk |= (notEmpty>>>30)&0b10;//+x
neighborMsk |= packedEmpty&1;//-x
neighborMsk |= (packedEmpty>>>30)&0b10;//+x
//notEmpty = (notEmpty != 0)?1:0;
neighborMsk |= (((i - 1) >> 10) == 0) ? 0b100 : 0;//-y
neighborMsk |= (((i - 1) >> 10) == 31) ? 0b1000 : 0;//+y
neighborMsk |= ((((i - 1) >> 5) & 0x1F) == 0) ? 0b10000 : 0;//-z
neighborMsk |= ((((i - 1) >> 5) & 0x1F) == 31) ? 0b100000 : 0;//+z
neighborMsk |= ((((i - 1) >> 10) == 0) ? 0b100 : 0)*(packedEmpty!=0?1:0);//-y
neighborMsk |= ((((i - 1) >> 10) == 31) ? 0b1000 : 0)*(packedEmpty!=0?1:0);//+y
neighborMsk |= (((((i - 33) >> 5) & 0x1F) == 0) ? 0b10000 : 0)*(((int)notEmpty)!=0?1:0);//-z
neighborMsk |= (((((i - 1) >> 5) & 0x1F) == 31) ? 0b100000 : 0)*((notEmpty>>>32)!=0?1:0);//+z
neighborAcquireMsk |= neighborMsk;
@@ -1542,7 +1549,7 @@ public class RenderDataFactory {
//THE EXCEPTION THAT THIS THROWS CAUSES MAJOR ISSUES
//Copy section data to end of array so that can mutate array while reading safely
section.copyDataTo(this.sectionData, 32*32*32);
//section.copyDataTo(this.sectionData, 32*32*32);
//We must reset _everything_ that could have changed as we dont exactly know the state due to how the model id exception
// throwing system works
@@ -1578,7 +1585,7 @@ public class RenderDataFactory {
Arrays.fill(this.fluidMasks, 0);
//Prepare everything
int neighborMsk = this.prepareSectionData();
int neighborMsk = this.prepareSectionData(section._unsafeGetRawDataArray());
if (neighborMsk>>31!=0) {//We failed to get everything so throw exception
throw new IdNotYetComputedException(neighborMsk&(~(1<<31)), true);
}

View File

@@ -43,7 +43,7 @@ public class RenderGenerationService {
int unique = COUNTER.incrementAndGet();
int lvl = WorldEngine.MAX_LOD_LAYER-WorldEngine.getLevel(this.position);
lvl = Math.min(lvl, 3);//Make the 2 highest quality have equal priority
this.priority = (((lvl*3L + Math.min(this.attempts, 5))*2 + this.addin) <<32) + Integer.toUnsignedLong(unique);
this.priority = (((lvl*3L + Math.min(this.attempts, 3))*2 + this.addin) <<32) + Integer.toUnsignedLong(unique);
this.addin = 0;
}
}
@@ -238,7 +238,7 @@ public class RenderGenerationService {
task.hasDoneModelRequestOuter = true;
}
task.addin = WorldEngine.getLevel(task.position)>2?3:0;//Single time addin which gives the models time to bake before the task executes
task.addin = WorldEngine.getLevel(task.position)>2?1:0;//Single time addin which gives the models time to bake before the task executes
}
//Keep the lock on the section, and attach it to the task, this prevents needing to re-aquire it later

View File

@@ -179,6 +179,7 @@ public class AsyncNodeManager {
private void run() {
if (this.workCounter.get() <= 0) {
//TODO: here, instead of parking, we can do more work on other sub-tasks such as filtering the mesh build queue
LockSupport.park();
if (this.workCounter.get() <= 0 || !this.running) {//No work
return;
@@ -245,8 +246,7 @@ public class AsyncNodeManager {
//Limit uploading as well as by geometry capacity being available
// must have 50 mb of free geometry space to upload
for (int limit = 0; limit < 200 && (this.geometryCapacity-this.geometryManager.getGeometryUsedBytes())>50_000_000; limit++)
{
for (int limit = 0; limit < 200 && ((this.geometryCapacity-this.geometryManager.getGeometryUsedBytes())>50_000_000); limit++) {
var job = this.geometryUpdateQueue.poll();
if (job == null)
break;
@@ -280,6 +280,7 @@ public class AsyncNodeManager {
break;
workDone++;
long ptr = job.address;
int zeroCount = 0;
for (int i = 0; i < NodeCleaner.OUTPUT_COUNT; i++) {
long pos = ((long) MemoryUtil.memGetInt(ptr)) << 32; ptr += 4;
pos |= Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr)); ptr += 4;
@@ -289,9 +290,8 @@ public class AsyncNodeManager {
continue;
}
if (pos == 0) {
//THIS SHOULD BE IMPOSSIBLE
//TODO: VVVVV MUCH MEGA FIX
if (pos == 0 && zeroCount++>0) {
Logger.error("Remove node pos is 0 " + zeroCount + " times, this is really bad, please report" );
continue;
}
@@ -314,6 +314,7 @@ public class AsyncNodeManager {
}
if (workDone == 0) {//Nothing happened, which is odd, but just return
//Should probably log that nothing happened, at least once
return;
}
//=====================
@@ -648,8 +649,12 @@ public class AsyncNodeManager {
public void addTopLevel(long section) {//Only called from render thread
if (!this.running) throw new IllegalStateException("Not running");
long stamp = this.tlnLock.writeLock();
int state = this.tlnAdd.add(section)?1:0;
state -= this.tlnRem.remove(section)?1:0;
int state = 0;
if (!this.tlnRem.remove(section)) {
state += this.tlnAdd.add(section)?1:0;
} else {
state -= 1;
}
if (state != 0) {
if (this.workCounter.getAndAdd(state) == 0) {
LockSupport.unpark(this.thread);
@@ -661,8 +666,12 @@ public class AsyncNodeManager {
public void removeTopLevel(long section) {//Only called from render thread
if (!this.running) throw new IllegalStateException("Not running");
long stamp = this.tlnLock.writeLock();
int state = this.tlnRem.add(section)?1:0;
state -= this.tlnAdd.remove(section)?1:0;
int state = 0;
if (!this.tlnAdd.remove(section)) {
state += this.tlnRem.add(section)?1:0;
} else {
state -= 1;
}
if (state != 0) {
if (this.workCounter.getAndAdd(state) == 0) {
LockSupport.unpark(this.thread);
@@ -741,10 +750,11 @@ public class AsyncNodeManager {
public void addDebug(List<String> debug) {
debug.add("UC/GC: " + (this.getUsedGeometryCapacity()/(1<<20))+"/"+(this.getGeometryCapacity()/(1<<20)));
//debug.add("GUQ/NRC: " + this.geometryUpdateQueue.size()+"/"+this.removeBatchQueue.size());
}
public boolean hasWork() {
return this.workCounter.get()!=0 && RESULT_HANDLE.get(this) != null;
return this.workCounter.get()!=0 || RESULT_HANDLE.get(this) != null;
}
public void worldEvent(WorldSection section, int flags) {

View File

@@ -7,6 +7,7 @@ import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.shader.AutoBindingShader;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
import me.cortex.voxy.client.core.rendering.util.PrintfDebugUtil;
import me.cortex.voxy.client.core.rendering.util.HiZBuffer;
import me.cortex.voxy.client.core.rendering.Viewport;
@@ -30,7 +31,7 @@ import static org.lwjgl.opengl.GL45.*;
public class HierarchicalOcclusionTraverser {
public static final boolean HIERARCHICAL_SHADER_DEBUG = System.getProperty("voxy.hierarchicalShaderDebug", "false").equals("true");
public static final int REQUEST_QUEUE_SIZE = 50;
public static final int MAX_REQUEST_QUEUE_SIZE = 50;
public static final int MAX_QUEUE_SIZE = 200_000;
@@ -39,6 +40,7 @@ public class HierarchicalOcclusionTraverser {
private final AsyncNodeManager nodeManager;
private final NodeCleaner nodeCleaner;
private final RenderGenerationService meshGen;
private final GlBuffer requestBuffer;
@@ -73,7 +75,7 @@ public class HierarchicalOcclusionTraverser {
.defineIf("DEBUG", HIERARCHICAL_SHADER_DEBUG)
.define("MAX_ITERATIONS", MAX_ITERATIONS)
.define("LOCAL_SIZE_BITS", LOCAL_WORK_SIZE_BITS)
.define("REQUEST_QUEUE_SIZE", REQUEST_QUEUE_SIZE)
.define("MAX_REQUEST_QUEUE_SIZE", MAX_REQUEST_QUEUE_SIZE)
.define("HIZ_BINDING", 0)
@@ -96,19 +98,18 @@ public class HierarchicalOcclusionTraverser {
.compile();
public HierarchicalOcclusionTraverser(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner) {
public HierarchicalOcclusionTraverser(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, RenderGenerationService meshGen) {
this.nodeCleaner = nodeCleaner;
this.nodeManager = nodeManager;
this.requestBuffer = new GlBuffer(REQUEST_QUEUE_SIZE*8L+8).zero();
this.meshGen = meshGen;
this.requestBuffer = new GlBuffer(MAX_REQUEST_QUEUE_SIZE*8L+8).zero();
this.nodeBuffer = new GlBuffer(nodeManager.maxNodeCount*16L).fill(-1);
glSamplerParameteri(this.hizSampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glSamplerParameteri(this.hizSampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glSamplerParameteri(this.hizSampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glSamplerParameteri(this.hizSampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glSamplerParameteri(this.hizSampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glSamplerParameteri(this.hizSampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glSamplerParameteri(this.hizSampler, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
glSamplerParameteri(this.hizSampler, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
this.traversal
.ubo("SCENE_UNIFORM_BINDING", this.uniformBuffer)
@@ -175,23 +176,31 @@ public class HierarchicalOcclusionTraverser {
viewport.section.getToAddress(ptr); ptr += 4*3;
MemoryUtil.memPutFloat(ptr, viewport.width); ptr += 4;
//MemoryUtil.memPutFloat(ptr, viewport.width); ptr += 4;
MemoryUtil.memPutInt(ptr, viewport.hiZBuffer.getPackedLevels()); ptr += 4;
viewport.innerTranslation.getToAddress(ptr); ptr += 4*3;
MemoryUtil.memPutFloat(ptr, viewport.height); ptr += 4;
setFrustum(viewport, ptr); ptr += 4*4*6;
MemoryUtil.memPutInt(ptr, (int) (viewport.getRenderList().size()/4-1)); ptr += 4;
//MemoryUtil.memPutFloat(ptr, viewport.height); ptr += 4;
final float screenspaceAreaDecreasingSize = VoxyConfig.CONFIG.subDivisionSize*VoxyConfig.CONFIG.subDivisionSize;
//Screen space size for descending
MemoryUtil.memPutFloat(ptr, (float) (screenspaceAreaDecreasingSize) /(viewport.width*viewport.height)); ptr += 4;
setFrustum(viewport, ptr); ptr += 4*4*6;
MemoryUtil.memPutInt(ptr, (int) (viewport.getRenderList().size()/4-1)); ptr += 4;
//VisibilityId
MemoryUtil.memPutInt(ptr, this.nodeCleaner.visibilityId); ptr += 4;
{
final double TARGET_COUNT = 4000;//TODO: make this configurable, or at least dynamically computed based on throughput rate of mesh gen
double iFillness = Math.max(0, (TARGET_COUNT - this.meshGen.getTaskCount()) / TARGET_COUNT);
iFillness = Math.pow(iFillness, 2);
final int requestSize = (int) Math.ceil(iFillness * MAX_REQUEST_QUEUE_SIZE);
MemoryUtil.memPutInt(ptr, Math.max(0, Math.min(MAX_REQUEST_QUEUE_SIZE, requestSize)));ptr += 4;
}
}
private void bindings(Viewport<?> viewport) {
@@ -203,10 +212,7 @@ public class HierarchicalOcclusionTraverser {
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, RENDER_QUEUE_BINDING, viewport.getRenderList().id);
}
public void doTraversal(Viewport<?> viewport, int depthBuffer) {
//Compute the mip chain
viewport.hiZBuffer.buildMipChain(depthBuffer, viewport.width, viewport.height);
public void doTraversal(Viewport<?> viewport) {
this.uploadUniform(viewport);
//UploadStream.INSTANCE.commit(); //Done inside traversal

View File

@@ -8,6 +8,7 @@ import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.rendering.util.PrintfDebugUtil;
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import org.lwjgl.opengl.ARBDirectStateAccess;
import org.lwjgl.system.MemoryUtil;
import static org.lwjgl.opengl.ARBDirectStateAccess.glCopyNamedBufferSubData;
@@ -112,7 +113,8 @@ public class NodeCleaner {
//TODO: choose whether this is in nodeSpace or section/geometryId space
//
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
glDispatchCompute((this.nodeManager.getCurrentMaxNodeId() + (SORTING_WORKER_SIZE+WORK_PER_THREAD) - 1) / (SORTING_WORKER_SIZE+WORK_PER_THREAD), 1, 1);
//This should (IN THEORY naturally align its self to the pow2 max boarder, if not... well undefined behavior is ok right?)
glDispatchCompute((this.nodeManager.getCurrentMaxNodeId() + (SORTING_WORKER_SIZE*WORK_PER_THREAD) - 1) / (SORTING_WORKER_SIZE*WORK_PER_THREAD), 1, 1);
this.resultTransformer.bind();
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, this.outputBuffer.id, 0, 4 * OUTPUT_COUNT);
@@ -165,6 +167,22 @@ public class NodeCleaner {
}
}
private void dumpDebugData() {
int[] outData = new int[OUTPUT_COUNT*3];
ARBDirectStateAccess.glGetNamedBufferSubData(this.outputBuffer.id, 0, outData);
for(int i =0;i < OUTPUT_COUNT; i++) {
System.out.println(outData[i]);
}
/*
System.out.println("---------------\n");
for(int i =0;i < OUTPUT_COUNT; i++) {
System.out.println(data[i*2+OUTPUT_COUNT]+", "+data[i*2+OUTPUT_COUNT+1]);
}*/
int[] visData = new int[(int) (this.visibilityBuffer.size()/4)];
ARBDirectStateAccess.glGetNamedBufferSubData(this.visibilityBuffer.id, 0, visData);
int a = 0;
}
public void free() {
this.sorter.free();
this.visibilityBuffer.free();

View File

@@ -162,7 +162,7 @@ public class NodeManager {
public void removeTopLevelNode(long pos) {
if (!this.topLevelNodes.remove(pos)) {
throw new IllegalStateException("Position not in top level map");
throw new IllegalStateException("Position not in top level map: " + WorldEngine.pprintPos(pos));
}
int nodeId = this.activeSectionMap.get(pos);
if (nodeId == -1) {
@@ -1213,7 +1213,8 @@ public class NodeManager {
if (!this.nodeData.isNodeGeometryInFlight(nodeId)) {
if (!this.watcher.watch(pos, WorldEngine.UPDATE_TYPE_BLOCK_BIT)) {
Logger.info("Node: " + nodeId + " at pos: " + WorldEngine.pprintPos(pos) + " got update request, but geometry was already being watched");
//Logger.info("Node: " + nodeId + " at pos: " + WorldEngine.pprintPos(pos) + " got update request, but geometry was already being watched");
this.invalidateNode(nodeId);//Who knows why but just invalidate the data just to keep in sync
} else {
this.nodeData.markNodeGeometryInFlight(nodeId);
}

View File

@@ -263,8 +263,6 @@ public final class NodeStore {
if (!this.nodeExists(nodeId)) {
MemoryUtil.memPutLong(ptr, -1);
MemoryUtil.memPutLong(ptr + 8, -1);
MemoryUtil.memPutLong(ptr + 16, -1);
MemoryUtil.memPutLong(ptr + 24, -1);
return;
}
long pos = this.nodePosition(nodeId);

View File

@@ -2,6 +2,7 @@ package me.cortex.voxy.client.core.rendering.section;
import me.cortex.voxy.client.RenderStatistics;
import me.cortex.voxy.client.core.gl.Capabilities;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.GlTexture;
import me.cortex.voxy.client.core.gl.shader.Shader;
@@ -34,6 +35,7 @@ import static org.lwjgl.opengl.GL42.glMemoryBarrier;
import static org.lwjgl.opengl.GL43.*;
import static org.lwjgl.opengl.GL45.glBindTextureUnit;
import static org.lwjgl.opengl.GL45.glClearNamedBufferData;
import static org.lwjgl.opengl.NVRepresentativeFragmentTest.GL_REPRESENTATIVE_FRAGMENT_TEST_NV;
//Uses MDIC to render the sections
public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, BasicSectionGeometryData> {
@@ -136,6 +138,7 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, B
this.bindRenderingBuffers(viewport, depthBoundTexture);
glMemoryBarrier(GL_COMMAND_BARRIER_BIT|GL_SHADER_STORAGE_BARRIER_BIT);//Barrier everything is needed
glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, indirectOffset, drawCountOffset, maxDrawCount, 0);
glEnable(GL_CULL_FACE);
@@ -169,6 +172,8 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, B
glBindVertexArray(RenderService.STATIC_VAO);//Needs to be before binding
this.bindRenderingBuffers(viewport, depthBoundTexture);
glMemoryBarrier(GL_COMMAND_BARRIER_BIT|GL_SHADER_STORAGE_BARRIER_BIT);//Barrier everything is needed
glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, TRANSLUCENT_OFFSET*5*4, 4*4, Math.min(this.geometryManager.getSectionCount(), 100_000), 0);
glEnable(GL_CULL_FACE);
@@ -201,6 +206,9 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, B
{//Test occlusion
this.cullShader.bind();
if (Capabilities.INSTANCE.repFragTest) {
glEnable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
}
glBindVertexArray(RenderService.STATIC_VAO);
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniform.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.geometryManager.getMetadataBuffer().id);
@@ -216,6 +224,9 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, B
glDepthMask(true);
glColorMask(true, true, true, true);
glDisable(GL_DEPTH_TEST);
if (Capabilities.INSTANCE.repFragTest) {
glDisable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
}
}

View File

@@ -16,7 +16,7 @@ import static me.cortex.voxy.client.core.rendering.section.geometry.BasicSection
//Is basicly the manager for an "undefined" data store, the underlying store is irrelevant
// this manager serves as an overlay, that is, it allows an implementation to do "async management" of the data store
public class BasicAsyncGeometryManager implements IGeometryManager {
private static final int GEOMETRY_ELEMENT_SIZE = 8;
private static final long GEOMETRY_ELEMENT_SIZE = 8;
private final HierarchicalBitSet allocationSet;
private final AllocationArena allocationHeap = new AllocationArena();
private final ObjectArrayList<SectionMeta> sectionMetadata = new ObjectArrayList<>(1<<15);

View File

@@ -69,12 +69,12 @@ public class HiZBuffer {
}
public void buildMipChain(int srcDepthTex, int width, int height) {
if (this.width != width || this.height != height) {
if (this.width != Integer.highestOneBit(width) || this.height != Integer.highestOneBit(height)) {
if (this.texture != null) {
this.texture.free();
this.texture = null;
}
this.alloc(width, height);
this.alloc(Integer.highestOneBit(width), Integer.highestOneBit(height));
}
glBindVertexArray(RenderService.STATIC_VAO);
int boundFB = GL11.glGetInteger(GL_DRAW_FRAMEBUFFER_BINDING);
@@ -86,26 +86,22 @@ public class HiZBuffer {
glEnable(GL_DEPTH_TEST);
//System.err.println("SRC: " + GlTexture.getRawTextureType(srcDepthTex) + " DST: " + this.texture.id);
glCopyImageSubData(srcDepthTex, GL_TEXTURE_2D, 0,0,0,0,
this.texture.id, GL_TEXTURE_2D, 0,0,0,0,
width, height, 1);
glBindTextureUnit(0, this.texture.id);
glBindTextureUnit(0, srcDepthTex);
glBindSampler(0, this.sampler);
glUniform1i(0, 0);
int cw = this.width;
int ch = this.height;
glViewport(0, 0, cw, ch);
for (int i = 0; i < this.levels-1; i++) {
glTextureParameteri(this.texture.id, GL_TEXTURE_BASE_LEVEL, i);
glTextureParameteri(this.texture.id, GL_TEXTURE_MAX_LEVEL, i);
this.fb.bind(GL_DEPTH_ATTACHMENT, this.texture, i+1);
cw = Math.max(cw/2, 1); ch = Math.max(ch/2, 1); glViewport(0, 0, cw, ch);
for (int i = 0; i < this.levels; i++) {
this.fb.bind(GL_DEPTH_ATTACHMENT, this.texture, i);
glViewport(0, 0, cw, ch); cw = Math.max(cw/2, 1); ch = Math.max(ch/2, 1);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glTextureBarrier();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT|GL_TEXTURE_FETCH_BARRIER_BIT);
glTextureParameteri(this.texture.id, GL_TEXTURE_BASE_LEVEL, i);
glTextureParameteri(this.texture.id, GL_TEXTURE_MAX_LEVEL, i);
if (i==0) {
glBindTextureUnit(0, this.texture.id);
}
}
glTextureParameteri(this.texture.id, GL_TEXTURE_BASE_LEVEL, 0);
glTextureParameteri(this.texture.id, GL_TEXTURE_MAX_LEVEL, 1000);//TODO: CHECK IF ITS -1 or -0
@@ -130,4 +126,8 @@ public class HiZBuffer {
public int getHizTextureId() {
return this.texture.id;
}
public int getPackedLevels() {
return ((Integer.numberOfTrailingZeros(this.width))<<16)|(Integer.numberOfTrailingZeros(this.height));//+1
}
}

View File

@@ -26,13 +26,6 @@ public abstract class MixinWorldRenderer implements IGetVoxyRenderSystem {
@Shadow private @Nullable ClientWorld world;
@Unique private VoxyRenderSystem renderer;
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;setupTerrain(Lnet/minecraft/client/render/Camera;Lnet/minecraft/client/render/Frustum;ZZ)V", shift = At.Shift.AFTER))
private void injectSetup(ObjectAllocator allocator, RenderTickCounter tickCounter, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, Matrix4f positionMatrix, Matrix4f projectionMatrix, CallbackInfo ci) {
if (this.renderer != null) {
this.renderer.renderSetup(this.frustum, camera);
}
}
@Override
public VoxyRenderSystem getVoxyRenderSystem() {
return this.renderer;

View File

@@ -14,6 +14,30 @@ public class Logger {
public static boolean SHUTUP = false;
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger("Voxy");
private static String callClsName() {
String className = "";
if (INSERT_CLASS) {
var stackEntry = new Throwable().getStackTrace()[2];
className = stackEntry.getClassName();
var builder = new StringBuilder();
var parts = className.split("\\.");
for (int i = 0; i < parts.length; i++) {
var part = parts[i];
if (i < parts.length-1) {//-2
builder.append(part.charAt(0)).append(part.charAt(part.length()-1));
} else {
builder.append(part);
}
if (i!=parts.length-1) {
builder.append(".");
}
}
className = builder.toString();
}
return className;
}
public static void error(Object... args) {
if (SHUTUP) {
return;
@@ -24,8 +48,8 @@ public class Logger {
throwable = (Throwable) i;
}
}
var stackEntry = new Throwable().getStackTrace()[1];
String error = (INSERT_CLASS?("["+stackEntry.getClassName()+"]: "):"") + Stream.of(args).map(Logger::objToString).collect(Collectors.joining(" "));
String error = (INSERT_CLASS?("["+callClsName()+"]: "):"") + Stream.of(args).map(Logger::objToString).collect(Collectors.joining(" "));
LOGGER.error(error, throwable);
if (VoxyCommon.IS_IN_MINECRAFT && !VoxyCommon.IS_DEDICATED_SERVER) {
var instance = MinecraftClient.getInstance();
@@ -48,8 +72,7 @@ public class Logger {
throwable = (Throwable) i;
}
}
var stackEntry = new Throwable().getStackTrace()[1];
LOGGER.warn((INSERT_CLASS?("["+stackEntry.getClassName()+"]: "):"") + Stream.of(args).map(Logger::objToString).collect(Collectors.joining(" ")), throwable);
LOGGER.warn((INSERT_CLASS?("["+callClsName()+"]: "):"") + Stream.of(args).map(Logger::objToString).collect(Collectors.joining(" ")), throwable);
}
public static void info(Object... args) {
@@ -62,8 +85,7 @@ public class Logger {
throwable = (Throwable) i;
}
}
var stackEntry = new Throwable().getStackTrace()[1];
LOGGER.info((INSERT_CLASS?("["+stackEntry.getClassName()+"]: "):"") + Stream.of(args).map(Logger::objToString).collect(Collectors.joining(" ")), throwable);
LOGGER.info((INSERT_CLASS?("["+callClsName()+"]: "):"") + Stream.of(args).map(Logger::objToString).collect(Collectors.joining(" ")), throwable);
}
private static String objToString(Object obj) {

View File

@@ -3,6 +3,7 @@ package me.cortex.voxy.common.config.storage.other;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.config.storage.StorageBackend;
import me.cortex.voxy.common.config.ConfigBuildCtx;
import me.cortex.voxy.common.config.storage.StorageConfig;
@@ -100,7 +101,7 @@ public class FragmentedStorageBackendAdaptor extends StorageBackend {
}
if (verification.size() != 1) {
System.err.println("Error id mapping not matching across all fragments, attempting to recover");
Logger.error("Error id mapping not matching across all fragments, attempting to recover");
Object2IntMap.Entry<Int2ObjectOpenHashMap<EqualingArray>> maxEntry = null;
for (var entry : verification.object2IntEntrySet()) {
if (maxEntry == null) { maxEntry = entry; }

View File

@@ -54,22 +54,39 @@ public class RocksDBStorageBackend extends StorageBackend {
//TODO: FIXME: DONT USE THE SAME options PER COLUMN FAMILY
final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions()
.setCompressionType(CompressionType.ZSTD_COMPRESSION)
.optimizeForSmallDb();
final ColumnFamilyOptions cfWorldSecOpts = new ColumnFamilyOptions()
.setCompressionType(CompressionType.NO_COMPRESSION)
.setCompactionPriority(CompactionPriority.MinOverlappingRatio)
.setLevelCompactionDynamicLevelBytes(true)
.optimizeForPointLookup(128);
var bCache = new HyperClockCache(128*1024L*1024L,0, 4, false);
var filter = new BloomFilter(10);
cfWorldSecOpts.setTableFormatConfig(new BlockBasedTableConfig()
.setCacheIndexAndFilterBlocksWithHighPriority(true)
.setBlockCache(bCache)
.setDataBlockHashTableUtilRatio(0.75)
//.setIndexType(IndexType.kHashSearch)//Maybe?
.setDataBlockIndexType(DataBlockIndexType.kDataBlockBinaryAndHash)
.setFilterPolicy(filter)
);
final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts),
new ColumnFamilyDescriptor("world_sections".getBytes(), cfOpts),
new ColumnFamilyDescriptor("world_sections".getBytes(), cfWorldSecOpts),
new ColumnFamilyDescriptor("id_mappings".getBytes(), cfOpts)
);
final DBOptions options = new DBOptions()
//.setUnorderedWrite(true)
.setAvoidUnnecessaryBlockingIO(true)
.setIncreaseParallelism(2)
.setCreateIfMissing(true)
.setCreateMissingColumnFamilies(true)
.setMaxTotalWalSize(1024*1024*512);//512 mb max WAL size
.setMaxTotalWalSize(1024*1024*128);//128 mb max WAL size
List<ColumnFamilyHandle> handles = new ArrayList<>();
@@ -85,8 +102,11 @@ public class RocksDBStorageBackend extends StorageBackend {
this.closeList.add(this.db);
this.closeList.add(options);
this.closeList.add(cfOpts);
this.closeList.add(cfWorldSecOpts);
this.closeList.add(this.sectionReadOps);
this.closeList.add(this.sectionWriteOps);
this.closeList.add(filter);
this.closeList.add(bCache);
this.worldSections = handles.get(1);
this.idMappings = handles.get(2);
@@ -193,6 +213,7 @@ public class RocksDBStorageBackend extends StorageBackend {
@Override
public void close() {
this.flush();
this.closeList.forEach(AbstractImmutableNativeReference::close);
}

View File

@@ -46,15 +46,19 @@ public class ServiceThreadPool {
//Set worker affinity if possible
CpuLayout.setThreadAffinity(CpuLayout.CORES[2 + (threadId % (CpuLayout.CORES.length - 2))]);
}
if (threadId != 0) {
ThreadUtils.SetSelfThreadPriorityWin32(ThreadUtils.WIN32_THREAD_PRIORITY_LOWEST);
//ThreadUtils.SetSelfThreadPriorityWin32(ThreadUtils.WIN32_THREAD_MODE_BACKGROUND_BEGIN);
}
this.worker(threadId);
});
worker.setDaemon(false);
worker.setName("Service worker #" + i);
if (i == 0) {//Give the first thread normal priority, this helps if the system is under huge load for voxy to get some work done
worker.setPriority(Thread.NORM_PRIORITY);
} else {
worker.setPriority(priority);
}
worker.start();
worker.setUncaughtExceptionHandler(this::handleUncaughtException);
this.workers[i] = worker;
@@ -274,8 +278,7 @@ public class ServiceThreadPool {
}
private void handleUncaughtException(Thread thread, Throwable throwable) {
System.err.println("Service worker thread has exploded unexpectedly! this is really not good very very bad.");
throwable.printStackTrace();
Logger.error("Service worker thread has exploded unexpectedly! this is really not good very very bad.", throwable);
}
public void shutdown() {

View File

@@ -2,6 +2,7 @@ package me.cortex.voxy.common.world;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.util.VolatileHolder;
import me.cortex.voxy.common.world.other.Mapper;
import org.jetbrains.annotations.Nullable;
@@ -115,7 +116,7 @@ public class ActiveSectionTracker {
if (status < 0) {
//TODO: Instead if throwing an exception do something better, like attempting to regen
//throw new IllegalStateException("Unable to load section: ");
System.err.println("Unable to load section " + section.key + " setting to air");
Logger.error("Unable to load section " + section.key + " setting to air");
status = 1;
}
@@ -129,9 +130,9 @@ public class ActiveSectionTracker {
}
section.acquire();
VarHandle.fullFence();//Do not reorder setting this object
VarHandle.storeStoreFence();//Do not reorder setting this object
holder.obj = section;
VarHandle.fullFence();
VarHandle.releaseFence();
if (nullOnEmpty && status == 1) {//If its air return null as stated, release the section aswell
section.release();
return null;

View File

@@ -1,6 +1,7 @@
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.UnsafeUtil;
import me.cortex.voxy.common.world.other.Mapper;
@@ -120,7 +121,7 @@ public class SaveLoadSystem {
if (section.key != key) {
//throw new IllegalStateException("Decompressed section not the same as requested. got: " + key + " expected: " + section.key);
System.err.println("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;
}
@@ -145,7 +146,7 @@ public class SaveLoadSystem {
long expectedHash = MemoryUtil.memGetLong(ptr); ptr += 8; if (VERIFY_MEMORY_ACCESS && data.size<(ptr-data.address)) throw new IllegalStateException("Memory access OOB");
if (expectedHash != hash) {
//throw new IllegalStateException("Hash mismatch got: " + hash + " expected: " + expectedHash);
System.err.println("Hash mismatch got: " + hash + " expected: " + expectedHash + " removing region");
Logger.error("Hash mismatch got: " + hash + " expected: " + expectedHash + " removing region");
return false;
}
}

View File

@@ -10,11 +10,9 @@ import me.cortex.voxy.commonImpl.VoxyCommon;
import org.lwjgl.system.MemoryUtil;
public class SaveLoadSystem3 {
private record SerializationCache(long[] blockStateCache, Long2ShortOpenHashMap lutMapCache, MemoryBuffer memoryBuffer) {
private record SerializationCache(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(new Long2ShortOpenHashMap(512), ThreadLocalMemoryBuffer.create(WorldSection.SECTION_VOLUME*2+WorldSection.SECTION_VOLUME*8+1024));
this.lutMapCache.defaultReturnValue((short) -1);
}
}
@@ -39,8 +37,7 @@ public class SaveLoadSystem3 {
//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);
var data = section.data;
Long2ShortOpenHashMap LUT = cache.lutMapCache; LUT.clear();

View File

@@ -163,15 +163,18 @@ public class WorldEngine {
}
public void markActive() {
if (!this.isLive) throw new IllegalStateException();
this.lastActiveTime = System.currentTimeMillis();
}
public void acquireRef() {
if (!this.isLive) throw new IllegalStateException();
this.refCount.incrementAndGet();
this.lastActiveTime = System.currentTimeMillis();
}
public void releaseRef() {
if (!this.isLive) throw new IllegalStateException();
if (this.refCount.decrementAndGet()<0) {
throw new IllegalStateException("ref count less than 0");
}

View File

@@ -1,6 +1,7 @@
package me.cortex.voxy.common.world.other;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.config.IMappingStorage;
import me.cortex.voxy.common.config.section.SectionStorage;
import net.minecraft.block.Block;
@@ -99,7 +100,7 @@ public class Mapper {
if (entryType == BLOCK_STATE_TYPE) {
var sentry = StateEntry.deserialize(id, entry.getValue());
if (sentry.state.isAir()) {
System.err.println("Deserialization was air, removed block");
Logger.error("Deserialization was air, removed block");
sentryErrors.add(new Pair<>(entry.getValue(), id));
continue;
}

View File

@@ -53,12 +53,18 @@ public class SectionSavingService {
//Hard limit the save count to prevent OOM
if (this.getTaskCount() > 5_000) {
while (this.getTaskCount() > 5_000) {
//wait a bit
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//If we are still full, process entries in the queue ourselves instead of waiting for the service
while (this.getTaskCount() > 5_000 && this.threads.isAlive()) {
if (!this.threads.steal()) {
break;
}
this.processJob();
}
}
@@ -69,7 +75,7 @@ public class SectionSavingService {
public void shutdown() {
if (this.threads.getJobCount() != 0) {
System.err.println("Voxy section saving still in progress, estimated " + this.threads.getJobCount() + " sections remaining.");
Logger.error("Voxy section saving still in progress, estimated " + this.threads.getJobCount() + " sections remaining.");
while (this.threads.getJobCount() != 0) {
Thread.onSpinWait();
}

View File

@@ -86,6 +86,9 @@ public class VoxelIngestService {
}
public void enqueueIngest(WorldEngine engine, WorldChunk chunk) {
if (!this.threads.isAlive()) {
return;
}
if (!engine.isLive()) {
throw new IllegalStateException("Tried inserting chunk into WorldEngine that was not alive");
}
@@ -117,7 +120,12 @@ public class VoxelIngestService {
}
this.ingestQueue.add(new IngestSection(chunk.getPos().x, i, chunk.getPos().z, engine, section, bl, sl));
try {
this.threads.execute();
} catch (Exception e) {
Logger.error("Executing had an error: assume shutting down, aborting",e);
break;
}
}
}

View File

@@ -131,7 +131,7 @@ public abstract class VoxyInstance {
if (this.activeWorlds.containsKey(identifier)) {
throw new IllegalStateException("Existing world with identifier");
}
Logger.info("Creating new world engine");
Logger.info("Creating new world engine: " + identifier.getLongHash());
var world = new WorldEngine(this.createStorage(identifier), this);
world.setSaveCallback(this.savingService::enqueueSave);
this.activeWorlds.put(identifier, world);
@@ -158,6 +158,7 @@ public abstract class VoxyInstance {
var world = this.activeWorlds.remove(id);
if (world == null) continue;//Race condition between unlock read and acquire write
if (!world.isWorldIdle()) {this.activeWorlds.put(id, world); continue;}//No longer idle
Logger.info("Shutting down idle world: " + id.getLongHash());
//If is here close and free the world
world.free();
}

View File

@@ -27,7 +27,7 @@ public class WorldIdentifier {
this.key = key;
this.biomeSeed = biomeSeed;
this.dimension = dimension;
this.hashCode = mixStafford13(key.hashCode()^biomeSeed)^mixStafford13(dimension.hashCode()^biomeSeed);
this.hashCode = mixStafford13(registryKeyHashCode(key))^mixStafford13(registryKeyHashCode(dimension))^mixStafford13(biomeSeed);
}
@Override
@@ -101,4 +101,16 @@ public class WorldIdentifier {
seed = (seed ^ seed >>> 27) * -7723592293110705685L;
return seed ^ seed >>> 31;
}
public long getLongHash() {
return this.hashCode;
}
private static long registryKeyHashCode(RegistryKey<?> key) {
var A = key.getRegistry();
var B = key.getValue();
int a = A==null?0:A.hashCode();
int b = B==null?0:B.hashCode();
return (Integer.toUnsignedLong(a)<<32)|Integer.toUnsignedLong(b);
}
}

View File

@@ -327,7 +327,7 @@ public class WorldImporter implements IDataImporter {
//TODO: create memory copy for each section
if (regionFile.size < ((sectorCount-1) + sectorStart) * 4096L) {
System.err.println("Cannot access chunk sector as it goes out of bounds. start bytes: " + (sectorStart*4096) + " sector count: " + sectorCount + " fileSize: " + regionFile.size);
Logger.warn("Cannot access chunk sector as it goes out of bounds. start bytes: " + (sectorStart*4096) + " sector count: " + sectorCount + " fileSize: " + regionFile.size);
continue;
}
@@ -341,7 +341,7 @@ public class WorldImporter implements IDataImporter {
} else {
int n = m - 1;
if (regionFile.size < (n + sectorStart*4096L)) {
System.err.println("Chunk stream to small");
Logger.warn("Chunk stream to small");
} else if ((b & 128) != 0) {
if (n != 0) {
Logger.error("Chunk has both internal and external streams");

View File

@@ -6,7 +6,7 @@ layout(binding = 0) uniform sampler2D depthTex;
void main() {
vec4 depths = textureGather(depthTex, uv, 0); // Get depth values from all surrounding texels.
bvec4 cv = lessThanEqual(vec4(0.999999f), depths);
bvec4 cv = lessThanEqual(vec4(0.999999999f), depths);
if (any(cv)) {//Patch holes (its very dodgy but should work :tm:, should clamp it to the first 3 levels)
depths = mix(vec4(0.0f), depths, cv);
}

View File

@@ -1,3 +1,10 @@
struct BlockModel {
uint faceData[6];
uint flagsA;
uint colourTint;
uint _pad[8];
};
//TODO: FIXME: this isnt actually correct cause depending on the face (i think) it could be 1/64 th of a position off
// but im going to assume that since we are dealing with huge render distances, this shouldent matter that much
float extractFaceIndentation(uint faceData) {
@@ -18,13 +25,13 @@ uint faceHasAlphaCuttoutOverride(uint faceData) {
}
bool modelHasBiomeLUT(BlockModel model) {
return ((model.flagsA)&2) != 0;
return ((model.flagsA)&2u) != 0;
}
bool modelIsTranslucent(BlockModel model) {
return ((model.flagsA)&4) != 0;
return ((model.flagsA)&4u) != 0;
}
bool modelHasMipmaps(BlockModel model) {
return ((model.flagsA)&8) != 0;
return ((model.flagsA)&8u) != 0;
}

View File

@@ -7,24 +7,6 @@ layout(binding = 0, std140) uniform SceneUniform {
vec3 cameraSubPos;
};
struct BlockModel {
uint faceData[6];
uint flagsA;
uint colourTint;
uint _pad[8];
};
struct SectionMeta {
uint posA;
uint posB;
uint AABB;
uint ptr;
uint cntA;
uint cntB;
uint cntC;
uint cntD;
};
//TODO: see if making the stride 2*4*4 bytes or something cause you get that 16 byte write
struct DrawCommand {
uint count;

View File

@@ -8,8 +8,8 @@ layout(local_size_x = 128) in;
#define SECTION_METADATA_BUFFER_BINDING 3
#define INDIRECT_SECTION_LOOKUP_BINDING 4
#import <voxy:lod/gl46/bindings.glsl>
#import <voxy:lod/section.glsl>
#import <voxy:lod/gl46/bindings.glsl>
/*
uint count;

View File

@@ -11,8 +11,8 @@ layout(local_size_x = 128) in;
#define POSITION_SCRATCH_BINDING 6
#define POSITION_SCRATCH_ACCESS writeonly
#import <voxy:lod/gl46/bindings.glsl>
#import <voxy:lod/section.glsl>
#import <voxy:lod/gl46/bindings.glsl>
//https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_shader_16bit_storage.txt
// adds support for uint8_t which can use for compact visibility buffer

View File

@@ -6,8 +6,8 @@
#define VISIBILITY_BUFFER_BINDING 2
#define INDIRECT_SECTION_LOOKUP_BINDING 3
#import <voxy:lod/gl46/bindings.glsl>
#import <voxy:lod/section.glsl>
#import <voxy:lod/gl46/bindings.glsl>
flat out uint id;
flat out uint value;

View File

@@ -1,4 +1,8 @@
#version 460 core
//Use quad shuffling to compute fragment mip
//#extension GL_KHR_shader_subgroup_quad: enable
layout(binding = 0) uniform sampler2D blockModelAtlas;
layout(binding = 2) uniform sampler2D depthTex;
@@ -13,22 +17,50 @@ layout(location = 2) in flat vec4 tinting;
layout(location = 3) in flat vec4 addin;
layout(location = 4) in flat uint flags;
layout(location = 5) in flat vec4 conditionalTinting;
layout(location = 6) in flat vec2 quadSize;
#ifdef DEBUG_RENDER
layout(location = 6) in flat uint quadDebug;
layout(location = 7) in flat uint quadDebug;
#endif
layout(location = 0) out vec4 outColour;
vec4 computeColour(vec4 colour) {
//Conditional tinting, TODO: FIXME: REPLACE WITH MASK OR SOMETHING, like encode data into the top bit of alpha
if ((flags&(1u<<2)) != 0 && abs(colour.r-colour.g) < 0.02f && abs(colour.g-colour.b) < 0.02f) {
colour *= conditionalTinting;
}
return (colour * tinting) + addin;
}
bool useMipmaps() {
return ((flags>>1)&1u)==0u;
}
void main() {
//Tile is the tile we are in
vec2 tile;
vec2 uv2 = modf(uv, tile)*(1.0/(vec2(3.0,2.0)*256.0));
vec4 colour = vec4(1);
vec2 texPos = uv2 + baseUV;
if (useMipmaps()) {
vec2 uvSmol = uv*(1.0/(vec2(3.0,2.0)*256.0));
vec2 dx = dFdx(uvSmol);//vec2(lDx, dDx);
vec2 dy = dFdy(uvSmol);//vec2(lDy, dDy);
colour = textureGrad(blockModelAtlas, texPos, dx, dy);
} else {
colour = texture(blockModelAtlas, texPos, -5.0);
}
if (any(notEqual(clamp(tile, vec2(0), quadSize), tile))) {
discard;
}
//Check the minimum bounding texture and ensure we are greater than it
if (gl_FragCoord.z < texelFetch(depthTex, ivec2(gl_FragCoord.xy), 0).r) {
discard;
}
vec2 uv = mod(uv, vec2(1.0))*(1.0/(vec2(3.0,2.0)*256.0));
vec2 texPos = uv + baseUV;
//vec4 colour = solidColour;
//TODO: FIXME, need to manually compute the mip colour
vec4 colour = texture(blockModelAtlas, texPos, ((flags>>1)&1u)*-5.0);//TODO: FIXME mipping needs to be fixed so that it doesnt go cross model bounds
//Also, small quad is really fking over the mipping level somehow
if ((flags&1u) == 1 && (texture(blockModelAtlas, texPos, -16.0).a <= 0.1f)) {
//This is stupidly stupidly bad for divergence
@@ -38,14 +70,7 @@ void main() {
#endif
}
//Conditional tinting, TODO: FIXME: REPLACE WITH MASK OR SOMETHING, like encode data into the top bit of alpha
if ((flags&(1u<<2)) != 0 && abs(colour.r-colour.g) < 0.02f && abs(colour.g-colour.b) < 0.02f) {
colour *= conditionalTinting;
}
outColour = (colour * tinting) + addin;
//outColour = vec4(uv + baseUV, 0, 1);
outColour = computeColour(colour);
#ifdef DEBUG_RENDER
@@ -57,3 +82,29 @@ void main() {
outColour = vec4(float(hash&15u)/15, float((hash>>4)&15u)/15, float((hash>>8)&15u)/15, 1);
#endif
}
//#ifdef GL_KHR_shader_subgroup_quad
/*
uint hash = (uint(tile.x)*(1<<16))^uint(tile.y);
uint horiz = subgroupQuadSwapHorizontal(hash);
bool sameTile = horiz==hash;
uint sv = mix(uint(-1), hash, sameTile);
uint vert = subgroupQuadSwapVertical(sv);
sameTile = sameTile&&vert==hash;
mipBias = sameTile?0:-5.0;
*/
/*
vec2 uvSmol = uv*(1.0/(vec2(3.0,2.0)*256.0));
float lDx = subgroupQuadSwapHorizontal(uvSmol.x)-uvSmol.x;
float lDy = subgroupQuadSwapVertical(uvSmol.y)-uvSmol.y;
float dDx = subgroupQuadSwapDiagonal(lDx);
float dDy = subgroupQuadSwapDiagonal(lDy);
vec2 dx = vec2(lDx, dDx);
vec2 dy = vec2(lDy, dDy);
colour = textureGrad(blockModelAtlas, texPos, dx, dy);
*/
//#else
//colour = texture(blockModelAtlas, texPos);
//#endif

View File

@@ -2,7 +2,6 @@
#extension GL_ARB_gpu_shader_int64 : enable
#define QUAD_BUFFER_BINDING 1
#define SECTION_METADATA_BUFFER_BINDING 2
#define MODEL_BUFFER_BINDING 3
#define MODEL_COLOUR_BUFFER_BINDING 4
#define POSITION_SCRATCH_BINDING 5
@@ -10,8 +9,8 @@
#import <voxy:lod/quad_format.glsl>
#import <voxy:lod/gl46/bindings.glsl>
#import <voxy:lod/block_model.glsl>
#import <voxy:lod/gl46/bindings.glsl>
//#define DEBUG_RENDER
@@ -21,9 +20,10 @@ layout(location = 2) out flat vec4 tinting;
layout(location = 3) out flat vec4 addin;
layout(location = 4) out flat uint flags;
layout(location = 5) out flat vec4 conditionalTinting;
layout(location = 6) out flat vec2 size;
#ifdef DEBUG_RENDER
layout(location = 6) out flat uint quadDebug;
layout(location = 7) out flat uint quadDebug;
#endif
/*
@@ -42,17 +42,17 @@ vec4 uint2vec4RGBA(uint colour) {
}
vec4 getFaceSize(uint faceData) {
float EPSILON = 0.0005f;
float EPSILON = 0.00005f;
vec4 faceOffsetsSizes = extractFaceSizes(faceData);
//Make the end relative to the start
faceOffsetsSizes.yw -= faceOffsetsSizes.xz;
//Expand the quads by a very small amount
faceOffsetsSizes.xz -= vec2(EPSILON);
faceOffsetsSizes.yw += vec2(EPSILON);
//Make the end relative to the start
faceOffsetsSizes.yw -= faceOffsetsSizes.xz;
return faceOffsetsSizes;
}
@@ -104,13 +104,16 @@ void main() {
uvec2 encPos = positionBuffer[gl_BaseInstance];
uint lodLevel = extractDetail(encPos);
ivec2 quadSize = extractSize(quad);
if (cornerIdx == 1) //Only if we are the provoking vertex
{
size = vec2(quadSize-1);
vec2 modelUV = vec2(modelId&0xFFu, (modelId>>8)&0xFFu)*(1.0/(256.0));
baseUV = modelUV + (vec2(face>>1, face&1u) * (1.0/(vec2(3.0, 2.0)*256.0)));
ivec2 quadSize = extractSize(quad);
{ //Generate tinting and flag data
//Generate tinting and flag data
flags = faceHasAlphaCuttout(faceData);
//We need to have a conditional override based on if the model size is < a full face + quadSize > 1

View File

@@ -9,8 +9,8 @@
#import <voxy:lod/quad_format.glsl>
#import <voxy:lod/gl46/bindings.glsl>
#import <voxy:lod/block_model.glsl>
#import <voxy:lod/gl46/bindings.glsl>
layout(location = 6) out flat uint quadDebug;

View File

@@ -93,10 +93,9 @@ void bubbleSortInital(uint vis, uint id) {
bool shouldSortId(uint id) {
UnpackedNode node;
if (unpackNode(node, gl_GlobalInvocationID.x)==uvec4(-1)) {
if (unpackNode(node, id)==uvec4(-1)) {
return false;//Unallocated node
}
if (isEmptyMesh(node) || (!hasMesh(node))) {//|| (!hasChildren(node))
return false;
}
@@ -105,6 +104,9 @@ bool shouldSortId(uint id) {
return false;//Cannot remove geometry from top level node
}
if (hasRequested(node)) {//If a node has a request its not valid to remove
return false;
}
/*THIS IS COMPLETLY WRONG, we need to check if all the children of the parent of the child are leaf nodes
// not this node
@@ -130,7 +132,7 @@ void main() {
// this means that insertion into the local buffer can be accelerated W.R.T global
for (uint i = 0; i < OPS_PER_THREAD; i++) {
//Copy in with warp size batch fetch
uint id = gl_LocalInvocationID.x + (i*WORK_SIZE);
uint id = (gl_LocalInvocationID.x*OPS_PER_THREAD) + i;
initalSort[id] = minVisIds[id]|(1u<<31);//Flag the id as being external
}
barrier();
@@ -158,7 +160,7 @@ void main() {
//Work size batching
for (uint i = 0; i < OPS_PER_THREAD; i++) {
barrier();//Probably unneeded, was just to keep warp coheriancy
uint id = gl_LocalInvocationID.x+(i*WORK_SIZE);
uint id = (gl_LocalInvocationID.x*OPS_PER_THREAD)+i;
uint sid = initalSort[id];
if ((sid&(1u<<31)) != 0) {
//The flag being external was set, meaning we should NOT insert this element

View File

@@ -12,7 +12,7 @@
// substantually for performance (for both persistent threads and incremental)
layout(binding = HIZ_BINDING) uniform sampler2DShadow hizDepthSampler;
layout(binding = HIZ_BINDING) uniform sampler2D hizDepthSampler;
//TODO: maybe do spher bounds aswell? cause they have different accuracies but are both over estimates (liberals (non conservative xD))
// so can do &&
@@ -39,7 +39,6 @@ bool checkPointInView(vec4 point) {
vec3 minBB = vec3(0.0f);
vec3 maxBB = vec3(0.0f);
vec2 size = vec2(0.0f);
bool insideFrustum = false;
float screenSize = 0.0f;
@@ -117,7 +116,8 @@ void setupScreenspace(in UnpackedNode node) {
minBB = min(min(min(p000, p100), min(p001, p101)), min(min(p010, p110), min(p011, p111)));
maxBB = max(max(max(p000, p100), max(p001, p101)), max(max(p010, p110), max(p011, p111)));
size = clamp(maxBB.xy - minBB.xy, vec2(0), vec2(1));
minBB = clamp(minBB, vec3(0), vec3(1));
maxBB = clamp(maxBB, vec3(0), vec3(1));
}
//Checks if the node is implicitly culled (outside frustum)
@@ -128,32 +128,31 @@ bool outsideFrustum() {
}
bool isCulledByHiz() {
vec2 ssize = size * vec2(screenW, screenH);
float miplevel = log2(max(max(ssize.x, ssize.y),1));
ivec2 ssize = ivec2(1)<<ivec2((packedHizSize>>16)&0xFFFF,packedHizSize&0xFFFF);
vec2 size = (maxBB.xy-minBB.xy)*ssize;
float miplevel = log2(max(max(size.x, size.y),1));
//TODO: make a path for if the miplevel would result in the textureSampler sampling a size of 1
miplevel = floor(miplevel)-1;
miplevel = clamp(miplevel, 0, textureQueryLevels(hizDepthSampler)-1);
int ml = int(miplevel);
ssize = max(ivec2(1), ssize>>ml);
ivec2 mxbb = ivec2(maxBB.xy*ssize);
ivec2 mnbb = ivec2(minBB.xy*ssize);
miplevel = ceil(miplevel);
miplevel = clamp(miplevel, 0, 20);
if (miplevel >= 10.0f) {//Level 9 or 10// TODO: FIX THIS JANK SHIT
//return false;
float pointSample = -1.0f;
//float pointSample2 = 0.0f;
for (int x = mnbb.x; x<=mxbb.x; x++) {
for (int y = mnbb.y; y<=mxbb.y; y++) {
float sp = texelFetch(hizDepthSampler, ivec2(x, y), ml).r;
//pointSample2 = max(sp, pointSample2);
//sp = mix(sp, pointSample, 0.9999999f<=sp);
pointSample = max(sp, pointSample);
}
}
//pointSample = mix(pointSample, pointSample2, pointSample<=0.000001f);
vec2 midpoint = (maxBB.xy + minBB.xy)*0.5f;
float testAgainst = minBB.z;
//the *2.0f-1.0f converts from the 0->1 range to -1->1 range that depth is in (not having this causes tighter bounds, but causes culling issues in caves)
testAgainst = testAgainst*2.0f-1.0f;
bool culled = textureLod(hizDepthSampler, clamp(vec3(midpoint, testAgainst), vec3(0), vec3(1)), miplevel) < 0.0001f;
//printf("HiZ sample point: (%f,%f)@%f against %f", midpoint.x, midpoint.y, miplevel, minBB.z);
//if ((culled) && node22.lodLevel == 0) {
// printf("HiZ sample point: (%f,%f)@%f against %f, value %f", midpoint.x, midpoint.y, miplevel, minBB.z, textureLod(hizDepthSampler, vec3(0.5f,0.5f, 0.000000001f), 9.0f));
//}
return culled;
return pointSample<=minBB.z;
}

View File

@@ -1,67 +0,0 @@
layout(binding = HIZ_BINDING_INDEX) uniform sampler2DShadow hizDepthSampler;
vec3 minBB;
vec3 maxBB;
vec2 size;
//Sets up screenspace with the given node id, returns true on success false on failure/should not continue
//Accesses data that is setup in the main traversal and is just shared to here
void setupScreenspace(in UnpackedNode node) {
//TODO: implment transform support
Transform transform = transforms[getTransformIndex(node)];
vec4 base = VP*vec4(vec3(((node.pos<<node.lodLevel)-camSecPos)<<5)-camSubSecPos, 1);
//TODO: AABB SIZES not just a max cube
//vec3 minPos = minSize + basePos;
//vec3 maxPos = maxSize + basePos;
minBB = base.xyz/base.w;
maxBB = minBB;
for (int i = 1; i < 8; i++) {
//NOTE!: cant this be precomputed and put in an array?? in the scene uniform??
vec4 pPoint = (VP*vec4(vec3((i&1)!=0,(i&2)!=0,(i&4)!=0),1))*(32<<node.lodLevel);//Size of section is 32x32x32 (need to change it to a bounding box in the future)
pPoint += base;
vec3 point = pPoint.xyz/pPoint.w;
//TODO: CLIP TO VIEWPORT
minBB = min(minBB, point);
maxBB = max(maxBB, point);
}
//TODO: MORE ACCURATLY DETERMIN SCREENSPACE AREA, this can be done by computing and adding
// the projected surface area of each face/quad which winding order faces the camera
// (this is just the dot product of 2 projected vectors)
//can do a funny by not doing the perspective divide except on the output of the area
//printf("Screenspace MIN: %f, %f, %f MAX: %f, %f, %f", minBB.x,minBB.y,minBB.z, maxBB.x,maxBB.y,maxBB.z);
size = maxBB.xy - minBB.xy;
}
//Checks if the node is implicitly culled (outside frustum)
bool outsideFrustum() {
return any(lessThanEqual(maxBB, vec3(-1f, -1f, 0f))) || any(lessThanEqual(vec3(1f, 1f, 1f), minBB));
}
bool isCulledByHiz() {
if (minBB.z < 0) {//Minpoint is behind the camera, its always going to pass
return false;
}
vec2 ssize = size.xy * vec2(ivec2(screenW, screenH));
float miplevel = ceil(log2(max(max(ssize.x, ssize.y),1)));
vec2 midpoint = (maxBB.xy + minBB.xy)*0.5;
return textureLod(hizDepthSampler, vec3(midpoint, minBB.z), miplevel) > 0.0001;
}
//Returns if we should decend into its children or not
bool shouldDecend() {
//printf("Screen area %f: %f, %f", (size.x*size.y*float(screenW)*float(screenH)), float(screenW), float(screenH));
return (size.x*size.y*screenW*screenH) > decendSSS;
}

View File

@@ -1,262 +0,0 @@
#version 460 core
#define WORKGROUP 4
#define MINI_BATCH_SIZE 32
//The entire uint is a minibatch (each idx is one)
#define MINI_BATCH_MSK (uint(-1))
//Each y dim is a quadrent in the octree
// multiple x dims to fill up workgroups
layout(local_size_x=WORKGROUP, local_size_y=8) in;
layout(binding = 1, std430) restrict buffer RequestSectionLoadQueue {
uint counter;
uint[] queue;
} requestQueue;
//SectionNodeData is a uvec4 that contains the position + flags + ptr to own render section data + ptr to children
layout(binding = 2, std430) restrict readonly buffer SectionNodeData {
uvec4[] sectionNodes;
};
layout(binding = 3, std430) restrict buffer ActiveWorkingNodeQueue {
uint feedbackStatus;
uint batchIndex;
uint end;
uint start;
uint maxSize;//Needs to be a multiple of local_size_x
uint[] queue;
} nodeQueue;
struct UnpackedNode {
ivec4 position;//x,y,z,detail
uint flags;//16 bits
uint self;
uint children;
};
UnpackedNode unpackNode(uvec4 data) {
UnpackedNode node;
return node;
}
//NOTE: this is different to nanite in the fact that if a node is not loaded, too bad dont render
shared UnpackedNode workingNodes[WORKGROUP];
shared uint miniBatchMsk;
void loadNode() {
if (gl_LocalInvocationIndex == 0) {//Check if we need to
batchMsk = 0;//Reset the minibatch
if (miniBatchMsk == MINI_BATCH_SIZE) {
}
}
barrier();
if (gl_LocalInvocationID.y == 0) {
//Need to make it work in y size 8, but only gl_LocalInvocationId.x == 0
workingNodes[gl_LocalInvocationID.x] = unpackNode(sectionNodes[id]);
}
barrier();//Synchonize, also acts as memory barrier
}
//Computes screensize of the node and whether it should render itself or its children
bool shouldRenderChildren(UnpackedNode node) {
}
//Process a single node and enqueue child nodes if needed into work queue, enqueue self to render and/or request children to load
void processNode(uint id) {//Called even if it doesnt have any work (id==-1) to ensure uniform control flow for barriers
//Bottom 2 bits are status flags, is air and children loaded
// node.flags
//If the childrenloaded flag is not set, send a request for the children of the node to be loaded
// if all the children are loaded but we are not and we need to render, render the children and dispatch
// a request to load self
if (shouldRenderChildren(node)) {
//Dont care about
} else {
}
}
//The activly schedualed/acquired work slot for this group
shared uint workingBatchIndex;
shared uint workingBatchOffset;
void process() {
if (gl_LocalInvocationIndex == 0) {//This includes both x and y
workingBatchIndex = atomicAdd(nodeQueue.batchIndex, BATCH_SIZE);
}
}
void main() {
while (true) {
barrier();
}
}
//when a node is processed,
// compute its screen bounding box is computed using fast trick (e.g. if your viewing it from a quadrent you already know its bounding points (min/max))
// frustum cull, check hiz
// if it passes culling, use the screensize to check wether it must render itself
// or dispatch its children to render
// IF its error is small enough, then render itself, its mesh should always be loaded, if not its a critical error (except maybe if its a top level node or something)
// if its error is too large,
// check that all children are loaded (or empty), if they are not all loaded, enqueu a request for the cpu to load
// that nodes children
// if the load queue is full, dont enqueue it to the queue
// then instead of rendering children, render its own mesh since it should always be loaded
//Can also reverse the above slightly and make it so that it checks the children before enqueuing them
//the main thing to worry about is if there is enough work to fill the inital few rounds of this
// before amplification takes effect
// can do a thing where it initally just blasts child nodes out until the size is small enough
// NOTE: since matrix multiplication distributes over addition
// can precompute the AABB corners with respect to the matrix
// then you can just add a translation vector
//TODO: can do in another way
// first compute the sections that should either render self or childs
// then in as a seperate job queue work though it
uint getChildCount(UnpackedNode node) {
}
//Checks whether a node should be culled based on hiz/frustum
bool cullNode(UnpackedNode node) {
}
//Should render this node, or recurse to children
bool shouldRenderChildrenInstead(UnpackedNode node) {
}
//Does the node have its own mesh loaded
bool nodeHasSelfMesh(UnpackedNode node) {
}
//Does the node its children loaded (note! not child meshes)
bool nodeHasChildrenLoaded(UnpackedNode node) {
}
//Are all the childrens meshes loaded
bool nodeHasChildMeshesLoaded(UnpackedNode node) {
}
void request(uint type, uint idx) {
}
void renderMesh(uint idx) {
}
void enqueueChildren(uint arg, UnpackedNode node) {
uint cnt = getChildCount(node);
//TODO: the queue needs 2 counters, the pre and post atomic,
// pre is incremented to get index
// queue is written to
// post is then incremented to signal
}
void reportCritical(uint type) {
}
void processNode(uint idx) {
UnpackedNode node = unpackNode(sectionNodes[idx]);
if (!cullNode(node)) {
//Should we render children instead of ourselves with respect to screenspace error
if (shouldRenderChildrenInstead(node)) {
if (nodeHasChildrenLoaded(node)) {
//Dispatch nodes to queue
enqueueChildren(0, node);
} else {
//Children arnt loaded so either render self mesh or if we cant
// abort basicly must request nodes
if (nodeHasSelfMesh(node)) {
//Render self and dispatch request to load children
renderMesh(node.self);
request(1, idx);
} else {
//Critical issue, no are loaded and self has no mesh
reportCritical(0);
}
}
} else {
if (nodeHasSelfMesh(node)) {
//render self
renderMesh(node.self);
} else {
//Request that self mesh is loaded
request(0, idx);
//render children instead
if (nodeHasChildrenLoaded(node)) {//Might need to be node nodeHasChildMeshesLoaded
enqueueChildren(1, node);
} else {
//This is very bad, it means cant render anything
reportCritical(1);
}
}
}
}
}
//Psudo code, one thread, one load
void main() {
while (true) {
//Try to process a node queue entry
uint work = atomicAdd(workingNodeQueuePos, 1);
uint idx = work&0xFFFFFFu;
uint arg = work>>24;
if (idx < workingNodeQueueEnd) {
} else {
//Do other queue work however we still have the work slot allocated
}
}
}

View File

@@ -1,14 +0,0 @@
//This provides per scene/viewport/transfrom access, that is, a node can be attached to a specific scene/viewport/transfrom, this is so that
// different nodes/models can have different viewports/scenes/transfrom which enables some very cool things like
// absolutly massive VS2 structures should... just work :tm: - todd howard
struct Transform {
mat4 transform;
ivec4 originPos;
ivec4 worldPos;
};
layout(binding = TRANSFORM_ARRAY_INDEX, std140) uniform TransformArray {
Transform transforms[32];
};

View File

@@ -10,13 +10,13 @@ layout(local_size_x=LOCAL_SIZE) in;//, local_size_y=1
layout(binding = SCENE_UNIFORM_BINDING, std140) uniform SceneUniform {
mat4 VP;
ivec3 camSecPos;
float screenW;
int packedHizSize;
vec3 camSubSecPos;
float screenH;
float minSSS;
Frustum frustum;
uint renderQueueMaxSize;
float minSSS;
uint frameId;
uint requestQueueSize;
};
#import <voxy:lod/hierarchical/queue.glsl>
@@ -49,9 +49,9 @@ layout(binding = STATISTICS_BUFFER_BINDING, std430) restrict buffer statisticsBu
void addRequest(inout UnpackedNode node) {
//printf("Put node decend request");
if (!hasRequested(node)) {
if (requestQueueIndex.x < REQUEST_QUEUE_SIZE) {
if (requestQueueIndex.x < requestQueueSize) {//Soft limit
uint atomRes = atomicAdd(requestQueueIndex.x, 1);
if (atomRes < REQUEST_QUEUE_SIZE) {
if (atomRes < MAX_REQUEST_QUEUE_SIZE) {//Hard limit
//Mark node as having a request submitted to prevent duplicate submissions
requestQueue[atomRes] = getRawPos(node);
markRequested(node);

View File

@@ -1,3 +1,14 @@
struct SectionMeta {
uint posA;
uint posB;
uint AABB;
uint ptr;
uint cntA;
uint cntB;
uint cntC;
uint cntD;
};
uint extractDetail(SectionMeta section) {
return section.posA>>28;
}
@@ -5,7 +16,7 @@ uint extractDetail(SectionMeta section) {
ivec3 extractPosition(SectionMeta section) {
int y = ((int(section.posA)<<4)>>24);
int x = (int(section.posB)<<4)>>8;
int z = int((section.posA&((1<<20)-1))<<4);
int z = int((section.posA&((1u<<20)-1))<<4);
z |= int(section.posB>>28);
z <<= 8;
z >>= 8;