138 Commits

Author SHA1 Message Date
mcrcortex
5c19a7f6d0 remove qualifiers 2025-09-06 08:53:49 +10:00
mcrcortex
aff34fb463 e 2025-07-18 13:12:56 +10:00
mcrcortex
94f2d03f90 notes 2025-07-15 13:38:40 +10:00
mcrcortex
45e2ea897f add textures 2025-07-15 12:10:33 +10:00
mcrcortex
2023ac6ad2 thanks lwjgl 2025-07-15 01:10:34 +10:00
mcrcortex
91b5710e00 Change structs to expected 2025-07-15 01:06:36 +10:00
mcrcortex
03b425eeb8 change to structs 2025-07-15 00:13:56 +10:00
mcrcortex
653dbc8a3a fix backface meshext 2025-07-14 19:44:41 +10:00
mcrcortex
97058f24b4 fix backface culling 2025-07-14 19:43:34 +10:00
mcrcortex
e227d84306 comment out iris 2025-07-14 17:26:26 +10:00
mcrcortex
f4e0dbb001 use 2025-07-14 16:53:48 +10:00
mcrcortex
43d04febd5 ext mesh 2025-07-14 16:53:33 +10:00
mcrcortex
1078507495 improvements and started frag impl (missing uv) 2025-07-14 16:01:28 +10:00
mcrcortex
294e1f9fb6 Demo mesh 2025-07-14 12:55:46 +10:00
mcrcortex
5fe5ebc0a2 Log 2025-07-13 21:37:25 +10:00
mcrcortex
aff30961bd tweek section meta 2025-07-13 18:13:15 +10:00
mcrcortex
64d211b333 use normal hiz 2025-07-13 17:40:23 +10:00
mcrcortex
606d3b2282 hiz2 2025-07-13 17:39:48 +10:00
mcrcortex
132c6aa2e8 thing 2025-07-12 18:48:25 +10:00
mcrcortex
4a140c110f Add more types 2025-07-12 14:14:53 +10:00
mcrcortex
1c8d052544 add todo and optimized imports 2025-07-12 14:13:16 +10:00
mcrcortex
3199b77ae5 Reorder operations in attempt to fix race conditions 2025-07-12 14:12:06 +10:00
mcrcortex
f0e1f18379 mark as compatible with both 1.21.7 and 1.21.6 2025-07-07 22:26:36 +10:00
mcrcortex
492e2a707a Fix compatibility when joml.fastmath is enabled, fixes physics mod causing everything to detonate
Version bump
2025-07-07 22:20:46 +10:00
mcrcortex
7551ca3484 readd override thing 2025-07-07 12:23:09 +10:00
mcrcortex
8f3fa2e7f2 start on no subgroup impl 2025-07-06 20:01:44 +10:00
mcrcortex
936619ce12 no abstract 2025-07-06 17:28:51 +10:00
mcrcortex
d6a42f8ef3 c 2025-07-03 11:06:15 +10:00
mcrcortex
bf43e405ff b 2025-07-03 00:49:42 +10:00
mcrcortex
0c7c33304d Attempt to fix login unable to get object 2025-07-03 00:31:55 +10:00
mcrcortex
f9b1d8a9e1 Check render type before baking model 2025-07-02 18:53:45 +10:00
mcrcortex
7b4fe4bd5c thing 2025-07-02 11:49:13 +10:00
mcrcortex
6ba3111ada fix default biomes on no biome section data biome 2025-07-02 00:03:14 +10:00
mcrcortex
258ccf89e0 move init 2025-07-01 10:33:47 +10:00
mcrcortex
3e193bb675 update 1.21.7 2025-07-01 09:04:31 +10:00
mcrcortex
69b96eee96 L 2025-06-30 19:23:07 +10:00
mcrcortex
e1ba2c4ebb make null before shutdown 2025-06-30 14:20:42 +10:00
mcrcortex
dfce9dae46 Logging and checking 2025-06-30 13:53:03 +10:00
mcrcortex
f4fca865bb add git ignore 2025-06-30 11:23:31 +10:00
mcrcortex
08fa0725d3 Beans 2025-06-30 11:21:43 +10:00
mcrcortex
b92b769f7b Attempt to fix weirdness on thread change while importing 2025-06-30 11:21:01 +10:00
mcrcortex
726517a8b6 Move to global cleaner + enable tracking for all but memory buffers 2025-06-27 22:35:52 +10:00
mcrcortex
51f54c6edd version bump 2025-06-27 20:14:44 +10:00
mcrcortex
f7f260777a changes to ingest 2025-06-26 11:58:40 +10:00
mcrcortex
dd9ac2819d version bump 2025-06-25 00:28:16 +10:00
mcrcortex
1a7bb8498e fast path on not windows 2025-06-25 00:17:26 +10:00
mcrcortex
fb2d26153d attempt ultimit jank to fix shader 2025-06-24 23:44:15 +10:00
mcrcortex
a640c0e62c Add geometry override 2025-06-24 20:14:22 +10:00
mcrcortex
784322db6f Added amd shader compiler driver segfault workaround 2025-06-24 20:01:45 +10:00
mcrcortex
355a63c46f Final attempt at fixing ingest lighting 2025-06-24 00:12:38 +10:00
mcrcortex
155eb75b82 Attempt fix tracking 2025-06-24 00:03:11 +10:00
mcrcortex
64d4ef0c03 attempt improve lighting thing 2025-06-23 23:22:48 +10:00
mcrcortex
edb15db8fa add classifier if not in gha 2025-06-23 22:29:06 +10:00
mcrcortex
883f140b41 Allow enabling debug flags 2025-06-23 22:26:53 +10:00
mcrcortex
90a6765e8a Move chunk ingest into client chunk manager 2025-06-23 22:22:40 +10:00
mcrcortex
b8ede978c2 fix very theoretical incorrect ordering issue 2025-06-23 21:30:43 +10:00
mcrcortex
c1091acc6b service name logging on error 2025-06-23 20:53:30 +10:00
mcrcortex
0034940082 manual artifact workflow 2025-06-23 20:30:24 +10:00
mcrcortex
4d35fad772 Try to get the chunk at all costs 2025-06-23 20:21:18 +10:00
mcrcortex
d86c3b2eb8 Decrease cache size if max memory is small 2025-06-23 19:39:18 +10:00
mcrcortex
b3556813a9 bvec3 2025-06-23 11:21:57 +10:00
mcrcortex
a94dcf1949 Fix mesa 2025-06-23 11:13:10 +10:00
mcrcortex
7fa07ae5ea Add support for vanilla enviromental fog 2025-06-23 00:51:54 +10:00
mcrcortex
cf60d31b75 update chunky 2025-06-22 23:03:54 +10:00
mcrcortex
e1b4e1ea6a micro optimizations 2025-06-22 21:51:07 +10:00
mcrcortex
4f6b0aa04d use textureLod 2025-06-22 17:42:32 +10:00
mcrcortex
8b5e2780c7 fix 64 sized warps 2025-06-22 16:52:08 +10:00
mcrcortex
0dd730d8de nope cant do that am stupid 2025-06-22 12:14:14 +10:00
mcrcortex
0f865c7afb dont remap pipeline 2025-06-22 11:58:00 +10:00
mcrcortex
688f24a409 bean 2025-06-22 11:52:30 +10:00
mcrcortex
dcacd279b3 readd nvidium support 2025-06-22 11:31:38 +10:00
mcrcortex
37d0b755af Fix issues 2025-06-22 11:06:15 +10:00
mcrcortex
26672ce34b Move more computation into frag shader 2025-06-22 10:48:51 +10:00
mcrcortex
d1be49f474 massivly shrinked interstage attributes 2025-06-21 20:55:53 +10:00
mcrcortex
87072a4edc attempt to improve mipping 2025-06-21 15:03:28 +10:00
mcrcortex
5f8679e5d2 Fix memory leak on reload while importing 2025-06-21 12:32:24 +10:00
mcrcortex
1a7cd37741 attempted to improve ingest perfomance by only saving on section unload 2025-06-21 12:25:46 +10:00
mcrcortex
ed181c1dcd changed priority 2025-06-19 22:43:27 +10:00
mcrcortex
4d839e3662 Attempt to reduce reaquires on miss 2025-06-19 22:15:29 +10:00
mcrcortex
156b30756d Attempted optimizations for world processing 2025-06-19 16:03:31 +10:00
mcrcortex
6326870525 Attempt to fix race condition.... _again_ 2025-06-19 15:33:38 +10:00
mcrcortex
a360c9349a woops 2025-06-19 13:08:45 +10:00
mcrcortex
9e6276e0fa Attempt fix capture index buffer before it gets large 2025-06-19 13:03:39 +10:00
mcrcortex
2bbc7a8999 change fence query 2025-06-19 12:45:21 +10:00
mcrcortex
fc3e05434f add fog override (hackily) back 2025-06-18 10:05:29 +10:00
mcrcortex
388764e9c8 mostly finished 1.21.6, except fog 2025-06-18 09:11:25 +10:00
mcrcortex
3fb8323dd0 Merge branch 'mc_1215' into mc_1216 2025-06-18 08:55:06 +10:00
mcrcortex
2327c6baf8 thing 2025-06-18 08:53:32 +10:00
mcrcortex
144faf5b21 move frex check loop 2025-06-17 12:52:55 +10:00
mcrcortex
dc6dd4bb11 remove pow for now 2025-06-16 23:40:10 +10:00
mcrcortex
84482e8998 Make the request queue dynamically capped, greatly increasing snappyness 2025-06-16 23:39:36 +10:00
mcrcortex
072ece7a3d add max 2025-06-16 21:36:39 +10:00
mcrcortex
22d557ed01 am stupid 2025-06-16 21:33:48 +10:00
mcrcortex
b454d54a99 it works 2025-06-16 20:45:49 +10:00
mcrcortex
341119386a poc 2025-06-16 20:17:44 +10:00
mcrcortex
1575d7319c big progress in fixing culling 2025-06-16 20:03:28 +10:00
mcrcortex
950e92d7c7 h 2025-06-16 20:03:12 +10:00
mcrcortex
0e98f52580 improved logger 2025-06-16 19:28:32 +10:00
mcrcortex
caf2703102 small thing 2025-06-16 12:41:29 +10:00
mcrcortex
b79923de3d Remove setup 2025-06-16 12:22:26 +10:00
mcrcortex
ee6d171ef6 Note 2025-06-16 12:21:37 +10:00
mcrcortex
1c30198347 Remove an info log 2025-06-14 13:54:31 +10:00
mcrcortex
4ed9199e1c attempt improvments is not :( 2025-06-14 00:20:23 +10:00
mcrcortex
2027cb064c works 2025-06-14 00:14:18 +10:00
mcrcortex
a00eec69b7 a 2025-06-13 23:19:23 +10:00
mcrcortex
3aa1c94c6a inital 1.21.6 2025-06-13 14:49:03 +10:00
mcrcortex
84c07c4115 tweeked fences? 2025-06-10 20:45:39 +10:00
mcrcortex
6398164d42 attempt to improve the data preperation of render factory 2025-06-10 20:37:34 +10:00
mcrcortex
fa42ad5a03 remove copies 2025-06-10 19:25:56 +10:00
mcrcortex
22553eb1f9 attempted to improve mipping by computing dx,dy accross entire uv instead of local uv 2025-06-10 13:08:26 +10:00
mcrcortex
cb599eea0b misc fixes 2025-06-09 20:47:40 +10:00
mcrcortex
f73413e7c0 Dont deadlock 2025-06-09 20:26:19 +10:00
mcrcortex
5b752d3f87 remove unused 2025-06-09 19:25:48 +10:00
mcrcortex
4f37d3b597 remove old memory patch 2025-06-07 20:32:42 +10:00
mcrcortex
21b497d2d4 force gl finish 2025-06-07 19:29:56 +10:00
mcrcortex
3bfc0c266d change to stable hash 2025-06-07 16:10:46 +10:00
mcrcortex
f252fa3a7a Make into error 2025-06-07 14:41:18 +10:00
mcrcortex
66266fb426 Note 2025-06-07 14:38:02 +10:00
mcrcortex
225e2d9d1a is ment to be * not + idiot 2025-06-07 14:37:02 +10:00
mcrcortex
3b4aa75890 am dumb stupid or also dumb, how this did not cause multiple things to immediatly explode 18 times, have no idea 2025-06-07 14:04:45 +10:00
mcrcortex
0c1917d56e hopefully fix issues 2025-06-07 12:02:30 +10:00
mcrcortex
35850082d5 Shuffled around shaders 2025-06-06 17:00:25 +10:00
mcrcortex
d24b719a93 Move tests 2025-06-06 12:02:54 +10:00
mcrcortex
a0c33a439b subtract 1gb from memory limit instead of 512 mb 2025-06-04 16:21:14 +10:00
mcrcortex
6bbd2c521a Use representitive fragment when possible 2025-06-03 22:14:46 +10:00
mcrcortex
7575c35b02 tweek build script 2025-06-03 16:18:31 +10:00
mcrcortex
f78a8df275 fix possible issue of deadlock with geometry cleaner 2025-06-03 01:02:57 +10:00
mcrcortex
8462dde374 use provoking vertex 2025-06-03 00:08:01 +10:00
mcrcortex
075e8f2897 attempt to improve rocksdb options 2025-06-02 22:12:29 +10:00
mcrcortex
204989b909 tweeks + version number in config 2025-06-02 21:28:15 +10:00
mcrcortex
c023e3b4f2 Fix face baking not being flipped, hopefully 2025-06-02 12:46:38 +10:00
mcrcortex
e7c4d6f132 more state checking 2025-06-02 12:23:06 +10:00
mcrcortex
9d0cf33a45 Attempted fix for world timeout 2025-06-02 11:47:12 +10:00
mcrcortex
34c5c71d77 change to logger 2025-05-30 20:48:37 +10:00
mcrcortex
03bede4067 more logic errors fixed 2025-05-29 23:32:33 +10:00
mcrcortex
f624f85698 nvm am just stupid 2025-05-29 23:29:44 +10:00
mcrcortex
985fa4b53c apparently . is converted into a folder and becomes .\ instead of .voxy 2025-05-29 23:26:53 +10:00
mcrcortex
6c6c08d188 better debug logging + fix tracking crash 2025-05-29 23:13:20 +10:00
111 changed files with 3650 additions and 1318 deletions

33
.github/workflows/manual-artifact.yml vendored Normal file
View 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
View File

@@ -4,3 +4,4 @@
/build/
/run/
/out/
/logs/

View File

@@ -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,12 +206,16 @@ processIncludeJars {
}
remapJar {
delete getDestinationDirectory().get()
doFirst {
delete fileTree(getDestinationDirectory().get())
}
if (!isInGHA) {
def hash = gitCommitHash();
if (!hash.equals("<UnknownCommit>")) {
archiveClassifier.set(hash);
}
}
}
project.ext.lwjglVersion = "3.3.3"
@@ -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'))

View File

@@ -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

View File

@@ -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);
}

