Compare commits
50 Commits
inverted_n
...
mc_1215
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2327c6baf8 | ||
|
|
144faf5b21 | ||
|
|
dc6dd4bb11 | ||
|
|
84482e8998 | ||
|
|
072ece7a3d | ||
|
|
22d557ed01 | ||
|
|
b454d54a99 | ||
|
|
341119386a | ||
|
|
1575d7319c | ||
|
|
950e92d7c7 | ||
|
|
0e98f52580 | ||
|
|
caf2703102 | ||
|
|
b79923de3d | ||
|
|
ee6d171ef6 | ||
|
|
1c30198347 | ||
|
|
4ed9199e1c | ||
|
|
2027cb064c | ||
|
|
a00eec69b7 | ||
|
|
84c07c4115 | ||
|
|
6398164d42 | ||
|
|
fa42ad5a03 | ||
|
|
22553eb1f9 | ||
|
|
cb599eea0b | ||
|
|
f73413e7c0 | ||
|
|
5b752d3f87 | ||
|
|
4f37d3b597 | ||
|
|
21b497d2d4 | ||
|
|
3bfc0c266d | ||
|
|
f252fa3a7a | ||
|
|
66266fb426 | ||
|
|
225e2d9d1a | ||
|
|
3b4aa75890 | ||
|
|
0c1917d56e | ||
|
|
35850082d5 | ||
|
|
d24b719a93 | ||
|
|
a0c33a439b | ||
|
|
6bbd2c521a | ||
|
|
7575c35b02 | ||
|
|
f78a8df275 | ||
|
|
8462dde374 | ||
|
|
075e8f2897 | ||
|
|
204989b909 | ||
|
|
c023e3b4f2 | ||
|
|
e7c4d6f132 | ||
|
|
9d0cf33a45 | ||
|
|
34c5c71d77 | ||
|
|
03bede4067 | ||
|
|
f624f85698 | ||
|
|
985fa4b53c | ||
|
|
6c6c08d188 |
@@ -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'))
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user