Compare commits
138 Commits
inverted_n
...
mc_1217_me
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c19a7f6d0 | ||
|
|
aff34fb463 | ||
|
|
94f2d03f90 | ||
|
|
45e2ea897f | ||
|
|
2023ac6ad2 | ||
|
|
91b5710e00 | ||
|
|
03b425eeb8 | ||
|
|
653dbc8a3a | ||
|
|
97058f24b4 | ||
|
|
e227d84306 | ||
|
|
f4e0dbb001 | ||
|
|
43d04febd5 | ||
|
|
1078507495 | ||
|
|
294e1f9fb6 | ||
|
|
5fe5ebc0a2 | ||
|
|
aff30961bd | ||
|
|
64d211b333 | ||
|
|
606d3b2282 | ||
|
|
132c6aa2e8 | ||
|
|
4a140c110f | ||
|
|
1c8d052544 | ||
|
|
3199b77ae5 | ||
|
|
f0e1f18379 | ||
|
|
492e2a707a | ||
|
|
7551ca3484 | ||
|
|
8f3fa2e7f2 | ||
|
|
936619ce12 | ||
|
|
d6a42f8ef3 | ||
|
|
bf43e405ff | ||
|
|
0c7c33304d | ||
|
|
f9b1d8a9e1 | ||
|
|
7b4fe4bd5c | ||
|
|
6ba3111ada | ||
|
|
258ccf89e0 | ||
|
|
3e193bb675 | ||
|
|
69b96eee96 | ||
|
|
e1ba2c4ebb | ||
|
|
dfce9dae46 | ||
|
|
f4fca865bb | ||
|
|
08fa0725d3 | ||
|
|
b92b769f7b | ||
|
|
726517a8b6 | ||
|
|
51f54c6edd | ||
|
|
f7f260777a | ||
|
|
dd9ac2819d | ||
|
|
1a7bb8498e | ||
|
|
fb2d26153d | ||
|
|
a640c0e62c | ||
|
|
784322db6f | ||
|
|
355a63c46f | ||
|
|
155eb75b82 | ||
|
|
64d4ef0c03 | ||
|
|
edb15db8fa | ||
|
|
883f140b41 | ||
|
|
90a6765e8a | ||
|
|
b8ede978c2 | ||
|
|
c1091acc6b | ||
|
|
0034940082 | ||
|
|
4d35fad772 | ||
|
|
d86c3b2eb8 | ||
|
|
b3556813a9 | ||
|
|
a94dcf1949 | ||
|
|
7fa07ae5ea | ||
|
|
cf60d31b75 | ||
|
|
e1b4e1ea6a | ||
|
|
4f6b0aa04d | ||
|
|
8b5e2780c7 | ||
|
|
0dd730d8de | ||
|
|
0f865c7afb | ||
|
|
688f24a409 | ||
|
|
dcacd279b3 | ||
|
|
37d0b755af | ||
|
|
26672ce34b | ||
|
|
d1be49f474 | ||
|
|
87072a4edc | ||
|
|
5f8679e5d2 | ||
|
|
1a7cd37741 | ||
|
|
ed181c1dcd | ||
|
|
4d839e3662 | ||
|
|
156b30756d | ||
|
|
6326870525 | ||
|
|
a360c9349a | ||
|
|
9e6276e0fa | ||
|
|
2bbc7a8999 | ||
|
|
fc3e05434f | ||
|
|
388764e9c8 | ||
|
|
3fb8323dd0 | ||
|
|
2327c6baf8 | ||
|
|
144faf5b21 | ||
|
|
dc6dd4bb11 | ||
|
|
84482e8998 | ||
|
|
072ece7a3d | ||
|
|
22d557ed01 | ||
|
|
b454d54a99 | ||
|
|
341119386a | ||
|
|
1575d7319c | ||
|
|
950e92d7c7 | ||
|
|
0e98f52580 | ||
|
|
caf2703102 | ||
|
|
b79923de3d | ||
|
|
ee6d171ef6 | ||
|
|
1c30198347 | ||
|
|
4ed9199e1c | ||
|
|
2027cb064c | ||
|
|
a00eec69b7 | ||
|
|
3aa1c94c6a | ||
|
|
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 |
33
.github/workflows/manual-artifact.yml
vendored
Normal file
33
.github/workflows/manual-artifact.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: manual-artifact
|
||||
|
||||
on: [ workflow_dispatch ]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Verify wrapper
|
||||
uses: gradle/actions/wrapper-validation@v3
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: false
|
||||
|
||||
- name: Gradle build
|
||||
run: ./gradlew build
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: voxy-artifacts
|
||||
path: build/libs/*.jar
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@
|
||||
/build/
|
||||
/run/
|
||||
/out/
|
||||
/logs/
|
||||
|
||||
58
build.gradle
58
build.gradle
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id 'fabric-loom' version "1.10.1"
|
||||
id 'fabric-loom' version "1.10-SNAPSHOT"
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
@@ -26,6 +26,24 @@ repositories {
|
||||
}
|
||||
maven { url = "https://maven.shedaniel.me/" }
|
||||
maven { url = "https://maven.terraformersmc.com/releases/" }
|
||||
|
||||
exclusiveContent {
|
||||
forRepository {
|
||||
ivy {
|
||||
name = "github"
|
||||
url = "https://github.com/"
|
||||
patternLayout {
|
||||
artifact '/[organisation]/[module]/releases/download/[revision]/[module]-[revision]-[classifier].[ext]'
|
||||
}
|
||||
metadataSources {
|
||||
artifact()
|
||||
}
|
||||
}
|
||||
}
|
||||
filter {
|
||||
includeModuleByRegex("[^\\.]+", "nvidium")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,29 +105,29 @@ dependencies {
|
||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
||||
|
||||
//TODO: this is to eventually not need sodium installed as atm its just used for parsing shaders
|
||||
modRuntimeOnlyMsk "maven.modrinth:sodium:mc1.21.5-0.6.13-fabric"
|
||||
modCompileOnly "maven.modrinth:sodium:mc1.21.5-0.6.13-fabric"
|
||||
modRuntimeOnlyMsk "maven.modrinth:sodium:mc1.21.6-0.6.13-fabric"
|
||||
modCompileOnly "maven.modrinth:sodium:mc1.21.6-0.6.13-fabric"
|
||||
|
||||
modImplementation("maven.modrinth:lithium:mc1.21.5-0.16.0-fabric")
|
||||
modImplementation("maven.modrinth:lithium:mc1.21.7-0.18.0-fabric")
|
||||
|
||||
//modRuntimeOnly "maven.modrinth:nvidium:0.2.6-beta"
|
||||
//modCompileOnly "maven.modrinth:nvidium:0.2.8-beta"
|
||||
//modRuntimeOnlyMsk "drouarb:nvidium:0.4.1-beta4:1.21.6@jar"
|
||||
modCompileOnly "drouarb:nvidium:0.4.1-beta4:1.21.6@jar"
|
||||
|
||||
modCompileOnly("maven.modrinth:modmenu:14.0.0-rc.2")
|
||||
modRuntimeOnlyMsk("maven.modrinth:modmenu:14.0.0-rc.2")
|
||||
modCompileOnly("maven.modrinth:modmenu:15.0.0-beta.3")
|
||||
modRuntimeOnlyMsk("maven.modrinth:modmenu:15.0.0-beta.3")
|
||||
|
||||
modCompileOnly("maven.modrinth:iris:1.8.11+1.21.5-fabric")
|
||||
modRuntimeOnlyMsk("maven.modrinth:iris:1.8.11+1.21.5-fabric")
|
||||
modCompileOnly("maven.modrinth:iris:1.9.1+1.21.7-fabric")
|
||||
//modRuntimeOnlyMsk("maven.modrinth:iris:1.9.1+1.21.7-fabric")
|
||||
|
||||
//modCompileOnly("maven.modrinth:starlight:1.1.3+1.20.4")
|
||||
|
||||
//modCompileOnly("maven.modrinth:immersiveportals:v5.1.7-mc1.20.4")
|
||||
|
||||
|
||||
modCompileOnly("maven.modrinth:chunky:1.4.36-fabric")
|
||||
modRuntimeOnlyMsk("maven.modrinth:chunky:1.4.36-fabric")
|
||||
modCompileOnly("maven.modrinth:chunky:1.4.40-fabric")
|
||||
modRuntimeOnlyMsk("maven.modrinth:chunky:1.4.40-fabric")
|
||||
|
||||
modRuntimeOnlyMsk("maven.modrinth:spark:1.10.121-fabric")
|
||||
modRuntimeOnlyMsk("maven.modrinth:spark:1.10.139-fabric")
|
||||
modRuntimeOnlyMsk("maven.modrinth:fabric-permissions-api:0.3.3")
|
||||
//modRuntimeOnly("maven.modrinth:nsight-loader:1.2.0")
|
||||
|
||||
@@ -188,11 +206,15 @@ processIncludeJars {
|
||||
}
|
||||
|
||||
remapJar {
|
||||
delete getDestinationDirectory().get()
|
||||
doFirst {
|
||||
delete fileTree(getDestinationDirectory().get())
|
||||
}
|
||||
|
||||
def hash = gitCommitHash();
|
||||
if (!hash.equals("<UnknownCommit>")) {
|
||||
archiveClassifier.set(hash);
|
||||
if (!isInGHA) {
|
||||
def hash = gitCommitHash();
|
||||
if (!hash.equals("<UnknownCommit>")) {
|
||||
archiveClassifier.set(hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +240,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'))
|
||||
|
||||
@@ -6,14 +6,14 @@ org.gradle.parallel=true
|
||||
|
||||
# Fabric Properties
|
||||
# check these on https://modmuss50.me/fabric.html
|
||||
minecraft_version=1.21.5
|
||||
yarn_mappings=1.21.5+build.1
|
||||
loader_version=0.16.10
|
||||
minecraft_version=1.21.7
|
||||
yarn_mappings=1.21.7+build.1
|
||||
loader_version=0.16.14
|
||||
|
||||
# Fabric API
|
||||
fabric_version=0.119.5+1.21.5
|
||||
fabric_version=0.128.1+1.21.7
|
||||
|
||||
# Mod Properties
|
||||
mod_version = 0.2.0-alpha
|
||||
mod_version = 0.2.3-alpha
|
||||
maven_group = me.cortex
|
||||
archives_base_name = voxy
|
||||
@@ -0,0 +1,7 @@
|
||||
package me.cortex.voxy.client;
|
||||
|
||||
import net.minecraft.world.chunk.WorldChunk;
|
||||
|
||||
public interface ICheekyClientChunkManager {
|
||||
WorldChunk voxy$cheekyGetChunk(int x, int z);
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package me.cortex.voxy.client;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.Capabilities;
|
||||
import me.cortex.voxy.client.core.model.bakery.BudgetBufferRenderer;
|
||||
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
@@ -18,9 +20,20 @@ public class VoxyClient implements ClientModInitializer {
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
ClientLifecycleEvents.CLIENT_STARTED.register(client->{
|
||||
Capabilities.init();//Ensure clinit is called
|
||||
|
||||
boolean systemSupported = Capabilities.INSTANCE.compute && Capabilities.INSTANCE.indirectParameters;
|
||||
if (systemSupported) {
|
||||
|
||||
SharedIndexBuffer.INSTANCE.id();
|
||||
BudgetBufferRenderer.init();
|
||||
|
||||
VoxyCommon.setInstanceFactory(VoxyClientInstance::new);
|
||||
|
||||
if (!Capabilities.INSTANCE.subgroup) {
|
||||
Logger.warn("GPU does not support subgroup operations, expect some performance degradation");
|
||||
}
|
||||
|
||||
} else {
|
||||
Logger.error("Voxy is unsupported on your system.");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
if (config.sectionStorageConfig == null) {
|
||||
throw new IllegalStateException("Config section storage null, reverting to default");
|
||||
Logger.error("Config deserialization null, reverting to default");
|
||||
} else {
|
||||
if (config.sectionStorageConfig == null) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
try {
|
||||
Files.writeString(json, Serialization.GSON.toJson(config));
|
||||
} 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,9 +28,10 @@ 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 useEnvironmentalFog = false;
|
||||
public boolean renderStatistics = false;
|
||||
|
||||
public static VoxyConfig loadOrCreate() {
|
||||
|
||||
@@ -70,13 +70,10 @@ public abstract class VoxyConfigScreenPages {
|
||||
|
||||
if (wasEnabled) {
|
||||
VoxyCommon.createInstance();
|
||||
|
||||
if (vrsh != null && s.enableRendering) {
|
||||
vrsh.createRenderer();
|
||||
}
|
||||
}
|
||||
}, s -> s.serviceThreads)
|
||||
.setImpact(OptionImpact.HIGH)
|
||||
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
|
||||
.build()
|
||||
).add(OptionImpl.createBuilder(boolean.class, storage)
|
||||
.setName(Text.translatable("voxy.config.general.ingest"))
|
||||
@@ -129,6 +126,14 @@ public abstract class VoxyConfigScreenPages {
|
||||
}, s -> s.sectionRenderDistance)
|
||||
.setImpact(OptionImpact.LOW)
|
||||
.build()
|
||||
).add(OptionImpl.createBuilder(boolean.class, storage)
|
||||
.setName(Text.translatable("voxy.config.general.environmental_fog"))
|
||||
.setTooltip(Text.translatable("voxy.config.general.environmental_fog.tooltip"))
|
||||
.setControl(TickBoxControl::new)
|
||||
.setImpact(OptionImpact.VARIES)
|
||||
.setBinding((s, v)-> s.useEnvironmentalFog = v, s -> s.useEnvironmentalFog)
|
||||
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
|
||||
.build()
|
||||
).add(OptionImpl.createBuilder(boolean.class, storage)
|
||||
.setName(Text.translatable("voxy.config.general.vanilla_fog"))
|
||||
.setTooltip(Text.translatable("voxy.config.general.vanilla_fog.tooltip"))
|
||||
|
||||
@@ -2,19 +2,15 @@ package me.cortex.voxy.client.core;
|
||||
|
||||
import com.mojang.blaze3d.opengl.GlConst;
|
||||
import com.mojang.blaze3d.opengl.GlStateManager;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import me.cortex.voxy.client.TimingStatistics;
|
||||
import me.cortex.voxy.client.VoxyClient;
|
||||
import me.cortex.voxy.client.config.VoxyConfig;
|
||||
import me.cortex.voxy.client.core.gl.Capabilities;
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
||||
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
|
||||
import me.cortex.voxy.client.core.rendering.ChunkBoundRenderer;
|
||||
import me.cortex.voxy.client.core.rendering.RenderDistanceTracker;
|
||||
import me.cortex.voxy.client.core.rendering.RenderService;
|
||||
import me.cortex.voxy.client.core.rendering.building.RenderDataFactory;
|
||||
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
|
||||
import me.cortex.voxy.client.core.rendering.post.PostProcessing;
|
||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
||||
import me.cortex.voxy.client.core.rendering.util.PrintfDebugUtil;
|
||||
@@ -24,31 +20,20 @@ import me.cortex.voxy.client.core.util.IrisUtil;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
import me.cortex.voxy.common.world.WorldSection;
|
||||
import me.cortex.voxy.common.world.other.Mapper;
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
import net.caffeinemc.mods.sodium.client.render.chunk.ChunkRenderMatrices;
|
||||
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.DefaultTerrainRenderPasses;
|
||||
import net.caffeinemc.mods.sodium.client.util.FogParameters;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gl.GlBackend;
|
||||
import net.minecraft.client.render.Camera;
|
||||
import net.minecraft.client.render.Frustum;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.GL_ONE;
|
||||
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
|
||||
import static org.lwjgl.opengl.GL11.GL_VIEWPORT;
|
||||
import static org.lwjgl.opengl.GL11.glGetIntegerv;
|
||||
import static org.lwjgl.opengl.GL11C.*;
|
||||
import static org.lwjgl.opengl.GL14.glBlendFuncSeparate;
|
||||
import static org.lwjgl.opengl.GL11C.glFinish;
|
||||
import static org.lwjgl.opengl.GL30C.GL_DRAW_FRAMEBUFFER_BINDING;
|
||||
import static org.lwjgl.opengl.GL30C.glBindFramebuffer;
|
||||
import static org.lwjgl.opengl.GL33.glBindSampler;
|
||||
@@ -61,34 +46,39 @@ public class VoxyRenderSystem {
|
||||
public final ChunkBoundRenderer chunkBoundRenderer;
|
||||
|
||||
public VoxyRenderSystem(WorldEngine world, ServiceThreadPool threadPool) {
|
||||
//Trigger the shared index buffer loading
|
||||
SharedIndexBuffer.INSTANCE.id();
|
||||
Capabilities.init();//Ensure clinit is called
|
||||
//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();
|
||||
|
||||
this.worldIn = world;
|
||||
this.renderer = new RenderService(world, threadPool);
|
||||
this.postProcessing = new PostProcessing();
|
||||
int minSec = MinecraftClient.getInstance().world.getBottomSectionCoord()>>5;
|
||||
int maxSec = (MinecraftClient.getInstance().world.getTopSectionCoord()-1)>>5;
|
||||
this.worldIn = world;
|
||||
this.renderer = new RenderService(world, threadPool);
|
||||
this.postProcessing = new PostProcessing();
|
||||
int minSec = MinecraftClient.getInstance().world.getBottomSectionCoord() >> 5;
|
||||
int maxSec = (MinecraftClient.getInstance().world.getTopSectionCoord() - 1) >> 5;
|
||||
|
||||
//Do some very cheeky stuff for MiB
|
||||
if (false) {
|
||||
minSec = -8;
|
||||
maxSec = 7;
|
||||
//Do some very cheeky stuff for MiB
|
||||
if (false) {
|
||||
minSec = -8;
|
||||
maxSec = 7;
|
||||
}
|
||||
|
||||
this.renderDistanceTracker = new RenderDistanceTracker(20,
|
||||
minSec,
|
||||
maxSec,
|
||||
this.renderer::addTopLevelNode,
|
||||
this.renderer::removeTopLevelNode);
|
||||
|
||||
this.renderDistanceTracker.setRenderDistance(VoxyConfig.CONFIG.sectionRenderDistance);
|
||||
|
||||
this.chunkBoundRenderer = new ChunkBoundRenderer();
|
||||
} catch (RuntimeException e) {
|
||||
world.releaseRef();//If something goes wrong, we must release the world first
|
||||
throw e;
|
||||
}
|
||||
|
||||
this.renderDistanceTracker = new RenderDistanceTracker(20,
|
||||
minSec,
|
||||
maxSec,
|
||||
this.renderer::addTopLevelNode,
|
||||
this.renderer::removeTopLevelNode);
|
||||
|
||||
this.renderDistanceTracker.setRenderDistance(VoxyConfig.CONFIG.sectionRenderDistance);
|
||||
|
||||
this.chunkBoundRenderer = new ChunkBoundRenderer();
|
||||
|
||||
//Keep the world loaded
|
||||
this.worldIn.acquireRef();
|
||||
}
|
||||
|
||||
public void setRenderDistance(int renderDistance) {
|
||||
@@ -96,40 +86,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
|
||||
@@ -168,10 +124,13 @@ public class VoxyRenderSystem {
|
||||
).mulLocal(makeProjectionMatrix(16, 16*3000));
|
||||
}
|
||||
|
||||
public void renderOpaque(ChunkRenderMatrices matrices, double cameraX, double cameraY, double cameraZ) {
|
||||
public void renderOpaque(ChunkRenderMatrices matrices, FogParameters fogParameters, double cameraX, double cameraY, double cameraZ) {
|
||||
if (IrisUtil.irisShadowActive()) {
|
||||
return;
|
||||
}
|
||||
TimingStatistics.resetSamplers();
|
||||
|
||||
|
||||
//Do some very cheeky stuff for MiB
|
||||
if (false) {
|
||||
int sector = (((int)Math.floor(cameraX)>>4)+512)>>10;
|
||||
@@ -202,11 +161,13 @@ public class VoxyRenderSystem {
|
||||
int[] dims = new int[4];
|
||||
glGetIntegerv(GL_VIEWPORT, dims);
|
||||
var viewport = this.renderer.getViewport();
|
||||
|
||||
viewport
|
||||
.setProjection(projection)
|
||||
.setModelView(new Matrix4f(matrices.modelView()))
|
||||
.setCamera(cameraX, cameraY, cameraZ)
|
||||
.setScreenSize(dims[2], dims[3])
|
||||
.setFogParameters(fogParameters)
|
||||
.update();
|
||||
viewport.frameId++;
|
||||
|
||||
@@ -218,7 +179,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();
|
||||
@@ -233,7 +194,7 @@ public class VoxyRenderSystem {
|
||||
|
||||
|
||||
TimingStatistics.F.start();
|
||||
this.postProcessing.renderPost(projection, matrices.projection(), boundFB);
|
||||
this.postProcessing.renderPost(viewport, matrices.projection(), boundFB);
|
||||
TimingStatistics.F.stop();
|
||||
|
||||
TimingStatistics.main.stop();
|
||||
@@ -296,129 +257,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;
|
||||
@@ -23,12 +24,15 @@ public class Capabilities {
|
||||
public final boolean compute;
|
||||
public final boolean indirectParameters;
|
||||
public final boolean isIntel;
|
||||
public final boolean subgroup;
|
||||
|
||||
public Capabilities() {
|
||||
var cap = GL.getCapabilities();
|
||||
this.compute = cap.glDispatchComputeIndirect != 0;
|
||||
this.subgroup = cap.GL_KHR_shader_subgroup;
|
||||
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
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package me.cortex.voxy.client.core.gl;
|
||||
|
||||
import org.lwjgl.opengl.GL;
|
||||
import org.lwjgl.system.JNI;
|
||||
|
||||
public class EXTMeshShader {
|
||||
public static final int
|
||||
GL_MESH_SHADER_EXT = 0x9559,
|
||||
GL_TASK_SHADER_EXT = 0x955A;
|
||||
|
||||
private static final long glDrawMeshTasksIndirectEXT_ptr;
|
||||
static {
|
||||
if (GL.getFunctionProvider() == null) {
|
||||
throw new IllegalStateException("Class must be initalized after gl context has been created");
|
||||
}
|
||||
glDrawMeshTasksIndirectEXT_ptr = GL.getFunctionProvider().getFunctionAddress("glDrawMeshTasksIndirectEXT");
|
||||
}
|
||||
|
||||
public static void glDrawMeshTasksIndirectEXT(long indirect) {
|
||||
if (glDrawMeshTasksIndirectEXT_ptr == 0) {
|
||||
throw new IllegalStateException("glDrawMeshTasksIndirectEXT not supported");
|
||||
}
|
||||
JNI.callPV(indirect, glDrawMeshTasksIndirectEXT_ptr);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package me.cortex.voxy.client.core.gl;
|
||||
|
||||
import me.cortex.voxy.common.util.TrackedObject;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import static org.lwjgl.opengl.GL32.*;
|
||||
|
||||
@@ -12,13 +13,24 @@ public class GlFence extends TrackedObject {
|
||||
this.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
}
|
||||
|
||||
private static final long SCRATCH = MemoryUtil.nmemCalloc(1,4);
|
||||
|
||||
public boolean signaled() {
|
||||
if (!this.signaled) {
|
||||
/*
|
||||
int ret = glClientWaitSync(this.fence, 0, 0);
|
||||
if (ret == GL_ALREADY_SIGNALED || ret == GL_CONDITION_SATISFIED) {
|
||||
this.signaled = true;
|
||||
} else if (ret != GL_TIMEOUT_EXPIRED) {
|
||||
throw new IllegalStateException("Poll for fence failed, glError: " + glGetError());
|
||||
}*/
|
||||
MemoryUtil.memPutInt(SCRATCH, -1);
|
||||
nglGetSynciv(this.fence, GL_SYNC_STATUS, 1, 0, SCRATCH);
|
||||
int val = MemoryUtil.memGetInt(SCRATCH);
|
||||
if (val == GL_SIGNALED) {
|
||||
this.signaled = true;
|
||||
} else if (val != GL_UNSIGNALED) {
|
||||
throw new IllegalStateException("Unknown data from glGetSync: "+val);
|
||||
}
|
||||
}
|
||||
return this.signaled;
|
||||
|
||||
@@ -101,8 +101,10 @@ public class GlTexture extends TrackedObject {
|
||||
private long getEstimatedSize() {
|
||||
this.assertAllocated();
|
||||
long elemSize = switch (this.format) {
|
||||
case GL_RGBA8, GL_DEPTH24_STENCIL8 -> 4;
|
||||
case GL_RGBA8, GL_DEPTH24_STENCIL8, GL_R32F -> 4;
|
||||
case GL_DEPTH_COMPONENT24 -> 4;//TODO: check this is right????
|
||||
case GL_DEPTH_COMPONENT32F -> 4;
|
||||
case GL_DEPTH_COMPONENT32 -> 4;
|
||||
|
||||
default -> throw new IllegalStateException("Unknown element size");
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ import static org.lwjgl.opengl.GL30.glBindBufferRange;
|
||||
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
|
||||
import static org.lwjgl.opengl.GL33.glBindSampler;
|
||||
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
|
||||
import static org.lwjgl.opengl.GL44.*;
|
||||
|
||||
|
||||
//TODO: rewrite the entire shader builder system
|
||||
@@ -26,6 +27,8 @@ public class AutoBindingShader extends Shader {
|
||||
private final List<BufferBinding> bindings = new ArrayList<>();
|
||||
private final List<TextureBinding> textureBindings = new ArrayList<>();
|
||||
|
||||
private boolean rebuild = true;
|
||||
|
||||
AutoBindingShader(Shader.Builder<AutoBindingShader> builder, int program) {
|
||||
super(program);
|
||||
this.defines = builder.defines;
|
||||
@@ -70,6 +73,8 @@ public class AutoBindingShader extends Shader {
|
||||
}
|
||||
|
||||
private void insertOrReplaceBinding(BufferBinding binding) {
|
||||
this.rebuild = true;
|
||||
|
||||
//Check if there is already a binding at the index with the target, if so, replace it
|
||||
for (int i = 0; i < this.bindings.size(); i++) {
|
||||
var entry = this.bindings.get(i);
|
||||
@@ -92,6 +97,16 @@ public class AutoBindingShader extends Shader {
|
||||
}
|
||||
|
||||
public AutoBindingShader texture(int unit, int sampler, GlTexture texture) {
|
||||
this.rebuild = true;
|
||||
|
||||
for (int i = 0; i < this.textureBindings.size(); i++) {
|
||||
var entry = this.textureBindings.get(i);
|
||||
if (entry.unit == unit) {
|
||||
this.textureBindings.set(i, new TextureBinding(unit, sampler, texture));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
this.textureBindings.add(new TextureBinding(unit, sampler, texture));
|
||||
return this;
|
||||
}
|
||||
@@ -99,6 +114,13 @@ public class AutoBindingShader extends Shader {
|
||||
@Override
|
||||
public void bind() {
|
||||
super.bind();
|
||||
//TODO: replace with multibind and use the invalidate flag
|
||||
/*
|
||||
glBindSamplers();
|
||||
glBindTextures();
|
||||
glBindBuffersBase();
|
||||
glBindBuffersRange();
|
||||
*/
|
||||
if (!this.bindings.isEmpty()) {
|
||||
for (var binding : this.bindings) {
|
||||
binding.buffer.assertNotFreed();
|
||||
|
||||
@@ -4,8 +4,11 @@ import me.cortex.voxy.client.core.gl.Capabilities;
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.gl.GlDebug;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.common.util.ThreadUtils;
|
||||
import me.cortex.voxy.common.util.TrackedObject;
|
||||
import org.lwjgl.opengl.GL20C;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -149,6 +152,7 @@ public class Shader extends TrackedObject {
|
||||
|
||||
public T compile() {
|
||||
this.defineIf("IS_INTEL", Capabilities.INSTANCE.isIntel);
|
||||
this.defineIf("IS_WINDOWS", ThreadUtils.isWindows);
|
||||
return this.constructor.make(this, this.compileToProgram());
|
||||
}
|
||||
|
||||
@@ -170,12 +174,18 @@ public class Shader extends TrackedObject {
|
||||
|
||||
private static int createShader(ShaderType type, String src) {
|
||||
int shader = GL20C.glCreateShader(type.gl);
|
||||
GL20C.glShaderSource(shader, src);
|
||||
{//https://github.com/CaffeineMC/sodium/blob/fc42a7b19836c98a35df46e63303608de0587ab6/src/main/java/me/jellysquid/mods/sodium/client/gl/shader/ShaderWorkarounds.java
|
||||
long ptr = MemoryUtil.memAddress(MemoryUtil.memUTF8(src, true));
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
GL20C.nglShaderSource(shader, 1, stack.pointers(ptr).address0(), 0);
|
||||
}
|
||||
MemoryUtil.nmemFree(ptr);
|
||||
}
|
||||
GL20C.glCompileShader(shader);
|
||||
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;
|
||||
@@ -17,6 +18,7 @@ import net.minecraft.block.LeavesBlock;
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.color.block.BlockColorProvider;
|
||||
import net.minecraft.client.render.BlockRenderLayer;
|
||||
import net.minecraft.client.render.RenderLayer;
|
||||
import net.minecraft.client.render.RenderLayers;
|
||||
import net.minecraft.fluid.FluidState;
|
||||
@@ -255,19 +257,19 @@ public class ModelFactory {
|
||||
this.fluidStateLUT[modelId] = clientFluidStateId;
|
||||
}
|
||||
|
||||
RenderLayer blockRenderLayer = null;
|
||||
BlockRenderLayer blockRenderLayer = null;
|
||||
if (blockState.getBlock() instanceof FluidBlock) {
|
||||
blockRenderLayer = RenderLayers.getFluidLayer(blockState.getFluidState());
|
||||
} else {
|
||||
if (blockState.getBlock() instanceof LeavesBlock) {
|
||||
blockRenderLayer = RenderLayer.getSolid();
|
||||
blockRenderLayer = BlockRenderLayer.SOLID;
|
||||
} else {
|
||||
blockRenderLayer = RenderLayers.getBlockLayer(blockState);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int checkMode = blockRenderLayer==RenderLayer.getSolid()?TextureUtils.WRITE_CHECK_STENCIL:TextureUtils.WRITE_CHECK_ALPHA;
|
||||
int checkMode = blockRenderLayer==BlockRenderLayer.SOLID?TextureUtils.WRITE_CHECK_STENCIL:TextureUtils.WRITE_CHECK_ALPHA;
|
||||
|
||||
if (Capabilities.INSTANCE.isMesa) {
|
||||
//Mesa does not work with GL_DEPTH_STENCIL_TEXTURE_MODE GL_STENCIL_INDEX
|
||||
@@ -338,7 +340,7 @@ public class ModelFactory {
|
||||
//Each face gets 1 byte, with the top 2 bytes being for whatever
|
||||
long metadata = 0;
|
||||
metadata |= isBiomeColourDependent?1:0;
|
||||
metadata |= blockRenderLayer == RenderLayer.getTranslucent()?2:0;
|
||||
metadata |= blockRenderLayer == BlockRenderLayer.TRANSLUCENT?2:0;
|
||||
metadata |= needsDoubleSidedQuads?4:0;
|
||||
metadata |= ((!isFluid) && !blockState.getFluidState().isEmpty())?8:0;//Has a fluid state accosiacted with it and is not itself a fluid
|
||||
metadata |= isFluid?16:0;//Is a fluid
|
||||
@@ -372,7 +374,7 @@ public class ModelFactory {
|
||||
|
||||
//TODO: add alot of config options for the following
|
||||
boolean occludesFace = true;
|
||||
occludesFace &= blockRenderLayer != RenderLayer.getTranslucent();//If its translucent, it doesnt occlude
|
||||
occludesFace &= blockRenderLayer != BlockRenderLayer.TRANSLUCENT;//If its translucent, it doesnt occlude
|
||||
|
||||
//TODO: make this an option, basicly if the face is really close, it occludes otherwise it doesnt
|
||||
occludesFace &= offset < 0.1;//If the face is rendered far away from the other face, then it doesnt occlude
|
||||
@@ -392,7 +394,7 @@ public class ModelFactory {
|
||||
metadata |= canBeOccluded?4:0;
|
||||
|
||||
//Face uses its own lighting if its not flat against the adjacent block & isnt traslucent
|
||||
metadata |= (offset > 0.01 || blockRenderLayer == RenderLayer.getTranslucent())?0b1000:0;
|
||||
metadata |= (offset > 0.01 || blockRenderLayer == BlockRenderLayer.TRANSLUCENT)?0b1000:0;
|
||||
|
||||
|
||||
|
||||
@@ -410,11 +412,11 @@ public class ModelFactory {
|
||||
int area = (faceSize[1]-faceSize[0]+1) * (faceSize[3]-faceSize[2]+1);
|
||||
boolean needsAlphaDiscard = ((float)writeCount)/area<0.9;//If the amount of area covered by written pixels is less than a threashold, disable discard as its not needed
|
||||
|
||||
needsAlphaDiscard |= blockRenderLayer != RenderLayer.getSolid();
|
||||
needsAlphaDiscard &= blockRenderLayer != RenderLayer.getTranslucent();//Translucent doesnt have alpha discard
|
||||
needsAlphaDiscard |= blockRenderLayer != BlockRenderLayer.SOLID;
|
||||
needsAlphaDiscard &= blockRenderLayer != BlockRenderLayer.TRANSLUCENT;//Translucent doesnt have alpha discard
|
||||
faceModelData |= needsAlphaDiscard?1<<22:0;
|
||||
|
||||
faceModelData |= ((!faceCoversFullBlock)&&blockRenderLayer != RenderLayer.getTranslucent())?1<<23:0;//Alpha discard override, translucency doesnt have alpha discard
|
||||
faceModelData |= ((!faceCoversFullBlock)&&blockRenderLayer != BlockRenderLayer.TRANSLUCENT)?1<<23:0;//Alpha discard override, translucency doesnt have alpha discard
|
||||
|
||||
|
||||
|
||||
@@ -434,8 +436,8 @@ public class ModelFactory {
|
||||
int modelFlags = 0;
|
||||
modelFlags |= colourProvider != null?1:0;
|
||||
modelFlags |= isBiomeColourDependent?2:0;//Basicly whether to use the next int as a colour or as a base index/id into a colour buffer for biome dependent colours
|
||||
modelFlags |= blockRenderLayer == RenderLayer.getTranslucent()?4:0;//Is translucent
|
||||
modelFlags |= blockRenderLayer == RenderLayer.getCutout()?0:8;//Dont use mipmaps (AND ALSO FKING SPECIFIES IF IT HAS AO, WHY??? GREAT QUESTION, TODO FIXE THIS)
|
||||
modelFlags |= blockRenderLayer == BlockRenderLayer.TRANSLUCENT?4:0;//Is translucent
|
||||
modelFlags |= blockRenderLayer == BlockRenderLayer.CUTOUT?0:8;//Dont use mipmaps (AND ALSO FKING SPECIFIES IF IT HAS AO, WHY??? GREAT QUESTION, TODO FIXE THIS)
|
||||
|
||||
//modelFlags |= blockRenderLayer == RenderLayer.getSolid()?0:1;// should discard alpha
|
||||
MemoryUtil.memPutInt(uploadPtr, modelFlags);
|
||||
@@ -490,7 +492,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;
|
||||
@@ -678,7 +680,7 @@ public class ModelFactory {
|
||||
private static final int LAYERS = Integer.numberOfTrailingZeros(MODEL_TEXTURE_SIZE);
|
||||
//TODO: redo to batch blit, instead of 6 seperate blits, and also fix mipping
|
||||
private void putTextures(int id, ColourDepthTextureData[] textures) {
|
||||
if (MODEL_TEXTURE_SIZE != 16) {throw new IllegalStateException("THIS METHOD MUST BE REDONE IF THIS CONST CHANGES");}
|
||||
//if (MODEL_TEXTURE_SIZE != 16) {throw new IllegalStateException("THIS METHOD MUST BE REDONE IF THIS CONST CHANGES");}
|
||||
|
||||
//TODO: need to use a write mask to see what pixels must be used to contribute to mipping
|
||||
// as in, using the depth/stencil info, check if pixel was written to, if so, use that pixel when blending, else dont
|
||||
|
||||
@@ -24,14 +24,14 @@ public class ModelStore {
|
||||
public ModelStore() {
|
||||
this.modelBuffer = new GlBuffer(MODEL_SIZE * (1<<16)).name("ModelData");
|
||||
this.modelColourBuffer = new GlBuffer(4 * (1<<16)).name("ModelColour");
|
||||
this.textures = new GlTexture().store(GL_RGBA8, 4, ModelFactory.MODEL_TEXTURE_SIZE*3*256,ModelFactory.MODEL_TEXTURE_SIZE*2*256).name("ModelTextures");
|
||||
this.textures = new GlTexture().store(GL_RGBA8, Integer.numberOfTrailingZeros(ModelFactory.MODEL_TEXTURE_SIZE), ModelFactory.MODEL_TEXTURE_SIZE*3*256,ModelFactory.MODEL_TEXTURE_SIZE*2*256).name("ModelTextures");
|
||||
|
||||
|
||||
|
||||
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
|
||||
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_LOD, 0);
|
||||
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAX_LOD, 4);
|
||||
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAX_LOD, Integer.numberOfTrailingZeros(ModelFactory.MODEL_TEXTURE_SIZE));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -46,6 +46,21 @@ public class BakedBlockEntityModel {
|
||||
this.layers.forEach(layer->layer.consumer.free());
|
||||
}
|
||||
|
||||
private static int getMetaFromLayer(RenderLayer layer) {
|
||||
boolean hasDiscard = layer == RenderLayer.getCutout() ||
|
||||
layer == RenderLayer.getCutoutMipped() ||
|
||||
layer == RenderLayer.getTripwire();
|
||||
|
||||
boolean isMipped = layer == RenderLayer.getCutoutMipped() ||
|
||||
layer == RenderLayer.getSolid() ||
|
||||
layer.isTranslucent() ||
|
||||
layer == RenderLayer.getTripwire();
|
||||
|
||||
int meta = hasDiscard?1:0;
|
||||
meta |= isMipped?2:0;
|
||||
return meta;
|
||||
}
|
||||
|
||||
public static BakedBlockEntityModel bake(BlockState state) {
|
||||
Map<RenderLayer, LayerConsumer> map = new HashMap<>();
|
||||
var entity = ((BlockEntityProvider)state.getBlock()).createBlockEntity(BlockPos.ORIGIN, state);
|
||||
@@ -56,7 +71,7 @@ public class BakedBlockEntityModel {
|
||||
entity.setWorld(MinecraftClient.getInstance().world);
|
||||
if (renderer != null) {
|
||||
try {
|
||||
renderer.render(entity, 0.0f, new MatrixStack(), layer->map.computeIfAbsent(layer, rl -> new LayerConsumer(rl, new ReuseVertexConsumer().setDefaultMeta(ModelTextureBakery.getMetaFromLayer(rl)))).consumer, 0, 0, new Vec3d(0,0,0));
|
||||
renderer.render(entity, 0.0f, new MatrixStack(), layer->map.computeIfAbsent(layer, rl -> new LayerConsumer(rl, new ReuseVertexConsumer().setDefaultMeta(getMetaFromLayer(rl)))).consumer, 0, 0, new Vec3d(0,0,0));
|
||||
} catch (Exception e) {
|
||||
Logger.error("Unable to bake block entity: " + entity, e);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ public class BudgetBufferRenderer {
|
||||
.compile();
|
||||
|
||||
|
||||
public static void init(){}
|
||||
private static final GlBuffer indexBuffer;
|
||||
static {
|
||||
var i = RenderSystem.getSequentialBuffer(VertexFormat.DrawMode.QUADS);
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
package me.cortex.voxy.client.core.model.bakery;
|
||||
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.block.FluidBlock;
|
||||
import net.minecraft.block.LeavesBlock;
|
||||
import net.minecraft.block.*;
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.render.RenderLayer;
|
||||
import net.minecraft.client.render.BlockRenderLayer;
|
||||
import net.minecraft.client.render.RenderLayers;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.fluid.FluidState;
|
||||
@@ -21,6 +18,8 @@ import net.minecraft.world.biome.ColorResolver;
|
||||
import net.minecraft.world.chunk.light.LightingProvider;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
@@ -43,22 +42,25 @@ public class ModelTextureBakery {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public static int getMetaFromLayer(RenderLayer layer) {
|
||||
boolean hasDiscard = layer == RenderLayer.getCutout() ||
|
||||
layer == RenderLayer.getCutoutMipped() ||
|
||||
layer == RenderLayer.getTripwire();
|
||||
public static int getMetaFromLayer(BlockRenderLayer layer) {
|
||||
boolean hasDiscard = layer == BlockRenderLayer.CUTOUT ||
|
||||
layer == BlockRenderLayer.CUTOUT_MIPPED ||
|
||||
layer == BlockRenderLayer.TRIPWIRE;
|
||||
|
||||
boolean isMipped = layer == RenderLayer.getCutoutMipped() ||
|
||||
layer == RenderLayer.getSolid() ||
|
||||
layer == RenderLayer.getTranslucent() ||
|
||||
layer == RenderLayer.getTripwire();
|
||||
boolean isMipped = layer == BlockRenderLayer.CUTOUT_MIPPED ||
|
||||
layer == BlockRenderLayer.SOLID ||
|
||||
layer == BlockRenderLayer.TRANSLUCENT ||
|
||||
layer == BlockRenderLayer.TRIPWIRE;
|
||||
|
||||
int meta = hasDiscard?1:0;
|
||||
meta |= isMipped?2:0;
|
||||
return meta;
|
||||
}
|
||||
|
||||
private void bakeBlockModel(BlockState state, RenderLayer layer) {
|
||||
private void bakeBlockModel(BlockState state, BlockRenderLayer layer) {
|
||||
if (state.getRenderType() == BlockRenderType.INVISIBLE) {
|
||||
return;//Dont bake if invisible
|
||||
}
|
||||
var model = MinecraftClient.getInstance()
|
||||
.getBakedModelManager()
|
||||
.getBlockModels()
|
||||
@@ -79,7 +81,7 @@ public class ModelTextureBakery {
|
||||
}
|
||||
|
||||
|
||||
private void bakeFluidState(BlockState state, RenderLayer layer, int face) {
|
||||
private void bakeFluidState(BlockState state, BlockRenderLayer layer, int face) {
|
||||
this.vc.setDefaultMeta(getMetaFromLayer(layer));//Set the meta while baking
|
||||
MinecraftClient.getInstance().getBlockRenderManager().renderFluid(BlockPos.ORIGIN, new BlockRenderView() {
|
||||
@Override
|
||||
@@ -163,13 +165,13 @@ public class ModelTextureBakery {
|
||||
public void renderToStream(BlockState state, int streamBuffer, int streamOffset) {
|
||||
this.capture.clear();
|
||||
boolean isBlock = true;
|
||||
RenderLayer layer;
|
||||
BlockRenderLayer layer;
|
||||
if (state.getBlock() instanceof FluidBlock) {
|
||||
layer = RenderLayers.getFluidLayer(state.getFluidState());
|
||||
isBlock = false;
|
||||
} else {
|
||||
if (state.getBlock() instanceof LeavesBlock) {
|
||||
layer = RenderLayer.getSolid();
|
||||
layer = BlockRenderLayer.SOLID;
|
||||
} else {
|
||||
layer = RenderLayers.getBlockLayer(state);
|
||||
}
|
||||
@@ -189,7 +191,7 @@ public class ModelTextureBakery {
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glEnable(GL_CULL_FACE);
|
||||
if (layer == RenderLayer.getTranslucent()) {
|
||||
if (layer == BlockRenderLayer.TRANSLUCENT) {
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFuncSeparate(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
} else {
|
||||
@@ -221,6 +223,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 +247,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 +278,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
|
||||
@@ -293,7 +313,7 @@ public class ModelTextureBakery {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, this.capture.framebuffer.id);
|
||||
glClearDepth(1);
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
if (layer == RenderLayer.getTranslucent()) {
|
||||
if (layer == BlockRenderLayer.TRANSLUCENT) {
|
||||
//reset the blend func
|
||||
GL14.glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
@@ -302,25 +322,37 @@ public class ModelTextureBakery {
|
||||
|
||||
|
||||
|
||||
static {
|
||||
//TODO: FIXME: need to bake in the correct orientation, HOWEVER some orientations require a flipped winding order!!!!
|
||||
static {
|
||||
//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.multiply(makeQuatFromAxisExact(new Vector3f(0,0,1), rotation));
|
||||
stack.multiply(makeQuatFromAxisExact(new Vector3f(1,0,0), pitch));
|
||||
stack.multiply(makeQuatFromAxisExact(new Vector3f(0,1,0), 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());
|
||||
}
|
||||
|
||||
private static Quaternionf makeQuatFromAxisExact(Vector3f vec, float angle) {
|
||||
angle = (float) Math.toRadians(angle);
|
||||
float hangle = angle / 2.0f;
|
||||
float sinAngle = (float) Math.sin(hangle);
|
||||
float invVLength = (float) (1/Math.sqrt(vec.lengthSquared()));
|
||||
return new Quaternionf(vec.x * invVLength * sinAngle,
|
||||
vec.y * invVLength * sinAngle,
|
||||
vec.z * invVLength * sinAngle,
|
||||
Math.cos(hangle));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,8 @@ import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
|
||||
import me.cortex.voxy.client.core.rendering.hierachical.AsyncNodeManager;
|
||||
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser;
|
||||
import me.cortex.voxy.client.core.rendering.hierachical.NodeCleaner;
|
||||
import me.cortex.voxy.client.core.rendering.section.AbstractSectionRenderer;
|
||||
import me.cortex.voxy.client.core.rendering.section.*;
|
||||
import me.cortex.voxy.client.core.rendering.section.geometry.*;
|
||||
import me.cortex.voxy.client.core.rendering.section.IUsesMeshlets;
|
||||
import me.cortex.voxy.client.core.rendering.section.MDICSectionRenderer;
|
||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
@@ -50,14 +48,18 @@ 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
|
||||
var override = System.getProperty("voxy.geometryBufferSizeOverrideMB", "");
|
||||
if (!override.isEmpty()) {
|
||||
geometryCapacity = Long.parseLong(override)*1024L*1024L;
|
||||
}
|
||||
return geometryCapacity;
|
||||
}
|
||||
|
||||
@@ -71,7 +73,9 @@ public class RenderService<T extends AbstractSectionRenderer<J, Q>, J extends Vi
|
||||
this.geometryData = (Q) new BasicSectionGeometryData(1<<20, geometryCapacity);
|
||||
|
||||
//Max sections: ~500k
|
||||
this.sectionRenderer = (T) new MDICSectionRenderer(this.modelService.getStore(), (BasicSectionGeometryData) this.geometryData);
|
||||
//this.sectionRenderer = (T) new MDICSectionRenderer(this.modelService.getStore(), (BasicSectionGeometryData) this.geometryData);
|
||||
//this.sectionRenderer = (T) new MeshSectionRenderer(this.modelService.getStore(), (BasicSectionGeometryData) this.geometryData);
|
||||
this.sectionRenderer = (T) new MeshEXTSectionRenderer(this.modelService.getStore(), (BasicSectionGeometryData) this.geometryData);
|
||||
Logger.info("Using renderer: " + this.sectionRenderer.getClass().getSimpleName() + " with geometry buffer of: " + geometryCapacity + " bytes");
|
||||
|
||||
//Do something incredibly hacky, we dont need to keep the reference to this around, so just connect and discard
|
||||
@@ -84,7 +88,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 +110,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 +137,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 +180,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();
|
||||
|
||||
@@ -2,12 +2,15 @@ package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.rendering.util.HiZBuffer;
|
||||
import me.cortex.voxy.client.core.rendering.util.HiZBuffer2;
|
||||
import net.caffeinemc.mods.sodium.client.util.FogParameters;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import org.joml.*;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public abstract class Viewport <A extends Viewport<A>> {
|
||||
//public final HiZBuffer2 hiZBuffer = new HiZBuffer2();
|
||||
public final HiZBuffer hiZBuffer = new HiZBuffer();
|
||||
private static final Field planesField;
|
||||
static {
|
||||
@@ -29,6 +32,7 @@ public abstract class Viewport <A extends Viewport<A>> {
|
||||
public double cameraX;
|
||||
public double cameraY;
|
||||
public double cameraZ;
|
||||
public FogParameters fogParameters;
|
||||
|
||||
public final Matrix4f MVP = new Matrix4f();
|
||||
public final Vector3i section = new Vector3i();
|
||||
@@ -75,6 +79,11 @@ public abstract class Viewport <A extends Viewport<A>> {
|
||||
return (A) this;
|
||||
}
|
||||
|
||||
public A setFogParameters(FogParameters fogParameters) {
|
||||
this.fogParameters = fogParameters;
|
||||
return (A) this;
|
||||
}
|
||||
|
||||
public A update() {
|
||||
//MVP
|
||||
this.projection.mul(this.modelView, this.MVP);
|
||||
|
||||
@@ -4,6 +4,7 @@ import me.cortex.voxy.client.core.model.IdNotYetComputedException;
|
||||
import me.cortex.voxy.client.core.model.ModelFactory;
|
||||
import me.cortex.voxy.client.core.model.ModelQueries;
|
||||
import me.cortex.voxy.client.core.util.ScanMesher2D;
|
||||
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.WorldEngine;
|
||||
@@ -207,17 +208,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 +232,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 +242,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 +1550,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 +1586,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);
|
||||
}
|
||||
@@ -1600,6 +1608,10 @@ public class RenderDataFactory {
|
||||
return BuiltSection.emptyWithChildren(section.key, section.getNonEmptyChildren());
|
||||
}
|
||||
|
||||
if (this.quadCount >= 1<<16) {
|
||||
Logger.warn("Large quad count for section " + WorldEngine.pprintPos(section.key) + " is " + this.quadCount);
|
||||
}
|
||||
|
||||
if (this.minX<0 || this.minY<0 || this.minZ<0 || 32<this.maxX || 32<this.maxY || 32<this.maxZ) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@@ -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_000L); 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,10 +7,10 @@ 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.util.PrintfDebugUtil;
|
||||
import me.cortex.voxy.client.core.rendering.util.HiZBuffer;
|
||||
import me.cortex.voxy.client.core.rendering.Viewport;
|
||||
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
|
||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
||||
import me.cortex.voxy.client.core.rendering.util.PrintfDebugUtil;
|
||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import me.cortex.voxy.common.util.MemoryBuffer;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
@@ -30,7 +30,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 +39,7 @@ public class HierarchicalOcclusionTraverser {
|
||||
|
||||
private final AsyncNodeManager nodeManager;
|
||||
private final NodeCleaner nodeCleaner;
|
||||
private final RenderGenerationService meshGen;
|
||||
|
||||
private final GlBuffer requestBuffer;
|
||||
|
||||
@@ -73,7 +74,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 +97,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 +175,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 +211,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);
|
||||
|
||||
@@ -3,6 +3,8 @@ package me.cortex.voxy.client.core.rendering.post;
|
||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.lwjgl.opengl.GL11C.GL_TRIANGLES;
|
||||
import static org.lwjgl.opengl.GL11C.glDrawArrays;
|
||||
import static org.lwjgl.opengl.GL30C.glBindVertexArray;
|
||||
@@ -13,9 +15,12 @@ public class FullscreenBlit {
|
||||
|
||||
private final Shader shader;
|
||||
public FullscreenBlit(String fragId) {
|
||||
this.shader = Shader.make()
|
||||
this(fragId, (a)->a);
|
||||
}
|
||||
public <T extends Shader> FullscreenBlit(String fragId, Function<Shader.Builder<T>, Shader.Builder<T>> builder) {
|
||||
this.shader = builder.apply((Shader.Builder<T>) Shader.make()
|
||||
.add(ShaderType.VERTEX, "voxy:post/fullscreen.vert")
|
||||
.add(ShaderType.FRAGMENT, fragId)
|
||||
.add(ShaderType.FRAGMENT, fragId))
|
||||
.compile();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package me.cortex.voxy.client.core.rendering.post;
|
||||
|
||||
import me.cortex.voxy.client.config.VoxyConfig;
|
||||
import me.cortex.voxy.client.core.gl.GlFramebuffer;
|
||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
||||
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.Viewport;
|
||||
import me.cortex.voxy.client.core.rendering.util.GlStateCapture;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
import org.lwjgl.opengl.GL11C;
|
||||
|
||||
import static org.lwjgl.opengl.ARBComputeShader.glDispatchCompute;
|
||||
import static org.lwjgl.opengl.ARBShaderImageLoadStore.glBindImageTexture;
|
||||
@@ -20,6 +20,7 @@ import static org.lwjgl.opengl.GL43.GL_DEPTH_STENCIL_TEXTURE_MODE;
|
||||
import static org.lwjgl.opengl.GL45C.*;
|
||||
|
||||
public class PostProcessing {
|
||||
private final boolean useEnvFog = VoxyConfig.CONFIG.useEnvironmentalFog;
|
||||
private final GlFramebuffer framebuffer;
|
||||
private final GlFramebuffer framebufferSSAO;
|
||||
private int width;
|
||||
@@ -31,7 +32,8 @@ public class PostProcessing {
|
||||
private final FullscreenBlit setDepth0 = new FullscreenBlit("voxy:post/depth0.frag");
|
||||
private final FullscreenBlit emptyBlit = new FullscreenBlit("voxy:post/noop.frag");
|
||||
//private final FullscreenBlit blitTexture = new FullscreenBlit("voxy:post/blit_texture_cutout.frag");
|
||||
private final FullscreenBlit blitTexture = new FullscreenBlit("voxy:post/blit_texture_depth_cutout.frag");
|
||||
private final FullscreenBlit blitTexture = new FullscreenBlit("voxy:post/blit_texture_depth_cutout.frag",
|
||||
a->a.defineIf("USE_ENV_FOG", useEnvFog));
|
||||
private final Shader ssaoComp = Shader.make()
|
||||
.add(ShaderType.COMPUTE, "voxy:post/ssao.comp")
|
||||
.compile();
|
||||
@@ -158,7 +160,7 @@ public class PostProcessing {
|
||||
|
||||
|
||||
//Executes the post processing and emits to whatever framebuffer is currently bound via a blit
|
||||
public void renderPost(Matrix4f fromProjection, Matrix4fc tooProjection, int outputFB) {
|
||||
public void renderPost(Viewport vp, Matrix4fc tooProjection, int outputFB) {
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
|
||||
|
||||
@@ -172,12 +174,17 @@ public class PostProcessing {
|
||||
this.blitTexture.bind();
|
||||
|
||||
float[] data = new float[4*4];
|
||||
var mat = new Matrix4f(fromProjection).invert();
|
||||
mat.get(data);
|
||||
new Matrix4f(vp.MVP).invert().get(data);
|
||||
glUniformMatrix4fv(2, false, data);//inverse fromProjection
|
||||
tooProjection.get(data);
|
||||
new Matrix4f(tooProjection).mul(vp.modelView).get(data);
|
||||
glUniformMatrix4fv(3, false, data);//tooProjection
|
||||
|
||||
if (useEnvFog) {
|
||||
float start = vp.fogParameters.environmentalStart();
|
||||
float end = vp.fogParameters.environmentalEnd();
|
||||
float invEndFogDelta = 1f/(end-start);
|
||||
glUniform3f(4, vp.fogParameters.environmentalEnd()/2f, invEndFogDelta, start*invEndFogDelta);
|
||||
glUniform3f(5, vp.fogParameters.red(), vp.fogParameters.green(), vp.fogParameters.blue());
|
||||
}
|
||||
|
||||
glBindTextureUnit(0, this.didSSAO?this.colourSSAO.id:this.colour.id);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package me.cortex.voxy.client.core.rendering.section;
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.rendering.Viewport;
|
||||
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser;
|
||||
import me.cortex.voxy.client.core.rendering.util.HiZBuffer;
|
||||
|
||||
public class MDICViewport extends Viewport<MDICViewport> {
|
||||
public final GlBuffer drawCountCallBuffer = new GlBuffer(1024).zero();
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
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;
|
||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||
import me.cortex.voxy.client.core.model.ModelStore;
|
||||
import me.cortex.voxy.client.core.rendering.RenderService;
|
||||
import me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData;
|
||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
||||
import me.cortex.voxy.client.core.rendering.util.LightMapHelper;
|
||||
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
|
||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static me.cortex.voxy.client.core.gl.EXTMeshShader.glDrawMeshTasksIndirectEXT;
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static org.lwjgl.opengl.GL11C.GL_UNSIGNED_INT;
|
||||
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
|
||||
import static org.lwjgl.opengl.GL15.glBindBuffer;
|
||||
import static org.lwjgl.opengl.GL30.glBindBufferBase;
|
||||
import static org.lwjgl.opengl.GL30.glBindVertexArray;
|
||||
import static org.lwjgl.opengl.GL30C.GL_R32UI;
|
||||
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
|
||||
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
|
||||
import static org.lwjgl.opengl.GL33.glBindSampler;
|
||||
import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER;
|
||||
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
|
||||
import static org.lwjgl.opengl.GL43.*;
|
||||
import static org.lwjgl.opengl.GL45.*;
|
||||
import static org.lwjgl.opengl.NVRepresentativeFragmentTest.GL_REPRESENTATIVE_FRAGMENT_TEST_NV;
|
||||
|
||||
//Uses MDIC to render the sections
|
||||
public class MeshEXTSectionRenderer extends AbstractSectionRenderer<MeshViewport, BasicSectionGeometryData> {
|
||||
private static final int STATISTICS_BUFFER_BINDING = 8;
|
||||
private final Shader terrainShader = Shader.make()
|
||||
.define("MESH_SIZE", 32)//16
|
||||
|
||||
.defineIf("HAS_STATISTICS", RenderStatistics.enabled)
|
||||
.defineIf("STATISTICS_BUFFER_BINDING", RenderStatistics.enabled, STATISTICS_BUFFER_BINDING)
|
||||
|
||||
.add(ShaderType.TASK, "voxy:lod/meshext/task.glsl")
|
||||
.add(ShaderType.MESH, "voxy:lod/meshext/mesh.glsl")
|
||||
.add(ShaderType.FRAGMENT, "voxy:lod/meshext/frag.glsl")
|
||||
.compile();
|
||||
|
||||
private final Shader cullShader = Shader.make()
|
||||
.add(ShaderType.VERTEX, "voxy:lod/gl46/cull/raster.vert")
|
||||
.add(ShaderType.FRAGMENT, "voxy:lod/gl46/cull/raster.frag")
|
||||
.compile();
|
||||
|
||||
private final GlBuffer uniform = new GlBuffer(1024).zero();
|
||||
private final GlBuffer cullAndMeshDrawCommand = new GlBuffer(8*4).zero();//TODO: this needs tobe in the viewport
|
||||
|
||||
//Statistics
|
||||
private final GlBuffer statisticsBuffer = new GlBuffer(1024).zero();
|
||||
|
||||
public MeshEXTSectionRenderer(ModelStore modelStore, BasicSectionGeometryData geometryData) {
|
||||
super(modelStore, geometryData);
|
||||
glClearNamedBufferSubData(this.cullAndMeshDrawCommand.id, GL_R32UI,0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{6*2*3});//count
|
||||
glClearNamedBufferSubData(this.cullAndMeshDrawCommand.id, GL_R32UI,8, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{(1<<16)*6*2});//firstIndex
|
||||
glClearNamedBufferSubData(this.cullAndMeshDrawCommand.id, GL_R32UI,5*4+4, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{1});//y
|
||||
glClearNamedBufferSubData(this.cullAndMeshDrawCommand.id, GL_R32UI,5*4+8, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{1});//z
|
||||
}
|
||||
|
||||
private void uploadUniformBuffer(MeshViewport viewport) {
|
||||
long ptr = UploadStream.INSTANCE.upload(this.uniform, 0, 1024);
|
||||
|
||||
var mat = new Matrix4f(viewport.MVP);
|
||||
mat.translate(-viewport.innerTranslation.x, -viewport.innerTranslation.y, -viewport.innerTranslation.z);
|
||||
mat.getToAddress(ptr); ptr += 4*4*4;
|
||||
|
||||
viewport.section.getToAddress(ptr); ptr += 4*3;
|
||||
|
||||
if (viewport.frameId<0) {
|
||||
Logger.error("Frame ID negative, this will cause things to break, wrapping around");
|
||||
viewport.frameId &= 0x7fffffff;
|
||||
}
|
||||
MemoryUtil.memPutInt(ptr, viewport.frameId&0x7fffffff); ptr += 4;
|
||||
viewport.innerTranslation.getToAddress(ptr); ptr += 4*3;
|
||||
|
||||
ptr += 4;// padd
|
||||
|
||||
MemoryUtil.memPutFloat(ptr, viewport.width); ptr += 4;
|
||||
MemoryUtil.memPutFloat(ptr, viewport.height); ptr += 4;
|
||||
|
||||
UploadStream.INSTANCE.commit();
|
||||
}
|
||||
|
||||
|
||||
private void bindRenderingBuffers(MeshViewport viewport, GlTexture depthBoundTexture) {
|
||||
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniform.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, viewport.getRenderList().id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.geometryManager.getMetadataBuffer().id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, viewport.visibilityBuffer.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, this.geometryManager.getGeometryBuffer().id);
|
||||
this.modelStore.bind(5, 6, 0);
|
||||
LightMapHelper.bind(1);
|
||||
glBindTextureUnit(2, depthBoundTexture.id);
|
||||
|
||||
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.cullAndMeshDrawCommand.id);
|
||||
|
||||
if (RenderStatistics.enabled) {
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, STATISTICS_BUFFER_BINDING, this.statisticsBuffer.id);
|
||||
}
|
||||
}
|
||||
|
||||
private void renderTerrain(MeshViewport viewport, GlTexture depthBoundTexture) {
|
||||
//RenderLayer.getCutoutMipped().startDrawing();
|
||||
glDisable(GL_CULL_FACE);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
this.terrainShader.bind();
|
||||
this.bindRenderingBuffers(viewport, depthBoundTexture);
|
||||
|
||||
glMemoryBarrier(GL_COMMAND_BARRIER_BIT|GL_SHADER_STORAGE_BARRIER_BIT);//Barrier everything is needed
|
||||
|
||||
glDrawMeshTasksIndirectEXT(20);
|
||||
|
||||
glEnable(GL_CULL_FACE);
|
||||
glBindSampler(0, 0);
|
||||
glBindTextureUnit(0, 0);
|
||||
glBindSampler(1, 0);
|
||||
glBindTextureUnit(1, 0);
|
||||
|
||||
//RenderLayer.getCutoutMipped().endDrawing();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderOpaque(MeshViewport viewport, GlTexture dbt) {
|
||||
if (this.geometryManager.getSectionCount() == 0) return;
|
||||
|
||||
this.uploadUniformBuffer(viewport);
|
||||
|
||||
this.renderTerrain(viewport, dbt);
|
||||
|
||||
//We need todo the statistics here as rastering is part of them, download then clear
|
||||
if (RenderStatistics.enabled) {
|
||||
DownloadStream.INSTANCE.download(this.statisticsBuffer, down->{
|
||||
final int LAYERS = WorldEngine.MAX_LOD_LAYER+1;
|
||||
for (int i = 0; i < LAYERS; i++) {
|
||||
RenderStatistics.visibleSections[i] = MemoryUtil.memGetInt(down.address+i*4L);
|
||||
}
|
||||
|
||||
for (int i = 0; i < LAYERS; i++) {
|
||||
RenderStatistics.quadCount[i] = MemoryUtil.memGetInt(down.address+LAYERS*4L+i*4L);
|
||||
}
|
||||
});
|
||||
|
||||
this.statisticsBuffer.zero();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderTranslucent(MeshViewport viewport, GlTexture depthBoundTexture) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildDrawCalls(MeshViewport viewport) {
|
||||
if (this.geometryManager.getSectionCount() == 0) return;
|
||||
this.uploadUniformBuffer(viewport);
|
||||
//Can do a sneeky trick, since the sectionRenderList is a list to things to render, it invokes the culler
|
||||
// which only marks visible sections
|
||||
|
||||
|
||||
{//Test occlusion
|
||||
glCopyNamedBufferSubData(viewport.getRenderList().id, this.cullAndMeshDrawCommand.id, 0, 4, 4);//Copy counts to the draw buffer
|
||||
glCopyNamedBufferSubData(viewport.getRenderList().id, this.cullAndMeshDrawCommand.id, 0, 20, 4);//Copy counts to the draw buffer
|
||||
|
||||
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);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, viewport.visibilityBuffer.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, viewport.getRenderList().id);
|
||||
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.cullAndMeshDrawCommand.id);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE.id());
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glColorMask(false, false, false, false);
|
||||
glDepthMask(false);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT|GL_COMMAND_BARRIER_BIT);
|
||||
glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_BYTE, 0);
|
||||
glDepthMask(true);
|
||||
glColorMask(true, true, true, true);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
if (Capabilities.INSTANCE.repFragTest) {
|
||||
glDisable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderTemporal(MeshViewport viewport, GlTexture dbt) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDebug(List<String> lines) {
|
||||
super.addDebug(lines);
|
||||
//lines.add("SC/GS: " + this.geometryManager.getSectionCount() + "/" + (this.geometryManager.getGeometryUsed()/(1024*1024)));//section count/geometry size (MB)
|
||||
}
|
||||
|
||||
@Override
|
||||
public MeshViewport createViewport() {
|
||||
return new MeshViewport(this.geometryManager.getMaxSectionCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free() {
|
||||
this.cullAndMeshDrawCommand.free();
|
||||
this.uniform.free();
|
||||
this.terrainShader.free();
|
||||
this.cullShader.free();
|
||||
this.statisticsBuffer.free();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
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;
|
||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||
import me.cortex.voxy.client.core.model.ModelStore;
|
||||
import me.cortex.voxy.client.core.rendering.RenderService;
|
||||
import me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData;
|
||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
||||
import me.cortex.voxy.client.core.rendering.util.LightMapHelper;
|
||||
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
|
||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.lwjgl.opengl.ARBIndirectParameters.GL_PARAMETER_BUFFER_ARB;
|
||||
import static org.lwjgl.opengl.ARBIndirectParameters.glMultiDrawElementsIndirectCountARB;
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static org.lwjgl.opengl.GL11C.GL_UNSIGNED_INT;
|
||||
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
|
||||
import static org.lwjgl.opengl.GL15.glBindBuffer;
|
||||
import static org.lwjgl.opengl.GL30.glBindBufferBase;
|
||||
import static org.lwjgl.opengl.GL30.glBindVertexArray;
|
||||
import static org.lwjgl.opengl.GL30C.GL_R32UI;
|
||||
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
|
||||
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
|
||||
import static org.lwjgl.opengl.GL33.glBindSampler;
|
||||
import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER;
|
||||
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
|
||||
import static org.lwjgl.opengl.GL43.*;
|
||||
import static org.lwjgl.opengl.GL45.*;
|
||||
import static org.lwjgl.opengl.GL45C.glClearNamedBufferData;
|
||||
import static org.lwjgl.opengl.NVMeshShader.glDrawMeshTasksIndirectNV;
|
||||
import static org.lwjgl.opengl.NVRepresentativeFragmentTest.GL_REPRESENTATIVE_FRAGMENT_TEST_NV;
|
||||
|
||||
//Uses MDIC to render the sections
|
||||
public class MeshSectionRenderer extends AbstractSectionRenderer<MeshViewport, BasicSectionGeometryData> {
|
||||
private static final int STATISTICS_BUFFER_BINDING = 8;
|
||||
private final Shader terrainShader = Shader.make()
|
||||
.define("MESH_SIZE", 32)//16
|
||||
|
||||
.defineIf("HAS_STATISTICS", RenderStatistics.enabled)
|
||||
.defineIf("STATISTICS_BUFFER_BINDING", RenderStatistics.enabled, STATISTICS_BUFFER_BINDING)
|
||||
|
||||
.add(ShaderType.TASK, "voxy:lod/mesh/task.glsl")
|
||||
.add(ShaderType.MESH, "voxy:lod/mesh/mesh.glsl")
|
||||
.add(ShaderType.FRAGMENT, "voxy:lod/mesh/frag.glsl")
|
||||
.compile();
|
||||
|
||||
private final Shader cullShader = Shader.make()
|
||||
.add(ShaderType.VERTEX, "voxy:lod/gl46/cull/raster.vert")
|
||||
.add(ShaderType.FRAGMENT, "voxy:lod/gl46/cull/raster.frag")
|
||||
.compile();
|
||||
|
||||
private final GlBuffer uniform = new GlBuffer(1024).zero();
|
||||
private final GlBuffer cullAndMeshDrawCommand = new GlBuffer(7*4).zero();//TODO: this needs tobe in the viewport
|
||||
|
||||
//Statistics
|
||||
private final GlBuffer statisticsBuffer = new GlBuffer(1024).zero();
|
||||
|
||||
public MeshSectionRenderer(ModelStore modelStore, BasicSectionGeometryData geometryData) {
|
||||
super(modelStore, geometryData);
|
||||
glClearNamedBufferSubData(this.cullAndMeshDrawCommand.id, GL_R32UI,0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{6*2*3});//count
|
||||
glClearNamedBufferSubData(this.cullAndMeshDrawCommand.id, GL_R32UI,8, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{(1<<16)*6*2});//firstIndex
|
||||
}
|
||||
|
||||
private void uploadUniformBuffer(MeshViewport viewport) {
|
||||
long ptr = UploadStream.INSTANCE.upload(this.uniform, 0, 1024);
|
||||
|
||||
var mat = new Matrix4f(viewport.MVP);
|
||||
mat.translate(-viewport.innerTranslation.x, -viewport.innerTranslation.y, -viewport.innerTranslation.z);
|
||||
mat.getToAddress(ptr); ptr += 4*4*4;
|
||||
|
||||
viewport.section.getToAddress(ptr); ptr += 4*3;
|
||||
|
||||
if (viewport.frameId<0) {
|
||||
Logger.error("Frame ID negative, this will cause things to break, wrapping around");
|
||||
viewport.frameId &= 0x7fffffff;
|
||||
}
|
||||
MemoryUtil.memPutInt(ptr, viewport.frameId&0x7fffffff); ptr += 4;
|
||||
viewport.innerTranslation.getToAddress(ptr); ptr += 4*3;
|
||||
|
||||
ptr += 4;// padd
|
||||
|
||||
MemoryUtil.memPutFloat(ptr, viewport.width); ptr += 4;
|
||||
MemoryUtil.memPutFloat(ptr, viewport.height); ptr += 4;
|
||||
|
||||
UploadStream.INSTANCE.commit();
|
||||
}
|
||||
|
||||
|
||||
private void bindRenderingBuffers(MeshViewport viewport, GlTexture depthBoundTexture) {
|
||||
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniform.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, viewport.getRenderList().id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.geometryManager.getMetadataBuffer().id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, viewport.visibilityBuffer.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, this.geometryManager.getGeometryBuffer().id);
|
||||
this.modelStore.bind(5, 6, 0);
|
||||
LightMapHelper.bind(1);
|
||||
glBindTextureUnit(2, depthBoundTexture.id);
|
||||
|
||||
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.cullAndMeshDrawCommand.id);
|
||||
|
||||
if (RenderStatistics.enabled) {
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, STATISTICS_BUFFER_BINDING, this.statisticsBuffer.id);
|
||||
}
|
||||
}
|
||||
|
||||
private void renderTerrain(MeshViewport viewport, GlTexture depthBoundTexture) {
|
||||
//RenderLayer.getCutoutMipped().startDrawing();
|
||||
glDisable(GL_CULL_FACE);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
this.terrainShader.bind();
|
||||
this.bindRenderingBuffers(viewport, depthBoundTexture);
|
||||
|
||||
glMemoryBarrier(GL_COMMAND_BARRIER_BIT|GL_SHADER_STORAGE_BARRIER_BIT);//Barrier everything is needed
|
||||
|
||||
glDrawMeshTasksIndirectNV(20);
|
||||
|
||||
glEnable(GL_CULL_FACE);
|
||||
glBindSampler(0, 0);
|
||||
glBindTextureUnit(0, 0);
|
||||
glBindSampler(1, 0);
|
||||
glBindTextureUnit(1, 0);
|
||||
|
||||
//RenderLayer.getCutoutMipped().endDrawing();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderOpaque(MeshViewport viewport, GlTexture dbt) {
|
||||
if (this.geometryManager.getSectionCount() == 0) return;
|
||||
|
||||
this.uploadUniformBuffer(viewport);
|
||||
|
||||
this.renderTerrain(viewport, dbt);
|
||||
|
||||
//We need todo the statistics here as rastering is part of them, download then clear
|
||||
if (RenderStatistics.enabled) {
|
||||
DownloadStream.INSTANCE.download(this.statisticsBuffer, down->{
|
||||
final int LAYERS = WorldEngine.MAX_LOD_LAYER+1;
|
||||
for (int i = 0; i < LAYERS; i++) {
|
||||
RenderStatistics.visibleSections[i] = MemoryUtil.memGetInt(down.address+i*4L);
|
||||
}
|
||||
|
||||
for (int i = 0; i < LAYERS; i++) {
|
||||
RenderStatistics.quadCount[i] = MemoryUtil.memGetInt(down.address+LAYERS*4L+i*4L);
|
||||
}
|
||||
});
|
||||
|
||||
this.statisticsBuffer.zero();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderTranslucent(MeshViewport viewport, GlTexture depthBoundTexture) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildDrawCalls(MeshViewport viewport) {
|
||||
if (this.geometryManager.getSectionCount() == 0) return;
|
||||
this.uploadUniformBuffer(viewport);
|
||||
//Can do a sneeky trick, since the sectionRenderList is a list to things to render, it invokes the culler
|
||||
// which only marks visible sections
|
||||
|
||||
|
||||
{//Test occlusion
|
||||
glCopyNamedBufferSubData(viewport.getRenderList().id, this.cullAndMeshDrawCommand.id, 0, 4, 4);//Copy counts to the draw buffer
|
||||
glCopyNamedBufferSubData(viewport.getRenderList().id, this.cullAndMeshDrawCommand.id, 0, 20, 4);//Copy counts to the draw buffer
|
||||
|
||||
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);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, viewport.visibilityBuffer.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, viewport.getRenderList().id);
|
||||
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.cullAndMeshDrawCommand.id);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE.id());
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glColorMask(false, false, false, false);
|
||||
glDepthMask(false);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT|GL_COMMAND_BARRIER_BIT);
|
||||
glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_BYTE, 0);
|
||||
glDepthMask(true);
|
||||
glColorMask(true, true, true, true);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
if (Capabilities.INSTANCE.repFragTest) {
|
||||
glDisable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderTemporal(MeshViewport viewport, GlTexture dbt) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDebug(List<String> lines) {
|
||||
super.addDebug(lines);
|
||||
//lines.add("SC/GS: " + this.geometryManager.getSectionCount() + "/" + (this.geometryManager.getGeometryUsed()/(1024*1024)));//section count/geometry size (MB)
|
||||
}
|
||||
|
||||
@Override
|
||||
public MeshViewport createViewport() {
|
||||
return new MeshViewport(this.geometryManager.getMaxSectionCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free() {
|
||||
this.cullAndMeshDrawCommand.free();
|
||||
this.uniform.free();
|
||||
this.terrainShader.free();
|
||||
this.cullShader.free();
|
||||
this.statisticsBuffer.free();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package me.cortex.voxy.client.core.rendering.section;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.rendering.Viewport;
|
||||
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser;
|
||||
|
||||
public class MeshViewport extends Viewport<MeshViewport> {
|
||||
public final GlBuffer indirectLookupBuffer = new GlBuffer(HierarchicalOcclusionTraverser.MAX_QUEUE_SIZE *4+4);
|
||||
public final GlBuffer visibilityBuffer;
|
||||
|
||||
public MeshViewport(int maxSectionCount) {
|
||||
this.visibilityBuffer = new GlBuffer(maxSectionCount*4L);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void delete0() {
|
||||
super.delete0();
|
||||
this.visibilityBuffer.free();
|
||||
this.indirectLookupBuffer.free();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlBuffer getRenderList() {
|
||||
return this.indirectLookupBuffer;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package me.cortex.voxy.client.core.rendering.util;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.GlFramebuffer;
|
||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
||||
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.RenderService;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import static org.lwjgl.opengl.ARBDirectStateAccess.*;
|
||||
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_TEXTURE_FETCH_BARRIER_BIT;
|
||||
import static org.lwjgl.opengl.GL11C.*;
|
||||
import static org.lwjgl.opengl.GL30C.*;
|
||||
import static org.lwjgl.opengl.GL30C.glBindVertexArray;
|
||||
import static org.lwjgl.opengl.GL33.glBindSampler;
|
||||
import static org.lwjgl.opengl.GL33.glGenSamplers;
|
||||
import static org.lwjgl.opengl.GL33C.glDeleteSamplers;
|
||||
import static org.lwjgl.opengl.GL33C.glSamplerParameteri;
|
||||
import static org.lwjgl.opengl.GL42C.*;
|
||||
import static org.lwjgl.opengl.GL43C.glDispatchCompute;
|
||||
import static org.lwjgl.opengl.GL45C.glTextureBarrier;
|
||||
|
||||
public class HiZBuffer2 {
|
||||
private final Shader hizMip = Shader.make()
|
||||
.add(ShaderType.COMPUTE, "voxy:hiz/hiz.comp")
|
||||
.compile();
|
||||
private final Shader hizInitial = Shader.make()
|
||||
.add(ShaderType.VERTEX, "voxy:hiz/blit.vsh")
|
||||
.add(ShaderType.FRAGMENT, "voxy:hiz/blit.fsh")
|
||||
.define("OUTPUT_COLOUR")
|
||||
.compile();
|
||||
private final GlFramebuffer fb = new GlFramebuffer().name("HiZ");
|
||||
private final int sampler = glGenSamplers();
|
||||
private final int type;
|
||||
private GlTexture texture;
|
||||
private int levels;
|
||||
private int width;
|
||||
private int height;
|
||||
|
||||
public HiZBuffer2() {
|
||||
this(GL_R32F);
|
||||
}
|
||||
public HiZBuffer2(int type) {
|
||||
glNamedFramebufferDrawBuffer(this.fb.id, GL_COLOR_ATTACHMENT0);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
private void alloc(int width, int height) {
|
||||
this.levels = (int)Math.ceil(Math.log(Math.max(width, height))/Math.log(2));
|
||||
//We dont care about e.g. 1x1 size texture since you dont get meshlets that big to cover such a large area
|
||||
//this.levels -= 1;//Arbitrary size, shinks the max level by alot and saves a significant amount of processing time
|
||||
// (could probably increase it to be defined by a max meshlet coverage computation thing)
|
||||
|
||||
//GL_DEPTH_COMPONENT32F //Cant use this as it does not match the depth format of the provided depth buffer
|
||||
this.texture = new GlTexture().store(this.type, this.levels, width, height).name("HiZ");
|
||||
glTextureParameteri(this.texture.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
|
||||
glTextureParameteri(this.texture.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTextureParameteri(this.texture.id, GL_TEXTURE_COMPARE_MODE, GL_NONE);
|
||||
glTextureParameteri(this.texture.id, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTextureParameteri(this.texture.id, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
glSamplerParameteri(this.sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
|
||||
glSamplerParameteri(this.sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glSamplerParameteri(this.sampler, GL_TEXTURE_COMPARE_MODE, GL_NONE);
|
||||
glSamplerParameteri(this.sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glSamplerParameteri(this.sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
this.fb.bind(GL_COLOR_ATTACHMENT0, this.texture, 0).verify();
|
||||
}
|
||||
|
||||
public void buildMipChain(int srcDepthTex, int width, int height) {
|
||||
if (this.width != Integer.highestOneBit(width) || this.height != Integer.highestOneBit(height)) {
|
||||
if (this.texture != null) {
|
||||
this.texture.free();
|
||||
this.texture = null;
|
||||
}
|
||||
this.alloc(Integer.highestOneBit(width), Integer.highestOneBit(height));
|
||||
}
|
||||
|
||||
|
||||
{//Mip down to initial chain
|
||||
int boundFB = GL11.glGetInteger(GL_DRAW_FRAMEBUFFER_BINDING);
|
||||
|
||||
glBindVertexArray(RenderService.STATIC_VAO);
|
||||
this.hizInitial.bind();
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, this.fb.id);
|
||||
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
|
||||
glBindTextureUnit(0, srcDepthTex);
|
||||
glBindSampler(0, this.sampler);
|
||||
glUniform1i(0, 0);
|
||||
|
||||
glViewport(0, 0, this.width, this.height);
|
||||
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
|
||||
glTextureBarrier();
|
||||
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT|GL_TEXTURE_FETCH_BARRIER_BIT);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, boundFB);
|
||||
glViewport(0, 0, width, height);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
{//Compute based Mipping
|
||||
this.hizMip.bind();
|
||||
|
||||
glUniform2f(0, 1f/this.width, 1f/this.height);
|
||||
glBindTextureUnit(0, this.texture.id);
|
||||
glBindSampler(0, this.sampler);
|
||||
for (int i = 1; i < 7; i++) {
|
||||
glBindImageTexture(i, this.texture.id, i, false, 0, GL_WRITE_ONLY, GL_R32F);
|
||||
}
|
||||
|
||||
glDispatchCompute(this.width/64, this.height/64, 1);
|
||||
|
||||
glBindSampler(0, 0);
|
||||
for (int i =0;i<7;i++)
|
||||
glBindTextureUnit(i, 0);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void free() {
|
||||
this.fb.free();
|
||||
if (this.texture != null) {
|
||||
this.texture.free();
|
||||
this.texture = null;
|
||||
}
|
||||
glDeleteSamplers(this.sampler);
|
||||
this.hizInitial.free();
|
||||
this.hizMip.free();
|
||||
}
|
||||
|
||||
public int getHizTextureId() {
|
||||
return this.texture.id;
|
||||
}
|
||||
|
||||
public int getPackedLevels() {
|
||||
return ((Integer.numberOfTrailingZeros(this.width))<<16)|(Integer.numberOfTrailingZeros(this.height));//+1
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,6 @@ import static org.lwjgl.opengl.GL45.glBindTextureUnit;
|
||||
public class LightMapHelper {
|
||||
public static void bind(int lightingIndex) {
|
||||
glBindSampler(lightingIndex, 0);
|
||||
glBindTextureUnit(lightingIndex, ((net.minecraft.client.texture.GlTexture)(MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().getGlTexture())).getGlId());
|
||||
glBindTextureUnit(lightingIndex, ((net.minecraft.client.texture.GlTexture)(MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().getGlTextureView().texture())).getGlId());
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package me.cortex.voxy.client.mixin.minecraft;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import me.cortex.voxy.client.config.VoxyConfig;
|
||||
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.render.BackgroundRenderer;
|
||||
import net.minecraft.client.render.Camera;
|
||||
import net.minecraft.client.render.Fog;
|
||||
import org.joml.Vector4f;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
|
||||
@Mixin(BackgroundRenderer.class)
|
||||
public class MixinBackgroundRenderer {
|
||||
@WrapMethod(method = "applyFog")
|
||||
private static Fog voxy$overrideFog(Camera camera, BackgroundRenderer.FogType fogType, Vector4f color, float viewDistance, boolean thickenFog, float tickProgress, Operation<Fog> original) {
|
||||
var vrs = (IGetVoxyRenderSystem)MinecraftClient.getInstance().worldRenderer;
|
||||
if (VoxyConfig.CONFIG.renderVanillaFog || vrs == null || vrs.getVoxyRenderSystem() == null) {
|
||||
return original.call(camera, fogType, color, viewDistance, thickenFog, tickProgress);
|
||||
} else {
|
||||
return Fog.DUMMY;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package me.cortex.voxy.client.mixin.minecraft;
|
||||
|
||||
import me.cortex.voxy.client.ICheekyClientChunkManager;
|
||||
import me.cortex.voxy.client.config.VoxyConfig;
|
||||
import me.cortex.voxy.common.world.service.VoxelIngestService;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.client.world.ClientChunkManager;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.world.chunk.WorldChunk;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(ClientChunkManager.class)
|
||||
public class MixinClientChunkManager implements ICheekyClientChunkManager {
|
||||
@Unique
|
||||
private static final boolean BOBBY_INSTALLED = FabricLoader.getInstance().isModLoaded("bobby");
|
||||
|
||||
@Shadow volatile ClientChunkManager.ClientChunkMap chunks;
|
||||
|
||||
@Override
|
||||
public WorldChunk voxy$cheekyGetChunk(int x, int z) {
|
||||
//This doesnt do the in range check stuff, it just gets the chunk at all costs
|
||||
return this.chunks.getChunk(this.chunks.getIndex(x, z));
|
||||
}
|
||||
|
||||
@Inject(method = "unload", at = @At("HEAD"))
|
||||
public void voxy$captureChunkBeforeUnload(ChunkPos pos, CallbackInfo ci) {
|
||||
if (VoxyConfig.CONFIG.ingestEnabled && BOBBY_INSTALLED) {
|
||||
var chunk = this.voxy$cheekyGetChunk(pos.x, pos.z);
|
||||
if (chunk != null) {
|
||||
VoxelIngestService.tryAutoIngestChunk(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,13 +18,16 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import java.time.Duration;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Mixin(ClientLoginNetworkHandler.class)
|
||||
@Mixin(ClientPlayNetworkHandler.class)
|
||||
public class MixinClientLoginNetworkHandler {
|
||||
@Inject(method = "<init>", at = @At(value = "TAIL"))
|
||||
private void voxy$init(ClientConnection connection, MinecraftClient client, ServerInfo serverInfo, Screen parentScreen, boolean newWorld, Duration worldLoadTime, Consumer statusConsumer, CookieStorage cookieStorage, CallbackInfo ci) {
|
||||
@Inject(method = "onGameJoin", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerInteractionManager;<init>(Lnet/minecraft/client/MinecraftClient;Lnet/minecraft/client/network/ClientPlayNetworkHandler;)V", shift = At.Shift.BY, by = 2))
|
||||
private void voxy$init(GameJoinS2CPacket packet, CallbackInfo ci) {
|
||||
if (VoxyCommon.isAvailable()) {
|
||||
VoxyClientInstance.isInGame = true;
|
||||
if (VoxyConfig.CONFIG.enabled) {
|
||||
if (VoxyCommon.getInstance() != null) {
|
||||
VoxyCommon.shutdownInstance();
|
||||
}
|
||||
VoxyCommon.createInstance();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package me.cortex.voxy.client.mixin.minecraft;
|
||||
|
||||
import me.cortex.voxy.client.config.VoxyConfig;
|
||||
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.render.fog.FogData;
|
||||
import net.minecraft.client.render.fog.FogRenderer;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(FogRenderer.class)
|
||||
public class MixinFogRenderer {
|
||||
@Redirect(method = "applyFog(Lnet/minecraft/client/render/Camera;IZLnet/minecraft/client/render/RenderTickCounter;FLnet/minecraft/client/world/ClientWorld;)Lorg/joml/Vector4f;", at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/fog/FogData;renderDistanceEnd:F", opcode = Opcodes.PUTFIELD), require = 0)
|
||||
private void voxy$modifyFog(FogData instance, float distance) {
|
||||
var vrs = (IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer;
|
||||
|
||||
if (VoxyConfig.CONFIG.renderVanillaFog || vrs == null || vrs.getVoxyRenderSystem() == null) {
|
||||
instance.renderDistanceEnd = distance;
|
||||
} else {
|
||||
instance.renderDistanceStart = 999999999;
|
||||
instance.renderDistanceEnd = 999999999;
|
||||
if (!VoxyConfig.CONFIG.useEnvironmentalFog) {
|
||||
instance.environmentalStart = 99999999;
|
||||
instance.environmentalEnd = 99999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package me.cortex.voxy.client.mixin.nvidium;
|
||||
|
||||
import me.cortex.nvidium.RenderPipeline;
|
||||
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
||||
import net.caffeinemc.mods.sodium.client.render.chunk.ChunkRenderMatrices;
|
||||
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.TerrainRenderPass;
|
||||
import net.caffeinemc.mods.sodium.client.render.viewport.Viewport;
|
||||
import net.caffeinemc.mods.sodium.client.util.FogParameters;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(value = RenderPipeline.class, remap = false)
|
||||
public class MixinRenderPipeline {
|
||||
@Inject(method = "renderFrame", at = @At("RETURN"))
|
||||
private void voxy$injectRender(TerrainRenderPass pass, Viewport frustum, FogParameters fogParameters, ChunkRenderMatrices crm, double px, double py, double pz, CallbackInfo ci) {
|
||||
var renderer = ((IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer).getVoxyRenderSystem();
|
||||
if (renderer != null) {
|
||||
renderer.renderOpaque(crm, fogParameters, px, py, pz);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import net.caffeinemc.mods.sodium.client.render.chunk.lists.ChunkRenderListItera
|
||||
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.DefaultTerrainRenderPasses;
|
||||
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.TerrainRenderPass;
|
||||
import net.caffeinemc.mods.sodium.client.render.viewport.CameraTransform;
|
||||
import net.caffeinemc.mods.sodium.client.util.FogParameters;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import org.joml.Matrix4f;
|
||||
@@ -20,11 +21,11 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
public class MixinDefaultChunkRenderer {
|
||||
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/caffeinemc/mods/sodium/client/render/chunk/ShaderChunkRenderer;end(Lnet/caffeinemc/mods/sodium/client/render/chunk/terrain/TerrainRenderPass;)V", shift = At.Shift.BEFORE))
|
||||
private void injectRender(ChunkRenderMatrices matrices, CommandList commandList, ChunkRenderListIterable renderLists, TerrainRenderPass renderPass, CameraTransform camera, CallbackInfo ci) {
|
||||
private void injectRender(ChunkRenderMatrices matrices, CommandList commandList, ChunkRenderListIterable renderLists, TerrainRenderPass renderPass, CameraTransform camera, FogParameters fogParameters, CallbackInfo ci) {
|
||||
if (renderPass == DefaultTerrainRenderPasses.CUTOUT) {
|
||||
var renderer = ((IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer).getVoxyRenderSystem();
|
||||
if (renderer != null) {
|
||||
renderer.renderOpaque(matrices, camera.x, camera.y, camera.z);
|
||||
renderer.renderOpaque(matrices, fogParameters, camera.x, camera.y, camera.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
package me.cortex.voxy.client.mixin.sodium;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||
import me.cortex.voxy.client.VoxyClientInstance;
|
||||
import me.cortex.voxy.client.ICheekyClientChunkManager;
|
||||
import me.cortex.voxy.client.config.VoxyConfig;
|
||||
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
||||
import me.cortex.voxy.client.core.VoxyRenderSystem;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
import me.cortex.voxy.common.world.service.VoxelIngestService;
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
import me.cortex.voxy.commonImpl.WorldIdentifier;
|
||||
import net.caffeinemc.mods.sodium.client.gl.device.CommandList;
|
||||
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection;
|
||||
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSectionFlags;
|
||||
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSectionManager;
|
||||
import net.caffeinemc.mods.sodium.client.render.chunk.data.BuiltSectionInfo;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.client.world.ClientWorld;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.util.math.ChunkSectionPos;
|
||||
import net.minecraft.world.chunk.WorldChunk;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
@@ -27,6 +25,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(value = RenderSectionManager.class, remap = false)
|
||||
public class MixinRenderSectionManager {
|
||||
@Unique
|
||||
private static final boolean BOBBY_INSTALLED = FabricLoader.getInstance().isModLoaded("bobby");
|
||||
|
||||
@Shadow @Final private ClientWorld level;
|
||||
|
||||
@Inject(method = "<init>", at = @At("TAIL"))
|
||||
@@ -39,6 +40,20 @@ public class MixinRenderSectionManager {
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "onChunkRemoved", at = @At("HEAD"))
|
||||
private void injectIngest(int x, int z, CallbackInfo ci) {
|
||||
//TODO: Am not quite sure if this is right
|
||||
if (VoxyConfig.CONFIG.ingestEnabled && !BOBBY_INSTALLED) {
|
||||
var cccm = (ICheekyClientChunkManager)this.level.getChunkManager();
|
||||
if (cccm != null) {
|
||||
var chunk = cccm.voxy$cheekyGetChunk(x, z);
|
||||
if (chunk != null) {
|
||||
VoxelIngestService.tryAutoIngestChunk(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@Inject(method = "onChunkAdded", at = @At("HEAD"))
|
||||
private void voxy$trackChunkAdd(int x, int z, CallbackInfo ci) {
|
||||
@@ -61,14 +76,6 @@ public class MixinRenderSectionManager {
|
||||
}
|
||||
}*/
|
||||
|
||||
@Inject(method = "onChunkRemoved", at = @At("HEAD"))
|
||||
private void injectIngest(int x, int z, CallbackInfo ci) {
|
||||
//TODO: Am not quite sure if this is right
|
||||
if (VoxyConfig.CONFIG.ingestEnabled) {
|
||||
VoxelIngestService.tryAutoIngestChunk(this.level.getChunk(x, z));
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "updateSectionInfo", at = @At(value = "INVOKE", target = "Lnet/caffeinemc/mods/sodium/client/render/chunk/RenderSection;setInfo(Lnet/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionInfo;)Z"))
|
||||
private boolean voxy$updateOnUpload(RenderSection instance, BuiltSectionInfo info) {
|
||||
boolean wasBuilt = instance.isBuilt();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -7,10 +7,10 @@ import me.cortex.voxy.common.world.SaveLoadSystem;
|
||||
|
||||
import java.lang.ref.Cleaner;
|
||||
|
||||
import static me.cortex.voxy.common.util.GlobalCleaner.CLEANER;
|
||||
import static org.lwjgl.util.zstd.Zstd.*;
|
||||
|
||||
public class ZSTDCompressor implements StorageCompressor {
|
||||
private static final Cleaner CLEANER = Cleaner.create();
|
||||
private record Ref(long ptr) {}
|
||||
|
||||
private static Ref createCleanableCompressionContext() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ public class ServiceSlice extends TrackedObject {
|
||||
//Check that we are still alive
|
||||
if (!this.alive) {
|
||||
if (this.activeCount.decrementAndGet() < 0) {
|
||||
throw new IllegalStateException("Alive count negative!");
|
||||
throw new IllegalStateException("Alive count negative!:" + this.name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -77,14 +77,14 @@ public class ServiceSlice extends TrackedObject {
|
||||
try {
|
||||
ctx.run();
|
||||
} catch (Exception e) {
|
||||
Logger.error("Unexpected error occurred while executing a service job, expect things to break badly", e);
|
||||
Logger.error("Unexpected error occurred while executing a service job, expect things to break badly: " + this.name, e);
|
||||
MinecraftClient.getInstance().execute(()->MinecraftClient.getInstance().player.sendMessage(Text.literal("A voxy service had an exception while executing please check logs and report error"), true));
|
||||
} finally {
|
||||
if (this.activeCount.decrementAndGet() < 0) {
|
||||
throw new IllegalStateException("Alive count negative!");
|
||||
throw new IllegalStateException("Alive count negative!: " + this.name);
|
||||
}
|
||||
if (this.jobCount2.decrementAndGet() < 0) {
|
||||
throw new IllegalStateException("Job count negative!");
|
||||
throw new IllegalStateException("Job count negative!" + this.name);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -96,9 +96,10 @@ public class ServiceSlice extends TrackedObject {
|
||||
Logger.error("Tried to do work on a dead service: " + this.name, new Throwable());
|
||||
return;
|
||||
}
|
||||
this.jobCount.release();
|
||||
this.threadPool.addWeight(this);
|
||||
this.jobCount2.incrementAndGet();
|
||||
this.threadPool.execute(this);
|
||||
this.jobCount.release();
|
||||
this.threadPool.execute();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
@@ -145,7 +146,7 @@ public class ServiceSlice extends TrackedObject {
|
||||
|
||||
public void blockTillEmpty() {
|
||||
while (this.activeCount.get() != 0 && this.alive) {
|
||||
while (this.jobCount2.get() != 0 && this.alive) {
|
||||
while ((this.jobCount2.get() != 0 || this.jobCount.availablePermits()!=0) && this.alive) {
|
||||
Thread.onSpinWait();
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
@@ -163,7 +164,7 @@ public class ServiceSlice extends TrackedObject {
|
||||
return false;
|
||||
}
|
||||
if (this.jobCount2.decrementAndGet() < 0) {
|
||||
throw new IllegalStateException("Job count negative!!!");
|
||||
throw new IllegalStateException("Job count negative!!!:" + this.name);
|
||||
}
|
||||
this.threadPool.steal(this, 1);
|
||||
return true;
|
||||
@@ -176,7 +177,7 @@ public class ServiceSlice extends TrackedObject {
|
||||
}
|
||||
|
||||
if (this.jobCount2.addAndGet(-count) < 0) {
|
||||
throw new IllegalStateException("Job count negative!!!");
|
||||
throw new IllegalStateException("Job count negative!!!:" + this.name);
|
||||
}
|
||||
this.threadPool.steal(this, count);
|
||||
return count;
|
||||
|
||||
@@ -29,7 +29,7 @@ public class ServiceThreadPool {
|
||||
private final ThreadGroup threadGroup;
|
||||
|
||||
public ServiceThreadPool(int threadCount) {
|
||||
this(threadCount, 1);//Maybe change to 3
|
||||
this(threadCount, 3);//Maybe change to 3
|
||||
}
|
||||
|
||||
public ServiceThreadPool(int threadCount, int priority) {
|
||||
@@ -46,15 +46,19 @@ public class ServiceThreadPool {
|
||||
//Set worker affinity if possible
|
||||
CpuLayout.setThreadAffinity(CpuLayout.CORES[2 + (threadId % (CpuLayout.CORES.length - 2))]);
|
||||
}
|
||||
|
||||
ThreadUtils.SetSelfThreadPriorityWin32(ThreadUtils.WIN32_THREAD_PRIORITY_LOWEST);
|
||||
//ThreadUtils.SetSelfThreadPriorityWin32(ThreadUtils.WIN32_THREAD_MODE_BACKGROUND_BEGIN);
|
||||
|
||||
if (threadId != 0) {
|
||||
ThreadUtils.SetSelfThreadPriorityWin32(-1);
|
||||
//ThreadUtils.SetSelfThreadPriorityWin32(ThreadUtils.WIN32_THREAD_MODE_BACKGROUND_BEGIN);
|
||||
}
|
||||
this.worker(threadId);
|
||||
});
|
||||
worker.setDaemon(false);
|
||||
worker.setName("Service worker #" + i);
|
||||
worker.setPriority(priority);
|
||||
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;
|
||||
@@ -133,8 +137,11 @@ public class ServiceThreadPool {
|
||||
this.serviceSlices = newArr;
|
||||
}
|
||||
|
||||
void execute(ServiceSlice service) {
|
||||
this.totalJobWeight.addAndGet(service.weightPerJob);
|
||||
long addWeight(ServiceSlice service) {
|
||||
return this.totalJobWeight.addAndGet(service.weightPerJob);
|
||||
}
|
||||
|
||||
void execute() {
|
||||
this.jobCounter.release(1);
|
||||
}
|
||||
|
||||
@@ -264,18 +271,25 @@ public class ServiceThreadPool {
|
||||
|
||||
//Consumed a job from the service, decrease weight by the amount
|
||||
if (this.totalJobWeight.addAndGet(-service.weightPerJob)<0) {
|
||||
throw new IllegalStateException("Total job weight is negative");
|
||||
Logger.error("Total job weight is negative");
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (this.totalJobWeight.get()<0) {
|
||||
throw new IllegalStateException("Total job weight still negative");
|
||||
}
|
||||
}
|
||||
|
||||
//Sleep for a bit after running a job, yeild the thread
|
||||
Thread.yield();
|
||||
//Thread.yield();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package me.cortex.voxy.common.util;
|
||||
|
||||
import java.lang.ref.Cleaner;
|
||||
|
||||
public class GlobalCleaner {
|
||||
public static final Cleaner CLEANER = Cleaner.create();
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package me.cortex.voxy.common.util;
|
||||
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -7,6 +8,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class MemoryBuffer extends TrackedObject {
|
||||
private static final boolean TRACK_MEMORY_BUFFERS = VoxyCommon.isVerificationFlagOn("trackBuffers");
|
||||
|
||||
public final long address;
|
||||
public final long size;
|
||||
private final boolean freeable;
|
||||
@@ -21,7 +24,7 @@ public class MemoryBuffer extends TrackedObject {
|
||||
}
|
||||
|
||||
private MemoryBuffer(boolean track, long address, long size, boolean freeable) {
|
||||
super(track);
|
||||
super(track && TRACK_MEMORY_BUFFERS);
|
||||
this.tracked = track;
|
||||
this.size = size;
|
||||
this.address = address;
|
||||
|
||||
@@ -7,13 +7,9 @@ import java.util.*;
|
||||
|
||||
public class MultiGson {
|
||||
private final List<Class<?>> classes;
|
||||
private final Gson GSON = new GsonBuilder()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||
.setPrettyPrinting()
|
||||
.excludeFieldsWithModifiers(Modifier.PRIVATE)
|
||||
.create();
|
||||
|
||||
private MultiGson(List<Class<?>> classes) {
|
||||
private final Gson gson;
|
||||
private MultiGson(Gson gson, List<Class<?>> classes) {
|
||||
this.gson = gson;
|
||||
this.classes = classes;
|
||||
}
|
||||
|
||||
@@ -38,27 +34,38 @@ public class MultiGson {
|
||||
|
||||
var json = new JsonObject();
|
||||
for (Object entry : map) {
|
||||
GSON.toJsonTree(entry).getAsJsonObject().asMap().forEach((i,j) -> {
|
||||
this.gson.toJsonTree(entry).getAsJsonObject().asMap().forEach((i,j) -> {
|
||||
if (json.has(i)) {
|
||||
throw new IllegalArgumentException("Duplicate name inside unified json: " + i);
|
||||
}
|
||||
json.add(i, j);
|
||||
});
|
||||
}
|
||||
return GSON.toJson(json);
|
||||
return this.gson.toJson(json);
|
||||
}
|
||||
|
||||
public Map<Class<?>, Object> fromJson(String json) {
|
||||
var obj = GSON.fromJson(json, JsonObject.class);
|
||||
var obj = this.gson.fromJson(json, JsonObject.class);
|
||||
LinkedHashMap<Class<?>, Object> objects = new LinkedHashMap<>();
|
||||
for (var cls : this.classes) {
|
||||
objects.put(cls, GSON.fromJson(obj, cls));
|
||||
objects.put(cls, this.gson.fromJson(obj, cls));
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final LinkedHashSet<Class<?>> classes = new LinkedHashSet<>();
|
||||
private final GsonBuilder gsonBuilder;
|
||||
public Builder(GsonBuilder gsonBuilder) {
|
||||
this.gsonBuilder = gsonBuilder;
|
||||
}
|
||||
public Builder() {
|
||||
this(new GsonBuilder()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||
.setPrettyPrinting()
|
||||
.excludeFieldsWithModifiers(Modifier.PRIVATE));
|
||||
}
|
||||
|
||||
public Builder add(Class<?> clz) {
|
||||
if (!this.classes.add(clz)) {
|
||||
throw new IllegalArgumentException("Class has already been added");
|
||||
@@ -67,7 +74,7 @@ public class MultiGson {
|
||||
}
|
||||
|
||||
public MultiGson build() {
|
||||
return new MultiGson(new ArrayList<>(this.classes));
|
||||
return new MultiGson(this.gsonBuilder.create(), new ArrayList<>(this.classes));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@ package me.cortex.voxy.common.util;
|
||||
|
||||
import java.lang.ref.Cleaner;
|
||||
|
||||
import static me.cortex.voxy.common.util.GlobalCleaner.CLEANER;
|
||||
|
||||
public class ThreadLocalMemoryBuffer {
|
||||
private static final Cleaner CLEANER = Cleaner.create();
|
||||
private static MemoryBuffer createMemoryBuffer(long size) {
|
||||
var buffer = new MemoryBuffer(size);
|
||||
var ref = MemoryBuffer.createUntrackedUnfreeableRawFrom(buffer.address, buffer.size);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class ThreadUtils {
|
||||
public static final int WIN32_THREAD_PRIORITY_LOWEST = -2;
|
||||
public static final int WIN32_THREAD_MODE_BACKGROUND_BEGIN = 0x00010000;
|
||||
public static final int WIN32_THREAD_MODE_BACKGROUND_END = 0x00020000;
|
||||
private static final boolean isWindows = Platform.get() == Platform.WINDOWS;
|
||||
public static final boolean isWindows = Platform.get() == Platform.WINDOWS;
|
||||
private static final long SetThreadPriority;
|
||||
private static final long SetThreadSelectedCpuSetMasks;
|
||||
private static final long schedSetaffinity;
|
||||
|
||||
@@ -5,9 +5,11 @@ import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
|
||||
import java.lang.ref.Cleaner;
|
||||
|
||||
import static me.cortex.voxy.common.util.GlobalCleaner.CLEANER;
|
||||
|
||||
public abstract class TrackedObject {
|
||||
//TODO: maybe make this false? for performance overhead?
|
||||
public static final boolean TRACK_OBJECT_ALLOCATIONS = VoxyCommon.isVerificationFlagOn("ensureTrackedObjectsAreFreed");
|
||||
public static final boolean TRACK_OBJECT_ALLOCATIONS = VoxyCommon.isVerificationFlagOn("ensureTrackedObjectsAreFreed", true);
|
||||
public static final boolean TRACK_OBJECT_ALLOCATION_STACKS = VoxyCommon.isVerificationFlagOn("trackObjectAllocationStacks");
|
||||
|
||||
private final Ref ref;
|
||||
@@ -49,14 +51,6 @@ public abstract class TrackedObject {
|
||||
|
||||
public record Ref(Cleaner.Cleanable cleanable, boolean[] freedRef) {}
|
||||
|
||||
private static final Cleaner cleaner;
|
||||
static {
|
||||
if (TRACK_OBJECT_ALLOCATIONS) {
|
||||
cleaner = Cleaner.create();
|
||||
} else {
|
||||
cleaner = null;
|
||||
}
|
||||
}
|
||||
public static Ref register(boolean track, Object obj) {
|
||||
boolean[] freed = new boolean[1];
|
||||
Cleaner.Cleanable cleanable = null;
|
||||
@@ -69,7 +63,7 @@ public abstract class TrackedObject {
|
||||
} else {
|
||||
trace = null;
|
||||
}
|
||||
cleanable = cleaner.register(obj, () -> {
|
||||
cleanable = CLEANER.register(obj, () -> {
|
||||
if (!freed[0]) {
|
||||
Logger.error("Object named: " + clazz + " was not freed, location at:\n", trace==null?"Enable allocation stack tracing":trace);
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package me.cortex.voxy.common.util;
|
||||
|
||||
public class VolatileHolder <T> {
|
||||
public volatile T obj;
|
||||
}
|
||||
@@ -10,6 +10,7 @@ public class VoxelizedSection {
|
||||
public int x;
|
||||
public int y;
|
||||
public int z;
|
||||
public int lvl0NonAirCount;
|
||||
public final long[] section;
|
||||
public VoxelizedSection(long[] section) {
|
||||
this.section = section;
|
||||
@@ -51,6 +52,7 @@ public class VoxelizedSection {
|
||||
}
|
||||
|
||||
public VoxelizedSection zero() {
|
||||
this.lvl0NonAirCount = 0;
|
||||
Arrays.fill(this.section, 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ public class WorldConversionFactory {
|
||||
}
|
||||
|
||||
|
||||
|
||||
int nonZeroCnt = 0;
|
||||
if (blockContainer.data.storage instanceof PackedIntegerArray bStor) {
|
||||
var bDat = bStor.getData();
|
||||
int iterPerLong = (64 / bStor.getElementBits()) - 1;
|
||||
@@ -168,7 +168,7 @@ public class WorldConversionFactory {
|
||||
sample >>>= eBits;
|
||||
|
||||
byte light = lightSupplier.supply(i&0xF, (i>>8)&0xF, (i>>4)&0xF);
|
||||
|
||||
nonZeroCnt += (bId != 0)?1:0;
|
||||
data[i] = Mapper.composeMappingId(light, bId, biomes[Integer.compress(i,0b1100_1100_1100)]);
|
||||
}
|
||||
} else {
|
||||
@@ -181,12 +181,14 @@ public class WorldConversionFactory {
|
||||
data[i] = Mapper.airWithLight(lightSupplier.supply(i&0xF, (i>>8)&0xF, (i>>4)&0xF));
|
||||
}
|
||||
} else {
|
||||
nonZeroCnt = 4096;
|
||||
for (int i = 0; i <= 0xFFF; i++) {
|
||||
byte light = lightSupplier.supply(i&0xF, (i>>8)&0xF, (i>>4)&0xF);
|
||||
data[i] = Mapper.composeMappingId(light, bId, biomes[Integer.compress(i,0b1100_1100_1100)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
section.lvl0NonAirCount = nonZeroCnt;
|
||||
return section;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@ 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.util.VolatileHolder;
|
||||
import me.cortex.voxy.common.world.other.Mapper;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.concurrent.locks.StampedLock;
|
||||
|
||||
public class ActiveSectionTracker {
|
||||
@@ -18,6 +18,21 @@ public class ActiveSectionTracker {
|
||||
public interface SectionLoader {int load(WorldSection section);}
|
||||
|
||||
//Loaded section world cache, TODO: get rid of VolatileHolder and use something more sane
|
||||
private static final class VolatileHolder <T> {
|
||||
private static final VarHandle PRE_ACQUIRE_COUNT;
|
||||
private static final VarHandle POST_ACQUIRE_COUNT;
|
||||
static {
|
||||
try {
|
||||
PRE_ACQUIRE_COUNT = MethodHandles.lookup().findVarHandle(VolatileHolder.class, "preAcquireCount", int.class);
|
||||
POST_ACQUIRE_COUNT = MethodHandles.lookup().findVarHandle(VolatileHolder.class, "postAcquireCount", int.class);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
public volatile int preAcquireCount;
|
||||
public volatile int postAcquireCount;
|
||||
public volatile T obj;
|
||||
}
|
||||
|
||||
private final AtomicInteger loadedSections = new AtomicInteger();
|
||||
private final Long2ObjectOpenHashMap<VolatileHolder<WorldSection>>[] loadedSectionCache;
|
||||
@@ -95,9 +110,13 @@ public class ActiveSectionTracker {
|
||||
|
||||
if (isLoader) {
|
||||
this.loadedSections.incrementAndGet();
|
||||
long stamp2 = lock.readLock();
|
||||
long stamp = this.lruLock.writeLock();
|
||||
section = this.lruSecondaryCache.remove(key);
|
||||
this.lruLock.unlockWrite(stamp);
|
||||
lock.unlockRead(stamp2);
|
||||
} else {
|
||||
VolatileHolder.PRE_ACQUIRE_COUNT.getAndAdd(holder, 1);
|
||||
}
|
||||
|
||||
//If this thread was the one to create the reference then its the thread to load the section
|
||||
@@ -115,7 +134,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;
|
||||
}
|
||||
|
||||
@@ -127,17 +146,22 @@ public class ActiveSectionTracker {
|
||||
} else {
|
||||
section.primeForReuse();
|
||||
}
|
||||
int preAcquireCount = (int) VolatileHolder.PRE_ACQUIRE_COUNT.getAndSet(holder, 0);
|
||||
section.acquire(preAcquireCount+1);//pre acquire amount
|
||||
VolatileHolder.POST_ACQUIRE_COUNT.set(holder, preAcquireCount);
|
||||
|
||||
section.acquire();
|
||||
VarHandle.fullFence();//Do not reorder setting this object
|
||||
//TODO: mark if the section was loaded null
|
||||
|
||||
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;
|
||||
}
|
||||
return section;
|
||||
} else {
|
||||
//TODO: mark the time the loading started in nanos, then here if it has been a while, spin lock, else jump back to the executing service and do work
|
||||
VarHandle.fullFence();
|
||||
while ((section = holder.obj) == null) {
|
||||
VarHandle.fullFence();
|
||||
@@ -145,32 +169,62 @@ public class ActiveSectionTracker {
|
||||
Thread.yield();
|
||||
}
|
||||
|
||||
//lock.lock();
|
||||
{//Dont think need to lock here
|
||||
if (section.tryAcquire()) {
|
||||
return section;
|
||||
//Try to acquire a pre lock
|
||||
if (0<((int)VolatileHolder.POST_ACQUIRE_COUNT.getAndAdd(holder, -1))) {
|
||||
//We managed to acquire one of the pre locks, so just return the section
|
||||
return section;
|
||||
} else {
|
||||
//lock.lock();
|
||||
{//Dont think need to lock here
|
||||
if (section.tryAcquire()) {
|
||||
return section;
|
||||
}
|
||||
}
|
||||
}
|
||||
//lock.unlock();
|
||||
//lock.unlock();
|
||||
|
||||
//We failed everything, try get it again
|
||||
return this.acquire(key, nullOnEmpty);
|
||||
//We failed everything, try get it again
|
||||
return this.acquire(key, nullOnEmpty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tryUnload(WorldSection section) {
|
||||
if (this.engine != null) this.engine.lastActiveTime = System.currentTimeMillis();
|
||||
if (section.isDirty&&this.engine!=null) {
|
||||
if (section.tryAcquire()) {
|
||||
if (section.setNotDirty()) {//If the section is dirty we must enqueue for saving
|
||||
this.engine.saveSection(section);
|
||||
}
|
||||
section.release(false);//Special
|
||||
}
|
||||
}
|
||||
|
||||
if (section.getRefCount() != 0) {
|
||||
return;
|
||||
}
|
||||
int index = this.getCacheArrayIndex(section.key);
|
||||
final var cache = this.loadedSectionCache[index];
|
||||
WorldSection sec = null;
|
||||
final var lock = this.locks[index];
|
||||
long stamp = lock.writeLock();
|
||||
{
|
||||
if (section.trySetFreed()) {
|
||||
VarHandle.loadLoadFence();
|
||||
if (section.isDirty) {
|
||||
if (section.tryAcquire()) {
|
||||
if (section.setNotDirty()) {//If the section is dirty we must enqueue for saving
|
||||
if (this.engine != null)
|
||||
this.engine.saveSection(section);
|
||||
}
|
||||
section.release(false);//Special
|
||||
} else {
|
||||
throw new IllegalStateException("Section was dirty but is also unloaded, this is very bad");
|
||||
}
|
||||
}
|
||||
if (section.getRefCount() == 0 && section.trySetFreed()) {
|
||||
var cached = cache.remove(section.key);
|
||||
var obj = cached.obj;
|
||||
if (obj == null) {
|
||||
throw new IllegalStateException("This should be impossible");
|
||||
throw new IllegalStateException("This should be impossible: " + WorldEngine.pprintPos(section.key));
|
||||
}
|
||||
if (obj != section) {
|
||||
throw new IllegalStateException("Removed section not the same as the referenced section in the cache: cached: " + obj + " got: " + section + " A: " + WorldSection.ATOMIC_STATE_HANDLE.get(obj) + " B: " +WorldSection.ATOMIC_STATE_HANDLE.get(section));
|
||||
|
||||
@@ -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,11 @@ import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
public class SaveLoadSystem3 {
|
||||
private record SerializationCache(long[] blockStateCache, Long2ShortOpenHashMap lutMapCache, MemoryBuffer memoryBuffer) {
|
||||
public static final int STORAGE_VERSION = 0;
|
||||
|
||||
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 +39,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();
|
||||
|
||||
@@ -63,6 +62,7 @@ public class SaveLoadSystem3 {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
//TODO: note! can actually have the first (last?) byte of metadata be the storage version!
|
||||
long metadata = 0;
|
||||
metadata |= Integer.toUnsignedLong(LUT.size());//Bottom 2 bytes
|
||||
metadata |= Byte.toUnsignedLong(section.getNonEmptyChildren())<<16;//Next byte
|
||||
@@ -85,21 +85,26 @@ public class SaveLoadSystem3 {
|
||||
return false;
|
||||
}
|
||||
|
||||
long metadata = MemoryUtil.memGetLong(ptr); ptr += 8;
|
||||
final long metadata = MemoryUtil.memGetLong(ptr); ptr += 8;
|
||||
section.nonEmptyChildren = (byte) ((metadata>>>16)&0xFF);
|
||||
|
||||
int nonEmptyBlockCount = 0;
|
||||
long lutBasePtr = ptr + WorldSection.SECTION_VOLUME*2;
|
||||
var blockData = section.data;
|
||||
for (int i = 0; i < WorldSection.SECTION_VOLUME; i++) {
|
||||
short lutId = MemoryUtil.memGetShort(ptr); ptr+=2;
|
||||
long blockId = MemoryUtil.memGetLong(lutBasePtr+Short.toUnsignedLong(lutId)*8L);
|
||||
nonEmptyBlockCount += Mapper.isAir(blockId)?0:1;
|
||||
blockData[i] = blockId;
|
||||
final long lutBasePtr = ptr + WorldSection.SECTION_VOLUME * 2;
|
||||
if (section.lvl == 0) {
|
||||
int nonEmptyBlockCount = 0;
|
||||
final var blockData = section.data;
|
||||
for (int i = 0; i < WorldSection.SECTION_VOLUME; i++) {
|
||||
final short lutId = MemoryUtil.memGetShort(ptr); ptr += 2;
|
||||
final long blockId = MemoryUtil.memGetLong(lutBasePtr + Short.toUnsignedLong(lutId) * 8L);
|
||||
nonEmptyBlockCount += Mapper.isAir(blockId) ? 0 : 1;
|
||||
blockData[i] = blockId;
|
||||
}
|
||||
section.nonEmptyBlockCount = nonEmptyBlockCount;
|
||||
} else {
|
||||
final var blockData = section.data;
|
||||
for (int i = 0; i < WorldSection.SECTION_VOLUME; i++) {
|
||||
blockData[i] = MemoryUtil.memGetLong(lutBasePtr + Short.toUnsignedLong(MemoryUtil.memGetShort(ptr)) * 8L);ptr += 2;
|
||||
}
|
||||
}
|
||||
section.nonEmptyBlockCount = nonEmptyBlockCount;
|
||||
ptr = lutBasePtr + (metadata&0xFFFF)*8L;
|
||||
|
||||
ptr = lutBasePtr + (metadata & 0xFFFF) * 8L;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,10 +52,15 @@ public class WorldEngine {
|
||||
public WorldEngine(SectionStorage storage, @Nullable VoxyInstance instance) {
|
||||
this.instanceIn = instance;
|
||||
|
||||
int cacheSize = 1024;
|
||||
if (Runtime.getRuntime().maxMemory()>=(1L<<32)-200<<20) {
|
||||
cacheSize = 2048;
|
||||
}
|
||||
|
||||
this.storage = storage;
|
||||
this.mapper = new Mapper(this.storage);
|
||||
//5 cache size bits means that the section tracker has 32 separate maps that it uses
|
||||
this.sectionTracker = new ActiveSectionTracker(6, storage::loadSection, 2048, this);
|
||||
this.sectionTracker = new ActiveSectionTracker(6, storage::loadSection, cacheSize, this);
|
||||
}
|
||||
|
||||
public WorldSection acquireIfExists(int lvl, int x, int y, int z) {
|
||||
@@ -118,8 +123,8 @@ public class WorldEngine {
|
||||
if (this.dirtyCallback != null) {
|
||||
this.dirtyCallback.accept(section, changeState);
|
||||
}
|
||||
if (this.saveCallback != null) {
|
||||
this.saveCallback.save(this, section);
|
||||
if (!section.inSaveQueue) {
|
||||
section.markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,19 +168,29 @@ 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");
|
||||
}
|
||||
//TODO: maybe dont need to tick the last active time?
|
||||
this.lastActiveTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void saveSection(WorldSection section) {
|
||||
section.setNotDirty();
|
||||
if (this.saveCallback != null) {
|
||||
this.saveCallback.save(this, section);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,16 @@ public final class WorldSection {
|
||||
static final VarHandle ATOMIC_STATE_HANDLE;
|
||||
private static final VarHandle NON_EMPTY_CHILD_HANDLE;
|
||||
private static final VarHandle NON_EMPTY_BLOCK_HANDLE;
|
||||
private static final VarHandle IN_SAVE_QUEUE_HANDLE;
|
||||
private static final VarHandle IS_DIRTY_HANDLE;
|
||||
|
||||
static {
|
||||
try {
|
||||
ATOMIC_STATE_HANDLE = MethodHandles.lookup().findVarHandle(WorldSection.class, "atomicState", int.class);
|
||||
NON_EMPTY_CHILD_HANDLE = MethodHandles.lookup().findVarHandle(WorldSection.class, "nonEmptyChildren", byte.class);
|
||||
NON_EMPTY_BLOCK_HANDLE = MethodHandles.lookup().findVarHandle(WorldSection.class, "nonEmptyBlockCount", int.class);
|
||||
IN_SAVE_QUEUE_HANDLE = MethodHandles.lookup().findVarHandle(WorldSection.class, "inSaveQueue", boolean.class);
|
||||
IS_DIRTY_HANDLE = MethodHandles.lookup().findVarHandle(WorldSection.class, "isDirty", boolean.class);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -51,11 +55,12 @@ public final class WorldSection {
|
||||
//Serialized states
|
||||
long metadata;
|
||||
long[] data = null;
|
||||
volatile int nonEmptyBlockCount = 0;
|
||||
volatile int nonEmptyBlockCount = 0;//Note: only needed for level 0 sections
|
||||
volatile byte nonEmptyChildren;
|
||||
|
||||
final ActiveSectionTracker tracker;
|
||||
public final AtomicBoolean inSaveQueue = new AtomicBoolean();
|
||||
volatile boolean inSaveQueue;
|
||||
volatile boolean isDirty;
|
||||
|
||||
//When the first bit is set it means its loaded
|
||||
@SuppressWarnings("all")
|
||||
@@ -120,7 +125,7 @@ public final class WorldSection {
|
||||
public int acquire(int count) {
|
||||
int state = ((int) ATOMIC_STATE_HANDLE.getAndAdd(this, count<<1)) + (count<<1);
|
||||
if ((state & 1) == 0) {
|
||||
throw new IllegalStateException("Tried to acquire unloaded section");
|
||||
throw new IllegalStateException("Tried to acquire unloaded section: " + WorldEngine.pprintPos(this.key));
|
||||
}
|
||||
return state>>1;
|
||||
}
|
||||
@@ -131,6 +136,10 @@ public final class WorldSection {
|
||||
|
||||
//TODO: add the ability to hint to the tracker that yes the section is unloaded, try to cache it in a secondary cache since it will be reused/needed later
|
||||
public int release() {
|
||||
return release(true);
|
||||
}
|
||||
|
||||
int release(boolean unload) {
|
||||
int state = ((int) ATOMIC_STATE_HANDLE.getAndAdd(this, -2)) - 2;
|
||||
if (state < 1) {
|
||||
throw new IllegalStateException("Section got into an invalid state");
|
||||
@@ -138,7 +147,7 @@ public final class WorldSection {
|
||||
if ((state & 1) == 0) {
|
||||
throw new IllegalStateException("Tried releasing a freed section");
|
||||
}
|
||||
if ((state>>1)==0) {
|
||||
if ((state>>1)==0 && unload) {
|
||||
if (this.tracker != null) {
|
||||
this.tracker.tryUnload(this);
|
||||
} else {
|
||||
@@ -271,4 +280,20 @@ public final class WorldSection {
|
||||
public static WorldSection _createRawUntrackedUnsafeSection(int lvl, int x, int y, int z) {
|
||||
return new WorldSection(lvl, x, y, z, null);
|
||||
}
|
||||
|
||||
public boolean exchangeIsInSaveQueue(boolean state) {
|
||||
return ((boolean) IN_SAVE_QUEUE_HANDLE.compareAndExchange(this, !state, state)) == !state;
|
||||
}
|
||||
|
||||
public void markDirty() {
|
||||
IS_DIRTY_HANDLE.getAndSet(this, true);
|
||||
}
|
||||
|
||||
public boolean setNotDirty() {
|
||||
return (boolean) IS_DIRTY_HANDLE.getAndSet(this, false);
|
||||
}
|
||||
|
||||
public boolean isFreed() {
|
||||
return (((int)ATOMIC_STATE_HANDLE.get(this))&1)==0;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import me.cortex.voxy.common.world.other.Mapper;
|
||||
import static me.cortex.voxy.common.world.WorldEngine.*;
|
||||
|
||||
public class WorldUpdater {
|
||||
//TODO: move this to auxilery class so that it can take into account larger than 4 mip levels
|
||||
//Executes an update to the world and automatically updates all the parent mip layers up to level 4 (e.g. where 1 chunk section is 1 block big)
|
||||
|
||||
//NOTE: THIS RUNS ON THE THREAD IT WAS EXECUTED ON, when this method exits, the calling method may assume that VoxelizedSection is no longer needed
|
||||
@@ -23,7 +22,7 @@ public class WorldUpdater {
|
||||
WorldSection previousSection = null;
|
||||
final var vdat = section.section;
|
||||
|
||||
for (int lvl = 0; lvl < MAX_LOD_LAYER+1; lvl++) {
|
||||
for (int lvl = 0; lvl <= MAX_LOD_LAYER; lvl++) {
|
||||
var worldSection = into.acquire(lvl, section.x >> (lvl + 1), section.y >> (lvl + 1), section.z >> (lvl + 1));
|
||||
|
||||
int emptinessStateChange = 0;
|
||||
@@ -41,36 +40,59 @@ public class WorldUpdater {
|
||||
int by = (section.y&msk)<<(4-lvl);
|
||||
int bz = (section.z&msk)<<(4-lvl);
|
||||
|
||||
int nonAirCountDelta = 0;
|
||||
int airCount = 0;
|
||||
boolean didStateChange = false;
|
||||
|
||||
|
||||
//TODO: remove the nonAirCountDelta stuff if level != 0
|
||||
|
||||
{//Do a bunch of funny math
|
||||
var secD = worldSection.data;
|
||||
|
||||
int baseVIdx = VoxelizedSection.getBaseIndexForLevel(lvl);
|
||||
int baseSec = bx | (bz << 5) | (by << 10);
|
||||
if (lvl == 0) {
|
||||
final int secMsk = 0b1100|(0xf << 5) | (0xf << 10);
|
||||
final int iSecMsk1 = (~secMsk) + 1;
|
||||
|
||||
int secMsk = 0xF >> lvl;
|
||||
secMsk |= (secMsk << 5) | (secMsk << 10);
|
||||
int iSecMsk1 =(~secMsk)+1;
|
||||
int secIdx = 0;
|
||||
//TODO: manually unroll and do e.g. 4 iterations per loop
|
||||
for (int i = 0; i <= 0xFFF; i+=4) {
|
||||
int cSecIdx = secIdx + baseSec;
|
||||
secIdx = (secIdx + iSecMsk1) & secMsk;
|
||||
|
||||
int secIdx = 0;
|
||||
//TODO: manually unroll and do e.g. 4 iterations per loop
|
||||
for (int i = baseVIdx; i <= (0xFFF >> (lvl * 3)) + baseVIdx; i++) {
|
||||
int cSecIdx = secIdx+baseSec;
|
||||
secIdx = (secIdx + iSecMsk1)&secMsk;
|
||||
long oldId0 = secD[cSecIdx+0]; secD[cSecIdx+0] = vdat[i+0];
|
||||
long oldId1 = secD[cSecIdx+1]; secD[cSecIdx+1] = vdat[i+1];
|
||||
long oldId2 = secD[cSecIdx+2]; secD[cSecIdx+2] = vdat[i+2];
|
||||
long oldId3 = secD[cSecIdx+3]; secD[cSecIdx+3] = vdat[i+3];
|
||||
|
||||
long newId = vdat[i];
|
||||
long oldId = secD[cSecIdx]; secD[cSecIdx] = newId;
|
||||
nonAirCountDelta += (Mapper.isAir(newId)?0:1)-(Mapper.isAir(oldId)?0:1);//its 0:1 cause its nonAir
|
||||
didStateChange |= newId != oldId;
|
||||
airCount += Mapper.isAir(oldId0)?1:0; didStateChange |= vdat[i+0] != oldId0;
|
||||
airCount += Mapper.isAir(oldId1)?1:0; didStateChange |= vdat[i+1] != oldId1;
|
||||
airCount += Mapper.isAir(oldId2)?1:0; didStateChange |= vdat[i+2] != oldId2;
|
||||
airCount += Mapper.isAir(oldId3)?1:0; didStateChange |= vdat[i+3] != oldId3;
|
||||
}
|
||||
} else {
|
||||
int baseVIdx = VoxelizedSection.getBaseIndexForLevel(lvl);
|
||||
|
||||
int secMsk = 0xF >> lvl;
|
||||
secMsk |= (secMsk << 5) | (secMsk << 10);
|
||||
int iSecMsk1 = (~secMsk) + 1;
|
||||
|
||||
int secIdx = 0;
|
||||
//TODO: manually unroll and do e.g. 4 iterations per loop
|
||||
for (int i = baseVIdx; i <= (0xFFF >> (lvl * 3)) + baseVIdx; i++) {
|
||||
int cSecIdx = secIdx + baseSec;
|
||||
secIdx = (secIdx + iSecMsk1) & secMsk;
|
||||
long newId = vdat[i];
|
||||
long oldId = secD[cSecIdx];
|
||||
didStateChange |= newId != oldId;
|
||||
secD[cSecIdx] = newId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nonAirCountDelta != 0) {
|
||||
worldSection.addNonEmptyBlockCount(nonAirCountDelta);
|
||||
if (lvl == 0) {
|
||||
if (lvl == 0) {
|
||||
int nonAirCountDelta = section.lvl0NonAirCount-(4096-airCount);
|
||||
if (nonAirCountDelta != 0) {
|
||||
worldSection.addNonEmptyBlockCount(nonAirCountDelta);
|
||||
emptinessStateChange = worldSection.updateLvl0State() ? 2 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
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;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.block.LeavesBlock;
|
||||
import net.minecraft.nbt.NbtCompound;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.nbt.NbtOps;
|
||||
@@ -53,9 +55,8 @@ public class Mapper {
|
||||
|
||||
|
||||
public static boolean isAir(long id) {
|
||||
int bId = getBlockId(id);
|
||||
//Note: air can mean void, cave or normal air, as the block state is remapped during ingesting
|
||||
return bId == 0;
|
||||
return (id&(((1L<<20)-1)<<27)) == 0;
|
||||
}
|
||||
|
||||
public static int getBlockId(long id) {
|
||||
@@ -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;
|
||||
}
|
||||
@@ -298,7 +299,12 @@ public class Mapper {
|
||||
public StateEntry(int id, BlockState state) {
|
||||
this.id = id;
|
||||
this.state = state;
|
||||
this.opacity = state.getOpacity();
|
||||
//Override opacity of leaves to be solid
|
||||
if (state.getBlock() instanceof LeavesBlock) {
|
||||
this.opacity = 15;
|
||||
} else {
|
||||
this.opacity = state.getOpacity();
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
|
||||
@@ -28,8 +28,9 @@ public class SectionSavingService {
|
||||
var section = task.section;
|
||||
section.assertNotFree();
|
||||
try {
|
||||
section.inSaveQueue.set(false);
|
||||
task.engine.storage.saveSection(section);
|
||||
if (section.exchangeIsInSaveQueue(false)) {
|
||||
task.engine.storage.saveSection(section);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.error("Voxy saver had an exception while executing please check logs and report error", e);
|
||||
}
|
||||
@@ -47,18 +48,24 @@ public class SectionSavingService {
|
||||
|
||||
public void enqueueSave(WorldEngine in, WorldSection section) {
|
||||
//If its not enqueued for saving then enqueue it
|
||||
if (!section.inSaveQueue.getAndSet(true)) {
|
||||
if (section.exchangeIsInSaveQueue(true)) {
|
||||
//Acquire the section for use
|
||||
section.acquire();
|
||||
|
||||
//Hard limit the save count to prevent OOM
|
||||
if (this.getTaskCount() > 5_000) {
|
||||
while (this.getTaskCount() > 5_000) {
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
//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 +76,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();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.ChunkNibbleArray;
|
||||
import net.minecraft.world.chunk.ChunkSection;
|
||||
import net.minecraft.world.chunk.WorldChunk;
|
||||
import net.minecraft.world.chunk.light.LightStorage;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
@@ -86,12 +87,14 @@ 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");
|
||||
}
|
||||
var lightingProvider = chunk.getWorld().getLightingProvider();
|
||||
var blp = lightingProvider.get(LightType.BLOCK);
|
||||
var slp = lightingProvider.get(LightType.SKY);
|
||||
boolean gotLighting = false;
|
||||
|
||||
int i = chunk.getBottomSectionCoord() - 1;
|
||||
for (var section : chunk.getSectionArray()) {
|
||||
@@ -99,25 +102,48 @@ public class VoxelIngestService {
|
||||
if (section == null || !shouldIngestSection(section, chunk.getPos().x, i, chunk.getPos().z)) continue;
|
||||
//if (section.isEmpty()) continue;
|
||||
var pos = ChunkSectionPos.from(chunk.getPos(), i);
|
||||
if (lightingProvider.getStatus(LightType.SKY, pos) != LightStorage.Status.LIGHT_AND_DATA && lightingProvider.getStatus(LightType.BLOCK, pos) != LightStorage.Status.LIGHT_AND_DATA)
|
||||
continue;
|
||||
gotLighting = true;
|
||||
}
|
||||
|
||||
if (!gotLighting) {
|
||||
return;
|
||||
}
|
||||
|
||||
var blp = lightingProvider.get(LightType.BLOCK);
|
||||
var slp = lightingProvider.get(LightType.SKY);
|
||||
|
||||
|
||||
i = chunk.getBottomSectionCoord() - 1;
|
||||
for (var section : chunk.getSectionArray()) {
|
||||
i++;
|
||||
if (section == null || !shouldIngestSection(section, chunk.getPos().x, i, chunk.getPos().z)) continue;
|
||||
//if (section.isEmpty()) continue;
|
||||
var pos = ChunkSectionPos.from(chunk.getPos(), i);
|
||||
|
||||
var bl = blp.getLightSection(pos);
|
||||
if (!(bl == null || bl.isUninitialized())) {
|
||||
if (bl != null) {
|
||||
bl = bl.copy();
|
||||
} else {
|
||||
bl = null;
|
||||
}
|
||||
var sl = slp.getLightSection(pos);
|
||||
if (!(sl == null || sl.isUninitialized())) {
|
||||
sl = sl.copy();
|
||||
} else {
|
||||
sl = null;
|
||||
}
|
||||
|
||||
if ((bl == null && sl == null) && section.isEmpty()) {
|
||||
continue;
|
||||
var sl = slp.getLightSection(pos);
|
||||
if (sl != null) {
|
||||
sl = sl.copy();
|
||||
}
|
||||
|
||||
//If its null for either, assume failure to obtain lighting and ignore section
|
||||
//if (blNone && slNone) {
|
||||
// continue;
|
||||
//}
|
||||
|
||||
this.ingestQueue.add(new IngestSection(chunk.getPos().x, i, chunk.getPos().z, engine, section, bl, sl));
|
||||
this.threads.execute();
|
||||
try {
|
||||
this.threads.execute();
|
||||
} catch (Exception e) {
|
||||
Logger.error("Executing had an error: assume shutting down, aborting",e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.ModContainer;
|
||||
|
||||
import java.lang.invoke.VarHandle;
|
||||
|
||||
public class VoxyCommon implements ModInitializer {
|
||||
public static final String MOD_VERSION;
|
||||
public static final boolean IS_DEDICATED_SERVER;
|
||||
@@ -30,9 +32,12 @@ public class VoxyCommon implements ModInitializer {
|
||||
}
|
||||
|
||||
//This is hardcoded like this because people do not understand what they are doing
|
||||
private static final boolean GlobalVerificationDisableOverride = true;//System.getProperty("voxy.verificationDisableOverride", "false").equals("true");
|
||||
public static boolean isVerificationFlagOn(String name) {
|
||||
return (!GlobalVerificationDisableOverride) && System.getProperty("voxy."+name, "true").equals("true");
|
||||
return isVerificationFlagOn(name, false);
|
||||
}
|
||||
|
||||
public static boolean isVerificationFlagOn(String name, boolean defaultOn) {
|
||||
return System.getProperty("voxy."+name, defaultOn?"true":"false").equals("true");
|
||||
}
|
||||
|
||||
public static void breakpoint() {
|
||||
@@ -61,8 +66,9 @@ public class VoxyCommon implements ModInitializer {
|
||||
|
||||
public static void shutdownInstance() {
|
||||
if (INSTANCE != null) {
|
||||
INSTANCE.shutdown();
|
||||
INSTANCE = null;
|
||||
var instance = INSTANCE;
|
||||
INSTANCE = null;//Make it null before shutdown
|
||||
instance.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ public abstract class VoxyInstance {
|
||||
// note, the reference count should be separate from the number of active chunks to prevent many issues
|
||||
// a world is no longer active once it has no reference counts and no active chunks associated with it
|
||||
public WorldEngine getNullable(WorldIdentifier identifier) {
|
||||
if (!this.isRunning) return null;
|
||||
var cache = identifier.cachedEngineObject;
|
||||
WorldEngine world;
|
||||
if (cache == null) {
|
||||
@@ -109,11 +110,21 @@ public abstract class VoxyInstance {
|
||||
}
|
||||
|
||||
public WorldEngine getOrCreate(WorldIdentifier identifier) {
|
||||
if (!this.isRunning) {
|
||||
Logger.error("Tried getting world object on voxy instance but its not running");
|
||||
return null;
|
||||
}
|
||||
var world = this.getNullable(identifier);
|
||||
if (world != null) {
|
||||
return world;
|
||||
}
|
||||
long stamp = this.activeWorldLock.writeLock();
|
||||
|
||||
if (!this.isRunning) {
|
||||
Logger.error("Tried getting world object on voxy instance but its not running");
|
||||
return null;
|
||||
}
|
||||
|
||||
world = this.activeWorlds.get(identifier);
|
||||
if (world == null) {
|
||||
//Create world here
|
||||
@@ -128,10 +139,13 @@ public abstract class VoxyInstance {
|
||||
protected abstract SectionStorage createStorage(WorldIdentifier identifier);
|
||||
|
||||
private WorldEngine createWorld(WorldIdentifier identifier) {
|
||||
if (!this.isRunning) {
|
||||
throw new IllegalStateException("Cannot create world while not running");
|
||||
}
|
||||
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() + "@" + System.identityHashCode(this));
|
||||
var world = new WorldEngine(this.createStorage(identifier), this);
|
||||
world.setSaveCallback(this.savingService::enqueueSave);
|
||||
this.activeWorlds.put(identifier, world);
|
||||
@@ -158,6 +172,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
|
||||
@@ -40,13 +40,19 @@ public class WorldIdentifier {
|
||||
if (obj instanceof WorldIdentifier other) {
|
||||
return other.hashCode == this.hashCode &&
|
||||
other.biomeSeed == this.biomeSeed &&
|
||||
other.key == this.key &&//other.key.equals(this.key) &&
|
||||
other.dimension == this.dimension//other.dimension.equals(this.dimension)
|
||||
equal(other.key, this.key) &&//other.key.equals(this.key) &&
|
||||
equal(other.dimension, this.dimension)//other.dimension.equals(this.dimension)
|
||||
;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static <T> boolean equal(RegistryKey<T> a, RegistryKey<T> b) {
|
||||
if (a == b) return true;
|
||||
if (a == null || b == null) return false;
|
||||
return a.getRegistry().equals(b.getRegistry()) && a.getValue().equals(b.getValue());
|
||||
}
|
||||
|
||||
//Quick access utility method to get or create a world object in the current instance
|
||||
public WorldEngine getOrCreateEngine() {
|
||||
var instance = VoxyCommon.getInstance();
|
||||
@@ -101,4 +107,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package me.cortex.voxy.commonImpl.configuration;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import me.cortex.voxy.client.config.VoxyConfig;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.common.util.MultiGson;
|
||||
import me.cortex.voxy.common.util.cpu.CpuLayout;
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class VoxyConfigStore {
|
||||
private final MultiGson gson;
|
||||
|
||||
private VoxyConfigStore(Object... defaultValues) {
|
||||
var gb = new GsonBuilder()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||
.setPrettyPrinting()
|
||||
.excludeFieldsWithModifiers(Modifier.PRIVATE);
|
||||
Map<Class<?>, Object> defaultValueMap = new HashMap<>();
|
||||
var mgb = new MultiGson.Builder(gb);
|
||||
for (var i : defaultValues) {
|
||||
mgb.add(i.getClass());
|
||||
defaultValueMap.put(i.getClass(), i);
|
||||
}
|
||||
gb.registerTypeAdapterFactory(new TypeAdapterFactory() {
|
||||
@Override
|
||||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
||||
if (defaultValueMap.containsKey(typeToken.getRawType())) {
|
||||
var defVal = (T)defaultValueMap.get(typeToken.getRawType());
|
||||
var adapter = gson.getDelegateAdapter(this, typeToken);
|
||||
return new TypeAdapter<T>() {
|
||||
@Override
|
||||
public void write(JsonWriter writer, T obj) throws IOException {
|
||||
var defJson = adapter.toJsonTree(defVal).getAsJsonObject();
|
||||
var val = adapter.toJsonTree(obj).getAsJsonObject();
|
||||
for (var key : defJson.keySet()) {
|
||||
if (val.has(key)) {
|
||||
if (defJson.get(key).equals(val.get(key))) {
|
||||
val.addProperty(key, "DEFAULT_VALUE");
|
||||
}
|
||||
}
|
||||
}
|
||||
gson.toJson(val, writer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T read(JsonReader reader) throws IOException {
|
||||
var defJson = adapter.toJsonTree(defVal).getAsJsonObject();
|
||||
var val = ((JsonElement)gson.fromJson(reader, JsonElement.class)).getAsJsonObject();
|
||||
for (var key : defJson.keySet()) {
|
||||
if (val.has(key)) {
|
||||
if (val.get(key).equals(new JsonPrimitive("DEFAULT_VALUE"))) {
|
||||
val.add(key, defJson.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
return adapter.fromJsonTree(val);
|
||||
}
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
this.gson = mgb.build();
|
||||
}
|
||||
|
||||
/*
|
||||
private static void loadOrCreate() {
|
||||
if (VoxyCommon.isAvailable()) {
|
||||
var path = getConfigPath();
|
||||
if (Files.exists(path)) {
|
||||
try (FileReader reader = new FileReader(path.toFile())) {
|
||||
var conf = GSON.fromJson(reader, VoxyConfig.class);
|
||||
if (conf != null) {
|
||||
conf.save();
|
||||
return conf;
|
||||
} else {
|
||||
Logger.error("Failed to load voxy config, resetting");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Logger.error("Could not parse config", e);
|
||||
}
|
||||
}
|
||||
var config = new VoxyConfig();
|
||||
config.save();
|
||||
return config;
|
||||
} else {
|
||||
var config = new VoxyConfig();
|
||||
config.enabled = false;
|
||||
config.enableRendering = false;
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
public void save() {
|
||||
try {
|
||||
Files.writeString(getConfigPath(), this.gson.toJson(this));
|
||||
} catch (IOException e) {
|
||||
Logger.error("Failed to write config file", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Path getConfigPath() {
|
||||
return FabricLoader.getInstance()
|
||||
.getConfigDir()
|
||||
.resolve("voxy-config.json");
|
||||
}
|
||||
}
|
||||
@@ -309,7 +309,16 @@ public class DHImporter implements IDataImporter {
|
||||
if ((x+1)%16==0) {
|
||||
for (int sz = 0; sz < 4; sz++) {
|
||||
for (int sy = 0; sy < this.worldHeightSections; sy++) {
|
||||
System.arraycopy(storage, (sz|(sy<<2))<<12, section.section, 0, 16 * 16 * 16);
|
||||
{
|
||||
int base = (sz|(sy<<2))<<12;
|
||||
int nonAirCount = 0;
|
||||
final var dat = section.section;
|
||||
for (int i = 0; i < 4096; i++) {
|
||||
nonAirCount += Mapper.isAir(dat[i] = storage[i+base])?0:1;
|
||||
}
|
||||
section.lvl0NonAirCount = nonAirCount;
|
||||
}
|
||||
|
||||
WorldConversionFactory.mipSection(section, this.engine.getMapper());
|
||||
|
||||
section.setPosition(X*4+(x>>4), sy+(this.bottomOfWorld>>4), (Z*4)+sz);
|
||||
|
||||
@@ -149,6 +149,10 @@ public class WorldImporter implements IDataImporter {
|
||||
this.world.releaseRef();
|
||||
this.threadPool.shutdown();
|
||||
}
|
||||
//Free all the remaining entries by running the lambda
|
||||
while (!this.jobQueue.isEmpty()) {
|
||||
this.jobQueue.poll().run();
|
||||
}
|
||||
}
|
||||
|
||||
private interface IImporterMethod <T> {
|
||||
@@ -327,7 +331,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 +345,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");
|
||||
@@ -481,8 +485,11 @@ public class WorldImporter implements IDataImporter {
|
||||
return;
|
||||
}
|
||||
var blockStates = blockStatesRes.getPartialOrThrow();
|
||||
var biomes = this.biomeCodec.parse(NbtOps.INSTANCE, section.getCompound("biomes").get()).result().orElse(this.defaultBiomeProvider);
|
||||
|
||||
var biomes = this.defaultBiomeProvider;
|
||||
var optBiomes = section.getCompound("biomes");
|
||||
if (optBiomes.isPresent()) {
|
||||
biomes = this.biomeCodec.parse(NbtOps.INSTANCE, optBiomes.get()).result().orElse(this.defaultBiomeProvider);
|
||||
}
|
||||
VoxelizedSection csec = WorldConversionFactory.convert(
|
||||
SECTION_CACHE.get().setPosition(x, y, z),
|
||||
this.world.getMapper(),
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
"voxy.config.general.renderDistance": "Render distance",
|
||||
"voxy.config.general.renderDistance.tooltip": "Render distance of voxy in chunks",
|
||||
|
||||
"voxy.config.general.environmental_fog": "Enable environmental fog",
|
||||
"voxy.config.general.environmental_fog.tooltip": "Enables or disables voxy rendering environmental fog",
|
||||
|
||||
"voxy.config.general.vanilla_fog": "Enable vanilla fog",
|
||||
"voxy.config.general.vanilla_fog.tooltip": "Enables or disables vanilla fog effect",
|
||||
|
||||
|
||||
@@ -3,13 +3,21 @@
|
||||
|
||||
layout(location = 0) in vec2 uv;
|
||||
layout(binding = 0) uniform sampler2D depthTex;
|
||||
#ifdef OUTPUT_COLOUR
|
||||
layout(location=0) out vec4 colour;
|
||||
#endif
|
||||
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);
|
||||
}
|
||||
float res = max(max(depths.x, depths.y), max(depths.z, depths.w));
|
||||
|
||||
gl_FragDepth = max(max(depths.x, depths.y), max(depths.z, depths.w)); // Write conservative depth.
|
||||
#ifdef OUTPUT_COLOUR
|
||||
colour = vec4(res);
|
||||
#else
|
||||
gl_FragDepth = res; // Write conservative depth.
|
||||
#endif
|
||||
}
|
||||
|
||||
93
src/main/resources/assets/voxy/shaders/hiz/hiz.comp
Normal file
93
src/main/resources/assets/voxy/shaders/hiz/hiz.comp
Normal file
@@ -0,0 +1,93 @@
|
||||
#version 460 core
|
||||
|
||||
#extension GL_KHR_shader_subgroup_arithmetic: require
|
||||
#extension GL_KHR_shader_subgroup_basic : require
|
||||
#extension GL_KHR_shader_subgroup_clustered : require
|
||||
|
||||
|
||||
//64x64 reduction
|
||||
layout(local_size_x=256) in;
|
||||
|
||||
const uint spread[64] = {
|
||||
0x11100100, 0x13120302, 0x31302120, 0x33322322, 0x15140504, 0x17160706, 0x35342524, 0x37362726,
|
||||
0x51504140, 0x53524342, 0x71706160, 0x73726362, 0x55544544, 0x57564746, 0x75746564, 0x77766766,
|
||||
0x19180908, 0x1b1a0b0a, 0x39382928, 0x3b3a2b2a, 0x1d1c0d0c, 0x1f1e0f0e, 0x3d3c2d2c, 0x3f3e2f2e,
|
||||
0x59584948, 0x5b5a4b4a, 0x79786968, 0x7b7a6b6a, 0x5d5c4d4c, 0x5f5e4f4e, 0x7d7c6d6c, 0x7f7e6f6e,
|
||||
0x91908180, 0x93928382, 0xb1b0a1a0, 0xb3b2a3a2, 0x95948584, 0x97968786, 0xb5b4a5a4, 0xb7b6a7a6,
|
||||
0xd1d0c1c0, 0xd3d2c3c2, 0xf1f0e1e0, 0xf3f2e3e2, 0xd5d4c5c4, 0xd7d6c7c6, 0xf5f4e5e4, 0xf7f6e7e6,
|
||||
0x99988988, 0x9b9a8b8a, 0xb9b8a9a8, 0xbbbaabaa, 0x9d9c8d8c, 0x9f9e8f8e, 0xbdbcadac, 0xbfbeafae,
|
||||
0xd9d8c9c8, 0xdbdacbca, 0xf9f8e9e8, 0xfbfaebea, 0xdddccdcc, 0xdfdecfce, 0xfdfcedec, 0xfffeefee
|
||||
};
|
||||
|
||||
uint swizzleId(uint id) {
|
||||
//swizzel to z curve
|
||||
return bitfieldExtract(spread[id>>2], (int(id)&3)*8, 8);
|
||||
}
|
||||
|
||||
layout(location = 0) uniform vec2 invImSize;
|
||||
|
||||
layout(binding = 0) uniform sampler2D mip_0;
|
||||
layout(binding = 1, r32f) uniform restrict writeonly image2D mip_1;
|
||||
layout(binding = 2, r32f) uniform restrict writeonly image2D mip_2;
|
||||
layout(binding = 3, r32f) uniform restrict writeonly image2D mip_3;
|
||||
layout(binding = 4, r32f) uniform restrict writeonly image2D mip_4;
|
||||
layout(binding = 5, r32f) uniform restrict writeonly image2D mip_5;
|
||||
layout(binding = 6, r32f) uniform restrict writeonly image2D mip_6;
|
||||
|
||||
float getReduce2x2(ivec2 pos) {//w.r.t mip_1
|
||||
vec4 data = textureGather(mip_0, vec2(pos*2+1)*invImSize);
|
||||
float ret = max(max(data.x,data.y),max(data.z,data.w));
|
||||
imageStore(mip_1, pos, vec4(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
float getReduce4x4(ivec2 pos) {//w.r.t mip_2
|
||||
ivec2 pos2 = pos*2;
|
||||
float ret = max(max(getReduce2x2(pos2+ivec2(0,0)),getReduce2x2(pos2+ivec2(0,1))),
|
||||
max(getReduce2x2(pos2+ivec2(1,0)),getReduce2x2(pos2+ivec2(1,1))));
|
||||
imageStore(mip_2, pos, vec4(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
//This is where the funny happens
|
||||
// since we swizzeled the id when getting the value, our ordering within the subgroup should be z ordered
|
||||
// we sadly cannot use the full subgroup reduction as wave size is 32 and we need a square pow2 values, so 16, sad beep
|
||||
float getReduceWave(ivec2 pos, float value) {
|
||||
float reduced;
|
||||
subgroupBarrier();//Wait for active threads in subgroup
|
||||
//Now do clustered reduction, with exploiting dropout
|
||||
reduced = subgroupClusteredMax(value, 4);
|
||||
if ((gl_SubgroupInvocationID&0x3)==0) {//root writes
|
||||
imageStore(mip_3, pos>>1, vec4(reduced));
|
||||
}
|
||||
//could exit 3/4 of the threads here if wanted
|
||||
subgroupBarrier();//Wait for active threads in subgroup
|
||||
reduced = subgroupClusteredMax(value, 16);
|
||||
if ((gl_SubgroupInvocationID&0xF)==0) {//root writes
|
||||
imageStore(mip_4, pos>>2, vec4(reduced));
|
||||
}
|
||||
return reduced;
|
||||
}
|
||||
shared float values[16];
|
||||
void main() {
|
||||
uint id = swizzleId(gl_LocalInvocationID.x);
|
||||
//(ivec2(gl_WorkGroupID.xy)*64+ivec2(id&0xFU, id>>4)*4)/4;
|
||||
ivec2 wavePos = ivec2(gl_WorkGroupID.xy)*16+ivec2(id&0xFU, id>>4);
|
||||
float value = getReduce4x4(wavePos);
|
||||
value = getReduceWave(wavePos, value);//Reduced to 4x4 across all threads and warps
|
||||
if ((gl_LocalInvocationID.x&0xFU)==0) {
|
||||
values[gl_LocalInvocationID.x>>4] = value;
|
||||
}
|
||||
barrier();//Wait for all
|
||||
if ((gl_LocalInvocationID.x>>2)!=0) {
|
||||
return;//Discard all but 4 threads
|
||||
}
|
||||
uint i = gl_LocalInvocationID.x*4;
|
||||
value = max(max(values[i],values[i+1]),max(values[i+2],values[i+3]));//Is funny is already in spread order
|
||||
imageStore(mip_5, ivec2(gl_WorkGroupID.xy)*2+ivec2(gl_LocalInvocationID.x&1u,gl_LocalInvocationID.x>>1), vec4(value));
|
||||
subgroupBarrier();
|
||||
value = subgroupMax(value);
|
||||
if (gl_LocalInvocationID.x==0) {
|
||||
imageStore(mip_6, ivec2(gl_WorkGroupID.xy), vec4(value));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
#line 1
|
||||
|
||||
layout(binding = 0, std140) uniform SceneUniform {
|
||||
mat4 MVP;
|
||||
ivec3 baseSectionPos;
|
||||
@@ -7,24 +5,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;
|
||||
@@ -47,5 +47,5 @@ void main() {
|
||||
dist = (TRANSLUCENT_WRITE_BASE-1)-min(dist, (TRANSLUCENT_WRITE_BASE-1));
|
||||
|
||||
uint drawPtr = atomicAdd(translucentCommandData[dist],1)+TRANSLUCENT_OFFSET;
|
||||
writeCmd(drawPtr, drawId, extractQuadStart(meta), meta.cntA&0xFFFF);
|
||||
writeCmd(drawPtr, drawId, extractQuadStart(meta), meta.b.x&0xFFFF);
|
||||
}
|
||||
@@ -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
|
||||
@@ -78,18 +78,18 @@ void main() {
|
||||
ivec3 relative = ipos-(baseSectionPos>>detail);
|
||||
uint drawId = gl_GlobalInvocationID.x;
|
||||
|
||||
positionBuffer[drawId] = uvec2(meta.posA, meta.posB);
|
||||
|
||||
positionBuffer[drawId] = extractRawPos(meta);
|
||||
uvec4 counts = meta.b;
|
||||
|
||||
uint msk = 0;
|
||||
msk |= uint(((meta.cntB &0xFFFF)!=0) && (relative.y>-1))<<0;
|
||||
msk |= uint((((meta.cntB>>16)&0xFFFF)!=0) && (relative.y<1 ))<<1;
|
||||
msk |= uint(((meta.cntC &0xFFFF)!=0) && (relative.z>-1))<<2;
|
||||
msk |= uint((((meta.cntC>>16)&0xFFFF)!=0) && (relative.z<1 ))<<3;
|
||||
msk |= uint(((meta.cntD &0xFFFF)!=0) && (relative.x>-1))<<4;
|
||||
msk |= uint((((meta.cntD>>16)&0xFFFF)!=0) && (relative.x<1 ))<<5;
|
||||
msk |= uint(((counts.y &0xFFFFu)!=0) && (relative.y>-1))<<0;
|
||||
msk |= uint((((counts.y>>16)&0xFFFFu)!=0) && (relative.y<1 ))<<1;
|
||||
msk |= uint(((counts.z &0xFFFFu)!=0) && (relative.z>-1))<<2;
|
||||
msk |= uint((((counts.z>>16)&0xFFFFu)!=0) && (relative.z<1 ))<<3;
|
||||
msk |= uint(((counts.w &0xFFFFu)!=0) && (relative.x>-1))<<4;
|
||||
msk |= uint((((counts.w>>16)&0xFFFFu)!=0) && (relative.x<1 ))<<5;
|
||||
|
||||
msk |= uint(((meta.cntA>>16)&0xFFFF)!=0)<<6;
|
||||
msk |= uint(((counts.x>>16)&0xFFFFu)!=0)<<6;
|
||||
|
||||
|
||||
uint cmdCnt = bitCount(msk);
|
||||
@@ -107,7 +107,7 @@ void main() {
|
||||
|
||||
uint count = 0;
|
||||
//Translucency
|
||||
count = meta.cntA&0xFFFF;
|
||||
count = counts.x&0xFFFFu;
|
||||
if (count != 0) {
|
||||
uint tp = atomicAdd(translucentDrawCount, 1)+TRANSLUCENT_WRITE_BASE;
|
||||
translucentCommandData[tp] = drawId;
|
||||
@@ -123,7 +123,7 @@ void main() {
|
||||
ptr += count;
|
||||
|
||||
//Double sided quads
|
||||
count = (meta.cntA>>16)&0xFFFF;
|
||||
count = (counts.x>>16)&0xFFFFu;
|
||||
if (count != 0) {
|
||||
writeCmd(cmdPtr++, drawId, ptr, count);
|
||||
if (renderTemporally) {
|
||||
@@ -136,7 +136,7 @@ void main() {
|
||||
ptr += count;
|
||||
|
||||
//Down
|
||||
count = (meta.cntB)&0xFFFF;
|
||||
count = (counts.y)&0xFFFFu;
|
||||
if (((msk&(1u<<0))!=0)) {
|
||||
writeCmd(cmdPtr++, drawId, ptr, count);
|
||||
if (renderTemporally) {
|
||||
@@ -149,7 +149,7 @@ void main() {
|
||||
ptr += count;
|
||||
|
||||
//Up
|
||||
count = (meta.cntB>>16)&0xFFFF;
|
||||
count = (counts.y>>16)&0xFFFFu;
|
||||
if ((msk&(1u<<1))!=0) {
|
||||
writeCmd(cmdPtr++, drawId, ptr, count);
|
||||
if (renderTemporally) {
|
||||
@@ -162,7 +162,7 @@ void main() {
|
||||
ptr += count;
|
||||
|
||||
//North
|
||||
count = (meta.cntC)&0xFFFF;
|
||||
count = (counts.z)&0xFFFFu;
|
||||
if ((msk&(1u<<2))!=0) {
|
||||
writeCmd(cmdPtr++, drawId, ptr, count);
|
||||
if (renderTemporally) {
|
||||
@@ -175,7 +175,7 @@ void main() {
|
||||
ptr += count;
|
||||
|
||||
//South
|
||||
count = (meta.cntC>>16)&0xFFFF;
|
||||
count = (counts.z>>16)&0xFFFFu;
|
||||
if ((msk&(1u<<3))!=0) {
|
||||
writeCmd(cmdPtr++, drawId, ptr, count);
|
||||
if (renderTemporally) {
|
||||
@@ -188,7 +188,7 @@ void main() {
|
||||
ptr += count;
|
||||
|
||||
//West
|
||||
count = (meta.cntD)&0xFFFF;
|
||||
count = (counts.w)&0xFFFFu;
|
||||
if ((msk&(1u<<4))!=0) {
|
||||
writeCmd(cmdPtr++, drawId, ptr, count);
|
||||
if (renderTemporally) {
|
||||
@@ -201,7 +201,7 @@ void main() {
|
||||
ptr += count;
|
||||
|
||||
//East
|
||||
count = (meta.cntD>>16)&0xFFFF;
|
||||
count = (counts.w>>16)&0xFFFFu;
|
||||
if ((msk&(1u<<5))!=0) {
|
||||
writeCmd(cmdPtr++, drawId, ptr, count);
|
||||
if (renderTemporally) {
|
||||
|
||||
@@ -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;
|
||||
@@ -32,6 +32,7 @@ void main() {
|
||||
//Write to the section id, to track temporal over time (litterally just need a single bit, 1 fking bit, but no)
|
||||
id = sid;
|
||||
|
||||
//Me when data race condition between visibilityData in the vert shader and frag shader
|
||||
uint previous = visibilityData[sid]&0x7fffffffu;
|
||||
bool wasVisibleLastFrame = previous==(frameId-1);
|
||||
value = (frameId&0x7fffffffu)|(uint(wasVisibleLastFrame)<<31);//Encode if it was visible last frame
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -8,29 +12,79 @@ layout(binding = 2) uniform sampler2D depthTex;
|
||||
// however they are not a full block
|
||||
|
||||
layout(location = 0) in vec2 uv;
|
||||
layout(location = 1) in flat vec2 baseUV;
|
||||
layout(location = 2) in flat vec4 tinting;
|
||||
layout(location = 3) in flat vec4 addin;
|
||||
layout(location = 4) in flat uint flags;
|
||||
layout(location = 5) in flat vec4 conditionalTinting;
|
||||
|
||||
layout(location = 1) in flat uvec4 interData;
|
||||
|
||||
#ifdef DEBUG_RENDER
|
||||
layout(location = 6) in flat uint quadDebug;
|
||||
layout(location = 7) in flat uint quadDebug;
|
||||
#endif
|
||||
layout(location = 0) out vec4 outColour;
|
||||
|
||||
|
||||
#import <voxy:lod/gl46/bindings.glsl>
|
||||
|
||||
vec4 uint2vec4RGBA(uint colour) {
|
||||
return vec4((uvec4(colour)>>uvec4(24,16,8,0))&uvec4(0xFF))/255.0;
|
||||
}
|
||||
|
||||
bool useMipmaps() {
|
||||
return (interData.x&2u)==0u;
|
||||
}
|
||||
|
||||
bool useTinting() {
|
||||
return (interData.x&4u)!=0u;
|
||||
}
|
||||
|
||||
bool useCutout() {
|
||||
return (interData.x&1u)==1u;
|
||||
}
|
||||
|
||||
vec4 computeColour(vec4 colour) {
|
||||
//Conditional tinting, TODO: FIXME: REPLACE WITH MASK OR SOMETHING, like encode data into the top bit of alpha
|
||||
if (useTinting() && abs(colour.r-colour.g) < 0.02f && abs(colour.g-colour.b) < 0.02f) {
|
||||
colour *= uint2vec4RGBA(interData.z).yzwx;
|
||||
}
|
||||
return (colour * uint2vec4RGBA(interData.y)) + vec4(0,0,0,float(interData.w&0xFFu)/255);
|
||||
}
|
||||
|
||||
|
||||
uint getFace() {
|
||||
return (interData.w>>8)&7u;
|
||||
}
|
||||
|
||||
vec2 getBaseUV() {
|
||||
uint face = getFace();
|
||||
uint modelId = interData.x>>16;
|
||||
vec2 modelUV = vec2(modelId&0xFFu, (modelId>>8)&0xFFu)*(1.0/(256.0));
|
||||
return modelUV + (vec2(face>>1, face&1u) * (1.0/(vec2(3.0, 2.0)*256.0)));
|
||||
}
|
||||
|
||||
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;
|
||||
vec2 texPos = uv2 + getBaseUV();
|
||||
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 = textureLod(blockModelAtlas, texPos, 0);
|
||||
}
|
||||
|
||||
if (any(notEqual(clamp(tile, vec2(0), vec2((interData.x>>8)&0xFu, (interData.x>>12)&0xFu)), 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)) {
|
||||
if (useCutout() && (textureLod(blockModelAtlas, texPos, 0).a <= 0.1f)) {
|
||||
//This is stupidly stupidly bad for divergence
|
||||
//TODO: FIXME, basicly what this do is sample the exact pixel (no lod) for discarding, this stops mipmapping fucking it over
|
||||
#ifndef DEBUG_RENDER
|
||||
@@ -38,14 +92,9 @@ 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;
|
||||
}
|
||||
colour = computeColour(colour);
|
||||
|
||||
outColour = (colour * tinting) + addin;
|
||||
|
||||
//outColour = vec4(uv + baseUV, 0, 1);
|
||||
outColour = colour;
|
||||
|
||||
|
||||
#ifdef DEBUG_RENDER
|
||||
@@ -56,4 +105,30 @@ void main() {
|
||||
hash = hash * 1827364925 + 123325621;
|
||||
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,20 +9,31 @@
|
||||
|
||||
|
||||
#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
|
||||
|
||||
layout(location = 0) out vec2 uv;
|
||||
layout(location = 1) out flat vec2 baseUV;
|
||||
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 = 1) out flat uvec4 interData;
|
||||
|
||||
uint packVec4(vec4 vec) {
|
||||
uvec4 vec_=uvec4(vec*255)<<uvec4(24,16,8,0);
|
||||
return vec_.x|vec_.y|vec_.z|vec_.w;
|
||||
}
|
||||
|
||||
void setSizeAndFlags(uint modelId, uint _flags, ivec2 quadSize) {
|
||||
interData.x = (modelId<<16) | _flags | (uint(quadSize.x-1)<<8) | (uint(quadSize.y-1)<<12);
|
||||
}
|
||||
|
||||
void setTintingAndExtra(vec4 _tinting, uint _conditionalTinting, uint addin) {
|
||||
interData.y = packVec4(_tinting);
|
||||
interData.z = _conditionalTinting;
|
||||
interData.w = addin;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_RENDER
|
||||
layout(location = 6) out flat uint quadDebug;
|
||||
layout(location = 7) out flat uint quadDebug;
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -37,18 +47,13 @@ ivec3 extractRelativeLodPos() {
|
||||
return (ivec3(gl_BaseInstance)<<ivec3(5,14,23))>>ivec3(23);
|
||||
}*/
|
||||
|
||||
vec4 uint2vec4RGBA(uint colour) {
|
||||
return vec4((uvec4(colour)>>uvec4(24,16,8,0))&uvec4(0xFF))/255.0;
|
||||
}
|
||||
|
||||
vec4 getFaceSize(uint faceData) {
|
||||
float EPSILON = 0.0005f;
|
||||
float EPSILON = 0.00005f;
|
||||
|
||||
vec4 faceOffsetsSizes = extractFaceSizes(faceData);
|
||||
|
||||
//Expand the quads by a very small amount
|
||||
//Expand the quads by a very small amount (because of the subtraction after this also becomes an implicit add)
|
||||
faceOffsetsSizes.xz -= vec2(EPSILON);
|
||||
faceOffsetsSizes.yw += vec2(EPSILON);
|
||||
|
||||
//Make the end relative to the start
|
||||
faceOffsetsSizes.yw -= faceOffsetsSizes.xz;
|
||||
@@ -56,19 +61,8 @@ vec4 getFaceSize(uint faceData) {
|
||||
return faceOffsetsSizes;
|
||||
}
|
||||
|
||||
//TODO: make branchless by using ternaries i think
|
||||
vec3 swizzelDataAxis(uint axis, vec3 data) {
|
||||
if (axis == 0) { //Up/down
|
||||
data = data.xzy;
|
||||
}
|
||||
//Not needed, here for readability
|
||||
//if (axis == 1) {//north/south
|
||||
// offset = offset.xyz;
|
||||
//}
|
||||
if (axis == 2) { //west/east
|
||||
data = data.zxy;
|
||||
}
|
||||
return data;
|
||||
return mix(mix(data.zxy,data.xzy,bvec3(axis==0)),data,bvec3(axis==1));
|
||||
}
|
||||
|
||||
uint extractDetail(uvec2 encPos) {
|
||||
@@ -104,14 +98,30 @@ void main() {
|
||||
uvec2 encPos = positionBuffer[gl_BaseInstance];
|
||||
uint lodLevel = extractDetail(encPos);
|
||||
|
||||
|
||||
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
|
||||
flags = faceHasAlphaCuttout(faceData);
|
||||
|
||||
|
||||
|
||||
vec4 faceSize = getFaceSize(faceData);
|
||||
|
||||
vec2 cQuadSize = (faceSize.yw + quadSize - 1) * vec2((cornerIdx>>1)&1, cornerIdx&1);
|
||||
uv = faceSize.xz + cQuadSize;
|
||||
|
||||
vec3 cornerPos = extractPos(quad);
|
||||
float depthOffset = extractFaceIndentation(faceData);
|
||||
cornerPos += swizzelDataAxis(face>>1, vec3(faceSize.xz, mix(depthOffset, 1-depthOffset, float(face&1u))));
|
||||
|
||||
vec3 origin = vec3(((extractLoDPosition(encPos)<<lodLevel) - baseSectionPos)<<5);
|
||||
vec3 pointPos = (cornerPos+swizzelDataAxis(face>>1,vec3(cQuadSize,0)))*(1<<lodLevel)+origin;
|
||||
gl_Position = MVP*vec4(pointPos, 1.0);
|
||||
|
||||
|
||||
|
||||
if (cornerIdx == 1) //Only if we are the provoking vertex
|
||||
{
|
||||
//Generate tinting and flag data
|
||||
uint flags = faceHasAlphaCuttout(faceData);
|
||||
|
||||
//We need to have a conditional override based on if the model size is < a full face + quadSize > 1
|
||||
flags |= uint(any(greaterThan(quadSize, ivec2(1)))) & faceHasAlphaCuttoutOverride(faceData);
|
||||
@@ -119,7 +129,7 @@ void main() {
|
||||
flags |= uint(!modelHasMipmaps(model))<<1;
|
||||
|
||||
//Compute lighting
|
||||
tinting = getLighting(extractLightId(quad));
|
||||
vec4 tinting = getLighting(extractLightId(quad));
|
||||
|
||||
//Apply model colour tinting
|
||||
uint tintColour = model.colourTint;
|
||||
@@ -127,13 +137,13 @@ void main() {
|
||||
tintColour = colourData[tintColour + extractBiomeId(quad)];
|
||||
}
|
||||
|
||||
conditionalTinting = vec4(0);
|
||||
uint conditionalTinting = 0;
|
||||
if (tintColour != uint(-1)) {
|
||||
flags |= 1u<<2;
|
||||
conditionalTinting = uint2vec4RGBA(tintColour).yzwx;
|
||||
conditionalTinting = tintColour;
|
||||
}
|
||||
|
||||
addin = vec4(0.0);
|
||||
uint addin = 0;
|
||||
if (!isTranslucent) {
|
||||
tinting.w = 0.0;
|
||||
//Encode the face, the lod level and
|
||||
@@ -141,7 +151,7 @@ void main() {
|
||||
encodedData |= face;
|
||||
encodedData |= (lodLevel<<3);
|
||||
encodedData |= uint(hasAO)<<6;
|
||||
addin.w = float(encodedData)/255.0;
|
||||
addin = encodedData;
|
||||
}
|
||||
|
||||
//Apply face tint
|
||||
@@ -156,25 +166,12 @@ void main() {
|
||||
tinting.xyz *= 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
setSizeAndFlags(modelId, flags, quadSize);
|
||||
setTintingAndExtra(tinting, conditionalTinting, addin|(face<<8));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
vec4 faceSize = getFaceSize(faceData);
|
||||
|
||||
vec2 cQuadSize = (faceSize.yw + quadSize - 1) * vec2((cornerIdx>>1)&1, cornerIdx&1);
|
||||
uv = faceSize.xz + cQuadSize;
|
||||
|
||||
vec3 cornerPos = extractPos(quad);
|
||||
float depthOffset = extractFaceIndentation(faceData);
|
||||
cornerPos += swizzelDataAxis(face>>1, vec3(faceSize.xz, mix(depthOffset, 1-depthOffset, float(face&1u))));
|
||||
|
||||
|
||||
vec3 origin = vec3(((extractLoDPosition(encPos)<<lodLevel) - baseSectionPos)<<5);
|
||||
gl_Position = MVP*vec4((cornerPos+swizzelDataAxis(face>>1,vec3(cQuadSize,0)))*(1<<lodLevel)+origin, 1.0);
|
||||
|
||||
#ifdef DEBUG_RENDER
|
||||
quadDebug = lodLevel;
|
||||
#endif
|
||||
|
||||
@@ -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,32 @@ 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, 6);
|
||||
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);
|
||||
|
||||
112
src/main/resources/assets/voxy/shaders/lod/mesh/frag.glsl
Normal file
112
src/main/resources/assets/voxy/shaders/lod/mesh/frag.glsl
Normal file
@@ -0,0 +1,112 @@
|
||||
#version 460 core
|
||||
|
||||
#extension GL_NV_fragment_shader_barycentric : require
|
||||
|
||||
layout(binding = 0) uniform sampler2D blockModelAtlas;
|
||||
layout(binding = 2) uniform sampler2D depthTex;
|
||||
|
||||
perprimitiveNV in uvec4 primData;
|
||||
perprimitiveNV in vec4 uvData;
|
||||
|
||||
layout(location = 0) out vec4 outColour;
|
||||
|
||||
vec4 uint2vec4RGBA(uint colour) {
|
||||
return vec4((uvec4(colour)>>uvec4(24,16,8,0))&uvec4(0xFF))/255.0;
|
||||
}
|
||||
|
||||
bool useMipmaps() {
|
||||
return (primData.x&2u)==0u;
|
||||
}
|
||||
|
||||
bool useTinting() {
|
||||
return (primData.x&4u)!=0u;
|
||||
}
|
||||
|
||||
bool useCutout() {
|
||||
return (primData.x&1u)==1u;
|
||||
}
|
||||
|
||||
vec4 computeColour(vec4 colour) {
|
||||
//Conditional tinting, TODO: FIXME: REPLACE WITH MASK OR SOMETHING, like encode data into the top bit of alpha
|
||||
if (useTinting() && abs(colour.r-colour.g) < 0.02f && abs(colour.g-colour.b) < 0.02f) {
|
||||
colour *= uint2vec4RGBA(primData.z).yzwx;
|
||||
}
|
||||
return (colour * uint2vec4RGBA(primData.y)) + vec4(0,0,0,float(primData.w&0xFFu)/255);
|
||||
}
|
||||
|
||||
|
||||
uint getFace() {
|
||||
return (primData.w>>8)&7u;
|
||||
}
|
||||
|
||||
vec2 getBaseUV() {
|
||||
uint face = getFace();
|
||||
uint modelId = primData.x>>16;
|
||||
vec2 modelUV = vec2(modelId&0xFFu, (modelId>>8)&0xFFu)*(1.0/(256.0));
|
||||
return modelUV + (vec2(face>>1, face&1u) * (1.0/(vec2(3.0, 2.0)*256.0)));
|
||||
}
|
||||
|
||||
bool isTri0() {
|
||||
return (gl_PrimitiveID&(1<<31))==0;
|
||||
}
|
||||
|
||||
void main() {
|
||||
bool tri0 = isTri0();
|
||||
//1,2,0
|
||||
//1,3,2
|
||||
//vec2((corner>>1)&1u, corner&1u)
|
||||
|
||||
//vec2(0,gl_BaryCoordNV.x)+vec2(gl_BaryCoordNV.y,0)+vec2(0,0);
|
||||
//vec2(0,gl_BaryCoordNV.x)+vec2(gl_BaryCoordNV.y,gl_BaryCoordNV.y)+vec2(gl_BaryCoordNV.z,0);
|
||||
|
||||
|
||||
vec2 uv = fma(mix(gl_BaryCoordNV.zx+gl_BaryCoordNV.y, gl_BaryCoordNV.yx, bvec2(tri0)), uvData.zw, uvData.xy);
|
||||
//Need to interpolate
|
||||
|
||||
//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;
|
||||
vec2 texPos = uv2 + getBaseUV();
|
||||
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 = textureLod(blockModelAtlas, texPos, 0);
|
||||
}
|
||||
|
||||
if (any(notEqual(clamp(tile, vec2(0), vec2((primData.x>>8)&0xFu, (primData.x>>12)&0xFu)), 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;
|
||||
}
|
||||
|
||||
|
||||
//Also, small quad is really fking over the mipping level somehow
|
||||
if (useCutout() && (textureLod(blockModelAtlas, texPos, 0).a <= 0.1f)) {
|
||||
//This is stupidly stupidly bad for divergence
|
||||
//TODO: FIXME, basicly what this do is sample the exact pixel (no lod) for discarding, this stops mipmapping fucking it over
|
||||
#ifndef DEBUG_RENDER
|
||||
discard;
|
||||
#endif
|
||||
}
|
||||
|
||||
colour = computeColour(colour);
|
||||
|
||||
outColour = colour;
|
||||
|
||||
|
||||
/*
|
||||
uint hash = gl_PrimitiveID*1231421+123141;
|
||||
hash ^= hash>>16;
|
||||
hash = hash*1231421+123141;
|
||||
hash ^= hash>>16;
|
||||
hash = hash * 1827364925 + 123325621;
|
||||
outColour = vec4(float(hash&15u)/15, float((hash>>4)&15u)/15, float((hash>>8)&15u)/15, 0);
|
||||
*/
|
||||
}
|
||||
361
src/main/resources/assets/voxy/shaders/lod/mesh/mesh.glsl
Normal file
361
src/main/resources/assets/voxy/shaders/lod/mesh/mesh.glsl
Normal file
@@ -0,0 +1,361 @@
|
||||
#version 460 core
|
||||
|
||||
#extension GL_NV_mesh_shader : require
|
||||
|
||||
#extension GL_ARB_gpu_shader_int64 : enable
|
||||
|
||||
#extension GL_KHR_shader_subgroup_arithmetic: require
|
||||
#extension GL_KHR_shader_subgroup_basic : require
|
||||
#extension GL_KHR_shader_subgroup_ballot : require
|
||||
#extension GL_KHR_shader_subgroup_vote : require
|
||||
|
||||
|
||||
//TODO: finetune the local size and emission size
|
||||
layout(local_size_x = MESH_SIZE) in;
|
||||
layout(triangles, max_vertices=(MESH_SIZE*4), max_primitives=(MESH_SIZE*2)) out;
|
||||
|
||||
layout(std430) taskNV in Task {
|
||||
//Tightly packed, prefix sum + offset
|
||||
//uvec4 binA;
|
||||
//uvec4 binB;
|
||||
uint bins[8];
|
||||
|
||||
vec3 cameraOffset;
|
||||
uint lodLvl;
|
||||
|
||||
uint baseQuad;
|
||||
uint quadCount;
|
||||
} task;
|
||||
|
||||
perprimitiveNV out uvec4 primData[MESH_SIZE*2];
|
||||
perprimitiveNV out vec4 uvData[MESH_SIZE*2];
|
||||
|
||||
uint getQuadId() {
|
||||
uint mid = gl_GlobalInvocationID.x;
|
||||
uint cv = (mid<<16)|0xFFFFu;
|
||||
/*
|
||||
//Funny method
|
||||
uvec4 a = mix(uvec4(0), uvec4( 1, 2, 4, 8), lessThanEqual(uvec4(task.bins[0],task.bins[1],task.bins[2],task.bins[3]), uvec4(cv))) +
|
||||
mix(uvec4(0), uvec4(16,32,64,128), lessThanEqual(uvec4(task.bins[4],task.bins[5],task.bins[6],task.bins[7]), uvec4(cv)));
|
||||
uint act = a.x+a.y+a.z+a.w;
|
||||
uint id = findLSB(act^(act>>1));
|
||||
|
||||
//uint point = mix(binB, binA, id<4)[id&3u];
|
||||
uint point = task.bins[id];
|
||||
|
||||
return (point&0xFFFFu)+(mid-(point>>16));
|
||||
*/
|
||||
|
||||
#pragma unroll
|
||||
for (uint i = 0; i<7; i++) {
|
||||
uint point = task.bins[i];
|
||||
if (point<=cv&&cv<task.bins[i+1]) {
|
||||
return (point&0xFFFFu)+(mid-(point>>16));
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
|
||||
|
||||
|
||||
/*
|
||||
for (uint i = 0; i<7; i++) {
|
||||
uint point = task.bins[i];
|
||||
if (point <= ((mid<<16)|0xFFFFu) && ((mid<<16)|0xFFFFu)<task.bins[i+1]) {
|
||||
binId = i;
|
||||
return (point&0xFFFFu)+(mid-(point>>16));
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
*/
|
||||
}
|
||||
|
||||
#import <voxy:lod/quad_format.glsl>
|
||||
#import <voxy:lod/block_model.glsl>
|
||||
|
||||
layout(binding = 0, std140) uniform SceneUniform {
|
||||
mat4 MVP;
|
||||
ivec3 baseSectionPos;
|
||||
uint frameId;
|
||||
vec3 cameraSubPos;
|
||||
uint pad_;
|
||||
vec2 screenSize;
|
||||
};
|
||||
|
||||
layout(binding = 4, std430) readonly restrict buffer QuadBuffer {
|
||||
Quad quadData[];
|
||||
};
|
||||
|
||||
layout(binding = 5, std430) readonly restrict buffer ModelBuffer {
|
||||
BlockModel modelData[];
|
||||
};
|
||||
|
||||
layout(binding = 6, std430) readonly restrict buffer ModelColourBuffer {
|
||||
uint colourData[];
|
||||
};
|
||||
|
||||
layout(binding = 1) uniform sampler2D lightSampler;
|
||||
vec4 getLighting(uint index) {
|
||||
int i2 = int(index);
|
||||
return texture(lightSampler, clamp((vec2((i2>>4)&0xF, i2&0xF))/16, vec2(8.0f/255), vec2(248.0f/255)));
|
||||
}
|
||||
|
||||
//===============
|
||||
|
||||
|
||||
vec3 swizzelDataAxis(uint axis, vec3 data) {
|
||||
return mix(mix(data.zxy,data.xzy,bvec3(axis==0)),data,bvec3(axis==1));
|
||||
}
|
||||
|
||||
vec4 getFaceSize(uint faceData) {
|
||||
float EPSILON = 0.00005f;
|
||||
|
||||
vec4 faceOffsetsSizes = extractFaceSizes(faceData);
|
||||
|
||||
//Expand the quads by a very small amount (because of the subtraction after this also becomes an implicit add)
|
||||
faceOffsetsSizes.xz -= vec2(EPSILON);
|
||||
|
||||
//Make the end relative to the start
|
||||
faceOffsetsSizes.yw -= faceOffsetsSizes.xz;
|
||||
|
||||
return faceOffsetsSizes;
|
||||
}
|
||||
|
||||
vec3 faceNormal(uint face) {
|
||||
//TODO: optimize this garbage
|
||||
return vec3(uint((face>>1)==2), uint((face>>1)==0), uint((face>>1)==1)) * (float(int(face)&1)*2-1);
|
||||
}
|
||||
|
||||
uint packVec4(vec4 vec) {
|
||||
uvec4 vec_=uvec4(vec*255)<<uvec4(24,16,8,0);
|
||||
return vec_.x|vec_.y|vec_.z|vec_.w;
|
||||
}
|
||||
|
||||
//===============
|
||||
vec3 cornerPos;//Does not include cameraSubPos to get exact need to - cameraSubPos
|
||||
vec2 axisFaceSize;
|
||||
uint face;
|
||||
|
||||
vec4 faceSize;
|
||||
|
||||
uint modelId;
|
||||
BlockModel model;
|
||||
uint faceData;
|
||||
bool isTranslucent;
|
||||
bool hasAO;
|
||||
bool isShaded;
|
||||
|
||||
void setup(Quad quad) {
|
||||
face = extractFace(quad);
|
||||
modelId = extractStateId(quad);
|
||||
model = modelData[modelId];
|
||||
faceData = model.faceData[face];
|
||||
isTranslucent = modelIsTranslucent(model);
|
||||
hasAO = modelHasMipmaps(model);//TODO: replace with per face AO flag
|
||||
isShaded = hasAO;//TODO: make this a per face flag
|
||||
|
||||
ivec2 quadSize = extractSize(quad);
|
||||
|
||||
faceSize = getFaceSize(faceData);
|
||||
|
||||
cornerPos = extractPos(quad);
|
||||
float depthOffset = extractFaceIndentation(faceData);
|
||||
cornerPos += swizzelDataAxis(face>>1, vec3(faceSize.xz, mix(depthOffset, 1-depthOffset, float(face&1u))));
|
||||
cornerPos *= (1<<task.lodLvl);
|
||||
cornerPos += task.cameraOffset;
|
||||
|
||||
axisFaceSize = (faceSize.yw + quadSize - 1);
|
||||
|
||||
//uv =
|
||||
|
||||
}
|
||||
|
||||
vec2 getUvCorner(uint corner) {
|
||||
return faceSize.xz + axisFaceSize*vec2((corner>>1)&1u, corner&1u);
|
||||
}
|
||||
|
||||
uvec4 createQuadData(Quad quad) {
|
||||
uint flags = faceHasAlphaCuttout(faceData);
|
||||
|
||||
ivec2 quadSize = extractSize(quad);
|
||||
//We need to have a conditional override based on if the model size is < a full face + quadSize > 1
|
||||
flags |= uint(any(greaterThan(quadSize, ivec2(1)))) & faceHasAlphaCuttoutOverride(faceData);
|
||||
|
||||
flags |= uint(!modelHasMipmaps(model))<<1;
|
||||
|
||||
//Compute lighting
|
||||
vec4 tinting = getLighting(extractLightId(quad));
|
||||
|
||||
//Apply model colour tinting
|
||||
uint tintColour = model.colourTint;
|
||||
if (modelHasBiomeLUT(model)) {
|
||||
tintColour = colourData[tintColour + extractBiomeId(quad)];
|
||||
}
|
||||
|
||||
uint conditionalTinting = 0;
|
||||
if (tintColour != uint(-1)) {
|
||||
flags |= 1u<<2;
|
||||
conditionalTinting = tintColour;
|
||||
}
|
||||
|
||||
uint addin = 0;
|
||||
if (!isTranslucent) {
|
||||
tinting.w = 0.0;
|
||||
//Encode the face, the lod level and
|
||||
uint encodedData = 0;
|
||||
encodedData |= face;
|
||||
encodedData |= (task.lodLvl<<3);
|
||||
encodedData |= uint(hasAO)<<6;
|
||||
addin = encodedData;
|
||||
}
|
||||
|
||||
//Apply face tint
|
||||
if (isShaded) {
|
||||
//TODO: make branchless, infact apply ahead of time to the texture itself in ModelManager since that is
|
||||
// per face
|
||||
if ((face>>1) == 1) {//NORTH, SOUTH
|
||||
tinting.xyz *= 0.8f;
|
||||
} else if ((face>>1) == 2) {//EAST, WEST
|
||||
tinting.xyz *= 0.6f;
|
||||
} else if (face == 0) {//DOWN
|
||||
tinting.xyz *= 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
uvec4 interData;
|
||||
|
||||
interData.x = (modelId<<16) | flags | (uint(quadSize.x-1)<<8) | (uint(quadSize.y-1)<<12);
|
||||
|
||||
interData.y = packVec4(tinting);
|
||||
interData.z = conditionalTinting;
|
||||
interData.w = addin|(face<<8);
|
||||
|
||||
return interData;
|
||||
}
|
||||
|
||||
vec4 emitVertexPos(int corner) {
|
||||
vec3 pointPos = swizzelDataAxis(face>>1,vec3(axisFaceSize*mix(vec2(0),vec2(1<<task.lodLvl),bvec2((corner>>1)&1, corner&1)),0))+cornerPos;
|
||||
return MVP*vec4(pointPos, 1.0);
|
||||
}
|
||||
|
||||
#ifdef HAS_STATISTICS
|
||||
layout(binding = STATISTICS_BUFFER_BINDING, std430) restrict buffer statisticsBuffer {
|
||||
uint visibleSectionCounts[5];
|
||||
uint quadCounts[5];
|
||||
};
|
||||
#endif
|
||||
|
||||
void main() {
|
||||
if (subgroupElect()) {
|
||||
gl_PrimitiveCountNV = 0;
|
||||
}
|
||||
if (task.quadCount<=gl_GlobalInvocationID.x) {
|
||||
return;//dont emit a quad
|
||||
}
|
||||
uint qid = getQuadId() + task.baseQuad;
|
||||
Quad quad = quadData[qid];
|
||||
setup(quad);
|
||||
|
||||
bool render = dot(faceNormal(face), cornerPos-cameraSubPos) <= 0;
|
||||
subgroupBarrier();
|
||||
uint qId = subgroupExclusiveAdd(render?1:0);
|
||||
if (render) {
|
||||
|
||||
uvec4 data = createQuadData(quad);
|
||||
subgroupBarrier();
|
||||
primData[qId*2] = data;
|
||||
uvData[qId*2] = vec4(faceSize.xz, axisFaceSize);
|
||||
primData[qId*2+1] = data;
|
||||
uvData[qId*2+1] = vec4(faceSize.xz, axisFaceSize);
|
||||
|
||||
#define VID(i) (gl_LocalInvocationIndex*4+i)
|
||||
|
||||
gl_MeshVerticesNV[VID(0)].gl_Position = emitVertexPos(1);
|
||||
gl_MeshVerticesNV[VID(1)].gl_Position = emitVertexPos(2);
|
||||
|
||||
gl_MeshVerticesNV[VID(2)].gl_Position = emitVertexPos(0);
|
||||
gl_MeshVerticesNV[VID(3)].gl_Position = emitVertexPos(3);
|
||||
|
||||
gl_PrimitiveIndicesNV[qId*6+0] = VID(0);
|
||||
gl_PrimitiveIndicesNV[qId*6+1] = VID(1);
|
||||
gl_PrimitiveIndicesNV[qId*6+2] = VID(2);
|
||||
|
||||
gl_PrimitiveIndicesNV[qId*6+3] = VID(0);
|
||||
gl_PrimitiveIndicesNV[qId*6+4] = VID(3);
|
||||
gl_PrimitiveIndicesNV[qId*6+5] = VID(1);
|
||||
|
||||
gl_MeshPrimitivesNV[qId*2].gl_PrimitiveID = int(qid|(0u<<31));
|
||||
gl_MeshPrimitivesNV[qId*2+1].gl_PrimitiveID = int(qid|(1u<<31));
|
||||
|
||||
/*
|
||||
//vec4 p1 = ;
|
||||
//vec4 p2 = ;
|
||||
//vec4 p0 = emitVertexPos(0);
|
||||
//vec4 p3 = emitVertexPos(3);
|
||||
|
||||
//Emit common
|
||||
|
||||
{
|
||||
gl_PrimitiveIndicesNV[idxId++] = vertId_+0;
|
||||
gl_PrimitiveIndicesNV[idxId++] = vertId_+1;
|
||||
gl_PrimitiveIndicesNV[idxId++] = vertId;
|
||||
|
||||
gl_MeshVerticesNV[vertId++].gl_Position = p0;
|
||||
|
||||
primOut[triId].data = data;
|
||||
primOut[triId].uvData = vec4(faceSize.xz, axisFaceSize);
|
||||
gl_MeshPrimitivesNV[triId++].gl_PrimitiveID = int(qid|(0u<<31));
|
||||
}
|
||||
|
||||
{
|
||||
gl_PrimitiveIndicesNV[idxId++] = vertId_+0;
|
||||
gl_PrimitiveIndicesNV[idxId++] = vertId;
|
||||
gl_PrimitiveIndicesNV[idxId++] = vertId_+1;
|
||||
|
||||
gl_MeshVerticesNV[vertId++].gl_Position = p3;
|
||||
|
||||
primOut[triId].data = data;
|
||||
primOut[triId].uvData = vec4(faceSize.xz, axisFaceSize);
|
||||
gl_MeshPrimitivesNV[triId++].gl_PrimitiveID = int(qid|(1u<<31));
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
subgroupBarrier();
|
||||
uint count = subgroupMax(qId);
|
||||
if (count != 0 && subgroupElect()) {
|
||||
count = count *2+2;
|
||||
gl_PrimitiveCountNV = count;
|
||||
#ifdef HAS_STATISTICS
|
||||
atomicAdd(quadCounts[task.lodLvl], count);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
uint triId = subgroupExclusiveAdd(render?2:0);
|
||||
uint vertId = subgroupExclusiveAdd(render?4:0);
|
||||
if (render) {
|
||||
//common corners
|
||||
gl_MeshVerticesNV[vertId+0].gl_Position = emitVertexPos(1);
|
||||
gl_MeshVerticesNV[vertId+1].gl_Position = emitVertexPos(2);
|
||||
|
||||
//tri corners
|
||||
gl_MeshVerticesNV[vertId+2].gl_Position = emitVertexPos(0);
|
||||
gl_MeshVerticesNV[vertId+3].gl_Position = emitVertexPos(3);
|
||||
|
||||
//Emit tris
|
||||
uint idxId = triId*3;
|
||||
gl_PrimitiveIndicesNV[idxId++] = vertId+0;
|
||||
gl_PrimitiveIndicesNV[idxId++] = vertId+1;
|
||||
gl_PrimitiveIndicesNV[idxId++] = vertId+2;
|
||||
|
||||
gl_PrimitiveIndicesNV[idxId++] = vertId+0;
|
||||
gl_PrimitiveIndicesNV[idxId++] = vertId+3;
|
||||
gl_PrimitiveIndicesNV[idxId++] = vertId+1;
|
||||
|
||||
gl_MeshPrimitivesNV[triId].gl_PrimitiveID = int(qid);
|
||||
gl_MeshPrimitivesNV[triId+1].gl_PrimitiveID = int(qid);
|
||||
}*/
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user