View File

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

View File

@@ -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.");
}

View File

@@ -29,6 +29,11 @@ public class VoxyClientInstance extends VoxyInstance {
private final Path basePath = getBasePath();
public VoxyClientInstance() {
super(VoxyConfig.CONFIG.serviceThreads);
try {
Files.createDirectories(this.basePath);
} catch (Exception e) {
throw new RuntimeException(e);
}
this.storageConfig = getCreateStorageConfig(this.basePath);
}
@@ -75,26 +80,25 @@ public class VoxyClientInstance extends VoxyInstance {
try {
config = Serialization.GSON.fromJson(Files.readString(json), Config.class);
if (config == null) {
throw new IllegalStateException("Config deserialization null, reverting to default");
}
Logger.error("Config deserialization null, reverting to default");
} else {
if (config.sectionStorageConfig == null) {
throw new IllegalStateException("Config section storage null, reverting to default");
Logger.error("Config section storage null, reverting to default");
config = null;
}
}
} catch (Exception e) {
Logger.error("Failed to load the storage configuration file, resetting it to default, this will probably break your save if you used a custom storage config", e);
}
}
try {
if (config == null) {
config = DEFAULT_STORAGE_CONFIG;
}
try {
Files.writeString(json, Serialization.GSON.toJson(config));
} catch (IOException e) {
throw new RuntimeException(e);
}
} catch (Exception e) {
throw new RuntimeException("Failed to deserialize the default config, aborting!", e);
throw new RuntimeException("Failed write the config, aborting!", e);
}
if (config == null) {
throw new IllegalStateException("Config is still null\n");
@@ -103,6 +107,7 @@ public class VoxyClientInstance extends VoxyInstance {
}
private static class Config {
public int version = 1;
public SectionStorageConfig sectionStorageConfig;
}
private static final Config DEFAULT_STORAGE_CONFIG;
@@ -152,96 +157,4 @@ public class VoxyClientInstance extends VoxyInstance {
}
return basePath.toAbsolutePath();
}
/*
private static void testDbPerformance(WorldEngine engine) {
Random r = new Random(123456);
r.nextLong();
long start = System.currentTimeMillis();
int c = 0;
long tA = 0;
long tR = 0;
for (int i = 0; i < 1_000_000; i++) {
if (i == 20_000) {
c = 0;
start = System.currentTimeMillis();
}
c++;
int x = (r.nextInt(256*2+2)-256);//-32
int z = (r.nextInt(256*2+2)-256);//-32
int y = r.nextInt(2)-1;
int lvl = 0;//r.nextInt(5);
long t = System.nanoTime();
var sec = engine.acquire(WorldEngine.getWorldSectionId(lvl, x>>lvl, y>>lvl, z>>lvl));
tA += System.nanoTime()-t;
t = System.nanoTime();
sec.release();
tR += System.nanoTime()-t;
}
long delta = System.currentTimeMillis() - start;
System.out.println("Total "+delta+"ms " + ((double)delta/c) + "ms average tA: " + tA + " tR: " + tR);
}
private static void testDbPerformance2(WorldEngine engine) {
Random r = new Random(123456);
r.nextLong();
ConcurrentLinkedDeque<Long> queue = new ConcurrentLinkedDeque<>();
var ser = engine.instanceIn.getThreadPool().createServiceNoCleanup("aa", 1, ()-> () ->{
var sec = engine.acquire(queue.poll());
sec.release();
});
int priming = 1_000_000;
for (int i = 0; i < 2_000_000+priming; i++) {
int x = (r.nextInt(256*2+2)-256)>>2;//-32
int z = (r.nextInt(256*2+2)-256)>>2;//-32
int y = r.nextInt(2)-1;
int lvl = 0;//r.nextInt(5);
queue.add(WorldEngine.getWorldSectionId(lvl, x>>lvl, y>>lvl, z>>lvl));
}
for (int i = 0; i < priming; i++) {
ser.execute();
}
ser.blockTillEmpty();
int c = queue.size();
long start = System.currentTimeMillis();
for (int i = 0; i < c; i++) {
ser.execute();
}
ser.blockTillEmpty();
long delta = System.currentTimeMillis() - start;
ser.shutdown();
System.out.println("Total "+delta+"ms " + ((double)delta/c) + "ms average total, avg wrt threads: " + (((double)delta/c)*engine.instanceIn.getThreadPool().getThreadCount()) + "ms");
}
private void verifyTopNodeChildren(int X, int Y, int Z) {
var world = this.getOrMakeRenderWorld(MinecraftClient.getInstance().world);
for (int lvl = 0; lvl < 5; lvl++) {
for (int y = (Y<<5)>>lvl; y < ((Y+1)<<5)>>lvl; y++) {
for (int x = (X<<5)>>lvl; x < ((X+1)<<5)>>lvl; x++) {
for (int z = (Z<<5)>>lvl; z < ((Z+1)<<5)>>lvl; z++) {
if (lvl == 0) {
var own = world.acquire(lvl, x, y, z);
if ((own.getNonEmptyChildren() != 0) ^ (own.getNonEmptyBlockCount() != 0)) {
Logger.error("Lvl 0 node not marked correctly " + WorldEngine.pprintPos(own.key));
}
own.release();
} else {
byte msk = 0;
for (int child = 0; child < 8; child++) {
var section = world.acquire(lvl-1, (child&1)+(x<<1), ((child>>2)&1)+(y<<1), ((child>>1)&1)+(z<<1));
msk |= (byte) (section.getNonEmptyBlockCount()!=0?(1<<child):0);
section.release();
}
var own = world.acquire(lvl, x, y, z);
if (own.getNonEmptyChildren() != msk) {
Logger.error("Section empty child mask not correct " + WorldEngine.pprintPos(own.key) + " got: " + String.format("%8s", Integer.toBinaryString(Byte.toUnsignedInt(own.getNonEmptyChildren()))).replace(' ', '0') + " expected: " + String.format("%8s", Integer.toBinaryString(Byte.toUnsignedInt(msk))).replace(' ', '0'));
}
own.release();
}
}
}
}
}
}
*/
}

View File

@@ -28,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() {

View File

@@ -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"))

View File

@@ -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,15 +46,19 @@ 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;
int minSec = MinecraftClient.getInstance().world.getBottomSectionCoord() >> 5;
int maxSec = (MinecraftClient.getInstance().world.getTopSectionCoord() - 1) >> 5;
//Do some very cheeky stuff for MiB
if (false) {
@@ -86,9 +75,10 @@ public class VoxyRenderSystem {
this.renderDistanceTracker.setRenderDistance(VoxyConfig.CONFIG.sectionRenderDistance);
this.chunkBoundRenderer = new ChunkBoundRenderer();
//Keep the world loaded
this.worldIn.acquireRef();
} catch (RuntimeException e) {
world.releaseRef();//If something goes wrong, we must release the world first
throw e;
}
}
public void setRenderDistance(int renderDistance) {
@@ -96,40 +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();
}
}

View File

@@ -13,6 +13,7 @@ public class Capabilities {
public static final Capabilities INSTANCE = new Capabilities();
public final boolean repFragTest;
public final boolean meshShaders;
public final boolean INT64_t;
public final long ssboMaxSize;
@@ -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

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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");
};

View File

@@ -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();

View File

@@ -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);

View File

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

View File

@@ -8,6 +8,7 @@ import me.cortex.voxy.client.core.gl.Capabilities;
import me.cortex.voxy.client.core.model.bakery.ModelTextureBakery;
import me.cortex.voxy.client.core.rendering.util.RawDownloadStream;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.util.MemoryBuffer;
import me.cortex.voxy.common.world.other.Mapper;
import net.minecraft.block.Block;
@@ -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

View File

@@ -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));
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);
}
@@ -303,24 +323,36 @@ public class ModelTextureBakery {
static {
//TODO: FIXME: need to bake in the correct orientation, HOWEVER some orientations require a flipped winding order!!!!
//the face/direction is the face (e.g. down is the down face)
addView(0, -90,0, 0, 0);//Direction.DOWN
addView(1, 90,0, 0, 0b100);//Direction.UP
addView(0, -90,0, 0, false);//Direction.DOWN
addView(1, 90,0, 0, false);//Direction.UP
addView(2, 0,180, 0, true);//Direction.NORTH
addView(3, 0,0, 0, false);//Direction.SOUTH
//TODO: check these arnt the wrong way round
addView(4, 0,90, 270, false);//Direction.EAST
addView(5, 0,270, 270, false);//Direction.WEST
addView(2, 0,180, 0, 0b001);//Direction.NORTH
addView(3, 0,0, 0, 0);//Direction.SOUTH
addView(4, 0,90, 270, 0b100);//Direction.WEST
addView(5, 0,270, 270, 0);//Direction.EAST
}
private static void addView(int i, float pitch, float yaw, float rotation, boolean flipX) {
private static void addView(int i, float pitch, float yaw, float rotation, int flip) {
var stack = new MatrixStack();
stack.translate(0.5f,0.5f,0.5f);
stack.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(rotation));
stack.multiply(RotationAxis.POSITIVE_X.rotationDegrees(pitch));
stack.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw));
stack.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));
}
}

View File

@@ -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();

View File

@@ -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);

View File

@@ -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();
}

View File

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

View File

@@ -179,6 +179,7 @@ public class AsyncNodeManager {
private void run() {
if (this.workCounter.get() <= 0) {
//TODO: here, instead of parking, we can do more work on other sub-tasks such as filtering the mesh build queue
LockSupport.park();
if (this.workCounter.get() <= 0 || !this.running) {//No work
return;
@@ -245,8 +246,7 @@ public class AsyncNodeManager {
//Limit uploading as well as by geometry capacity being available
// must have 50 mb of free geometry space to upload
for (int limit = 0; limit < 200 && (this.geometryCapacity-this.geometryManager.getGeometryUsedBytes())>50_000_000; limit++)
{
for (int limit = 0; limit < 200 && ((this.geometryCapacity-this.geometryManager.getGeometryUsedBytes())>50_000_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) {

View File

@@ -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

View File

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

View File

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

View File

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

View File

@@ -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();
}

View File

@@ -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);

View File

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

View File

@@ -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();

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

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

View File

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

View File

@@ -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
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}
}

View File

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

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();

View File

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

View File

@@ -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() {

View File

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

View File

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

View File

@@ -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;

View File

@@ -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);
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);
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() {

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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));
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -1,5 +0,0 @@
package me.cortex.voxy.common.util;
public class VolatileHolder <T> {
public volatile T obj;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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,6 +169,11 @@ public class ActiveSectionTracker {
Thread.yield();
}
//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()) {
@@ -157,20 +186,45 @@ public class ActiveSectionTracker {
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));

View File

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

View File

@@ -10,11 +10,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);
final long lutBasePtr = ptr + WorldSection.SECTION_VOLUME * 2;
if (section.lvl == 0) {
int nonEmptyBlockCount = 0;
long lutBasePtr = ptr + WorldSection.SECTION_VOLUME*2;
var blockData = section.data;
final 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;
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;
ptr = lutBasePtr + (metadata&0xFFFF)*8L;
} 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;
}
}
ptr = lutBasePtr + (metadata & 0xFFFF) * 8L;
return true;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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 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;
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];
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 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;
int cSecIdx = secIdx + baseSec;
secIdx = (secIdx + iSecMsk1) & secMsk;
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
long oldId = secD[cSecIdx];
didStateChange |= newId != oldId;
secD[cSecIdx] = newId;
}
}
}
if (lvl == 0) {
int nonAirCountDelta = section.lvl0NonAirCount-(4096-airCount);
if (nonAirCountDelta != 0) {
worldSection.addNonEmptyBlockCount(nonAirCountDelta);
if (lvl == 0) {
emptinessStateChange = worldSection.updateLvl0State() ? 2 : 0;
}
}

View File

@@ -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,8 +299,13 @@ public class Mapper {
public StateEntry(int id, BlockState state) {
this.id = id;
this.state = state;
//Override opacity of leaves to be solid
if (state.getBlock() instanceof LeavesBlock) {
this.opacity = 15;
} else {
this.opacity = state.getOpacity();
}
}
public byte[] serialize() {
try {

View File

@@ -28,8 +28,9 @@ public class SectionSavingService {
var section = task.section;
section.assertNotFree();
try {
section.inSaveQueue.set(false);
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) {
//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();
}

View File

@@ -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);
var bl = blp.getLightSection(pos);
if (!(bl == null || bl.isUninitialized())) {
bl = bl.copy();
} else {
bl = null;
}
var sl = slp.getLightSection(pos);
if (!(sl == null || sl.isUninitialized())) {
sl = sl.copy();
} else {
sl = null;
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 ((bl == null && sl == null) && section.isEmpty()) {
continue;
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 = bl.copy();
}
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));
try {
this.threads.execute();
} catch (Exception e) {
Logger.error("Executing had an error: assume shutting down, aborting",e);
break;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}

View File

@@ -27,7 +27,7 @@ public class WorldIdentifier {
this.key = key;
this.biomeSeed = biomeSeed;
this.dimension = dimension;
this.hashCode = mixStafford13(key.hashCode()^biomeSeed)^mixStafford13(dimension.hashCode()^biomeSeed);
this.hashCode = mixStafford13(registryKeyHashCode(key))^mixStafford13(registryKeyHashCode(dimension))^mixStafford13(biomeSeed);
}
@Override
@@ -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);
}
}

View File

@@ -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");
}
}

View File

@@ -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);

View File

@@ -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(),

View File

@@ -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",

View File

@@ -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
}

View 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));
}
}

View File

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

View File

@@ -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;

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,8 @@
#version 460 core
//Use quad shuffling to compute fragment mip
//#extension GL_KHR_shader_subgroup_quad: enable
layout(binding = 0) uniform sampler2D blockModelAtlas;
layout(binding = 2) uniform sampler2D depthTex;
@@ -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
@@ -57,3 +106,29 @@ void main() {
outColour = vec4(float(hash&15u)/15, float((hash>>4)&15u)/15, float((hash>>8)&15u)/15, 1);
#endif
}
//#ifdef GL_KHR_shader_subgroup_quad
/*
uint hash = (uint(tile.x)*(1<<16))^uint(tile.y);
uint horiz = subgroupQuadSwapHorizontal(hash);
bool sameTile = horiz==hash;
uint sv = mix(uint(-1), hash, sameTile);
uint vert = subgroupQuadSwapVertical(sv);
sameTile = sameTile&&vert==hash;
mipBias = sameTile?0:-5.0;
*/
/*
vec2 uvSmol = uv*(1.0/(vec2(3.0,2.0)*256.0));
float lDx = subgroupQuadSwapHorizontal(uvSmol.x)-uvSmol.x;
float lDy = subgroupQuadSwapVertical(uvSmol.y)-uvSmol.y;
float dDx = subgroupQuadSwapDiagonal(lDx);
float dDy = subgroupQuadSwapDiagonal(lDy);
vec2 dx = vec2(lDx, dDx);
vec2 dy = vec2(lDy, dDy);
colour = textureGrad(blockModelAtlas, texPos, dx, dy);
*/
//#else
//colour = texture(blockModelAtlas, texPos);
//#endif

View File

@@ -2,7 +2,6 @@
#extension GL_ARB_gpu_shader_int64 : enable
#define QUAD_BUFFER_BINDING 1
#define SECTION_METADATA_BUFFER_BINDING 2
#define MODEL_BUFFER_BINDING 3
#define MODEL_COLOUR_BUFFER_BINDING 4
#define POSITION_SCRATCH_BINDING 5
@@ -10,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

View File

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

View File

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

View File

@@ -12,7 +12,7 @@
// substantually for performance (for both persistent threads and incremental)
layout(binding = HIZ_BINDING) uniform sampler2DShadow hizDepthSampler;
layout(binding = HIZ_BINDING) uniform sampler2D hizDepthSampler;
//TODO: maybe do spher bounds aswell? cause they have different accuracies but are both over estimates (liberals (non conservative xD))
// so can do &&
@@ -39,7 +39,6 @@ bool checkPointInView(vec4 point) {
vec3 minBB = vec3(0.0f);
vec3 maxBB = vec3(0.0f);
vec2 size = vec2(0.0f);
bool insideFrustum = false;
float screenSize = 0.0f;
@@ -117,7 +116,8 @@ void setupScreenspace(in UnpackedNode node) {
minBB = min(min(min(p000, p100), min(p001, p101)), min(min(p010, p110), min(p011, p111)));
maxBB = max(max(max(p000, p100), max(p001, p101)), max(max(p010, p110), max(p011, p111)));
size = clamp(maxBB.xy - minBB.xy, vec2(0), vec2(1));
minBB = clamp(minBB, vec3(0), vec3(1));
maxBB = clamp(maxBB, vec3(0), vec3(1));
}
//Checks if the node is implicitly culled (outside frustum)
@@ -128,32 +128,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;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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);
*/
}

View 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