Compare commits
358 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e065e4df4 | |||
| 4c0399ca40 | |||
| 49e92b4190 | |||
| fa751fd473 | |||
| 0ccd024f37 | |||
| 2236bf19a5 | |||
|
|
c5e447125b | ||
|
|
cff4866499 | ||
|
|
935685ad08 | ||
|
|
848efe60a8 | ||
|
|
470534831b | ||
|
|
df90323fc7 | ||
|
|
f703d83b91 | ||
|
|
51f1197c9f | ||
|
|
1e92902724 | ||
|
|
0f6a099345 | ||
|
|
a7dc2112fa | ||
|
|
837b779aa9 | ||
|
|
692ac95549 | ||
|
|
3f0a1466ac | ||
|
|
7bc2c2b960 | ||
|
|
1e7b199660 | ||
|
|
61da430895 | ||
|
|
263f93215a | ||
|
|
79890fde1e | ||
|
|
3cc5afc1e1 | ||
|
|
3bcdbbec90 | ||
|
|
6212d95cdd | ||
|
|
b086832333 | ||
|
|
e86beefbc7 | ||
|
|
45ff6c4414 | ||
|
|
ee7ec50d44 | ||
|
|
f272042a76 | ||
|
|
b5c31478fb | ||
|
|
b68d5b3c66 | ||
|
|
342043674d | ||
|
|
d30ea7ecc7 | ||
|
|
bca46143fb | ||
|
|
1e4500a912 | ||
|
|
bc995f9c0f | ||
|
|
86ce0c0f98 | ||
|
|
4ffb7583a7 | ||
|
|
ef1a296998 | ||
|
|
0428153cf5 | ||
|
|
fb9b7923b0 | ||
|
|
5b697ddb04 | ||
|
|
25463a4c11 | ||
|
|
8247248c62 | ||
|
|
6beb6b2037 | ||
|
|
65e10c2c68 | ||
|
|
362998cc5f | ||
|
|
76cfef5b3c | ||
|
|
15604c16bf | ||
|
|
85638fce98 | ||
|
|
2bf9af00e5 | ||
|
|
6724157f9a | ||
|
|
bbf7d60abe | ||
|
|
b7f5798ecd | ||
|
|
c4f799ff53 | ||
|
|
561337e10c | ||
|
|
4ca9cdbaa8 | ||
|
|
a7ea5b2f99 | ||
|
|
c7cf4a74d5 | ||
|
|
03d971385c | ||
|
|
afe41a10b5 | ||
|
|
2458a4a3f6 | ||
|
|
edd0ce33ef | ||
|
|
f713ef2e8f | ||
|
|
0f9287adcb | ||
|
|
823babef81 | ||
|
|
66a2061813 | ||
|
|
26189d4739 | ||
|
|
53f857771e | ||
|
|
aa314781d6 | ||
|
|
7f49a84215 | ||
|
|
43ed8145ff | ||
|
|
37b7feecd5 | ||
|
|
e7deeed34e | ||
|
|
18b13370d8 | ||
|
|
d371e80e3b | ||
|
|
f887b1ac60 | ||
|
|
e873ce76c6 | ||
|
|
b688dc38d6 | ||
|
|
9d8ade88f3 | ||
|
|
4d8de7eb3d | ||
|
|
560ba41fa5 | ||
|
|
937eaa5fb1 | ||
|
|
bf5b8d76d2 | ||
|
|
1c1d812bbf | ||
|
|
8426006435 | ||
|
|
1c9ef1ea16 | ||
|
|
4d5de7aa36 | ||
|
|
8c6e6f75c6 | ||
|
|
9aebf20aad | ||
|
|
5baf4ce2db | ||
|
|
86882d5ac9 | ||
|
|
601c2a2fea | ||
|
|
141c2b1f98 | ||
|
|
96bd651f5c | ||
|
|
f91f59f1f8 | ||
|
|
e81d442b8b | ||
|
|
a8075158ba | ||
|
|
36325a0946 | ||
|
|
c8f552f389 | ||
|
|
b27d6323e7 | ||
|
|
a17711429c | ||
|
|
31cbe85588 | ||
|
|
4172ba357e | ||
|
|
b6741241f6 | ||
|
|
d909d82c33 | ||
|
|
8d4d823537 | ||
|
|
ec221ed820 | ||
|
|
72fd785855 | ||
|
|
45f3397ca9 | ||
|
|
703bf5f1d8 | ||
|
|
2534740cda | ||
|
|
85ce43be12 | ||
|
|
3df37b1102 | ||
|
|
4863f607b8 | ||
|
|
7b1b08c997 | ||
|
|
e3f3a7536d | ||
|
|
210ed1f8af | ||
|
|
dd854b5478 | ||
|
|
c739e545f2 | ||
|
|
d2a5b1e607 | ||
|
|
6ffa90f064 | ||
|
|
3de5740790 | ||
|
|
146cffc8d9 | ||
|
|
cb084e116e | ||
|
|
bc590c9d81 | ||
|
|
842987e891 | ||
|
|
c4769c4f1a | ||
|
|
4a98df4740 | ||
|
|
fbe001f559 | ||
|
|
b15b70860b | ||
|
|
01fe58172c | ||
|
|
a09708bb30 | ||
|
|
b465871bd7 | ||
|
|
b59cf5d700 | ||
|
|
06537a8815 | ||
|
|
46a0c982e1 | ||
|
|
87c7f37bb1 | ||
|
|
a54b97d04b | ||
|
|
2c62c876fa | ||
|
|
a0512ef044 | ||
|
|
ddbc1c27e4 | ||
|
|
2f49d03953 | ||
|
|
3350e01eca | ||
|
|
eb8cd8ba62 | ||
|
|
dbac73048f | ||
|
|
c9eddb2cbb | ||
|
|
8f34fc24fb | ||
|
|
f4509d6f0d | ||
|
|
0d79e316e3 | ||
|
|
12b0c29c01 | ||
|
|
4dcb7cad6d | ||
|
|
e2beabe2d3 | ||
|
|
34caf07d8c | ||
|
|
ab1c8b0f71 | ||
|
|
b2d62ec807 | ||
|
|
72d1fccacf | ||
|
|
241edb946b | ||
|
|
e855f87011 | ||
|
|
792f248c38 | ||
|
|
7cf33bc0cd | ||
|
|
4d4f4abcfc | ||
|
|
5ca07e574a | ||
|
|
02c9d0aa5e | ||
|
|
69511b6d1f | ||
|
|
8e111aa7e4 | ||
|
|
f846ec0825 | ||
|
|
1d53f658a7 | ||
|
|
303d3f0f46 | ||
|
|
35bd5fa414 | ||
|
|
c2f6fda5c8 | ||
|
|
83eea856e1 | ||
|
|
da5d7bc95e | ||
|
|
1438c16558 | ||
|
|
0968b25968 | ||
|
|
b364713268 | ||
|
|
ff7aecadb2 | ||
|
|
02d0d024d6 | ||
|
|
471d00b534 | ||
|
|
0cf46b3d5d | ||
|
|
bc4bf03a64 | ||
|
|
77d51dd27d | ||
|
|
474a8a7e3c | ||
|
|
ae7004f5d8 | ||
|
|
944e1c3c7f | ||
|
|
83975c8a98 | ||
|
|
6f748cbe12 | ||
|
|
7ca910e35a | ||
|
|
3f94bba49e | ||
|
|
301d587535 | ||
|
|
7b15dbbea3 | ||
|
|
e0d125906b | ||
|
|
d5ab22c7f3 | ||
|
|
000fa24b2f | ||
|
|
b1582e5a1f | ||
|
|
4ba2b1ca1f | ||
|
|
7ec41006f7 | ||
|
|
25ac827865 | ||
|
|
a6710c3e2e | ||
|
|
6539d67087 | ||
|
|
5781f17858 | ||
|
|
3ceba131f1 | ||
|
|
f0efd36674 | ||
|
|
d78653a76f | ||
|
|
b86546a178 | ||
|
|
465a55a77e | ||
|
|
1b023f859b | ||
|
|
9edb680114 | ||
|
|
a47876aca8 | ||
|
|
ad182d4170 | ||
|
|
2a4d6d085c | ||
|
|
7605ebf48d | ||
|
|
9f4282e37b | ||
|
|
81741974bb | ||
|
|
a2722b98de | ||
|
|
a012cead98 | ||
|
|
3513192907 | ||
|
|
99ee9a5ad2 | ||
|
|
cb9b41baf6 | ||
|
|
effabf95fd | ||
|
|
8615f29132 | ||
|
|
dc94b60121 | ||
|
|
8c5672d5fb | ||
|
|
75a9df969b | ||
|
|
c268306bd6 | ||
|
|
e2342a75dd | ||
|
|
37323f9170 | ||
|
|
aa48dbbf86 | ||
|
|
ae54c6ebea | ||
|
|
8384b9f88b | ||
|
|
ea884f1583 | ||
|
|
c506865d7b | ||
|
|
920d7db5f6 | ||
|
|
35f93122e5 | ||
|
|
ed04e21861 | ||
|
|
c25643525a | ||
|
|
f031d761b3 | ||
|
|
f3fa371ef9 | ||
|
|
0d09f4c11f | ||
|
|
dd4f1de695 | ||
|
|
269c7da0ac | ||
|
|
0ed453fe1d | ||
|
|
f2bcfca8e8 | ||
|
|
d8324dacd4 | ||
|
|
544c1df366 | ||
|
|
63a969e5df | ||
|
|
552a6e33a6 | ||
|
|
3533d2356c | ||
|
|
120f1e2018 | ||
|
|
c92c1d5b4a | ||
|
|
2fd686a5e6 | ||
|
|
aa185f11d7 | ||
|
|
7fab36ff3e | ||
|
|
cc609bbb07 | ||
|
|
9c74f92147 | ||
|
|
3b113e4914 | ||
|
|
33d50aed67 | ||
|
|
a9c2d57bc5 | ||
|
|
3d2693796d | ||
|
|
70a937bcaa | ||
|
|
3e50c95c91 | ||
|
|
a0f9b78162 | ||
|
|
d507429b9b | ||
|
|
634d187c11 | ||
|
|
11f7041df0 | ||
|
|
2956872970 | ||
|
|
c8b0df6ff9 | ||
|
|
16952e13e4 | ||
|
|
e9fba367c0 | ||
|
|
db06516f97 | ||
|
|
ff8e96e293 | ||
|
|
454a5a0e11 | ||
|
|
7b456e3d98 | ||
|
|
0514528a4c | ||
|
|
bdcdae791e | ||
|
|
4d59d05ad6 | ||
|
|
00928fdb88 | ||
|
|
4a34b29b46 | ||
|
|
f590ad704e | ||
|
|
99a1bb1dc9 | ||
|
|
5fe5ebc0a2 | ||
|
|
aff30961bd | ||
|
|
64d211b333 | ||
|
|
606d3b2282 | ||
|
|
132c6aa2e8 | ||
|
|
4a140c110f | ||
|
|
1c8d052544 | ||
|
|
3199b77ae5 | ||
|
|
f0e1f18379 | ||
|
|
492e2a707a | ||
|
|
7551ca3484 | ||
|
|
8f3fa2e7f2 | ||
|
|
936619ce12 | ||
|
|
d6a42f8ef3 | ||
|
|
bf43e405ff | ||
|
|
0c7c33304d | ||
|
|
f9b1d8a9e1 | ||
|
|
7b4fe4bd5c | ||
|
|
6ba3111ada | ||
|
|
258ccf89e0 | ||
|
|
3e193bb675 | ||
|
|
69b96eee96 | ||
|
|
e1ba2c4ebb | ||
|
|
dfce9dae46 | ||
|
|
f4fca865bb | ||
|
|
08fa0725d3 | ||
|
|
b92b769f7b | ||
|
|
726517a8b6 | ||
|
|
51f54c6edd | ||
|
|
f7f260777a | ||
|
|
dd9ac2819d | ||
|
|
1a7bb8498e | ||
|
|
fb2d26153d | ||
|
|
a640c0e62c | ||
|
|
784322db6f | ||
|
|
355a63c46f | ||
|
|
155eb75b82 | ||
|
|
64d4ef0c03 | ||
|
|
edb15db8fa | ||
|
|
883f140b41 | ||
|
|
90a6765e8a | ||
|
|
b8ede978c2 | ||
|
|
c1091acc6b | ||
|
|
0034940082 | ||
|
|
4d35fad772 | ||
|
|
d86c3b2eb8 | ||
|
|
b3556813a9 | ||
|
|
a94dcf1949 | ||
|
|
7fa07ae5ea | ||
|
|
cf60d31b75 | ||
|
|
e1b4e1ea6a | ||
|
|
4f6b0aa04d | ||
|
|
8b5e2780c7 | ||
|
|
0dd730d8de | ||
|
|
0f865c7afb | ||
|
|
688f24a409 | ||
|
|
dcacd279b3 | ||
|
|
37d0b755af | ||
|
|
26672ce34b | ||
|
|
d1be49f474 | ||
|
|
87072a4edc | ||
|
|
5f8679e5d2 | ||
|
|
1a7cd37741 | ||
|
|
ed181c1dcd | ||
|
|
4d839e3662 | ||
|
|
156b30756d | ||
|
|
6326870525 | ||
|
|
a360c9349a | ||
|
|
9e6276e0fa | ||
|
|
2bbc7a8999 | ||
|
|
fc3e05434f | ||
|
|
388764e9c8 | ||
|
|
3fb8323dd0 | ||
|
|
3aa1c94c6a |
3
.github/workflows/check-does-build-pr.yml
vendored
3
.github/workflows/check-does-build-pr.yml
vendored
@@ -9,9 +9,6 @@ jobs:
|
|||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Verify wrapper
|
|
||||||
uses: gradle/actions/wrapper-validation@v3
|
|
||||||
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
13
.github/workflows/check-does-build.yml
vendored
13
.github/workflows/check-does-build.yml
vendored
@@ -9,19 +9,24 @@ jobs:
|
|||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Verify wrapper
|
|
||||||
uses: gradle/actions/wrapper-validation@v3
|
|
||||||
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 21
|
java-version: 21
|
||||||
|
|
||||||
|
- name: Loom Cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: "**/.gradle/loom-cache"
|
||||||
|
key: "${{ runner.os }}-gradle-${{ hashFiles('**/libs.versions.*', '**/*.gradle*', '**/gradle-wrapper.properties') }}"
|
||||||
|
restore-keys: "${{ runner.os }}-gradle-"
|
||||||
|
|
||||||
- name: Setup Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
cache-read-only: false
|
cache-read-only: false
|
||||||
|
cache-cleanup: never
|
||||||
|
|
||||||
- name: Gradle build
|
- name: Gradle build
|
||||||
run: ./gradlew build
|
run: ./gradlew -I init.gradle build
|
||||||
31
.github/workflows/manual-artifact.yml
vendored
Normal file
31
.github/workflows/manual-artifact.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
name: manual-artifact
|
||||||
|
|
||||||
|
on: [ workflow_dispatch ]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- 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
|
||||||
|
cache-cleanup: always
|
||||||
|
|
||||||
|
- name: Gradle build
|
||||||
|
run: ./gradlew build
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: voxy-artifacts
|
||||||
|
path: build/libs/*.jar
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@
|
|||||||
/build/
|
/build/
|
||||||
/run/
|
/run/
|
||||||
/out/
|
/out/
|
||||||
|
/logs/
|
||||||
|
|||||||
5
LICENSE.md
Normal file
5
LICENSE.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Copyright 2025 MCRcortex
|
||||||
|
All rights reserved.
|
||||||
|
Do not redistribute.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
92
build.gradle
92
build.gradle
@@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'fabric-loom' version "1.10.1"
|
id 'fabric-loom' version "1.14-SNAPSHOT"
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,8 +24,41 @@ repositories {
|
|||||||
includeGroup "maven.modrinth"
|
includeGroup "maven.modrinth"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exclusiveContent {
|
||||||
|
forRepository {
|
||||||
|
maven {
|
||||||
|
name "caffeinemcRepository"
|
||||||
|
url "https://maven.caffeinemc.net/snapshots"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filter {
|
||||||
|
includeGroup "net.caffeinemc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
maven { url = "https://maven.shedaniel.me/" }
|
maven { url = "https://maven.shedaniel.me/" }
|
||||||
maven { url = "https://maven.terraformersmc.com/releases/" }
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -48,6 +81,7 @@ def gitCommitHash = { ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
def buildtime = {System.currentTimeSeconds()}
|
def buildtime = {System.currentTimeSeconds()}
|
||||||
|
|
||||||
processResources {
|
processResources {
|
||||||
def time = buildtime()
|
def time = buildtime()
|
||||||
def hash = gitCommitHash()
|
def hash = gitCommitHash()
|
||||||
@@ -75,7 +109,7 @@ if (!isInGHA) {
|
|||||||
dependencies {
|
dependencies {
|
||||||
// To change the versions see the gradle.properties file
|
// To change the versions see the gradle.properties file
|
||||||
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
||||||
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
|
mappings loom.officialMojangMappings()
|
||||||
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
|
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
|
||||||
|
|
||||||
|
|
||||||
@@ -86,49 +120,47 @@ dependencies {
|
|||||||
|
|
||||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
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
|
if (true) {
|
||||||
modRuntimeOnlyMsk "maven.modrinth:sodium:mc1.21.5-0.6.13-fabric"
|
modImplementation "maven.modrinth:sodium:mc1.21.11-0.8.2-fabric"
|
||||||
modCompileOnly "maven.modrinth:sodium:mc1.21.5-0.6.13-fabric"
|
} else {
|
||||||
|
modImplementation "net.caffeinemc:sodium-fabric:0.8.2-SNAPSHOT+mc1.21.11+"
|
||||||
|
}
|
||||||
|
|
||||||
modImplementation("maven.modrinth:lithium:mc1.21.5-0.16.0-fabric")
|
modImplementation("maven.modrinth:lithium:mc1.21.11-0.21.0-fabric")
|
||||||
|
|
||||||
//modRuntimeOnly "maven.modrinth:nvidium:0.2.6-beta"
|
//modRuntimeOnlyMsk "drouarb:nvidium:0.4.1-beta4:1.21.6@jar"
|
||||||
//modCompileOnly "maven.modrinth:nvidium:0.2.8-beta"
|
modCompileOnly "drouarb:nvidium:0.4.1-beta4:1.21.6@jar"
|
||||||
|
|
||||||
modCompileOnly("maven.modrinth:modmenu:14.0.0-rc.2")
|
modCompileOnly("maven.modrinth:modmenu:17.0.0-alpha.1")
|
||||||
modRuntimeOnlyMsk("maven.modrinth:modmenu:14.0.0-rc.2")
|
modRuntimeOnlyMsk("maven.modrinth:modmenu:17.0.0-alpha.1")
|
||||||
|
|
||||||
modCompileOnly("maven.modrinth:iris:1.8.11+1.21.5-fabric")
|
modCompileOnly("maven.modrinth:iris:1.10.4+1.21.11-fabric")
|
||||||
modRuntimeOnlyMsk("maven.modrinth:iris:1.8.11+1.21.5-fabric")
|
modRuntimeOnlyMsk("maven.modrinth:iris:1.10.4+1.21.11-fabric")
|
||||||
|
|
||||||
//modCompileOnly("maven.modrinth:starlight:1.1.3+1.20.4")
|
//modCompileOnly("maven.modrinth:starlight:1.1.3+1.20.4")
|
||||||
|
|
||||||
//modCompileOnly("maven.modrinth:immersiveportals:v5.1.7-mc1.20.4")
|
//modCompileOnly("maven.modrinth:immersiveportals:v5.1.7-mc1.20.4")
|
||||||
|
|
||||||
|
modCompileOnly("maven.modrinth:chunky:1.4.54-fabric")
|
||||||
|
//modRuntimeOnlyMsk("maven.modrinth:chunky:1.4.40-fabric")
|
||||||
|
|
||||||
modCompileOnly("maven.modrinth:chunky:1.4.36-fabric")
|
modRuntimeOnlyMsk("maven.modrinth:spark:1.10.152-fabric")
|
||||||
modRuntimeOnlyMsk("maven.modrinth:chunky:1.4.36-fabric")
|
|
||||||
|
|
||||||
modRuntimeOnlyMsk("maven.modrinth:spark:1.10.121-fabric")
|
|
||||||
modRuntimeOnlyMsk("maven.modrinth:fabric-permissions-api:0.3.3")
|
modRuntimeOnlyMsk("maven.modrinth:fabric-permissions-api:0.3.3")
|
||||||
//modRuntimeOnly("maven.modrinth:nsight-loader:1.2.0")
|
//modRuntimeOnly("maven.modrinth:nsight-loader:1.2.0")
|
||||||
|
|
||||||
//modImplementation('io.github.douira:glsl-transformer:2.0.1')
|
//modImplementation('io.github.douira:glsl-transformer:2.0.1')
|
||||||
|
|
||||||
modCompileOnly("maven.modrinth:vivecraft:1.21.1-1.1.14-b2-fabric")
|
modCompileOnly("maven.modrinth:vivecraft:1.21.9-1.3.2-fabric")
|
||||||
|
|
||||||
|
modCompileOnly("maven.modrinth:flashback:rNCr1Rbs")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def targetJavaVersion = 21
|
def targetJavaVersion = 21
|
||||||
tasks.withType(JavaCompile).configureEach {
|
tasks.withType(JavaCompile).configureEach {
|
||||||
// ensure that the encoding is set to UTF-8, no matter what the system default is
|
|
||||||
// this fixes some edge cases with special characters not displaying correctly
|
|
||||||
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
|
|
||||||
// If Javadoc is generated, this must be specified in that task too.
|
|
||||||
it.options.encoding = "UTF-8"
|
it.options.encoding = "UTF-8"
|
||||||
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) {
|
|
||||||
it.options.release = targetJavaVersion
|
it.options.release = targetJavaVersion
|
||||||
}
|
it.options.deprecation = true
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
@@ -192,11 +224,13 @@ remapJar {
|
|||||||
delete fileTree(getDestinationDirectory().get())
|
delete fileTree(getDestinationDirectory().get())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isInGHA) {
|
||||||
def hash = gitCommitHash();
|
def hash = gitCommitHash();
|
||||||
if (!hash.equals("<UnknownCommit>")) {
|
if (!hash.equals("<UnknownCommit>")) {
|
||||||
archiveClassifier.set(hash);
|
archiveClassifier.set(hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
project.ext.lwjglVersion = "3.3.3"
|
project.ext.lwjglVersion = "3.3.3"
|
||||||
project.ext.lwjglNatives = "natives-windows"
|
project.ext.lwjglNatives = "natives-windows"
|
||||||
@@ -211,19 +245,20 @@ dependencies {
|
|||||||
|
|
||||||
implementation "org.lwjgl:lwjgl"
|
implementation "org.lwjgl:lwjgl"
|
||||||
include(implementation "org.lwjgl:lwjgl-lmdb")
|
include(implementation "org.lwjgl:lwjgl-lmdb")
|
||||||
include(implementation "org.lwjgl:lwjgl-zstd")
|
//include(implementation "org.lwjgl:lwjgl-zstd")
|
||||||
runtimeOnly "org.lwjgl:lwjgl:$lwjglVersion:natives-windows"
|
runtimeOnly "org.lwjgl:lwjgl:$lwjglVersion:natives-windows"
|
||||||
runtimeOnly "org.lwjgl:lwjgl:$lwjglVersion:natives-linux"
|
runtimeOnly "org.lwjgl:lwjgl:$lwjglVersion:natives-linux"
|
||||||
include(runtimeOnly "org.lwjgl:lwjgl-lmdb:$lwjglVersion:natives-windows")
|
include(runtimeOnly "org.lwjgl:lwjgl-lmdb:$lwjglVersion:natives-windows")
|
||||||
include(runtimeOnly "org.lwjgl:lwjgl-zstd:$lwjglVersion:natives-windows")
|
//include(runtimeOnly "org.lwjgl:lwjgl-zstd:$lwjglVersion:natives-windows")
|
||||||
include(runtimeOnly "org.lwjgl:lwjgl-lmdb:$lwjglVersion:natives-linux")
|
include(runtimeOnly "org.lwjgl:lwjgl-lmdb:$lwjglVersion:natives-linux")
|
||||||
include(runtimeOnly "org.lwjgl:lwjgl-zstd:$lwjglVersion:natives-linux")
|
//include(runtimeOnly "org.lwjgl:lwjgl-zstd:$lwjglVersion:natives-linux")
|
||||||
|
|
||||||
include(implementation 'redis.clients:jedis:5.1.0')
|
include(implementation 'redis.clients:jedis:5.1.0')
|
||||||
include(implementation('org.rocksdb:rocksdbjni:10.2.1'))
|
include(implementation('org.rocksdb:rocksdbjni:10.2.1'))
|
||||||
include(implementation 'org.apache.commons:commons-pool2:2.12.0')
|
include(implementation 'org.apache.commons:commons-pool2:2.12.0')
|
||||||
include(implementation 'org.lz4:lz4-java:1.8.0')
|
include(implementation 'org.lz4:lz4-java:1.8.0')
|
||||||
include(implementation('org.tukaani:xz:1.10'))
|
include(implementation('org.tukaani:xz:1.10'))
|
||||||
|
include(implementation 'com.github.luben:zstd-jni:1.5.5-1')
|
||||||
|
|
||||||
if (true) {
|
if (true) {
|
||||||
if (!isInGHA) {
|
if (!isInGHA) {
|
||||||
@@ -244,8 +279,9 @@ if (!isInGHA) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
modRuntimeOnly('me.djtheredstoner:DevAuth-fabric:1.1.0') {
|
/*
|
||||||
|
modRuntimeOnly('me.djtheredstoner:DevAuth-fabric:1.2.1') {
|
||||||
exclude group: 'net.fabricmc', module: 'fabric-loader'
|
exclude group: 'net.fabricmc', module: 'fabric-loader'
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,17 +3,19 @@ org.gradle.jvmargs=-Xmx2G
|
|||||||
|
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
|
org.gradle.daemon = false
|
||||||
|
|
||||||
# Fabric Properties
|
# Fabric Properties
|
||||||
# check these on https://modmuss50.me/fabric.html
|
# check these on https://modmuss50.me/fabric.html
|
||||||
minecraft_version=1.21.5
|
minecraft_version=1.21.11
|
||||||
yarn_mappings=1.21.5+build.1
|
loader_version=0.18.2
|
||||||
loader_version=0.16.10
|
loom_version=1.14-SNAPSHOT
|
||||||
|
|
||||||
# Fabric API
|
# Fabric API
|
||||||
fabric_version=0.119.5+1.21.5
|
fabric_version=0.140.2+1.21.11
|
||||||
|
|
||||||
|
|
||||||
# Mod Properties
|
# Mod Properties
|
||||||
mod_version = 0.2.0-alpha
|
mod_version = 0.2.9-alpha
|
||||||
maven_group = me.cortex
|
maven_group = me.cortex
|
||||||
archives_base_name = voxy
|
archives_base_name = voxy
|
||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
16
init.gradle
Normal file
16
init.gradle
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
if (System.getenv("GITHUB_ACTIONS") == "true") {
|
||||||
|
beforeSettings { settings ->
|
||||||
|
def cleanupTime = Long.parseLong(System.getProperty("CLEANUP_TIME", Long.toString(System.currentTimeMillis()-(60_000*10))));//Remove unused entries from more then 10 min old
|
||||||
|
|
||||||
|
settings.caches {
|
||||||
|
//Note: this could be Cleanup.DEFAULT
|
||||||
|
cleanup = Cleanup.ALWAYS
|
||||||
|
|
||||||
|
releasedWrappers.setRemoveUnusedEntriesOlderThan(cleanupTime)
|
||||||
|
snapshotWrappers.setRemoveUnusedEntriesOlderThan(cleanupTime)
|
||||||
|
downloadedResources.setRemoveUnusedEntriesOlderThan(cleanupTime)
|
||||||
|
createdResources.setRemoveUnusedEntriesOlderThan(cleanupTime)
|
||||||
|
buildCache.setRemoveUnusedEntriesOlderThan(cleanupTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +1,26 @@
|
|||||||
package me.cortex.voxy.client;
|
package me.cortex.voxy.client;
|
||||||
|
|
||||||
import me.cortex.voxy.client.taskbar.Taskbar;
|
|
||||||
import me.cortex.voxy.common.Logger;
|
import me.cortex.voxy.common.Logger;
|
||||||
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
|
||||||
import me.cortex.voxy.commonImpl.ImportManager;
|
import me.cortex.voxy.commonImpl.ImportManager;
|
||||||
import me.cortex.voxy.commonImpl.importers.IDataImporter;
|
import me.cortex.voxy.commonImpl.importers.IDataImporter;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.hud.ClientBossBar;
|
import net.minecraft.client.gui.components.LerpingBossEvent;
|
||||||
import net.minecraft.entity.boss.BossBar;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.util.Mth;
|
||||||
import net.minecraft.util.math.MathHelper;
|
import net.minecraft.world.BossEvent;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.BooleanSupplier;
|
|
||||||
|
|
||||||
public class ClientImportManager extends ImportManager {
|
public class ClientImportManager extends ImportManager {
|
||||||
protected class ClientImportTask extends ImportTask {
|
protected class ClientImportTask extends ImportTask {
|
||||||
private final UUID bossbarUUID;
|
private final UUID bossbarUUID;
|
||||||
private final ClientBossBar bossBar;
|
private final LerpingBossEvent bossBar;
|
||||||
protected ClientImportTask(IDataImporter importer) {
|
protected ClientImportTask(IDataImporter importer) {
|
||||||
super(importer);
|
super(importer);
|
||||||
|
|
||||||
this.bossbarUUID = MathHelper.randomUuid();
|
this.bossbarUUID = Mth.createInsecureUUID();
|
||||||
this.bossBar = new ClientBossBar(this.bossbarUUID, Text.of("Voxy world importer"), 0.0f, BossBar.Color.GREEN, BossBar.Style.PROGRESS, false, false, false);
|
this.bossBar = new LerpingBossEvent(this.bossbarUUID, Component.nullToEmpty("Voxy world importer"), 0.0f, BossEvent.BossBarColor.GREEN, BossEvent.BossBarOverlay.PROGRESS, false, false, false);
|
||||||
MinecraftClient.getInstance().execute(()->{
|
Minecraft.getInstance().execute(()->{
|
||||||
MinecraftClient.getInstance().inGameHud.getBossBarHud().bossBars.put(bossBar.getUuid(), bossBar);
|
Minecraft.getInstance().gui.getBossOverlay().events.put(bossBar.getId(), bossBar);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,9 +29,9 @@ public class ClientImportManager extends ImportManager {
|
|||||||
if (!super.onUpdate(completed, outOf)) {
|
if (!super.onUpdate(completed, outOf)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
MinecraftClient.getInstance().execute(()->{
|
Minecraft.getInstance().execute(()->{
|
||||||
this.bossBar.setPercent((float) (((double)completed) / ((double) Math.max(1, outOf))));
|
this.bossBar.setProgress((float) (((double)completed) / ((double) Math.max(1, outOf))));
|
||||||
this.bossBar.setName(Text.of("Voxy import: " + completed + "/" + outOf + " chunks"));
|
this.bossBar.setName(Component.nullToEmpty("Voxy import: " + completed + "/" + outOf + " chunks"));
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -43,12 +39,12 @@ public class ClientImportManager extends ImportManager {
|
|||||||
@Override
|
@Override
|
||||||
protected void onCompleted(int total) {
|
protected void onCompleted(int total) {
|
||||||
super.onCompleted(total);
|
super.onCompleted(total);
|
||||||
MinecraftClient.getInstance().execute(()->{
|
Minecraft.getInstance().execute(()->{
|
||||||
MinecraftClient.getInstance().inGameHud.getBossBarHud().bossBars.remove(this.bossbarUUID);
|
Minecraft.getInstance().gui.getBossOverlay().events.remove(this.bossbarUUID);
|
||||||
long delta = Math.max(System.currentTimeMillis() - this.startTime, 1);
|
long delta = Math.max(System.currentTimeMillis() - this.startTime, 1);
|
||||||
|
|
||||||
String msg = "Voxy world import finished in " + (delta/1000) + " seconds, averaging " + (int)(total/(delta/1000f)) + " chunks per second";
|
String msg = "Voxy world import finished in " + (delta/1000) + " seconds, averaging " + (int)(total/(delta/1000f)) + " chunks per second";
|
||||||
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.literal(msg));
|
Minecraft.getInstance().gui.getChat().addMessage(Component.literal(msg));
|
||||||
Logger.info(msg);
|
Logger.info(msg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package me.cortex.voxy.client;
|
||||||
|
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
|
|
||||||
|
public interface ICheekyClientChunkCache {
|
||||||
|
LevelChunk voxy$cheekyGetChunk(int x, int z);
|
||||||
|
}
|
||||||
@@ -2,6 +2,10 @@ package me.cortex.voxy.client;
|
|||||||
|
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class RenderStatistics {
|
public class RenderStatistics {
|
||||||
public static boolean enabled = false;
|
public static boolean enabled = false;
|
||||||
|
|
||||||
@@ -9,4 +13,24 @@ public class RenderStatistics {
|
|||||||
public static final int[] hierarchicalRenderSections = new int[WorldEngine.MAX_LOD_LAYER+1];
|
public static final int[] hierarchicalRenderSections = new int[WorldEngine.MAX_LOD_LAYER+1];
|
||||||
public static final int[] visibleSections = new int[WorldEngine.MAX_LOD_LAYER+1];
|
public static final int[] visibleSections = new int[WorldEngine.MAX_LOD_LAYER+1];
|
||||||
public static final int[] quadCount = new int[WorldEngine.MAX_LOD_LAYER+1];
|
public static final int[] quadCount = new int[WorldEngine.MAX_LOD_LAYER+1];
|
||||||
|
|
||||||
|
|
||||||
|
public static void addDebug(List<String> debug) {
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debug.add("HTC: [" + Arrays.stream(flipCopy(RenderStatistics.hierarchicalTraversalCounts)).mapToObj(Integer::toString).collect(Collectors.joining(", "))+"]");
|
||||||
|
debug.add("HRS: [" + Arrays.stream(flipCopy(RenderStatistics.hierarchicalRenderSections)).mapToObj(Integer::toString).collect(Collectors.joining(", "))+"]");
|
||||||
|
debug.add("VS: [" + Arrays.stream(flipCopy(RenderStatistics.visibleSections)).mapToObj(Integer::toString).collect(Collectors.joining(", "))+"]");
|
||||||
|
debug.add("QC: [" + Arrays.stream(flipCopy(RenderStatistics.quadCount)).mapToObj(Integer::toString).collect(Collectors.joining(", "))+"]");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int[] flipCopy(int[] array) {
|
||||||
|
int[] ret = new int[array.length];
|
||||||
|
int i = ret.length;
|
||||||
|
for (int j : array) {
|
||||||
|
ret[--i] = j;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package me.cortex.voxy.client;
|
package me.cortex.voxy.client;
|
||||||
|
|
||||||
import java.lang.invoke.VarHandle;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class TimingStatistics {
|
public class TimingStatistics {
|
||||||
|
|||||||
@@ -1,32 +1,85 @@
|
|||||||
package me.cortex.voxy.client;
|
package me.cortex.voxy.client;
|
||||||
|
|
||||||
|
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
||||||
|
import me.cortex.voxy.client.core.VoxyRenderSystem;
|
||||||
import me.cortex.voxy.client.core.gl.Capabilities;
|
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.common.Logger;
|
||||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||||
import net.fabricmc.api.ClientModInitializer;
|
import net.fabricmc.api.ClientModInitializer;
|
||||||
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
|
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
|
|
||||||
import net.fabricmc.loader.api.FabricLoader;
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.components.debug.DebugScreenDisplayer;
|
||||||
|
import net.minecraft.client.gui.components.debug.DebugScreenEntries;
|
||||||
|
import net.minecraft.client.gui.components.debug.DebugScreenEntry;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||||
|
import me.cortex.voxy.common.network.VoxyNetwork;
|
||||||
|
import me.cortex.voxy.commonImpl.WorldIdentifier;
|
||||||
|
|
||||||
public class VoxyClient implements ClientModInitializer {
|
public class VoxyClient implements ClientModInitializer {
|
||||||
private static final HashSet<String> FREX = new HashSet<>();
|
private static final HashSet<String> FREX = new HashSet<>();
|
||||||
|
|
||||||
@Override
|
public static void initVoxyClient() {
|
||||||
public void onInitializeClient() {
|
Capabilities.init();//Ensure clinit is called
|
||||||
ClientLifecycleEvents.CLIENT_STARTED.register(client->{
|
|
||||||
boolean systemSupported = Capabilities.INSTANCE.compute && Capabilities.INSTANCE.indirectParameters;
|
|
||||||
|
if (Capabilities.INSTANCE.hasBrokenDepthSampler) {
|
||||||
|
Logger.error("AMD broken depth sampler detected, voxy does not work correctly and has been disabled, this will hopefully be fixed in the future");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean systemSupported = Capabilities.INSTANCE.compute && Capabilities.INSTANCE.indirectParameters && !Capabilities.INSTANCE.hasBrokenDepthSampler;
|
||||||
if (systemSupported) {
|
if (systemSupported) {
|
||||||
|
|
||||||
|
SharedIndexBuffer.INSTANCE.id();
|
||||||
|
BudgetBufferRenderer.init();
|
||||||
|
|
||||||
VoxyCommon.setInstanceFactory(VoxyClientInstance::new);
|
VoxyCommon.setInstanceFactory(VoxyClientInstance::new);
|
||||||
|
|
||||||
|
if (!Capabilities.INSTANCE.subgroup) {
|
||||||
|
Logger.warn("GPU does not support subgroup operations, expect some performance degradation");
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Logger.error("Voxy is unsupported on your system.");
|
Logger.error("Voxy is unsupported on your system.");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitializeClient() {
|
||||||
|
DebugScreenEntries.register(Identifier.fromNamespaceAndPath("voxy", "version"), new DebugScreenEntry() {
|
||||||
|
@Override
|
||||||
|
public void display(DebugScreenDisplayer lines, @Nullable Level level, @Nullable LevelChunk levelChunk, @Nullable LevelChunk levelChunk2) {
|
||||||
|
if (!VoxyCommon.isAvailable()) {
|
||||||
|
lines.addLine(ChatFormatting.RED + "voxy-"+VoxyCommon.MOD_VERSION);//Voxy installed, not avalible
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var instance = VoxyCommon.getInstance();
|
||||||
|
if (instance == null) {
|
||||||
|
lines.addLine(ChatFormatting.YELLOW + "voxy-" + VoxyCommon.MOD_VERSION);//Voxy avalible, no instance active
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VoxyRenderSystem vrs = null;
|
||||||
|
var wr = Minecraft.getInstance().levelRenderer;
|
||||||
|
if (wr != null) vrs = ((IGetVoxyRenderSystem) wr).getVoxyRenderSystem();
|
||||||
|
|
||||||
|
//Voxy instance active
|
||||||
|
lines.addLine((vrs==null?ChatFormatting.DARK_GREEN:ChatFormatting.GREEN)+"voxy-"+VoxyCommon.MOD_VERSION);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
DebugScreenEntries.register(Identifier.fromNamespaceAndPath("voxy","debug"), new VoxyDebugScreenEntry());
|
||||||
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
|
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
|
||||||
if (VoxyCommon.isAvailable()) {
|
if (VoxyCommon.isAvailable()) {
|
||||||
dispatcher.register(VoxyCommands.register());
|
dispatcher.register(VoxyCommands.register());
|
||||||
@@ -40,9 +93,50 @@ public class VoxyClient implements ClientModInitializer {
|
|||||||
} else {
|
} else {
|
||||||
FREX.remove(name);
|
FREX.remove(name);
|
||||||
}}));
|
}}));
|
||||||
|
|
||||||
|
ClientPlayNetworking.registerGlobalReceiver(VoxyNetwork.ConfigSyncPayload.TYPE, (payload, context) -> {
|
||||||
|
context.client().execute(() -> {
|
||||||
|
// Update client render distance cap if needed, or store server settings
|
||||||
|
// For now, we can perhaps log it or update a transient config
|
||||||
|
Logger.info("Received server view distance: " + payload.viewDistance());
|
||||||
|
// We might want to clamp the local render distance to the server's max if strictly enforced,
|
||||||
|
// or just use it as a hint for where to expect data.
|
||||||
|
// The user requirement says: "server sends max view distance to client... client renders up to client setting, server updates outside sim distance up to server max"
|
||||||
|
// So we should probably store this "server max view distance" somewhere in VoxyClientInstance.
|
||||||
|
if (VoxyCommon.getInstance() instanceof VoxyClientInstance clientInstance) {
|
||||||
|
clientInstance.setServerViewDistance(payload.viewDistance());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ClientPlayNetworking.registerGlobalReceiver(VoxyNetwork.LodUpdatePayload.TYPE, (payload, context) -> {
|
||||||
|
// Deserialize off-thread if possible? Packet handling is on netty thread or main thread depending on configuration.
|
||||||
|
// But we can just schedule it.
|
||||||
|
// Actually deserialize needs Mapper which is world specific?
|
||||||
|
// The packet doesn't contain world ID?
|
||||||
|
// We assume it's for the current client world.
|
||||||
|
|
||||||
|
// Wait, we need to know WHICH world this update is for if we have dimensions?
|
||||||
|
// Usually packets are for the current world the player is in.
|
||||||
|
|
||||||
|
context.client().execute(() -> {
|
||||||
|
// Logger.info("Received LOD update packet, size: " + payload.data().length);
|
||||||
|
if (VoxyCommon.getInstance() instanceof VoxyClientInstance clientInstance) {
|
||||||
|
clientInstance.handleLodUpdate(payload);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isFrexActive() {
|
public static boolean isFrexActive() {
|
||||||
return !FREX.isEmpty();
|
return !FREX.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getOcclusionDebugState() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean disableSodiumChunkRender() {
|
||||||
|
return false;// getOcclusionDebugState() != 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
package me.cortex.voxy.client;
|
package me.cortex.voxy.client;
|
||||||
|
|
||||||
import me.cortex.voxy.client.config.VoxyConfig;
|
import me.cortex.voxy.client.compat.FlashbackCompat;
|
||||||
|
import me.cortex.voxy.common.config.VoxyConfig;
|
||||||
|
import me.cortex.voxy.client.mixin.sodium.AccessorSodiumWorldRenderer;
|
||||||
import me.cortex.voxy.common.Logger;
|
import me.cortex.voxy.common.Logger;
|
||||||
|
import me.cortex.voxy.common.StorageConfigUtil;
|
||||||
import me.cortex.voxy.common.config.ConfigBuildCtx;
|
import me.cortex.voxy.common.config.ConfigBuildCtx;
|
||||||
import me.cortex.voxy.common.config.Serialization;
|
import me.cortex.voxy.common.config.Serialization;
|
||||||
import me.cortex.voxy.common.config.compressors.ZSTDCompressor;
|
import me.cortex.voxy.common.config.compressors.ZSTDCompressor;
|
||||||
@@ -13,28 +16,95 @@ import me.cortex.voxy.common.config.storage.rocksdb.RocksDBStorageBackend;
|
|||||||
import me.cortex.voxy.commonImpl.ImportManager;
|
import me.cortex.voxy.commonImpl.ImportManager;
|
||||||
import me.cortex.voxy.commonImpl.VoxyInstance;
|
import me.cortex.voxy.commonImpl.VoxyInstance;
|
||||||
import me.cortex.voxy.commonImpl.WorldIdentifier;
|
import me.cortex.voxy.commonImpl.WorldIdentifier;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer;
|
||||||
import net.minecraft.util.WorldSavePath;
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.world.level.storage.LevelResource;
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
public class VoxyClientInstance extends VoxyInstance {
|
public class VoxyClientInstance extends VoxyInstance {
|
||||||
public static boolean isInGame = false;
|
public static boolean isInGame = false;
|
||||||
|
|
||||||
private final SectionStorageConfig storageConfig;
|
private final SectionStorageConfig storageConfig;
|
||||||
private final Path basePath = getBasePath();
|
private final Path basePath;
|
||||||
|
private final boolean noIngestOverride;
|
||||||
|
private int serverViewDistance = 32;
|
||||||
|
|
||||||
public VoxyClientInstance() {
|
public VoxyClientInstance() {
|
||||||
super(VoxyConfig.CONFIG.serviceThreads);
|
super();
|
||||||
try {
|
var path = FlashbackCompat.getReplayStoragePath();
|
||||||
Files.createDirectories(this.basePath);
|
this.noIngestOverride = path != null;
|
||||||
} catch (Exception e) {
|
if (path == null) {
|
||||||
throw new RuntimeException(e);
|
path = getBasePath();
|
||||||
}
|
}
|
||||||
this.storageConfig = getCreateStorageConfig(this.basePath);
|
this.basePath = path;
|
||||||
|
this.storageConfig = StorageConfigUtil.getCreateStorageConfig(Config.class, c->c.version==1&&c.sectionStorageConfig!=null, ()->DEFAULT_STORAGE_CONFIG, path).sectionStorageConfig;
|
||||||
|
this.updateDedicatedThreads();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLastLodUpdate() {
|
||||||
|
return lastLodUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLodUpdatesReceived() {
|
||||||
|
return lodUpdatesReceived;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServerViewDistance(int distance) {
|
||||||
|
this.serverViewDistance = distance;
|
||||||
|
// Trigger a re-evaluation of render distance?
|
||||||
|
Logger.info("Updating client view distance from server to: " + distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getServerViewDistance() {
|
||||||
|
return this.serverViewDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long lastLodUpdate;
|
||||||
|
private int lodUpdatesReceived;
|
||||||
|
|
||||||
|
public void handleLodUpdate(me.cortex.voxy.common.network.VoxyNetwork.LodUpdatePayload payload) {
|
||||||
|
this.lastLodUpdate = System.currentTimeMillis();
|
||||||
|
this.lodUpdatesReceived++;
|
||||||
|
// 1. Get current client world
|
||||||
|
var player = Minecraft.getInstance().player;
|
||||||
|
if (player == null) return;
|
||||||
|
var level = player.level();
|
||||||
|
var wi = WorldIdentifier.of(level);
|
||||||
|
if (wi == null) return;
|
||||||
|
|
||||||
|
var engine = this.getNullable(wi);
|
||||||
|
if (engine == null) return;
|
||||||
|
|
||||||
|
// 2. Deserialize payload using engine's mapper
|
||||||
|
// Note: Payload deserialization requires Mapper to resolve IDs.
|
||||||
|
// We need to ensure the engine's mapper is up to date with the server's palette?
|
||||||
|
// No, the payload contains palette strings/states.
|
||||||
|
// The deserializer maps them to LOCAL IDs using the provided mapper.
|
||||||
|
|
||||||
|
try {
|
||||||
|
var section = payload.deserialize(engine.getMapper());
|
||||||
|
// 3. Insert update into engine
|
||||||
|
me.cortex.voxy.common.world.WorldUpdater.insertUpdate(engine, section);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.error("Failed to handle LOD update", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateDedicatedThreads() {
|
||||||
|
int target = VoxyConfig.CONFIG.serviceThreads;
|
||||||
|
if (!VoxyConfig.CONFIG.dontUseSodiumBuilderThreads) {
|
||||||
|
var swr = SodiumWorldRenderer.instanceNullable();
|
||||||
|
if (swr != null) {
|
||||||
|
var rsm = ((AccessorSodiumWorldRenderer) swr).getRenderSectionManager();
|
||||||
|
if (rsm != null) {
|
||||||
|
this.setNumThreads(Math.max(1, target - rsm.getBuilder().getTotalThreadCount()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setNumThreads(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -46,103 +116,44 @@ public class VoxyClientInstance extends VoxyInstance {
|
|||||||
protected SectionStorage createStorage(WorldIdentifier identifier) {
|
protected SectionStorage createStorage(WorldIdentifier identifier) {
|
||||||
var ctx = new ConfigBuildCtx();
|
var ctx = new ConfigBuildCtx();
|
||||||
ctx.setProperty(ConfigBuildCtx.BASE_SAVE_PATH, this.basePath.toString());
|
ctx.setProperty(ConfigBuildCtx.BASE_SAVE_PATH, this.basePath.toString());
|
||||||
ctx.setProperty(ConfigBuildCtx.WORLD_IDENTIFIER, getWorldId(identifier));
|
ctx.setProperty(ConfigBuildCtx.WORLD_IDENTIFIER, identifier.getWorldId());
|
||||||
ctx.pushPath(ConfigBuildCtx.DEFAULT_STORAGE_PATH);
|
ctx.pushPath(ConfigBuildCtx.DEFAULT_STORAGE_PATH);
|
||||||
return this.storageConfig.build(ctx);
|
return this.storageConfig.build(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String bytesToHex(byte[] hash) {
|
public Path getStorageBasePath() {
|
||||||
StringBuilder hexString = new StringBuilder(2 * hash.length);
|
return this.basePath;
|
||||||
for (byte b : hash) {
|
|
||||||
String hex = Integer.toHexString(0xff & b);
|
|
||||||
if (hex.length() == 1) {
|
|
||||||
hexString.append('0');
|
|
||||||
}
|
|
||||||
hexString.append(hex);
|
|
||||||
}
|
|
||||||
return hexString.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getWorldId(WorldIdentifier identifier) {
|
@Override
|
||||||
String data = identifier.biomeSeed + identifier.key.toString();
|
public boolean isIngestEnabled(WorldIdentifier worldId) {
|
||||||
try {
|
return (!this.noIngestOverride) && VoxyConfig.CONFIG.ingestEnabled;
|
||||||
return bytesToHex(MessageDigest.getInstance("SHA-256").digest(data.getBytes())).substring(0, 32);
|
|
||||||
} catch (
|
|
||||||
NoSuchAlgorithmException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SectionStorageConfig getCreateStorageConfig(Path path) {
|
|
||||||
var json = path.resolve("config.json");
|
|
||||||
Config config = null;
|
|
||||||
if (Files.exists(json)) {
|
|
||||||
try {
|
|
||||||
config = Serialization.GSON.fromJson(Files.readString(json), Config.class);
|
|
||||||
if (config == null) {
|
|
||||||
Logger.error("Config deserialization null, reverting to default");
|
|
||||||
} else {
|
|
||||||
if (config.sectionStorageConfig == null) {
|
|
||||||
Logger.error("Config section storage null, reverting to default");
|
|
||||||
config = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Logger.error("Failed to load the storage configuration file, resetting it to default, this will probably break your save if you used a custom storage config", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config == null) {
|
|
||||||
config = DEFAULT_STORAGE_CONFIG;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Files.writeString(json, Serialization.GSON.toJson(config));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Failed write the config, aborting!", e);
|
|
||||||
}
|
|
||||||
if (config == null) {
|
|
||||||
throw new IllegalStateException("Config is still null\n");
|
|
||||||
}
|
|
||||||
return config.sectionStorageConfig;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Config {
|
private static class Config {
|
||||||
public int version = 1;
|
public int version = 1;
|
||||||
public SectionStorageConfig sectionStorageConfig;
|
public SectionStorageConfig sectionStorageConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Config DEFAULT_STORAGE_CONFIG;
|
private static final Config DEFAULT_STORAGE_CONFIG;
|
||||||
static {
|
static {
|
||||||
var config = new Config();
|
var config = new Config();
|
||||||
|
config.sectionStorageConfig = StorageConfigUtil.createDefaultSerializer();
|
||||||
//Load the default config
|
|
||||||
var baseDB = new RocksDBStorageBackend.Config();
|
|
||||||
|
|
||||||
var compressor = new ZSTDCompressor.Config();
|
|
||||||
compressor.compressionLevel = 1;
|
|
||||||
|
|
||||||
var compression = new CompressionStorageAdaptor.Config();
|
|
||||||
compression.delegate = baseDB;
|
|
||||||
compression.compressor = compressor;
|
|
||||||
|
|
||||||
var serializer = new SectionSerializationStorage.Config();
|
|
||||||
serializer.storage = compression;
|
|
||||||
config.sectionStorageConfig = serializer;
|
|
||||||
|
|
||||||
DEFAULT_STORAGE_CONFIG = config;
|
DEFAULT_STORAGE_CONFIG = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Path getBasePath() {
|
private static Path getBasePath() {
|
||||||
Path basePath = MinecraftClient.getInstance().runDirectory.toPath().resolve(".voxy").resolve("saves");
|
Path basePath = Minecraft.getInstance().gameDirectory.toPath().resolve(".voxy").resolve("saves");
|
||||||
var iserver = MinecraftClient.getInstance().getServer();
|
var iserver = Minecraft.getInstance().getSingleplayerServer();
|
||||||
if (iserver != null) {
|
if (iserver != null) {
|
||||||
basePath = iserver.getSavePath(WorldSavePath.ROOT).resolve("voxy");
|
basePath = iserver.getWorldPath(LevelResource.ROOT).resolve("voxy");
|
||||||
} else {
|
} else {
|
||||||
var netHandle = MinecraftClient.getInstance().interactionManager;
|
var netHandle = Minecraft.getInstance().gameMode;
|
||||||
if (netHandle == null) {
|
if (netHandle == null) {
|
||||||
Logger.error("Network handle null");
|
Logger.error("Network handle null");
|
||||||
basePath = basePath.resolve("UNKNOWN");
|
basePath = basePath.resolve("UNKNOWN");
|
||||||
} else {
|
} else {
|
||||||
var info = netHandle.networkHandler.getServerInfo();
|
var info = netHandle.connection.getServerData();
|
||||||
if (info == null) {
|
if (info == null) {
|
||||||
Logger.error("Server info null");
|
Logger.error("Server info null");
|
||||||
basePath = basePath.resolve("UNKNOWN");
|
basePath = basePath.resolve("UNKNOWN");
|
||||||
@@ -150,7 +161,7 @@ public class VoxyClientInstance extends VoxyInstance {
|
|||||||
if (info.isRealm()) {
|
if (info.isRealm()) {
|
||||||
basePath = basePath.resolve("realms");
|
basePath = basePath.resolve("realms");
|
||||||
} else {
|
} else {
|
||||||
basePath = basePath.resolve(info.address.replace(":", "_"));
|
basePath = basePath.resolve(info.ip.replace(":", "_"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,20 +6,27 @@ import com.mojang.brigadier.context.CommandContext;
|
|||||||
import com.mojang.brigadier.suggestion.Suggestions;
|
import com.mojang.brigadier.suggestion.Suggestions;
|
||||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||||
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.common.Logger;
|
||||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||||
import me.cortex.voxy.commonImpl.WorldIdentifier;
|
import me.cortex.voxy.commonImpl.WorldIdentifier;
|
||||||
import me.cortex.voxy.commonImpl.importers.DHImporter;
|
import me.cortex.voxy.commonImpl.importers.DHImporter;
|
||||||
import me.cortex.voxy.commonImpl.importers.WorldImporter;
|
import me.cortex.voxy.commonImpl.importers.WorldImporter;
|
||||||
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
|
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
|
||||||
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
|
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.command.CommandSource;
|
import net.minecraft.commands.SharedSuggestionProvider;
|
||||||
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.world.level.dimension.DimensionType;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
|
||||||
@@ -53,7 +60,7 @@ public class VoxyCommands {
|
|||||||
.executes(VoxyCommands::importDistantHorizons)));
|
.executes(VoxyCommands::importDistantHorizons)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ClientCommandManager.literal("voxy").requires((ctx)-> VoxyCommon.getInstance() != null)
|
return ClientCommandManager.literal("voxy")//.requires((ctx)-> VoxyCommon.getInstance() != null)
|
||||||
.then(ClientCommandManager.literal("reload")
|
.then(ClientCommandManager.literal("reload")
|
||||||
.executes(VoxyCommands::reloadInstance))
|
.executes(VoxyCommands::reloadInstance))
|
||||||
.then(imports);
|
.then(imports);
|
||||||
@@ -62,18 +69,20 @@ public class VoxyCommands {
|
|||||||
private static int reloadInstance(CommandContext<FabricClientCommandSource> ctx) {
|
private static int reloadInstance(CommandContext<FabricClientCommandSource> ctx) {
|
||||||
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
|
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
|
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
var wr = MinecraftClient.getInstance().worldRenderer;
|
var wr = Minecraft.getInstance().levelRenderer;
|
||||||
if (wr!=null) {
|
if (wr!=null) {
|
||||||
((IGetVoxyRenderSystem)wr).shutdownRenderer();
|
((IGetVoxyRenderSystem)wr).shutdownRenderer();
|
||||||
}
|
}
|
||||||
|
|
||||||
VoxyCommon.shutdownInstance();
|
VoxyCommon.shutdownInstance();
|
||||||
|
System.gc();
|
||||||
VoxyCommon.createInstance();
|
VoxyCommon.createInstance();
|
||||||
if (wr!=null) {
|
|
||||||
((IGetVoxyRenderSystem)wr).createRenderer();
|
var r = Minecraft.getInstance().levelRenderer;
|
||||||
}
|
if (r != null) r.allChanged();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +92,7 @@ public class VoxyCommands {
|
|||||||
private static int importDistantHorizons(CommandContext<FabricClientCommandSource> ctx) {
|
private static int importDistantHorizons(CommandContext<FabricClientCommandSource> ctx) {
|
||||||
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
|
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
|
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
var dbFile = new File(ctx.getArgument("sqlDbPath", String.class));
|
var dbFile = new File(ctx.getArgument("sqlDbPath", String.class));
|
||||||
@@ -97,10 +107,10 @@ public class VoxyCommands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
File dbFile_ = dbFile;
|
File dbFile_ = dbFile;
|
||||||
var engine = WorldIdentifier.ofEngine(MinecraftClient.getInstance().player.clientWorld);
|
var engine = WorldIdentifier.ofEngine(Minecraft.getInstance().level);
|
||||||
if (engine==null)return 1;
|
if (engine==null)return 1;
|
||||||
return instance.getImportManager().makeAndRunIfNone(engine, ()->
|
return instance.getImportManager().makeAndRunIfNone(engine, ()->
|
||||||
new DHImporter(dbFile_, engine, MinecraftClient.getInstance().player.clientWorld, instance.getThreadPool(), instance.savingServiceRateLimiter))?0:1;
|
new DHImporter(dbFile_, engine, Minecraft.getInstance().level, instance.getServiceManager(), instance.savingServiceRateLimiter))?0:1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean fileBasedImporter(File directory) {
|
private static boolean fileBasedImporter(File directory) {
|
||||||
@@ -109,29 +119,39 @@ public class VoxyCommands {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var engine = WorldIdentifier.ofEngine(MinecraftClient.getInstance().player.clientWorld);
|
var engine = WorldIdentifier.ofEngine(Minecraft.getInstance().level);
|
||||||
if (engine==null) return false;
|
if (engine==null) return false;
|
||||||
return instance.getImportManager().makeAndRunIfNone(engine, ()->{
|
return instance.getImportManager().makeAndRunIfNone(engine, ()->{
|
||||||
var importer = new WorldImporter(engine, MinecraftClient.getInstance().player.clientWorld, instance.getThreadPool(), instance.savingServiceRateLimiter);
|
var importer = new WorldImporter(engine, Minecraft.getInstance().level, instance.getServiceManager(), instance.savingServiceRateLimiter);
|
||||||
importer.importRegionDirectoryAsync(directory);
|
importer.importRegionDirectoryAsync(directory);
|
||||||
return importer;
|
return importer;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int importRaw(CommandContext<FabricClientCommandSource> ctx) {
|
private static int importRaw(CommandContext<FabricClientCommandSource> ctx) {
|
||||||
|
if (VoxyCommon.getInstance() == null) {
|
||||||
|
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
return fileBasedImporter(new File(ctx.getArgument("path", String.class)))?0:1;
|
return fileBasedImporter(new File(ctx.getArgument("path", String.class)))?0:1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int importBobby(CommandContext<FabricClientCommandSource> ctx) {
|
private static int importBobby(CommandContext<FabricClientCommandSource> ctx) {
|
||||||
|
if (VoxyCommon.getInstance() == null) {
|
||||||
|
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
var file = new File(".bobby").toPath().resolve(ctx.getArgument("world_name", String.class)).toFile();
|
var file = new File(".bobby").toPath().resolve(ctx.getArgument("world_name", String.class)).toFile();
|
||||||
return fileBasedImporter(file)?0:1;
|
return fileBasedImporter(file)?0:1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CompletableFuture<Suggestions> importWorldSuggester(CommandContext<FabricClientCommandSource> ctx, SuggestionsBuilder sb) {
|
private static CompletableFuture<Suggestions> importWorldSuggester(CommandContext<FabricClientCommandSource> ctx, SuggestionsBuilder sb) {
|
||||||
return fileDirectorySuggester(MinecraftClient.getInstance().runDirectory.toPath().resolve("saves"), sb);
|
return fileDirectorySuggester(Minecraft.getInstance().gameDirectory.toPath().resolve("saves"), sb);
|
||||||
}
|
}
|
||||||
private static CompletableFuture<Suggestions> importBobbySuggester(CommandContext<FabricClientCommandSource> ctx, SuggestionsBuilder sb) {
|
private static CompletableFuture<Suggestions> importBobbySuggester(CommandContext<FabricClientCommandSource> ctx, SuggestionsBuilder sb) {
|
||||||
return fileDirectorySuggester(MinecraftClient.getInstance().runDirectory.toPath().resolve(".bobby"), sb);
|
return fileDirectorySuggester(Minecraft.getInstance().gameDirectory.toPath().resolve(".bobby"), sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CompletableFuture<Suggestions> fileDirectorySuggester(Path dir, SuggestionsBuilder sb) {
|
private static CompletableFuture<Suggestions> fileDirectorySuggester(Path dir, SuggestionsBuilder sb) {
|
||||||
@@ -166,7 +186,7 @@ public class VoxyCommands {
|
|||||||
if (wn.equals(remaining)) {
|
if (wn.equals(remaining)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (CommandSource.shouldSuggest(remaining, wn) || CommandSource.shouldSuggest(remaining, '"'+wn)) {
|
if (SharedSuggestionProvider.matchesSubStr(remaining, wn) || SharedSuggestionProvider.matchesSubStr(remaining, '"'+wn)) {
|
||||||
wn = str+wn + "/";
|
wn = str+wn + "/";
|
||||||
sb.suggest(StringArgumentType.escapeIfRequired(wn));
|
sb.suggest(StringArgumentType.escapeIfRequired(wn));
|
||||||
}
|
}
|
||||||
@@ -177,17 +197,43 @@ public class VoxyCommands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static int importWorld(CommandContext<FabricClientCommandSource> ctx) {
|
private static int importWorld(CommandContext<FabricClientCommandSource> ctx) {
|
||||||
|
if (VoxyCommon.getInstance() == null) {
|
||||||
|
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
var name = ctx.getArgument("world_name", String.class);
|
var name = ctx.getArgument("world_name", String.class);
|
||||||
var file = new File("saves").toPath().resolve(name);
|
var file = new File("saves").toPath().resolve(name);
|
||||||
name = name.toLowerCase();
|
name = name.toLowerCase(Locale.ROOT);
|
||||||
if (name.endsWith("/")) {
|
if (name.endsWith("/")) {
|
||||||
name = name.substring(0, name.length()-1);
|
name = name.substring(0, name.length()-1);
|
||||||
}
|
}
|
||||||
|
if (file.resolve("level.dat").toFile().exists()) {
|
||||||
|
var dimFile = DimensionType.getStorageFolder(Minecraft.getInstance().level.dimension(), file)
|
||||||
|
.resolve("region")
|
||||||
|
.toFile();
|
||||||
|
if (!dimFile.isDirectory()) return 1;
|
||||||
|
return fileBasedImporter(dimFile)?0:1;
|
||||||
|
//We are in a world directory, so import the current dimension we are in
|
||||||
|
/*
|
||||||
|
for (var dim : new String[]{"overworld", "the_nether", "the_end"}) {//This is so annoying that you cant loop through all the dimensions
|
||||||
|
var id = ResourceKey.create(Registries.DIMENSION, Identifier.withDefaultNamespace(dim));
|
||||||
|
var dimPath = DimensionType.getStorageFolder(id, file);
|
||||||
|
dimPath = dimPath.resolve("region");
|
||||||
|
var dimFile = dimPath.toFile();
|
||||||
|
if (dimFile.isDirectory()) {//exists and is a directory
|
||||||
|
if (!fileBasedImporter(dimFile)) {
|
||||||
|
Logger.error("Failed to import dimension: " + id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
} else {
|
||||||
if (!(name.endsWith("region"))) {
|
if (!(name.endsWith("region"))) {
|
||||||
file = file.resolve("region");
|
file = file.resolve("region");
|
||||||
}
|
}
|
||||||
return fileBasedImporter(file.toFile()) ? 0 : 1;
|
return fileBasedImporter(file.toFile()) ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static int importZip(CommandContext<FabricClientCommandSource> ctx) {
|
private static int importZip(CommandContext<FabricClientCommandSource> ctx) {
|
||||||
var zip = new File(ctx.getArgument("zipPath", String.class));
|
var zip = new File(ctx.getArgument("zipPath", String.class));
|
||||||
@@ -198,14 +244,15 @@ public class VoxyCommands {
|
|||||||
|
|
||||||
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
|
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
|
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
String finalInnerDir = innerDir;
|
String finalInnerDir = innerDir;
|
||||||
|
|
||||||
var engine = WorldIdentifier.ofEngine(MinecraftClient.getInstance().player.clientWorld);
|
var engine = WorldIdentifier.ofEngine(Minecraft.getInstance().level);
|
||||||
if (engine != null) {
|
if (engine != null) {
|
||||||
return instance.getImportManager().makeAndRunIfNone(engine, () -> {
|
return instance.getImportManager().makeAndRunIfNone(engine, () -> {
|
||||||
var importer = new WorldImporter(engine, MinecraftClient.getInstance().player.clientWorld, instance.getThreadPool(), instance.savingServiceRateLimiter);
|
var importer = new WorldImporter(engine, Minecraft.getInstance().level, instance.getServiceManager(), instance.savingServiceRateLimiter);
|
||||||
importer.importZippedRegionDirectoryAsync(zip, finalInnerDir);
|
importer.importZippedRegionDirectoryAsync(zip, finalInnerDir);
|
||||||
return importer;
|
return importer;
|
||||||
}) ? 0 : 1;
|
}) ? 0 : 1;
|
||||||
@@ -213,12 +260,13 @@ public class VoxyCommands {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int cancelImport(CommandContext<FabricClientCommandSource> fabricClientCommandSourceCommandContext) {
|
private static int cancelImport(CommandContext<FabricClientCommandSource> ctx) {
|
||||||
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
|
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
|
ctx.getSource().sendError(Component.translatable("Voxy must be enabled in settings to use this"));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
var world = WorldIdentifier.ofEngineNullable(MinecraftClient.getInstance().player.clientWorld);
|
var world = WorldIdentifier.ofEngineNullable(Minecraft.getInstance().level);
|
||||||
if (world != null) {
|
if (world != null) {
|
||||||
return instance.getImportManager().cancelImport(world)?0:1;
|
return instance.getImportManager().cancelImport(world)?0:1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package me.cortex.voxy.client;
|
||||||
|
|
||||||
|
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
||||||
|
import me.cortex.voxy.client.core.VoxyRenderSystem;
|
||||||
|
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.components.debug.DebugScreenDisplayer;
|
||||||
|
import net.minecraft.client.gui.components.debug.DebugScreenEntry;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class VoxyDebugScreenEntry implements DebugScreenEntry {
|
||||||
|
@Override
|
||||||
|
public void display(DebugScreenDisplayer lines, @Nullable Level world, @Nullable LevelChunk clientChunk, @Nullable LevelChunk chunk) {
|
||||||
|
if (!VoxyCommon.isAvailable()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var instance = VoxyCommon.getInstance();
|
||||||
|
if (instance == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VoxyRenderSystem vrs = null;
|
||||||
|
var wr = Minecraft.getInstance().levelRenderer;
|
||||||
|
if (wr != null) vrs = ((IGetVoxyRenderSystem) wr).getVoxyRenderSystem();
|
||||||
|
|
||||||
|
//lines.addLineToSection();
|
||||||
|
List<String> instanceLines = new ArrayList<>();
|
||||||
|
instance.addDebug(instanceLines);
|
||||||
|
lines.addToGroup(Identifier.fromNamespaceAndPath("voxy", "instance_debug"), instanceLines);
|
||||||
|
|
||||||
|
if (vrs != null) {
|
||||||
|
List<String> renderLines = new ArrayList<>();
|
||||||
|
vrs.addDebugInfo(renderLines);
|
||||||
|
lines.addToGroup(Identifier.fromNamespaceAndPath("voxy", "render_debug"), renderLines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package me.cortex.voxy.client.compat;
|
||||||
|
|
||||||
|
import com.moulberry.flashback.Flashback;
|
||||||
|
import com.moulberry.flashback.playback.ReplayServer;
|
||||||
|
import com.moulberry.flashback.record.FlashbackMeta;
|
||||||
|
import me.cortex.voxy.common.Logger;
|
||||||
|
import me.cortex.voxy.common.config.section.SectionStorageConfig;
|
||||||
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class FlashbackCompat {
|
||||||
|
public static final boolean FLASHBACK_INSTALLED = FabricLoader.getInstance().isModLoaded("flashback");
|
||||||
|
|
||||||
|
public static Path getReplayStoragePath() {
|
||||||
|
if (!FLASHBACK_INSTALLED) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getReplayStoragePath0();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Path getReplayStoragePath0() {
|
||||||
|
ReplayServer replayServer = Flashback.getReplayServer();
|
||||||
|
if (replayServer != null) {
|
||||||
|
FlashbackMeta meta = replayServer.getMetadata();
|
||||||
|
if (meta != null) {
|
||||||
|
var path = ((IFlashbackMeta)meta).getVoxyPath();
|
||||||
|
if (path != null) {
|
||||||
|
Logger.info("Flashback replay server exists and meta exists");
|
||||||
|
if (path.exists()) {
|
||||||
|
Logger.info("Flashback voxy path exists in filesystem, using this as lod data source");
|
||||||
|
return path.toPath();
|
||||||
|
} else {
|
||||||
|
Logger.warn("Flashback meta had voxy path saved but path doesnt exist");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package me.cortex.voxy.client.compat;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public interface IFlashbackMeta {
|
||||||
|
void setVoxyPath(File path);
|
||||||
|
File getVoxyPath();
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package me.cortex.voxy.client.compat;
|
||||||
|
|
||||||
|
import me.cortex.voxy.common.thread.MultiThreadPrioritySemaphore;
|
||||||
|
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
|
||||||
|
public class SemaphoreBlockImpersonator extends Semaphore {
|
||||||
|
private final MultiThreadPrioritySemaphore.Block block;
|
||||||
|
public SemaphoreBlockImpersonator(MultiThreadPrioritySemaphore.Block block) {
|
||||||
|
super(0);
|
||||||
|
this.block = block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release(int permits) {
|
||||||
|
this.block.release(permits);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void acquire() throws InterruptedException {
|
||||||
|
this.block.acquire();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean tryAcquire() {
|
||||||
|
return this.block.tryAcquire();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int availablePermits() {
|
||||||
|
return this.block.availablePermits();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package me.cortex.voxy.client.config;
|
||||||
|
|
||||||
|
import net.caffeinemc.mods.sodium.client.config.structure.OptionPage;
|
||||||
|
import net.caffeinemc.mods.sodium.client.config.structure.Page;
|
||||||
|
|
||||||
|
public interface IConfigPageSetter {
|
||||||
|
void voxy$setPageJump(OptionPage page);
|
||||||
|
}
|
||||||
@@ -4,25 +4,18 @@ import com.terraformersmc.modmenu.api.ConfigScreenFactory;
|
|||||||
import com.terraformersmc.modmenu.api.ModMenuApi;
|
import com.terraformersmc.modmenu.api.ModMenuApi;
|
||||||
import me.cortex.voxy.common.Logger;
|
import me.cortex.voxy.common.Logger;
|
||||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||||
import net.caffeinemc.mods.sodium.client.gui.SodiumOptionsGUI;
|
import net.caffeinemc.mods.sodium.client.config.ConfigManager;
|
||||||
|
import net.caffeinemc.mods.sodium.client.config.structure.OptionPage;
|
||||||
|
import net.caffeinemc.mods.sodium.client.gui.VideoSettingsScreen;
|
||||||
|
|
||||||
public class ModMenuIntegration implements ModMenuApi {
|
public class ModMenuIntegration implements ModMenuApi {
|
||||||
@Override
|
@Override
|
||||||
public ConfigScreenFactory<?> getModConfigScreenFactory() {
|
public ConfigScreenFactory<?> getModConfigScreenFactory() {
|
||||||
return parent -> {
|
return parent -> {
|
||||||
if (VoxyCommon.isAvailable()) {
|
if (VoxyCommon.isAvailable()) {
|
||||||
var screen = (SodiumOptionsGUI) SodiumOptionsGUI.createScreen(parent);
|
var screen = (VideoSettingsScreen)VideoSettingsScreen.createScreen(parent);
|
||||||
//Sorry jelly and douira, please dont hurt me
|
var page = (OptionPage) ConfigManager.CONFIG.getModOptions().stream().filter(a->a.configId().equals("voxy")).findFirst().get().pages().get(0);
|
||||||
try {
|
((IConfigPageSetter)screen).voxy$setPageJump(page);
|
||||||
//We cant use .setPage() as that invokes rebuildGui, however the screen hasnt been initalized yet
|
|
||||||
// causing things to crash
|
|
||||||
var field = SodiumOptionsGUI.class.getDeclaredField("currentPage");
|
|
||||||
field.setAccessible(true);
|
|
||||||
field.set(screen, VoxyConfigScreenPages.voxyOptionPage);
|
|
||||||
field.setAccessible(false);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Logger.error("Failed to set the current page to voxy", e);
|
|
||||||
}
|
|
||||||
return screen;
|
return screen;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -0,0 +1,326 @@
|
|||||||
|
package me.cortex.voxy.client.config;
|
||||||
|
|
||||||
|
import me.cortex.voxy.common.util.Pair;
|
||||||
|
import net.caffeinemc.mods.sodium.api.config.ConfigState;
|
||||||
|
import net.caffeinemc.mods.sodium.api.config.StorageEventHandler;
|
||||||
|
import net.caffeinemc.mods.sodium.api.config.option.*;
|
||||||
|
import net.caffeinemc.mods.sodium.api.config.structure.*;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.network.chat.contents.TranslatableContents;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.*;
|
||||||
|
|
||||||
|
public class SodiumConfigBuilder {
|
||||||
|
|
||||||
|
private static record Enabler(Predicate<ConfigState> tester, Identifier[] dependencies) {
|
||||||
|
public Enabler(Predicate<ConfigState> tester, String[] dependencies) {
|
||||||
|
this(tester, mapIds(dependencies));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract static class Enableable <TYPE extends Enableable<TYPE>> {
|
||||||
|
private @Nullable Enabler prevEnabler;
|
||||||
|
protected @Nullable Enabler enabler;
|
||||||
|
|
||||||
|
private TYPE setEnabler0(Enabler enabler) {
|
||||||
|
this.prevEnabler = this.enabler;
|
||||||
|
this.enabler = enabler;
|
||||||
|
{
|
||||||
|
var children = this.getEnablerChildren();
|
||||||
|
if (children != null) {
|
||||||
|
for (var child : children) {
|
||||||
|
if (child.enabler == null || child.enabler == this.prevEnabler) {
|
||||||
|
child.setEnabler0(this.enabler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (TYPE) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TYPE setEnabler(Predicate<ConfigState> enabler, String... dependencies) {
|
||||||
|
return this.setEnabler0(new Enabler(enabler, dependencies));
|
||||||
|
}
|
||||||
|
|
||||||
|
public TYPE setEnabler(String enabler) {
|
||||||
|
if (enabler == null) {
|
||||||
|
return this.setEnabler(s->true);
|
||||||
|
}
|
||||||
|
var id = Identifier.parse(enabler);
|
||||||
|
return this.setEnabler(s->s.readBooleanOption(id), enabler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TYPE setEnablerAND(String... enablers) {
|
||||||
|
var enablersId = mapIds(enablers);
|
||||||
|
return this.setEnabler0(new Enabler(s->{
|
||||||
|
for (var id : enablersId) {
|
||||||
|
if (!s.readBooleanOption(id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}, enablersId));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Enableable[] getEnablerChildren() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Page extends Enableable<Page> {
|
||||||
|
protected Component name;
|
||||||
|
protected Group[] groups;
|
||||||
|
public Page(Component name, Group... groups) {
|
||||||
|
this.name = name;
|
||||||
|
this.groups = groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected OptionPageBuilder create(ConfigBuilder builder, BuildCtx ctx) {
|
||||||
|
var page = builder.createOptionPage();
|
||||||
|
page.setName(this.name);
|
||||||
|
for (var group : this.groups) {
|
||||||
|
page.addOptionGroup(group.create(builder, ctx));
|
||||||
|
}
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Enableable[] getEnablerChildren() {
|
||||||
|
return this.groups;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Group extends Enableable<Group> {
|
||||||
|
protected Option[] options;
|
||||||
|
public Group(Option... options) {
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected OptionGroupBuilder create(ConfigBuilder builder, BuildCtx ctx) {
|
||||||
|
var group = builder.createOptionGroup();
|
||||||
|
for (var option : this.options) {
|
||||||
|
group.addOption(option.create(builder, ctx));
|
||||||
|
}
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Enableable[] getEnablerChildren() {
|
||||||
|
return this.options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static abstract class Option <TYPE, OPTION extends Option<TYPE,OPTION,STYPE>, STYPE extends StatefulOptionBuilder<TYPE>> extends Enableable<Option<TYPE,OPTION,STYPE>> {
|
||||||
|
//Setter returns a post save update set
|
||||||
|
protected String id;
|
||||||
|
protected Component name;
|
||||||
|
protected Component tooltip;
|
||||||
|
protected Supplier<TYPE> getter;
|
||||||
|
protected Consumer<TYPE> setter;
|
||||||
|
public Option(String id, Component name, Component tooltip, Supplier<TYPE> getter, Consumer<TYPE> setter) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.tooltip = tooltip;
|
||||||
|
this.getter = getter;
|
||||||
|
this.setter = setter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Option(String id, Component name, Supplier<TYPE> getter, Consumer<TYPE> setter) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.getter = getter;
|
||||||
|
this.setter = setter;
|
||||||
|
if (name.getContents() instanceof TranslatableContents tc) {
|
||||||
|
this.tooltip = Component.translatable(tc.getKey() + ".tooltip");
|
||||||
|
} else {
|
||||||
|
this.tooltip = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected Consumer<TYPE> postRunner;
|
||||||
|
protected Identifier[] postRunnerConflicts;
|
||||||
|
protected Identifier[] postChangeFlags;
|
||||||
|
public OPTION setPostChangeRunner(Consumer<TYPE> postRunner, String... dontRunIfChangedVars) {
|
||||||
|
this.postRunner = postRunner;
|
||||||
|
this.postRunnerConflicts = mapIds(dontRunIfChangedVars);
|
||||||
|
return (OPTION) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OPTION setPostChangeFlags(String... flags) {
|
||||||
|
this.postChangeFlags = mapIds(flags);
|
||||||
|
return (OPTION) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract STYPE createType(ConfigBuilder builder);
|
||||||
|
|
||||||
|
protected STYPE create(ConfigBuilder builder, BuildCtx ctx) {
|
||||||
|
var option = this.createType(builder);
|
||||||
|
option.setName(this.name);
|
||||||
|
option.setTooltip(this.tooltip);
|
||||||
|
|
||||||
|
Set<Identifier> flags = new LinkedHashSet<>();
|
||||||
|
if (this.postRunner != null) {
|
||||||
|
var id = Identifier.parse(this.id);
|
||||||
|
var runner = this.postRunner;
|
||||||
|
var getter = this.getter;
|
||||||
|
ctx.postRunner.register(id, ()->runner.accept(getter.get()), this.postRunnerConflicts);
|
||||||
|
flags.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.postChangeFlags != null) {
|
||||||
|
flags.addAll(List.of(this.postChangeFlags));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!flags.isEmpty()) {
|
||||||
|
option.setFlags(flags.toArray(Identifier[]::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
option.setBinding(this.setter, this.getter);
|
||||||
|
if (this.enabler != null) {
|
||||||
|
var pred = this.enabler.tester;
|
||||||
|
option.setEnabledProvider(s->pred.test(s), this.enabler.dependencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
option.setStorageHandler(ctx.saveHandler);
|
||||||
|
|
||||||
|
option.setDefaultValue(this.getter.get());
|
||||||
|
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class IntOption extends Option<Integer, IntOption, IntegerOptionBuilder> {
|
||||||
|
protected Function<ConfigState, Range> rangeProvider;
|
||||||
|
protected String[] rangeDependencies;
|
||||||
|
protected ControlValueFormatter formatter = v->Component.literal(Integer.toString(v));
|
||||||
|
|
||||||
|
public IntOption(String id, Component name, Component tooltip, Supplier<Integer> getter, Consumer<Integer> setter, Range range) {
|
||||||
|
super(id, name, tooltip, getter, setter);
|
||||||
|
this.rangeProvider = s->range;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntOption(String id, Component name, Supplier<Integer> getter, Consumer<Integer> setter, Range range) {
|
||||||
|
super(id, name, getter, setter);
|
||||||
|
this.rangeProvider = s->range;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntOption setFormatter(IntFunction<Component> formatter) {
|
||||||
|
this.formatter = v->formatter.apply(v);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IntegerOptionBuilder createType(ConfigBuilder builder) {
|
||||||
|
return builder.createIntegerOption(Identifier.parse(this.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IntegerOptionBuilder create(ConfigBuilder builder, BuildCtx ctx) {
|
||||||
|
var option = super.create(builder, ctx);
|
||||||
|
if (this.rangeDependencies == null || this.rangeDependencies.length == 0) {
|
||||||
|
option.setRange(this.rangeProvider.apply(null));
|
||||||
|
} else {
|
||||||
|
option.setRangeProvider((Function<ConfigState, SteppedValidator>)(Object) this.rangeProvider, mapIds(this.rangeDependencies));
|
||||||
|
}
|
||||||
|
option.setValueFormatter(this.formatter);
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class BoolOption extends Option<Boolean, BoolOption, BooleanOptionBuilder> {
|
||||||
|
public BoolOption(String id, Component name, Component tooltip, Supplier<Boolean> getter, Consumer<Boolean> setter) {
|
||||||
|
super(id, name, tooltip, getter, setter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BoolOption(String id, Component name, Supplier<Boolean> getter, Consumer<Boolean> setter) {
|
||||||
|
super(id, name, getter, setter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BooleanOptionBuilder createType(ConfigBuilder builder) {
|
||||||
|
return builder.createBooleanOption(Identifier.parse(this.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <F,T> T[] map(F[] from, Function<F,T> mapper, Function<Integer,T[]> factory) {
|
||||||
|
T[] arr = factory.apply(from.length);
|
||||||
|
for (int i = 0; i < from.length; i++) {
|
||||||
|
arr[i] = mapper.apply(from[i]);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Identifier[] mapIds(String[] strings) {
|
||||||
|
return map(strings, Identifier::parse, Identifier[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class PostApplyOps implements FlagHook {
|
||||||
|
private record Hook(Identifier name, Runnable runnable, Set<Identifier> conflicts) {}
|
||||||
|
private Map<Identifier, Hook> hooks = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
public PostApplyOps register(String name, Runnable postRunner, String... conflicts) {
|
||||||
|
return this.register(Identifier.parse(name), postRunner, mapIds(conflicts));
|
||||||
|
}
|
||||||
|
|
||||||
|
public PostApplyOps register(Identifier name, Runnable postRunner, Identifier... conflicts) {
|
||||||
|
this.hooks.put(name, new Hook(name, postRunner, new LinkedHashSet<>(List.of(conflicts))));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PostApplyOps build() {
|
||||||
|
boolean changed = false;
|
||||||
|
do {
|
||||||
|
changed = false;
|
||||||
|
for (var hook : this.hooks.values()) {
|
||||||
|
for (var ref : new LinkedHashSet<>(hook.conflicts)) {
|
||||||
|
var other = this.hooks.getOrDefault(ref, null);
|
||||||
|
if (other != null) {
|
||||||
|
changed |= hook.conflicts.addAll(other.conflicts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (changed);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Identifier> getTriggers() {
|
||||||
|
return this.hooks.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(Collection<Identifier> identifiers, ConfigState configState) {
|
||||||
|
for (var id : identifiers) {
|
||||||
|
var hook = this.hooks.get(id);
|
||||||
|
if (hook != null) {
|
||||||
|
if (Collections.disjoint(identifiers, hook.conflicts)) {
|
||||||
|
hook.runnable.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static final class BuildCtx {
|
||||||
|
public PostApplyOps postRunner = new PostApplyOps();
|
||||||
|
public StorageEventHandler saveHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void buildToSodium(ConfigBuilder builder, ModOptionsBuilder options, StorageEventHandler saveHandler, Consumer<PostApplyOps> registerOps, Page... pages) {
|
||||||
|
var ctx = new BuildCtx();
|
||||||
|
registerOps.accept(ctx.postRunner);
|
||||||
|
ctx.saveHandler = saveHandler;
|
||||||
|
for (var page : pages) {
|
||||||
|
options.addPage(page.create(builder, ctx));
|
||||||
|
}
|
||||||
|
options.registerFlagHook(ctx.postRunner.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
154
src/main/java/me/cortex/voxy/client/config/VoxyConfigMenu.java
Normal file
154
src/main/java/me/cortex/voxy/client/config/VoxyConfigMenu.java
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
package me.cortex.voxy.client.config;
|
||||||
|
|
||||||
|
import me.cortex.voxy.client.RenderStatistics;
|
||||||
|
import me.cortex.voxy.client.config.SodiumConfigBuilder.*;
|
||||||
|
import me.cortex.voxy.common.config.VoxyConfig;
|
||||||
|
import me.cortex.voxy.client.VoxyClient;
|
||||||
|
import me.cortex.voxy.client.VoxyClientInstance;
|
||||||
|
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
||||||
|
import me.cortex.voxy.client.core.util.IrisUtil;
|
||||||
|
import me.cortex.voxy.common.util.cpu.CpuLayout;
|
||||||
|
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||||
|
import net.caffeinemc.mods.sodium.api.config.ConfigEntryPoint;
|
||||||
|
import net.caffeinemc.mods.sodium.api.config.option.OptionFlag;
|
||||||
|
import net.caffeinemc.mods.sodium.api.config.option.OptionImpact;
|
||||||
|
import net.caffeinemc.mods.sodium.api.config.option.Range;
|
||||||
|
import net.caffeinemc.mods.sodium.api.config.structure.ConfigBuilder;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
|
public class VoxyConfigMenu implements ConfigEntryPoint {
|
||||||
|
@Override
|
||||||
|
public void registerConfigLate(ConfigBuilder B) {
|
||||||
|
if (!VoxyCommon.isAvailable()) return;//Dont even register the config if its not avalible
|
||||||
|
|
||||||
|
var CFG = VoxyConfig.CONFIG;
|
||||||
|
|
||||||
|
var cc = B.registerModOptions("voxy", "Voxy", VoxyCommon.MOD_VERSION)
|
||||||
|
.setIcon(Identifier.parse("voxy:icon.png"));
|
||||||
|
|
||||||
|
final var RENDER_RELOAD = OptionFlag.REQUIRES_RENDERER_RELOAD.getId().toString();
|
||||||
|
|
||||||
|
SodiumConfigBuilder.buildToSodium(B, cc, CFG::save, postOp->{
|
||||||
|
postOp.register("voxy:update_threads", ()->{
|
||||||
|
var instance = VoxyCommon.getInstance();
|
||||||
|
if (instance != null) {
|
||||||
|
instance.updateDedicatedThreads();
|
||||||
|
}
|
||||||
|
}, "voxy:enabled").register("voxy:iris_reload", ()->IrisUtil.reload());
|
||||||
|
},
|
||||||
|
new Page(Component.translatable("voxy.config.general"),
|
||||||
|
new Group(
|
||||||
|
new BoolOption(
|
||||||
|
"voxy:enabled",
|
||||||
|
Component.translatable("voxy.config.general.enabled"),
|
||||||
|
()->CFG.enabled, v->{
|
||||||
|
CFG.enabled=v;
|
||||||
|
//we need to special case enabled, since the render reload flag runs befor us and its quite important we get it right
|
||||||
|
if (v&&VoxyClientInstance.isInGame) {
|
||||||
|
VoxyCommon.createInstance();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setPostChangeRunner(c->{
|
||||||
|
if (!c) {
|
||||||
|
var vrsh = (IGetVoxyRenderSystem) Minecraft.getInstance().levelRenderer;
|
||||||
|
if (vrsh != null) {
|
||||||
|
vrsh.shutdownRenderer();
|
||||||
|
}
|
||||||
|
VoxyCommon.shutdownInstance();
|
||||||
|
}
|
||||||
|
}).setPostChangeFlags(RENDER_RELOAD, "voxy:iris_reload").setEnabler(null)
|
||||||
|
), new Group(
|
||||||
|
new IntOption(
|
||||||
|
"voxy:thread_count",
|
||||||
|
Component.translatable("voxy.config.general.serviceThreads"),
|
||||||
|
()->CFG.serviceThreads, v->CFG.serviceThreads=v,
|
||||||
|
new Range(1, CpuLayout.getCoreCount(), 1))
|
||||||
|
.setPostChangeFlags("voxy:update_threads"),
|
||||||
|
new BoolOption(
|
||||||
|
"voxy:use_sodium_threads",
|
||||||
|
Component.translatable("voxy.config.general.useSodiumBuilder"),
|
||||||
|
()->!CFG.dontUseSodiumBuilderThreads, v->CFG.dontUseSodiumBuilderThreads=!v)
|
||||||
|
.setPostChangeFlags("voxy:update_threads")
|
||||||
|
), new Group(
|
||||||
|
new BoolOption(
|
||||||
|
"voxy:ingest_enabled",
|
||||||
|
Component.translatable("voxy.config.general.ingest"),
|
||||||
|
()->CFG.ingestEnabled, v->CFG.ingestEnabled=v)
|
||||||
|
)
|
||||||
|
).setEnabler("voxy:enabled"),
|
||||||
|
new Page(Component.translatable("voxy.config.rendering"),
|
||||||
|
new Group(
|
||||||
|
new BoolOption(
|
||||||
|
"voxy:rendering",
|
||||||
|
Component.translatable("voxy.config.general.rendering"),
|
||||||
|
()->CFG.enableRendering, v->CFG.enableRendering=v)
|
||||||
|
.setPostChangeRunner(c->{
|
||||||
|
var vrsh = (IGetVoxyRenderSystem)Minecraft.getInstance().levelRenderer;
|
||||||
|
if (vrsh != null) {
|
||||||
|
if (c) {
|
||||||
|
vrsh.createRenderer();
|
||||||
|
} else {
|
||||||
|
vrsh.shutdownRenderer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},"voxy:enabled", RENDER_RELOAD)
|
||||||
|
.setPostChangeFlags("voxy:iris_reload")
|
||||||
|
.setEnabler("voxy:enabled")
|
||||||
|
), new Group(
|
||||||
|
new IntOption(
|
||||||
|
"voxy:subdivsize",
|
||||||
|
Component.translatable("voxy.config.general.subDivisionSize"),
|
||||||
|
()->subDiv2ln(CFG.subDivisionSize), v->CFG.subDivisionSize=ln2subDiv(v),
|
||||||
|
new Range(0, SUBDIV_IN_MAX, 1))
|
||||||
|
.setFormatter(v->Component.literal(Integer.toString(Math.round(ln2subDiv(v))))),
|
||||||
|
new IntOption(
|
||||||
|
"voxy:render_distance",
|
||||||
|
Component.translatable("voxy.config.general.renderDistance"),
|
||||||
|
()->CFG.sectionRenderDistance, v->CFG.sectionRenderDistance=v,
|
||||||
|
new Range(2, 64, 1))
|
||||||
|
.setFormatter(v->Component.literal(Integer.toString(v*32)))//Top level rd == 32 chunks
|
||||||
|
.setPostChangeRunner(c->{
|
||||||
|
var vrsh = (IGetVoxyRenderSystem)Minecraft.getInstance().levelRenderer;
|
||||||
|
if (vrsh != null) {
|
||||||
|
var vrs = vrsh.getVoxyRenderSystem();
|
||||||
|
if (vrs != null) {
|
||||||
|
vrs.setRenderDistance(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, "voxy:rendering", RENDER_RELOAD)
|
||||||
|
), new Group(
|
||||||
|
new BoolOption(
|
||||||
|
"voxy:eviromental_fog",
|
||||||
|
Component.translatable("voxy.config.general.environmental_fog"),
|
||||||
|
()->CFG.useEnvironmentalFog, v->CFG.useEnvironmentalFog=v)
|
||||||
|
.setPostChangeFlags(RENDER_RELOAD)
|
||||||
|
), new Group(
|
||||||
|
new BoolOption(
|
||||||
|
"voxy:render_debug",
|
||||||
|
Component.translatable("voxy.config.general.render_statistics"),
|
||||||
|
()-> RenderStatistics.enabled, v->RenderStatistics.enabled=v)
|
||||||
|
.setPostChangeFlags(RENDER_RELOAD))
|
||||||
|
).setEnablerAND("voxy:enabled", "voxy:rendering"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static final int SUBDIV_IN_MAX = 100;
|
||||||
|
private static final double SUBDIV_MIN = 28;
|
||||||
|
private static final double SUBDIV_MAX = 256;
|
||||||
|
private static final double SUBDIV_CONST = Math.log(SUBDIV_MAX/SUBDIV_MIN)/Math.log(2);
|
||||||
|
|
||||||
|
//In range is 0->200
|
||||||
|
//Out range is 28->256
|
||||||
|
private static float ln2subDiv(int in) {
|
||||||
|
return (float) (SUBDIV_MIN*Math.pow(2, SUBDIV_CONST*((double)in/SUBDIV_IN_MAX)));
|
||||||
|
}
|
||||||
|
|
||||||
|
//In range is ... any?
|
||||||
|
//Out range is 0->200
|
||||||
|
private static int subDiv2ln(float in) {
|
||||||
|
return (int) (((Math.log(((double)in)/SUBDIV_MIN)/Math.log(2))/SUBDIV_CONST)*SUBDIV_IN_MAX);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
package me.cortex.voxy.client.config;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import me.cortex.voxy.client.RenderStatistics;
|
|
||||||
import me.cortex.voxy.client.VoxyClientInstance;
|
|
||||||
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
|
||||||
import me.cortex.voxy.common.util.cpu.CpuLayout;
|
|
||||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
|
||||||
import net.caffeinemc.mods.sodium.client.gui.options.*;
|
|
||||||
import net.caffeinemc.mods.sodium.client.gui.options.control.SliderControl;
|
|
||||||
import net.caffeinemc.mods.sodium.client.gui.options.control.TickBoxControl;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.text.Text;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public abstract class VoxyConfigScreenPages {
|
|
||||||
private VoxyConfigScreenPages(){}
|
|
||||||
|
|
||||||
public static OptionPage voxyOptionPage = null;
|
|
||||||
|
|
||||||
public static OptionPage page() {
|
|
||||||
List<OptionGroup> groups = new ArrayList<>();
|
|
||||||
VoxyConfig storage = VoxyConfig.CONFIG;
|
|
||||||
|
|
||||||
//General
|
|
||||||
groups.add(OptionGroup.createBuilder()
|
|
||||||
.add(OptionImpl.createBuilder(boolean.class, storage)
|
|
||||||
.setName(Text.translatable("voxy.config.general.enabled"))
|
|
||||||
.setTooltip(Text.translatable("voxy.config.general.enabled.tooltip"))
|
|
||||||
.setControl(TickBoxControl::new)
|
|
||||||
.setBinding((s, v)->{
|
|
||||||
s.enabled = v;
|
|
||||||
if (v) {
|
|
||||||
if (VoxyClientInstance.isInGame) {
|
|
||||||
VoxyCommon.createInstance();
|
|
||||||
var vrsh = (IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer;
|
|
||||||
if (vrsh != null && s.enableRendering) {
|
|
||||||
vrsh.createRenderer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var vrsh = (IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer;
|
|
||||||
if (vrsh != null) {
|
|
||||||
vrsh.shutdownRenderer();
|
|
||||||
}
|
|
||||||
VoxyCommon.shutdownInstance();
|
|
||||||
}
|
|
||||||
}, s -> s.enabled)
|
|
||||||
.build()
|
|
||||||
).add(OptionImpl.createBuilder(int.class, storage)
|
|
||||||
.setName(Text.translatable("voxy.config.general.serviceThreads"))
|
|
||||||
.setTooltip(Text.translatable("voxy.config.general.serviceThreads.tooltip"))
|
|
||||||
.setControl(opt->new SliderControl(opt, 1,
|
|
||||||
CpuLayout.CORES.length, //Just do core size as max
|
|
||||||
//Runtime.getRuntime().availableProcessors(),//Note: this is threads not cores, the default value is half the core count, is fine as this should technically be the limit but CpuLayout.CORES.length is more realistic
|
|
||||||
1, v->Text.literal(Integer.toString(v))))
|
|
||||||
.setBinding((s, v)->{
|
|
||||||
boolean wasEnabled = VoxyCommon.getInstance() != null;
|
|
||||||
var vrsh = (IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer;
|
|
||||||
if (wasEnabled) {
|
|
||||||
if (vrsh != null) {
|
|
||||||
vrsh.shutdownRenderer();
|
|
||||||
}
|
|
||||||
VoxyCommon.shutdownInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
s.serviceThreads = v;
|
|
||||||
|
|
||||||
if (wasEnabled) {
|
|
||||||
VoxyCommon.createInstance();
|
|
||||||
|
|
||||||
if (vrsh != null && s.enableRendering) {
|
|
||||||
vrsh.createRenderer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, s -> s.serviceThreads)
|
|
||||||
.setImpact(OptionImpact.HIGH)
|
|
||||||
.build()
|
|
||||||
).add(OptionImpl.createBuilder(boolean.class, storage)
|
|
||||||
.setName(Text.translatable("voxy.config.general.ingest"))
|
|
||||||
.setTooltip(Text.translatable("voxy.config.general.ingest.tooltip"))
|
|
||||||
.setControl(TickBoxControl::new)
|
|
||||||
.setBinding((s, v) -> s.ingestEnabled = v, s -> s.ingestEnabled)
|
|
||||||
.setImpact(OptionImpact.MEDIUM)
|
|
||||||
.build()
|
|
||||||
).build()
|
|
||||||
);
|
|
||||||
|
|
||||||
groups.add(OptionGroup.createBuilder()
|
|
||||||
.add(OptionImpl.createBuilder(boolean.class, storage)
|
|
||||||
.setName(Text.translatable("voxy.config.general.rendering"))
|
|
||||||
.setTooltip(Text.translatable("voxy.config.general.rendering.tooltip"))
|
|
||||||
.setControl(TickBoxControl::new)
|
|
||||||
.setBinding((s, v)->{
|
|
||||||
s.enableRendering = v;
|
|
||||||
var vrsh = (IGetVoxyRenderSystem)MinecraftClient.getInstance().worldRenderer;
|
|
||||||
if (vrsh != null) {
|
|
||||||
if (v) {
|
|
||||||
vrsh.createRenderer();
|
|
||||||
} else {
|
|
||||||
vrsh.shutdownRenderer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, s -> s.enableRendering)
|
|
||||||
.setImpact(OptionImpact.HIGH)
|
|
||||||
.build()
|
|
||||||
).add(OptionImpl.createBuilder(int.class, storage)
|
|
||||||
.setName(Text.translatable("voxy.config.general.subDivisionSize"))
|
|
||||||
.setTooltip(Text.translatable("voxy.config.general.subDivisionSize.tooltip"))
|
|
||||||
.setControl(opt->new SliderControl(opt, 0, SUBDIV_IN_MAX, 1, v->Text.literal(Integer.toString(Math.round(ln2subDiv(v))))))
|
|
||||||
.setBinding((s, v) -> s.subDivisionSize = ln2subDiv(v), s -> subDiv2ln(s.subDivisionSize))
|
|
||||||
.setImpact(OptionImpact.HIGH)
|
|
||||||
.build()
|
|
||||||
).add(OptionImpl.createBuilder(int.class, storage)
|
|
||||||
.setName(Text.translatable("voxy.config.general.renderDistance"))
|
|
||||||
.setTooltip(Text.translatable("voxy.config.general.renderDistance.tooltip"))
|
|
||||||
.setControl(opt->new SliderControl(opt, 2, 64, 1, v->Text.literal(Integer.toString(v * 32))))//Every unit is equal to 32 vanilla chunks
|
|
||||||
.setBinding((s, v)-> {
|
|
||||||
s.sectionRenderDistance = v;
|
|
||||||
var vrsh = (IGetVoxyRenderSystem)MinecraftClient.getInstance().worldRenderer;
|
|
||||||
if (vrsh != null) {
|
|
||||||
var vrs = vrsh.getVoxyRenderSystem();
|
|
||||||
if (vrs != null) {
|
|
||||||
vrs.setRenderDistance(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, s -> s.sectionRenderDistance)
|
|
||||||
.setImpact(OptionImpact.LOW)
|
|
||||||
.build()
|
|
||||||
).add(OptionImpl.createBuilder(boolean.class, storage)
|
|
||||||
.setName(Text.translatable("voxy.config.general.vanilla_fog"))
|
|
||||||
.setTooltip(Text.translatable("voxy.config.general.vanilla_fog.tooltip"))
|
|
||||||
.setControl(TickBoxControl::new)
|
|
||||||
.setBinding((s, v)-> s.renderVanillaFog = v, s -> s.renderVanillaFog)
|
|
||||||
.build()
|
|
||||||
).add(OptionImpl.createBuilder(boolean.class, storage)
|
|
||||||
.setName(Text.translatable("voxy.config.general.render_statistics"))
|
|
||||||
.setTooltip(Text.translatable("voxy.config.general.render_statistics.tooltip"))
|
|
||||||
.setControl(TickBoxControl::new)
|
|
||||||
.setBinding((s, v)-> RenderStatistics.enabled = v, s -> RenderStatistics.enabled)
|
|
||||||
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
|
|
||||||
.build()
|
|
||||||
).build()
|
|
||||||
);
|
|
||||||
return new OptionPage(Text.translatable("voxy.config.title"), ImmutableList.copyOf(groups));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final int SUBDIV_IN_MAX = 100;
|
|
||||||
private static final double SUBDIV_MIN = 28;
|
|
||||||
private static final double SUBDIV_MAX = 256;
|
|
||||||
private static final double SUBDIV_CONST = Math.log(SUBDIV_MAX/SUBDIV_MIN)/Math.log(2);
|
|
||||||
|
|
||||||
|
|
||||||
//In range is 0->200
|
|
||||||
//Out range is 28->256
|
|
||||||
private static float ln2subDiv(int in) {
|
|
||||||
return (float) (SUBDIV_MIN*Math.pow(2, SUBDIV_CONST*((double)in/SUBDIV_IN_MAX)));
|
|
||||||
}
|
|
||||||
|
|
||||||
//In range is ... any?
|
|
||||||
//Out range is 0->200
|
|
||||||
private static int subDiv2ln(float in) {
|
|
||||||
return (int) (((Math.log(((double)in)/SUBDIV_MIN)/Math.log(2))/SUBDIV_CONST)*SUBDIV_IN_MAX);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
package me.cortex.voxy.client.core;
|
||||||
|
|
||||||
|
import me.cortex.voxy.client.RenderStatistics;
|
||||||
|
import me.cortex.voxy.client.TimingStatistics;
|
||||||
|
import me.cortex.voxy.client.VoxyClient;
|
||||||
|
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
|
||||||
|
import me.cortex.voxy.client.core.rendering.Viewport;
|
||||||
|
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.post.FullscreenBlit;
|
||||||
|
import me.cortex.voxy.client.core.rendering.section.backend.AbstractSectionRenderer;
|
||||||
|
import me.cortex.voxy.client.core.rendering.util.DepthFramebuffer;
|
||||||
|
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
||||||
|
import me.cortex.voxy.common.util.TrackedObject;
|
||||||
|
import org.joml.Matrix4f;
|
||||||
|
import org.lwjgl.opengl.GL30;
|
||||||
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.GL11C.GL_ALWAYS;
|
||||||
|
import static org.lwjgl.opengl.GL11C.GL_DEPTH_TEST;
|
||||||
|
import static org.lwjgl.opengl.GL11C.GL_EQUAL;
|
||||||
|
import static org.lwjgl.opengl.GL11C.GL_KEEP;
|
||||||
|
import static org.lwjgl.opengl.GL11C.GL_REPLACE;
|
||||||
|
import static org.lwjgl.opengl.GL11C.GL_STENCIL_TEST;
|
||||||
|
import static org.lwjgl.opengl.GL11C.glColorMask;
|
||||||
|
import static org.lwjgl.opengl.GL11C.glDisable;
|
||||||
|
import static org.lwjgl.opengl.GL11C.glEnable;
|
||||||
|
import static org.lwjgl.opengl.GL11C.glStencilFunc;
|
||||||
|
import static org.lwjgl.opengl.GL11C.glStencilMask;
|
||||||
|
import static org.lwjgl.opengl.GL11C.glStencilOp;
|
||||||
|
import static org.lwjgl.opengl.GL30C.GL_DEPTH24_STENCIL8;
|
||||||
|
import static org.lwjgl.opengl.GL30C.GL_FRAMEBUFFER;
|
||||||
|
import static org.lwjgl.opengl.GL30C.glBindFramebuffer;
|
||||||
|
import static org.lwjgl.opengl.GL42.GL_LEQUAL;
|
||||||
|
import static org.lwjgl.opengl.GL42.GL_NOTEQUAL;
|
||||||
|
import static org.lwjgl.opengl.GL42.glDepthFunc;
|
||||||
|
import static org.lwjgl.opengl.GL42.*;
|
||||||
|
import static org.lwjgl.opengl.GL45.glClearNamedFramebufferfi;
|
||||||
|
import static org.lwjgl.opengl.GL45.glGetNamedFramebufferAttachmentParameteri;
|
||||||
|
import static org.lwjgl.opengl.GL45C.glBindTextureUnit;
|
||||||
|
|
||||||
|
public abstract class AbstractRenderPipeline extends TrackedObject {
|
||||||
|
private final BooleanSupplier frexStillHasWork;
|
||||||
|
|
||||||
|
private final AsyncNodeManager nodeManager;
|
||||||
|
private final NodeCleaner nodeCleaner;
|
||||||
|
private final HierarchicalOcclusionTraverser traversal;
|
||||||
|
|
||||||
|
protected AbstractSectionRenderer<?,?> sectionRenderer;
|
||||||
|
|
||||||
|
private final FullscreenBlit depthMaskBlit = new FullscreenBlit("voxy:post/fullscreen2.vert", "voxy:post/noop.frag");
|
||||||
|
private final FullscreenBlit depthSetBlit = new FullscreenBlit("voxy:post/fullscreen2.vert", "voxy:post/depth0.frag");
|
||||||
|
private final FullscreenBlit depthCopy = new FullscreenBlit("voxy:post/fullscreen2.vert", "voxy:post/depth_copy.frag");
|
||||||
|
|
||||||
|
public final DepthFramebuffer fb = new DepthFramebuffer(GL_DEPTH24_STENCIL8);
|
||||||
|
|
||||||
|
protected final boolean deferTranslucency;
|
||||||
|
|
||||||
|
private static final int DEPTH_SAMPLER = glGenSamplers();
|
||||||
|
static {
|
||||||
|
glSamplerParameteri(DEPTH_SAMPLER, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
glSamplerParameteri(DEPTH_SAMPLER, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AbstractRenderPipeline(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier, boolean deferTranslucency) {
|
||||||
|
this.frexStillHasWork = frexSupplier;
|
||||||
|
this.nodeManager = nodeManager;
|
||||||
|
this.nodeCleaner = nodeCleaner;
|
||||||
|
this.traversal = traversal;
|
||||||
|
this.deferTranslucency = deferTranslucency;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Allows pipelines to configure model baking system
|
||||||
|
public void setupExtraModelBakeryData(ModelBakerySubsystem modelService) {}
|
||||||
|
|
||||||
|
public final void setSectionRenderer(AbstractSectionRenderer<?,?> sectionRenderer) {//Stupid java ordering not allowing something pre super
|
||||||
|
if (this.sectionRenderer != null) throw new IllegalStateException();
|
||||||
|
this.sectionRenderer = sectionRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Called before the pipeline starts running, used to update uniforms etc
|
||||||
|
public void preSetup(Viewport<?> viewport) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract int setup(Viewport<?> viewport, int sourceFramebuffer, int srcWidth, int srcHeight);
|
||||||
|
protected abstract void postOpaquePreTranslucent(Viewport<?> viewport);
|
||||||
|
protected void finish(Viewport<?> viewport, int sourceFrameBuffer, int srcWidth, int srcHeight) {
|
||||||
|
glDisable(GL_STENCIL_TEST);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, sourceFrameBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void runPipeline(Viewport<?> viewport, int sourceFrameBuffer, int srcWidth, int srcHeight) {
|
||||||
|
int depthTexture = this.setup(viewport, sourceFrameBuffer, srcWidth, srcHeight);
|
||||||
|
|
||||||
|
var rs = ((AbstractSectionRenderer)this.sectionRenderer);
|
||||||
|
rs.renderOpaque(viewport);
|
||||||
|
var occlusionDebug = VoxyClient.getOcclusionDebugState();
|
||||||
|
if (occlusionDebug==0) {
|
||||||
|
this.innerPrimaryWork(viewport, depthTexture);
|
||||||
|
}
|
||||||
|
if (occlusionDebug<=1) {
|
||||||
|
rs.buildDrawCalls(viewport);
|
||||||
|
}
|
||||||
|
rs.renderTemporal(viewport);
|
||||||
|
|
||||||
|
this.postOpaquePreTranslucent(viewport);
|
||||||
|
|
||||||
|
if (!this.deferTranslucency) {
|
||||||
|
rs.renderTranslucent(viewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.finish(viewport, sourceFrameBuffer, srcWidth, srcHeight);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, sourceFrameBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initDepthStencil(int sourceFrameBuffer, int targetFb, int srcWidth, int srcHeight, int width, int height) {
|
||||||
|
glClearNamedFramebufferfi(targetFb, GL_DEPTH_STENCIL, 0, 1.0f, 1);
|
||||||
|
// using blit to copy depth from mismatched depth formats is not portable so instead a full screen pass is performed for a depth copy
|
||||||
|
// the mismatched formats in this case is the d32 to d24s8
|
||||||
|
glBindFramebuffer(GL30.GL_FRAMEBUFFER, targetFb);
|
||||||
|
|
||||||
|
this.depthCopy.bind();
|
||||||
|
int depthTexture = glGetNamedFramebufferAttachmentParameteri(sourceFrameBuffer, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
|
||||||
|
glBindTextureUnit(0, depthTexture);
|
||||||
|
glBindSampler(0, DEPTH_SAMPLER);
|
||||||
|
glUniform2f(1,((float)width)/srcWidth, ((float)height)/srcHeight);
|
||||||
|
glColorMask(false,false,false,false);
|
||||||
|
this.depthCopy.blit();
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (Capabilities.INSTANCE.isMesa){
|
||||||
|
glClearStencil(1);
|
||||||
|
glClear(GL_STENCIL_BUFFER_BIT);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
//This whole thing is hell, we basicly want to create a mask stenicel/depth mask specificiclly
|
||||||
|
// in theory we could do this in a single pass by passing in the depth buffer from the sourceFrambuffer
|
||||||
|
// but the current implmentation does a 2 pass system
|
||||||
|
glEnable(GL_STENCIL_TEST);
|
||||||
|
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
|
||||||
|
glStencilFunc(GL_ALWAYS, 0, 0xFF);
|
||||||
|
glStencilMask(0xFF);
|
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
glDepthFunc(GL_NOTEQUAL);//If != 1 pass
|
||||||
|
//We do here
|
||||||
|
this.depthMaskBlit.blit();
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
|
||||||
|
//Blit depth 0 where stencil is 0
|
||||||
|
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
|
||||||
|
glStencilFunc(GL_EQUAL, 0, 0xFF);
|
||||||
|
|
||||||
|
this.depthSetBlit.blit();
|
||||||
|
|
||||||
|
glDepthFunc(GL_LEQUAL);
|
||||||
|
glColorMask(true,true,true,true);
|
||||||
|
|
||||||
|
//Make voxy terrain render only where there isnt mc terrain
|
||||||
|
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
|
||||||
|
glStencilFunc(GL_EQUAL, 1, 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final long SCRATCH = MemoryUtil.nmemAlloc(4*4*4);
|
||||||
|
protected static void transformBlitDepth(FullscreenBlit blitShader, int srcDepthTex, int dstFB, Viewport<?> viewport, Matrix4f targetTransform) {
|
||||||
|
// at this point the dst frame buffer doesn't have a stencil attachment so we don't need to keep the stencil test on for the blit
|
||||||
|
// in the worst case the dstFB does have a stencil attachment causing this pass to become 'corrupted'
|
||||||
|
glDisable(GL_STENCIL_TEST);
|
||||||
|
glBindFramebuffer(GL30.GL_FRAMEBUFFER, dstFB);
|
||||||
|
|
||||||
|
blitShader.bind();
|
||||||
|
glBindTextureUnit(0, srcDepthTex);
|
||||||
|
new Matrix4f(viewport.MVP).invert().getToAddress(SCRATCH);
|
||||||
|
nglUniformMatrix4fv(1, 1, false, SCRATCH);//inverse fromProjection
|
||||||
|
targetTransform.getToAddress(SCRATCH);//new Matrix4f(tooProjection).mul(vp.modelView).get(data);
|
||||||
|
nglUniformMatrix4fv(2, 1, false, SCRATCH);//tooProjection
|
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
blitShader.blit();
|
||||||
|
glDisable(GL_STENCIL_TEST);
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void innerPrimaryWork(Viewport<?> viewport, int depthBuffer) {
|
||||||
|
|
||||||
|
//Compute the mip chain
|
||||||
|
viewport.hiZBuffer.buildMipChain(depthBuffer, viewport.width, viewport.height);
|
||||||
|
|
||||||
|
do {
|
||||||
|
TimingStatistics.main.stop();
|
||||||
|
TimingStatistics.dynamic.start();
|
||||||
|
|
||||||
|
TimingStatistics.D.start();
|
||||||
|
//Tick download stream
|
||||||
|
DownloadStream.INSTANCE.tick();
|
||||||
|
TimingStatistics.D.stop();
|
||||||
|
|
||||||
|
this.nodeManager.tick(this.traversal.getNodeBuffer(), this.nodeCleaner);
|
||||||
|
//glFlush();
|
||||||
|
|
||||||
|
this.nodeCleaner.tick(this.traversal.getNodeBuffer());//Probably do this here??
|
||||||
|
|
||||||
|
TimingStatistics.dynamic.stop();
|
||||||
|
TimingStatistics.main.start();
|
||||||
|
|
||||||
|
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT | GL_PIXEL_BUFFER_BARRIER_BIT);
|
||||||
|
|
||||||
|
TimingStatistics.F.start();
|
||||||
|
this.traversal.doTraversal(viewport);
|
||||||
|
TimingStatistics.F.stop();
|
||||||
|
} while (this.frexStillHasWork.getAsBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void free0() {
|
||||||
|
this.fb.free();
|
||||||
|
this.sectionRenderer.free();
|
||||||
|
this.depthMaskBlit.delete();
|
||||||
|
this.depthSetBlit.delete();
|
||||||
|
this.depthCopy.delete();
|
||||||
|
super.free0();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDebug(List<String> debug) {
|
||||||
|
this.sectionRenderer.addDebug(debug);
|
||||||
|
RenderStatistics.addDebug(debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Binds the framebuffer and any other bindings needed for rendering
|
||||||
|
public abstract void setupAndBindOpaque(Viewport<?> viewport);
|
||||||
|
public abstract void setupAndBindTranslucent(Viewport<?> viewport);
|
||||||
|
|
||||||
|
|
||||||
|
public void bindUniforms() {
|
||||||
|
this.bindUniforms(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bindUniforms(int index) {
|
||||||
|
}
|
||||||
|
|
||||||
|
//null means no function, otherwise return the taa injection function
|
||||||
|
public String taaFunction(String functionName) {
|
||||||
|
return this.taaFunction(-1, functionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String taaFunction(int uboBindingPoint, String functionName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//null means dont transform the shader
|
||||||
|
public String patchOpaqueShader(AbstractSectionRenderer<?,?> renderer, String input) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Returning null means apply the same patch as the opaque
|
||||||
|
public String patchTranslucentShader(AbstractSectionRenderer<?,?> renderer, String input) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Null means no scaling factor
|
||||||
|
public float[] getRenderScalingFactor() {return null;}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,15 @@
|
|||||||
package me.cortex.voxy.client.core;
|
package me.cortex.voxy.client.core;
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
|
||||||
public interface IGetVoxyRenderSystem {
|
public interface IGetVoxyRenderSystem {
|
||||||
VoxyRenderSystem getVoxyRenderSystem();
|
VoxyRenderSystem getVoxyRenderSystem();
|
||||||
void shutdownRenderer();
|
void shutdownRenderer();
|
||||||
void createRenderer();
|
void createRenderer();
|
||||||
|
|
||||||
|
static VoxyRenderSystem getNullable() {
|
||||||
|
var lr = (IGetVoxyRenderSystem)Minecraft.getInstance().levelRenderer;
|
||||||
|
if (lr == null) return null;
|
||||||
|
return lr.getVoxyRenderSystem();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,264 @@
|
|||||||
|
package me.cortex.voxy.client.core;
|
||||||
|
|
||||||
|
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||||
|
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
|
||||||
|
import me.cortex.voxy.client.core.rendering.Viewport;
|
||||||
|
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.post.FullscreenBlit;
|
||||||
|
import me.cortex.voxy.client.core.rendering.section.backend.AbstractSectionRenderer;
|
||||||
|
import me.cortex.voxy.client.core.rendering.util.DepthFramebuffer;
|
||||||
|
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||||
|
import me.cortex.voxy.client.iris.IrisVoxyRenderPipelineData;
|
||||||
|
import net.irisshaders.iris.shaderpack.materialmap.WorldRenderingSettings;
|
||||||
|
import org.joml.Matrix4f;
|
||||||
|
import org.lwjgl.opengl.GL30;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
|
||||||
|
import static org.lwjgl.opengl.GL30C.*;
|
||||||
|
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
|
||||||
|
import static org.lwjgl.opengl.GL45C.*;
|
||||||
|
|
||||||
|
public class IrisVoxyRenderPipeline extends AbstractRenderPipeline {
|
||||||
|
private final IrisVoxyRenderPipelineData data;
|
||||||
|
private final FullscreenBlit depthBlit = new FullscreenBlit("voxy:post/blit_texture_depth_cutout.frag");
|
||||||
|
public final DepthFramebuffer fbTranslucent = new DepthFramebuffer(this.fb.getFormat());
|
||||||
|
|
||||||
|
private final GlBuffer shaderUniforms;
|
||||||
|
|
||||||
|
public IrisVoxyRenderPipeline(IrisVoxyRenderPipelineData data, AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) {
|
||||||
|
super(nodeManager, nodeCleaner, traversal, frexSupplier, data.shouldDeferTranslucency());
|
||||||
|
this.data = data;
|
||||||
|
if (this.data.thePipeline != null) {
|
||||||
|
throw new IllegalStateException("Pipeline data already bound");
|
||||||
|
}
|
||||||
|
this.data.thePipeline = this;
|
||||||
|
|
||||||
|
//Bind the drawbuffers
|
||||||
|
var oDT = this.data.opaqueDrawTargets;
|
||||||
|
int[] binding = new int[oDT.length];
|
||||||
|
for (int i = 0; i < oDT.length; i++) {
|
||||||
|
binding[i] = GL30.GL_COLOR_ATTACHMENT0+i;
|
||||||
|
glNamedFramebufferTexture(this.fb.framebuffer.id, GL30.GL_COLOR_ATTACHMENT0+i, oDT[i], 0);
|
||||||
|
}
|
||||||
|
glNamedFramebufferDrawBuffers(this.fb.framebuffer.id, binding);
|
||||||
|
|
||||||
|
var tDT = this.data.translucentDrawTargets;
|
||||||
|
binding = new int[tDT.length];
|
||||||
|
for (int i = 0; i < tDT.length; i++) {
|
||||||
|
binding[i] = GL30.GL_COLOR_ATTACHMENT0+i;
|
||||||
|
glNamedFramebufferTexture(this.fbTranslucent.framebuffer.id, GL30.GL_COLOR_ATTACHMENT0+i, tDT[i], 0);
|
||||||
|
}
|
||||||
|
glNamedFramebufferDrawBuffers(this.fbTranslucent.framebuffer.id, binding);
|
||||||
|
|
||||||
|
this.fb.framebuffer.verify();
|
||||||
|
this.fbTranslucent.framebuffer.verify();
|
||||||
|
|
||||||
|
if (data.getUniforms() != null) {
|
||||||
|
this.shaderUniforms = new GlBuffer(data.getUniforms().size());
|
||||||
|
} else {
|
||||||
|
this.shaderUniforms = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setupExtraModelBakeryData(ModelBakerySubsystem modelService) {
|
||||||
|
modelService.factory.setCustomBlockStateMapping(WorldRenderingSettings.INSTANCE.getBlockStateIds());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void free() {
|
||||||
|
if (this.data.thePipeline != this) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
this.data.thePipeline = null;
|
||||||
|
|
||||||
|
this.depthBlit.delete();
|
||||||
|
this.fbTranslucent.free();
|
||||||
|
|
||||||
|
if (this.shaderUniforms != null) {
|
||||||
|
this.shaderUniforms.free();
|
||||||
|
}
|
||||||
|
|
||||||
|
super.free0();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preSetup(Viewport<?> viewport) {
|
||||||
|
super.preSetup(viewport);
|
||||||
|
if (this.shaderUniforms != null) {
|
||||||
|
//Update the uniforms
|
||||||
|
long ptr = UploadStream.INSTANCE.uploadTo(this.shaderUniforms);
|
||||||
|
this.data.getUniforms().updater().accept(ptr);
|
||||||
|
UploadStream.INSTANCE.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int setup(Viewport<?> viewport, int sourceFramebuffer, int srcWidth, int srcHeight) {
|
||||||
|
|
||||||
|
this.fb.resize(viewport.width, viewport.height);
|
||||||
|
this.fbTranslucent.resize(viewport.width, viewport.height);
|
||||||
|
|
||||||
|
if (false) {//TODO: only do this if shader specifies
|
||||||
|
//Clear the colour component
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, this.fb.framebuffer.id);
|
||||||
|
glClearColor(0, 0, 0, 0);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.data.useViewportDims) {
|
||||||
|
srcWidth = viewport.width;
|
||||||
|
srcHeight = viewport.height;
|
||||||
|
}
|
||||||
|
this.initDepthStencil(sourceFramebuffer, this.fb.framebuffer.id, srcWidth, srcHeight, viewport.width, viewport.height);
|
||||||
|
return this.fb.getDepthTex().id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void postOpaquePreTranslucent(Viewport<?> viewport) {
|
||||||
|
int msk = GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT;
|
||||||
|
if (true) {//TODO: make shader specified
|
||||||
|
if (false) {//TODO: only do this if shader specifies
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, this.fbTranslucent.framebuffer.id);
|
||||||
|
glClearColor(0, 0, 0, 0);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
msk |= GL_COLOR_BUFFER_BIT;
|
||||||
|
}
|
||||||
|
glBlitNamedFramebuffer(this.fb.framebuffer.id, this.fbTranslucent.framebuffer.id, 0,0, viewport.width, viewport.height, 0,0, viewport.width, viewport.height, msk, GL_NEAREST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void finish(Viewport<?> viewport, int sourceFrameBuffer, int srcWidth, int srcHeight) {
|
||||||
|
if (this.data.renderToVanillaDepth && srcWidth == viewport.width && srcHeight == viewport.height) {//We can only depthblit out if destination size is the same
|
||||||
|
glColorMask(false, false, false, false);
|
||||||
|
AbstractRenderPipeline.transformBlitDepth(this.depthBlit,
|
||||||
|
this.fbTranslucent.getDepthTex().id, sourceFrameBuffer,
|
||||||
|
viewport, new Matrix4f(viewport.vanillaProjection).mul(viewport.modelView));
|
||||||
|
glColorMask(true, true, true, true);
|
||||||
|
} else {
|
||||||
|
// normally disabled by AbstractRenderPipeline but since we are skipping it we do it here
|
||||||
|
glDisable(GL_STENCIL_TEST);
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bindUniforms() {
|
||||||
|
this.bindUniforms(UNIFORM_BINDING_POINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bindUniforms(int bindingPoint) {
|
||||||
|
if (this.shaderUniforms != null) {
|
||||||
|
GL30.glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, this.shaderUniforms.id);// todo: dont randomly select this to 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doBindings() {
|
||||||
|
this.bindUniforms();
|
||||||
|
if (this.data.getSsboSet() != null) {
|
||||||
|
this.data.getSsboSet().bindingFunction().accept(10);
|
||||||
|
}
|
||||||
|
if (this.data.getImageSet() != null) {
|
||||||
|
this.data.getImageSet().bindingFunction().accept(6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void setupAndBindOpaque(Viewport<?> viewport) {
|
||||||
|
this.fb.bind();
|
||||||
|
this.doBindings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setupAndBindTranslucent(Viewport<?> viewport) {
|
||||||
|
this.fbTranslucent.bind();
|
||||||
|
this.doBindings();
|
||||||
|
if (this.data.getBlender() != null) {
|
||||||
|
this.data.getBlender().run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addDebug(List<String> debug) {
|
||||||
|
debug.add("Using: " + this.getClass().getSimpleName());
|
||||||
|
super.addDebug(debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int UNIFORM_BINDING_POINT = 5;//TODO make ths binding point... not randomly 5
|
||||||
|
|
||||||
|
private StringBuilder buildGenericShaderHeader(AbstractSectionRenderer<?, ?> renderer, String input) {
|
||||||
|
StringBuilder builder = new StringBuilder(input).append("\n\n\n");
|
||||||
|
|
||||||
|
if (this.data.getUniforms() != null) {
|
||||||
|
builder.append("layout(binding = "+UNIFORM_BINDING_POINT+", std140) uniform ShaderUniformBindings ")
|
||||||
|
.append(this.data.getUniforms().layout())
|
||||||
|
.append(";\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.data.getSsboSet() != null) {
|
||||||
|
builder.append("#define BUFFER_BINDING_INDEX_BASE 10\n");//TODO: DONT RANDOMLY MAKE THIS 10
|
||||||
|
builder.append(this.data.getSsboSet().layout()).append("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.data.getImageSet() != null) {
|
||||||
|
builder.append("#define BASE_SAMPLER_BINDING_INDEX 6\n");//TODO: DONT RANDOMLY MAKE THIS 6
|
||||||
|
builder.append(this.data.getImageSet().layout()).append("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.append("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String patchOpaqueShader(AbstractSectionRenderer<?, ?> renderer, String input) {
|
||||||
|
var builder = this.buildGenericShaderHeader(renderer, input);
|
||||||
|
|
||||||
|
builder.append(this.data.opaqueFragPatch());
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String patchTranslucentShader(AbstractSectionRenderer<?, ?> renderer, String input) {
|
||||||
|
if (this.data.translucentFragPatch() == null) return null;
|
||||||
|
|
||||||
|
var builder = this.buildGenericShaderHeader(renderer, input);
|
||||||
|
builder.append(this.data.translucentFragPatch());
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String taaFunction(String functionName) {
|
||||||
|
return this.taaFunction(UNIFORM_BINDING_POINT, functionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String taaFunction(int uboBindingPoint, String functionName) {
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
if (this.data.getUniforms() != null) {
|
||||||
|
builder.append("layout(binding = "+uboBindingPoint+", std140) uniform ShaderUniformBindings ")
|
||||||
|
.append(this.data.getUniforms().layout())
|
||||||
|
.append(";\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append("vec2 ").append(functionName).append("()\n");
|
||||||
|
builder.append(this.data.TAA);
|
||||||
|
builder.append("\n");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float[] getRenderScalingFactor() {
|
||||||
|
return this.data.resolutionScale;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
package me.cortex.voxy.client.core;
|
||||||
|
|
||||||
|
import me.cortex.voxy.common.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.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.post.FullscreenBlit;
|
||||||
|
import me.cortex.voxy.client.core.rendering.util.DepthFramebuffer;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import org.joml.Matrix4f;
|
||||||
|
import org.lwjgl.system.MemoryStack;
|
||||||
|
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.ARBComputeShader.glDispatchCompute;
|
||||||
|
import static org.lwjgl.opengl.ARBShaderImageLoadStore.glBindImageTexture;
|
||||||
|
import static org.lwjgl.opengl.GL11.GL_BLEND;
|
||||||
|
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.glEnable;
|
||||||
|
import static org.lwjgl.opengl.GL11C.GL_NEAREST;
|
||||||
|
import static org.lwjgl.opengl.GL11C.GL_RGBA8;
|
||||||
|
import static org.lwjgl.opengl.GL14.glBlendFuncSeparate;
|
||||||
|
import static org.lwjgl.opengl.GL15.GL_READ_WRITE;
|
||||||
|
import static org.lwjgl.opengl.GL30C.*;
|
||||||
|
import static org.lwjgl.opengl.GL43.GL_DEPTH_STENCIL_TEXTURE_MODE;
|
||||||
|
import static org.lwjgl.opengl.GL45C.glBindTextureUnit;
|
||||||
|
import static org.lwjgl.opengl.GL45C.glTextureParameterf;
|
||||||
|
|
||||||
|
public class NormalRenderPipeline extends AbstractRenderPipeline {
|
||||||
|
private GlTexture colourTex;
|
||||||
|
private GlTexture colourSSAOTex;
|
||||||
|
private final GlFramebuffer fbSSAO = new GlFramebuffer();
|
||||||
|
|
||||||
|
private final boolean useEnvFog;
|
||||||
|
private final FullscreenBlit finalBlit;
|
||||||
|
|
||||||
|
private final Shader ssaoCompute = Shader.make()
|
||||||
|
.add(ShaderType.COMPUTE, "voxy:post/ssao.comp")
|
||||||
|
.compile();
|
||||||
|
|
||||||
|
protected NormalRenderPipeline(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) {
|
||||||
|
super(nodeManager, nodeCleaner, traversal, frexSupplier, false);
|
||||||
|
this.useEnvFog = VoxyConfig.CONFIG.useEnvironmentalFog;
|
||||||
|
this.finalBlit = new FullscreenBlit("voxy:post/blit_texture_depth_cutout.frag",
|
||||||
|
a->a.defineIf("USE_ENV_FOG", this.useEnvFog).define("EMIT_COLOUR"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int setup(Viewport<?> viewport, int sourceFB, int srcWidth, int srcHeight) {
|
||||||
|
if (this.colourTex == null || this.colourTex.getHeight() != viewport.height || this.colourTex.getWidth() != viewport.width) {
|
||||||
|
if (this.colourTex != null) {
|
||||||
|
this.colourTex.free();
|
||||||
|
this.colourSSAOTex.free();
|
||||||
|
}
|
||||||
|
this.fb.resize(viewport.width, viewport.height);
|
||||||
|
|
||||||
|
this.colourTex = new GlTexture().store(GL_RGBA8, 1, viewport.width, viewport.height);
|
||||||
|
this.colourSSAOTex = new GlTexture().store(GL_RGBA8, 1, viewport.width, viewport.height);
|
||||||
|
|
||||||
|
this.fb.framebuffer.bind(GL_COLOR_ATTACHMENT0, this.colourTex).verify();
|
||||||
|
this.fbSSAO.bind(this.fb.getDepthAttachmentType(), this.fb.getDepthTex()).bind(GL_COLOR_ATTACHMENT0, this.colourSSAOTex).verify();
|
||||||
|
|
||||||
|
|
||||||
|
glTextureParameterf(this.colourTex.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
|
glTextureParameterf(this.colourTex.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
glTextureParameterf(this.colourSSAOTex.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
|
glTextureParameterf(this.colourSSAOTex.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
glTextureParameterf(this.fb.getDepthTex().id, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_DEPTH_COMPONENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initDepthStencil(sourceFB, this.fb.framebuffer.id, viewport.width, viewport.height, viewport.width, viewport.height);
|
||||||
|
|
||||||
|
return this.fb.getDepthTex().id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void postOpaquePreTranslucent(Viewport<?> viewport) {
|
||||||
|
this.ssaoCompute.bind();
|
||||||
|
try (var stack = MemoryStack.stackPush()) {
|
||||||
|
long ptr = stack.nmalloc(4*4*4);
|
||||||
|
viewport.MVP.getToAddress(ptr);
|
||||||
|
nglUniformMatrix4fv(3, 1, false, ptr);//MVP
|
||||||
|
viewport.MVP.invert(new Matrix4f()).getToAddress(ptr);
|
||||||
|
nglUniformMatrix4fv(4, 1, false, ptr);//invMVP
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
glBindImageTexture(0, this.colourSSAOTex.id, 0, false,0, GL_READ_WRITE, GL_RGBA8);
|
||||||
|
glBindTextureUnit(1, this.fb.getDepthTex().id);
|
||||||
|
glBindTextureUnit(2, this.colourTex.id);
|
||||||
|
|
||||||
|
glDispatchCompute((viewport.width+31)/32, (viewport.height+31)/32, 1);
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, this.fbSSAO.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void finish(Viewport<?> viewport, int sourceFrameBuffer, int srcWidth, int srcHeight) {
|
||||||
|
this.finalBlit.bind();
|
||||||
|
if (this.useEnvFog) {
|
||||||
|
float start = viewport.fogParameters.environmentalStart();
|
||||||
|
float end = viewport.fogParameters.environmentalEnd();
|
||||||
|
if (Math.abs(end-start)>1) {
|
||||||
|
float invEndFogDelta = 1f / (end - start);
|
||||||
|
float endDistance = Math.max(Minecraft.getInstance().gameRenderer.getRenderDistance(), 20*16);//TODO: make this constant a config option
|
||||||
|
endDistance *= (float)Math.sqrt(3);
|
||||||
|
float startDelta = -start * invEndFogDelta;
|
||||||
|
glUniform4f(4, invEndFogDelta, startDelta, Math.clamp(endDistance*invEndFogDelta+startDelta, 0, 1),0);//
|
||||||
|
glUniform4f(5, viewport.fogParameters.red(), viewport.fogParameters.green(), viewport.fogParameters.blue(), viewport.fogParameters.alpha());
|
||||||
|
} else {
|
||||||
|
glUniform4f(4, 0, 0, 0, 0);
|
||||||
|
glUniform4f(5, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindTextureUnit(3, this.colourSSAOTex.id);
|
||||||
|
|
||||||
|
//Do alpha blending
|
||||||
|
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
AbstractRenderPipeline.transformBlitDepth(this.finalBlit, this.fb.getDepthTex().id, sourceFrameBuffer, viewport, new Matrix4f(viewport.vanillaProjection).mul(viewport.modelView));
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
//glBlitNamedFramebuffer(this.fbSSAO.id, sourceFrameBuffer, 0,0, viewport.width, viewport.height, 0,0, viewport.width, viewport.height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setupAndBindOpaque(Viewport<?> viewport) {
|
||||||
|
this.fb.bind();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setupAndBindTranslucent(Viewport<?> viewport) {
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, this.fbSSAO.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void free() {
|
||||||
|
this.finalBlit.delete();
|
||||||
|
this.ssaoCompute.free();
|
||||||
|
this.fbSSAO.free();
|
||||||
|
if (this.colourTex != null) {
|
||||||
|
this.colourTex.free();
|
||||||
|
this.colourSSAOTex.free();
|
||||||
|
}
|
||||||
|
super.free0();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package me.cortex.voxy.client.core;
|
||||||
|
|
||||||
|
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.util.IrisUtil;
|
||||||
|
import me.cortex.voxy.client.iris.IGetIrisVoxyPipelineData;
|
||||||
|
import me.cortex.voxy.common.Logger;
|
||||||
|
import net.irisshaders.iris.Iris;
|
||||||
|
import net.irisshaders.iris.api.v0.IrisApi;
|
||||||
|
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
|
public class RenderPipelineFactory {
|
||||||
|
public static AbstractRenderPipeline createPipeline(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) {
|
||||||
|
//Note this is where will choose/create e.g. IrisRenderPipeline or normal pipeline
|
||||||
|
AbstractRenderPipeline pipeline = null;
|
||||||
|
if (IrisUtil.IRIS_INSTALLED && IrisUtil.SHADER_SUPPORT) {
|
||||||
|
pipeline = createIrisPipeline(nodeManager, nodeCleaner, traversal, frexSupplier);
|
||||||
|
}
|
||||||
|
if (pipeline == null) {
|
||||||
|
pipeline = new NormalRenderPipeline(nodeManager, nodeCleaner, traversal, frexSupplier);
|
||||||
|
}
|
||||||
|
return pipeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AbstractRenderPipeline createIrisPipeline(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) {
|
||||||
|
var irisPipe = Iris.getPipelineManager().getPipelineNullable();
|
||||||
|
if (irisPipe == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (irisPipe instanceof IGetIrisVoxyPipelineData getVoxyPipeData) {
|
||||||
|
var pipeData = getVoxyPipeData.voxy$getPipelineData();
|
||||||
|
if (pipeData == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Logger.info("Creating voxy iris render pipeline");
|
||||||
|
try {
|
||||||
|
return new IrisVoxyRenderPipeline(pipeData, nodeManager, nodeCleaner, traversal, frexSupplier);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.error("Failed to create iris render pipeline", e);
|
||||||
|
IrisUtil.disableIrisShaders();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,85 +2,137 @@ package me.cortex.voxy.client.core;
|
|||||||
|
|
||||||
import com.mojang.blaze3d.opengl.GlConst;
|
import com.mojang.blaze3d.opengl.GlConst;
|
||||||
import com.mojang.blaze3d.opengl.GlStateManager;
|
import com.mojang.blaze3d.opengl.GlStateManager;
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
|
||||||
import me.cortex.voxy.client.TimingStatistics;
|
import me.cortex.voxy.client.TimingStatistics;
|
||||||
import me.cortex.voxy.client.VoxyClient;
|
import me.cortex.voxy.client.VoxyClient;
|
||||||
import me.cortex.voxy.client.config.VoxyConfig;
|
import me.cortex.voxy.common.config.VoxyConfig;
|
||||||
import me.cortex.voxy.client.core.gl.Capabilities;
|
import me.cortex.voxy.client.core.gl.Capabilities;
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
import me.cortex.voxy.client.core.gl.GlTexture;
|
||||||
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
|
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
|
||||||
|
import me.cortex.voxy.client.core.model.ModelStore;
|
||||||
import me.cortex.voxy.client.core.rendering.ChunkBoundRenderer;
|
import me.cortex.voxy.client.core.rendering.ChunkBoundRenderer;
|
||||||
import me.cortex.voxy.client.core.rendering.RenderDistanceTracker;
|
import me.cortex.voxy.client.core.rendering.RenderDistanceTracker;
|
||||||
import me.cortex.voxy.client.core.rendering.RenderService;
|
import me.cortex.voxy.client.core.rendering.Viewport;
|
||||||
import me.cortex.voxy.client.core.rendering.building.RenderDataFactory;
|
import me.cortex.voxy.client.core.rendering.ViewportSelector;
|
||||||
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
|
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
|
||||||
import me.cortex.voxy.client.core.rendering.post.PostProcessing;
|
import me.cortex.voxy.client.core.rendering.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.IUsesMeshlets;
|
||||||
|
import me.cortex.voxy.client.core.rendering.section.backend.AbstractSectionRenderer;
|
||||||
|
import me.cortex.voxy.client.core.rendering.section.backend.mdic.MDICSectionRenderer;
|
||||||
|
import me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData;
|
||||||
|
import me.cortex.voxy.client.core.rendering.section.geometry.IGeometryData;
|
||||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
||||||
import me.cortex.voxy.client.core.rendering.util.PrintfDebugUtil;
|
import me.cortex.voxy.client.core.rendering.util.PrintfDebugUtil;
|
||||||
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
|
|
||||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||||
|
import me.cortex.voxy.client.core.util.GPUTiming;
|
||||||
import me.cortex.voxy.client.core.util.IrisUtil;
|
import me.cortex.voxy.client.core.util.IrisUtil;
|
||||||
import me.cortex.voxy.common.Logger;
|
import me.cortex.voxy.common.Logger;
|
||||||
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
import me.cortex.voxy.common.thread.ServiceManager;
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
import me.cortex.voxy.common.world.WorldSection;
|
|
||||||
import me.cortex.voxy.common.world.other.Mapper;
|
|
||||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||||
import net.caffeinemc.mods.sodium.client.render.chunk.ChunkRenderMatrices;
|
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.Minecraft;
|
||||||
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.Matrix4f;
|
||||||
import org.joml.Matrix4fc;
|
import org.joml.Matrix4fc;
|
||||||
import org.lwjgl.opengl.GL11;
|
import org.lwjgl.opengl.GL11;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
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.GL_VIEWPORT;
|
||||||
import static org.lwjgl.opengl.GL11.glGetIntegerv;
|
import static org.lwjgl.opengl.GL11.glGetIntegerv;
|
||||||
import static org.lwjgl.opengl.GL11C.*;
|
import static org.lwjgl.opengl.GL11C.*;
|
||||||
import static org.lwjgl.opengl.GL14.glBlendFuncSeparate;
|
import static org.lwjgl.opengl.GL30C.*;
|
||||||
import static org.lwjgl.opengl.GL30C.GL_DRAW_FRAMEBUFFER_BINDING;
|
|
||||||
import static org.lwjgl.opengl.GL30C.glBindFramebuffer;
|
|
||||||
import static org.lwjgl.opengl.GL33.glBindSampler;
|
import static org.lwjgl.opengl.GL33.glBindSampler;
|
||||||
|
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
|
||||||
|
import static org.lwjgl.opengl.GL43C.GL_SHADER_STORAGE_BUFFER_BINDING;
|
||||||
|
|
||||||
|
import me.cortex.voxy.client.VoxyClientInstance;
|
||||||
|
import me.cortex.voxy.common.config.VoxyServerConfig;
|
||||||
|
|
||||||
public class VoxyRenderSystem {
|
public class VoxyRenderSystem {
|
||||||
private final RenderService renderer;
|
|
||||||
private final PostProcessing postProcessing;
|
|
||||||
private final WorldEngine worldIn;
|
private final WorldEngine worldIn;
|
||||||
|
|
||||||
|
|
||||||
|
private final ModelBakerySubsystem modelService;
|
||||||
|
private final RenderGenerationService renderGen;
|
||||||
|
private final IGeometryData geometryData;
|
||||||
|
private final AsyncNodeManager nodeManager;
|
||||||
|
private final NodeCleaner nodeCleaner;
|
||||||
|
private final HierarchicalOcclusionTraverser traversal;
|
||||||
|
|
||||||
|
|
||||||
private final RenderDistanceTracker renderDistanceTracker;
|
private final RenderDistanceTracker renderDistanceTracker;
|
||||||
public final ChunkBoundRenderer chunkBoundRenderer;
|
public final ChunkBoundRenderer chunkBoundRenderer;
|
||||||
|
|
||||||
public VoxyRenderSystem(WorldEngine world, ServiceThreadPool threadPool) {
|
private final ViewportSelector<?> viewportSelector;
|
||||||
|
|
||||||
|
private final AbstractRenderPipeline pipeline;
|
||||||
|
|
||||||
|
private static AbstractSectionRenderer.Factory<?,? extends IGeometryData> getRenderBackendFactory() {
|
||||||
|
//TODO: need todo a thing where selects optimal section render based on if supports the pipeline and geometry data type
|
||||||
|
return MDICSectionRenderer.FACTORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VoxyRenderSystem(WorldEngine world, ServiceManager sm) {
|
||||||
//Keep the world loaded, NOTE: this is done FIRST, to keep and ensure that even if the rest of loading takes more
|
//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
|
// than timeout, we keep the world acquired
|
||||||
world.acquireRef();
|
world.acquireRef();
|
||||||
|
System.gc();
|
||||||
|
|
||||||
|
if (Minecraft.getInstance().options.getEffectiveRenderDistance()<3) {
|
||||||
|
Logger.warn("Having a vanilla render distance of 2 can cause rare culling near the edge of your screen issues, please use 3 or more");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Fking HATE EVERYTHING AAAAAAAAAAAAAAAA
|
||||||
|
int[] oldBufferBindings = new int[10];
|
||||||
|
for (int i = 0; i < oldBufferBindings.length; i++) {
|
||||||
|
oldBufferBindings[i] = glGetIntegeri(GL_SHADER_STORAGE_BUFFER_BINDING, i);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
//wait for opengl to be finished, this should hopefully ensure all memory allocations are free
|
//wait for opengl to be finished, this should hopefully ensure all memory allocations are free
|
||||||
glFinish();
|
glFinish();
|
||||||
glFinish();
|
glFinish();
|
||||||
|
|
||||||
//Trigger the shared index buffer loading
|
|
||||||
SharedIndexBuffer.INSTANCE.id();
|
|
||||||
Capabilities.init();//Ensure clinit is called
|
|
||||||
|
|
||||||
this.worldIn = world;
|
this.worldIn = world;
|
||||||
this.renderer = new RenderService(world, threadPool);
|
|
||||||
this.postProcessing = new PostProcessing();
|
long geometryCapacity = getGeometryBufferSize();
|
||||||
int minSec = MinecraftClient.getInstance().world.getBottomSectionCoord() >> 5;
|
var backendFactory = getRenderBackendFactory();
|
||||||
int maxSec = (MinecraftClient.getInstance().world.getTopSectionCoord() - 1) >> 5;
|
|
||||||
|
{
|
||||||
|
this.modelService = new ModelBakerySubsystem(world.getMapper());
|
||||||
|
this.renderGen = new RenderGenerationService(world, this.modelService, sm, IUsesMeshlets.class.isAssignableFrom(backendFactory.clz()));
|
||||||
|
|
||||||
|
this.geometryData = new BasicSectionGeometryData(1 << 20, geometryCapacity);
|
||||||
|
|
||||||
|
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.renderGen);
|
||||||
|
|
||||||
|
world.setDirtyCallback(this.nodeManager::worldEvent);
|
||||||
|
|
||||||
|
Arrays.stream(world.getMapper().getBiomeEntries()).forEach(this.modelService::addBiome);
|
||||||
|
world.getMapper().setBiomeCallback(this.modelService::addBiome);
|
||||||
|
|
||||||
|
this.nodeManager.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pipeline = RenderPipelineFactory.createPipeline(this.nodeManager, this.nodeCleaner, this.traversal, this::frexStillHasWork);
|
||||||
|
this.pipeline.setupExtraModelBakeryData(this.modelService);//Configure the model service
|
||||||
|
var sectionRenderer = backendFactory.create(this.pipeline, this.modelService.getStore(), this.geometryData);
|
||||||
|
this.pipeline.setSectionRenderer(sectionRenderer);
|
||||||
|
this.viewportSelector = new ViewportSelector<>(sectionRenderer::createViewport);
|
||||||
|
|
||||||
|
{
|
||||||
|
int minSec = Minecraft.getInstance().level.getMinSectionY() >> 5;
|
||||||
|
int maxSec = (Minecraft.getInstance().level.getMaxSectionY() - 1) >> 5;
|
||||||
|
|
||||||
//Do some very cheeky stuff for MiB
|
//Do some very cheeky stuff for MiB
|
||||||
if (false) {
|
if (VoxyCommon.IS_MINE_IN_ABYSS) {//TODO: make this somehow configurable
|
||||||
minSec = -8;
|
minSec = -8;
|
||||||
maxSec = 7;
|
maxSec = 7;
|
||||||
}
|
}
|
||||||
@@ -88,84 +140,107 @@ public class VoxyRenderSystem {
|
|||||||
this.renderDistanceTracker = new RenderDistanceTracker(20,
|
this.renderDistanceTracker = new RenderDistanceTracker(20,
|
||||||
minSec,
|
minSec,
|
||||||
maxSec,
|
maxSec,
|
||||||
this.renderer::addTopLevelNode,
|
this.nodeManager::addTopLevel,
|
||||||
this.renderer::removeTopLevelNode);
|
this.nodeManager::removeTopLevel);
|
||||||
|
|
||||||
this.renderDistanceTracker.setRenderDistance(VoxyConfig.CONFIG.sectionRenderDistance);
|
this.setRenderDistance(VoxyConfig.CONFIG.sectionRenderDistance);
|
||||||
|
}
|
||||||
|
|
||||||
this.chunkBoundRenderer = new ChunkBoundRenderer();
|
this.chunkBoundRenderer = new ChunkBoundRenderer(this.pipeline);
|
||||||
|
|
||||||
|
Logger.info("Voxy render system created with " + geometryCapacity + " geometry capacity, using pipeline '" + this.pipeline.getClass().getSimpleName() + "' with renderer '" + sectionRenderer.getClass().getSimpleName() + "'");
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
world.releaseRef();//If something goes wrong, we must release the world first
|
world.releaseRef();//If something goes wrong, we must release the world first
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < oldBufferBindings.length; i++) {
|
||||||
|
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, i, oldBufferBindings[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRenderDistance(int renderDistance) {
|
for (int i = 0; i < 12; i++) {
|
||||||
this.renderDistanceTracker.setRenderDistance(renderDistance);
|
GlStateManager._activeTexture(GlConst.GL_TEXTURE0+i);
|
||||||
}
|
GlStateManager._bindTexture(0);
|
||||||
|
glBindSampler(i, 0);
|
||||||
|
|
||||||
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
|
|
||||||
boolean canDecreaseSize = this.renderer.getMeshQueueCount() < 5000;
|
|
||||||
float CHANGE_PER_SECOND = 30;
|
|
||||||
//Auto fps targeting
|
|
||||||
if (MinecraftClient.getInstance().getCurrentFps() < 45) {
|
|
||||||
VoxyConfig.CONFIG.subDivisionSize = Math.min(VoxyConfig.CONFIG.subDivisionSize + CHANGE_PER_SECOND / Math.max(1f, MinecraftClient.getInstance().getCurrentFps()), 256);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (55 < MinecraftClient.getInstance().getCurrentFps() && canDecreaseSize) {
|
|
||||||
VoxyConfig.CONFIG.subDivisionSize = Math.max(VoxyConfig.CONFIG.subDivisionSize - CHANGE_PER_SECOND / Math.max(1f, MinecraftClient.getInstance().getCurrentFps()), 30);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Matrix4f makeProjectionMatrix(float near, float far) {
|
|
||||||
//TODO: use the existing projection matrix use mulLocal by the inverse of the projection and then mulLocal our projection
|
|
||||||
|
|
||||||
var projection = new Matrix4f();
|
public Viewport<?> setupViewport(ChunkRenderMatrices matrices, FogParameters fogParameters, double cameraX, double cameraY, double cameraZ) {
|
||||||
var client = MinecraftClient.getInstance();
|
var viewport = this.getViewport();
|
||||||
var gameRenderer = client.gameRenderer;//tickCounter.getTickDelta(true);
|
if (viewport == null) {
|
||||||
|
return null;
|
||||||
float fov = gameRenderer.getFov(gameRenderer.getCamera(), client.getRenderTickCounter().getTickProgress(true), true);
|
|
||||||
|
|
||||||
projection.setPerspective(fov * 0.01745329238474369f,
|
|
||||||
(float) client.getWindow().getFramebufferWidth() / (float)client.getWindow().getFramebufferHeight(),
|
|
||||||
near, far);
|
|
||||||
return projection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Make a reverse z buffer
|
|
||||||
private static Matrix4f computeProjectionMat(Matrix4fc base) {
|
|
||||||
return base.mulLocal(
|
|
||||||
makeProjectionMatrix(0.05f, MinecraftClient.getInstance().gameRenderer.getFarPlaneDistance()).invert(),
|
|
||||||
new Matrix4f()
|
|
||||||
).mulLocal(makeProjectionMatrix(16, 16*3000));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void renderOpaque(ChunkRenderMatrices matrices, double cameraX, double cameraY, double cameraZ) {
|
|
||||||
if (IrisUtil.irisShadowActive()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TimingStatistics.resetSamplers();
|
|
||||||
|
|
||||||
|
|
||||||
//Do some very cheeky stuff for MiB
|
//Do some very cheeky stuff for MiB
|
||||||
if (false) {
|
if (VoxyCommon.IS_MINE_IN_ABYSS) {
|
||||||
int sector = (((int)Math.floor(cameraX)>>4)+512)>>10;
|
int sector = (((int)Math.floor(cameraX)>>4)+512)>>10;
|
||||||
cameraX -= sector<<14;//10+4
|
cameraX -= sector<<14;//10+4
|
||||||
cameraY += (16+(256-32-sector*30))*16;
|
cameraY += (16+(256-32-sector*30))*16;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//cameraY += 100;
|
||||||
|
var projection = computeProjectionMat(matrices.projection());//RenderSystem.getProjectionMatrix();
|
||||||
|
//var projection = ShadowMatrices.createOrthoMatrix(160, -16*300, 16*300);
|
||||||
|
//var projection = new Matrix4f(matrices.projection());
|
||||||
|
|
||||||
|
int[] dims = new int[4];
|
||||||
|
glGetIntegerv(GL_VIEWPORT, dims);
|
||||||
|
|
||||||
|
int width = dims[2];
|
||||||
|
int height = dims[3];
|
||||||
|
|
||||||
|
{//Apply render scaling factor
|
||||||
|
var factor = this.pipeline.getRenderScalingFactor();
|
||||||
|
if (factor != null) {
|
||||||
|
width = (int) (width*factor[0]);
|
||||||
|
height = (int) (height*factor[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewport
|
||||||
|
.setVanillaProjection(matrices.projection())
|
||||||
|
.setProjection(projection)
|
||||||
|
.setModelView(new Matrix4f(matrices.modelView()))
|
||||||
|
.setCamera(cameraX, cameraY, cameraZ)
|
||||||
|
.setScreenSize(width, height)
|
||||||
|
.setFogParameters(fogParameters)
|
||||||
|
.update();
|
||||||
|
|
||||||
|
if (VoxyClient.getOcclusionDebugState()==0) {
|
||||||
|
viewport.frameId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return viewport;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void renderOpaque(Viewport<?> viewport) {
|
||||||
|
if (viewport == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimingStatistics.resetSamplers();
|
||||||
|
|
||||||
long startTime = System.nanoTime();
|
long startTime = System.nanoTime();
|
||||||
TimingStatistics.all.start();
|
TimingStatistics.all.start();
|
||||||
|
GPUTiming.INSTANCE.marker();//Start marker
|
||||||
TimingStatistics.main.start();
|
TimingStatistics.main.start();
|
||||||
|
|
||||||
|
//TODO: optimize
|
||||||
|
int[] oldBufferBindings = new int[10];
|
||||||
|
for (int i = 0; i < oldBufferBindings.length; i++) {
|
||||||
|
oldBufferBindings[i] = glGetIntegeri(GL_SHADER_STORAGE_BUFFER_BINDING, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int oldFB = GL11.glGetInteger(GL_DRAW_FRAMEBUFFER_BINDING);
|
int oldFB = GL11.glGetInteger(GL_DRAW_FRAMEBUFFER_BINDING);
|
||||||
int boundFB = oldFB;
|
int boundFB = oldFB;
|
||||||
|
|
||||||
|
int[] dims = new int[4];
|
||||||
|
glGetIntegerv(GL_VIEWPORT, dims);
|
||||||
|
|
||||||
|
glViewport(0,0, viewport.width, viewport.height);
|
||||||
|
|
||||||
//var target = DefaultTerrainRenderPasses.CUTOUT.getTarget();
|
//var target = DefaultTerrainRenderPasses.CUTOUT.getTarget();
|
||||||
//boundFB = ((net.minecraft.client.texture.GlTexture) target.getColorAttachment()).getOrCreateFramebuffer(((GlBackend) RenderSystem.getDevice()).getFramebufferManager(), target.getDepthAttachment());
|
//boundFB = ((net.minecraft.client.texture.GlTexture) target.getColorAttachment()).getOrCreateFramebuffer(((GlBackend) RenderSystem.getDevice()).getFramebufferManager(), target.getDepthAttachment());
|
||||||
if (boundFB == 0) {
|
if (boundFB == 0) {
|
||||||
@@ -174,24 +249,78 @@ public class VoxyRenderSystem {
|
|||||||
|
|
||||||
//this.autoBalanceSubDivSize();
|
//this.autoBalanceSubDivSize();
|
||||||
|
|
||||||
var projection = computeProjectionMat(matrices.projection());//RenderSystem.getProjectionMatrix();
|
this.pipeline.preSetup(viewport);
|
||||||
//var projection = new Matrix4f(matrices.projection());
|
|
||||||
|
|
||||||
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])
|
|
||||||
.update();
|
|
||||||
viewport.frameId++;
|
|
||||||
|
|
||||||
TimingStatistics.E.start();
|
TimingStatistics.E.start();
|
||||||
|
if ((!VoxyClient.disableSodiumChunkRender())&&!IrisUtil.irisShadowActive()) {
|
||||||
this.chunkBoundRenderer.render(viewport);
|
this.chunkBoundRenderer.render(viewport);
|
||||||
|
} else {
|
||||||
|
viewport.depthBoundingBuffer.clear(0);
|
||||||
|
}
|
||||||
TimingStatistics.E.stop();
|
TimingStatistics.E.stop();
|
||||||
|
|
||||||
|
|
||||||
|
GPUTiming.INSTANCE.marker();
|
||||||
|
//The entire rendering pipeline (excluding the chunkbound thing)
|
||||||
|
this.pipeline.runPipeline(viewport, boundFB, dims[2], dims[3]);
|
||||||
|
GPUTiming.INSTANCE.marker();
|
||||||
|
|
||||||
|
|
||||||
|
TimingStatistics.main.stop();
|
||||||
|
TimingStatistics.postDynamic.start();
|
||||||
|
|
||||||
|
PrintfDebugUtil.tick();
|
||||||
|
|
||||||
|
//As much dynamic runtime stuff here
|
||||||
|
{
|
||||||
|
//Tick upload stream (this is ok to do here as upload ticking is just memory management)
|
||||||
|
UploadStream.INSTANCE.tick();
|
||||||
|
|
||||||
|
while (this.renderDistanceTracker.setCenterAndProcess(viewport.cameraX, viewport.cameraZ) && VoxyClient.isFrexActive());//While FF is active, run until everything is processed
|
||||||
|
TimingStatistics.H.start();
|
||||||
|
//Done here as is allows less gl state resetup
|
||||||
|
do { this.modelService.tick(900_000); } while (VoxyClient.isFrexActive() && !this.modelService.areQueuesEmpty());
|
||||||
|
TimingStatistics.H.stop();
|
||||||
|
}
|
||||||
|
GPUTiming.INSTANCE.marker();
|
||||||
|
TimingStatistics.postDynamic.stop();
|
||||||
|
|
||||||
|
GPUTiming.INSTANCE.tick();
|
||||||
|
|
||||||
|
glBindFramebuffer(GlConst.GL_FRAMEBUFFER, oldFB);
|
||||||
|
glViewport(dims[0], dims[1], dims[2], dims[3]);
|
||||||
|
|
||||||
|
{//Reset state manager stuffs
|
||||||
|
glUseProgram(0);
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
|
||||||
|
GlStateManager._glBindVertexArray(0);//Clear binding
|
||||||
|
|
||||||
|
GlStateManager._activeTexture(GlConst.GL_TEXTURE1);
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
GlStateManager._activeTexture(GlConst.GL_TEXTURE0+i);
|
||||||
|
GlStateManager._bindTexture(0);
|
||||||
|
glBindSampler(i, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
IrisUtil.clearIrisSamplers();//Thanks iris (sigh)
|
||||||
|
|
||||||
|
//TODO: should/needto actually restore all of these, not just clear them
|
||||||
|
//Clear all the bindings
|
||||||
|
for (int i = 0; i < oldBufferBindings.length; i++) {
|
||||||
|
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, i, oldBufferBindings[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
//((SodiumShader) Iris.getPipelineManager().getPipelineNullable().getSodiumPrograms().getProgram(DefaultTerrainRenderPasses.CUTOUT).getInterface()).setupState(DefaultTerrainRenderPasses.CUTOUT, fogParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
TimingStatistics.all.stop();
|
||||||
|
|
||||||
|
//TimingStatistics.I.start();
|
||||||
|
//glFlush();
|
||||||
|
//TimingStatistics.I.stop();
|
||||||
|
|
||||||
|
/*
|
||||||
TimingStatistics.F.start();
|
TimingStatistics.F.start();
|
||||||
this.postProcessing.setup(viewport.width, viewport.height, boundFB);
|
this.postProcessing.setup(viewport.width, viewport.height, boundFB);
|
||||||
TimingStatistics.F.stop();
|
TimingStatistics.F.stop();
|
||||||
@@ -211,55 +340,111 @@ public class VoxyRenderSystem {
|
|||||||
|
|
||||||
|
|
||||||
TimingStatistics.F.start();
|
TimingStatistics.F.start();
|
||||||
this.postProcessing.renderPost(projection, matrices.projection(), boundFB);
|
this.postProcessing.renderPost(viewport, matrices.projection(), boundFB);
|
||||||
TimingStatistics.F.stop();
|
TimingStatistics.F.stop();
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
TimingStatistics.main.stop();
|
|
||||||
TimingStatistics.postDynamic.start();
|
|
||||||
|
|
||||||
PrintfDebugUtil.tick();
|
|
||||||
|
|
||||||
//As much dynamic runtime stuff here
|
private void autoBalanceSubDivSize() {
|
||||||
{
|
//only increase quality while there are very few mesh queues, this stops,
|
||||||
//Tick upload stream (this is ok to do here as upload ticking is just memory management)
|
// e.g. while flying and is rendering alot of low quality chunks
|
||||||
|
boolean canDecreaseSize = this.renderGen.getTaskCount() < 300;
|
||||||
|
int MIN_FPS = 55;
|
||||||
|
int MAX_FPS = 65;
|
||||||
|
float INCREASE_PER_SECOND = 60;
|
||||||
|
float DECREASE_PER_SECOND = 30;
|
||||||
|
//Auto fps targeting
|
||||||
|
if (Minecraft.getInstance().getFps() < MIN_FPS) {
|
||||||
|
VoxyConfig.CONFIG.subDivisionSize = Math.min(VoxyConfig.CONFIG.subDivisionSize + INCREASE_PER_SECOND / Math.max(1f, Minecraft.getInstance().getFps()), 256);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MAX_FPS < Minecraft.getInstance().getFps() && canDecreaseSize) {
|
||||||
|
VoxyConfig.CONFIG.subDivisionSize = Math.max(VoxyConfig.CONFIG.subDivisionSize - DECREASE_PER_SECOND / Math.max(1f, Minecraft.getInstance().getFps()), 28);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Matrix4f makeProjectionMatrix(float near, float far) {
|
||||||
|
//TODO: use the existing projection matrix use mulLocal by the inverse of the projection and then mulLocal our projection
|
||||||
|
|
||||||
|
var projection = new Matrix4f();
|
||||||
|
var client = Minecraft.getInstance();
|
||||||
|
var gameRenderer = client.gameRenderer;//tickCounter.getTickDelta(true);
|
||||||
|
|
||||||
|
float fov = gameRenderer.getFov(gameRenderer.getMainCamera(), client.getDeltaTracker().getGameTimeDeltaPartialTick(true), true);
|
||||||
|
|
||||||
|
projection.setPerspective(fov * 0.01745329238474369f,
|
||||||
|
(float) client.getWindow().getWidth() / (float)client.getWindow().getHeight(),
|
||||||
|
near, far);
|
||||||
|
return projection;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: Make a reverse z buffer
|
||||||
|
private static Matrix4f computeProjectionMat(Matrix4fc base) {
|
||||||
|
//THis is a wild and insane problem to have
|
||||||
|
// at short render distances the vanilla terrain doesnt end up covering the 16f near plane voxy uses
|
||||||
|
// meaning that it explodes (due to near plane clipping).. _badly_ with the rastered culling being wrong in rare cases for the immediate
|
||||||
|
// sections rendered after the vanilla render distance
|
||||||
|
float nearVoxy = Minecraft.getInstance().gameRenderer.getRenderDistance()<=32.0f?8f:16f;
|
||||||
|
nearVoxy = VoxyClient.disableSodiumChunkRender()?0.1f:nearVoxy;
|
||||||
|
|
||||||
|
return base.mulLocal(
|
||||||
|
makeProjectionMatrix(0.05f, Minecraft.getInstance().gameRenderer.getDepthFar()).invert(),
|
||||||
|
new Matrix4f()
|
||||||
|
).mulLocal(makeProjectionMatrix(nearVoxy, 16*3000));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean frexStillHasWork() {
|
||||||
|
if (!VoxyClient.isFrexActive()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//If frex is running we must tick everything to ensure correctness
|
||||||
UploadStream.INSTANCE.tick();
|
UploadStream.INSTANCE.tick();
|
||||||
|
|
||||||
while (this.renderDistanceTracker.setCenterAndProcess(cameraX, cameraZ) && VoxyClient.isFrexActive());//While FF is active, run until everything is processed
|
|
||||||
|
|
||||||
//Done here as is allows less gl state resetup
|
//Done here as is allows less gl state resetup
|
||||||
this.renderer.tickModelService(Math.max(3_000_000-(System.nanoTime()-startTime), 500_000));
|
this.modelService.tick(100_000_000);
|
||||||
|
GL11.glFinish();
|
||||||
|
return this.nodeManager.hasWork() || this.renderGen.getTaskCount()!=0 || !this.modelService.areQueuesEmpty();
|
||||||
}
|
}
|
||||||
TimingStatistics.postDynamic.stop();
|
|
||||||
|
|
||||||
glBindFramebuffer(GlConst.GL_FRAMEBUFFER, oldFB);
|
public void setRenderDistance(int renderDistance) {
|
||||||
|
this.renderDistanceTracker.setRenderDistance(renderDistance);
|
||||||
{//Reset state manager stuffs
|
|
||||||
GlStateManager._glBindVertexArray(0);//Clear binding
|
|
||||||
|
|
||||||
GlStateManager._activeTexture(GlConst.GL_TEXTURE0);
|
|
||||||
GlStateManager._bindTexture(0);
|
|
||||||
glBindSampler(0, 0);
|
|
||||||
|
|
||||||
GlStateManager._activeTexture(GlConst.GL_TEXTURE1);
|
|
||||||
GlStateManager._bindTexture(0);
|
|
||||||
glBindSampler(1, 0);
|
|
||||||
|
|
||||||
GlStateManager._activeTexture(GlConst.GL_TEXTURE2);
|
|
||||||
GlStateManager._bindTexture(0);
|
|
||||||
glBindSampler(2, 0);
|
|
||||||
}
|
}
|
||||||
TimingStatistics.all.stop();
|
|
||||||
|
public Viewport<?> getViewport() {
|
||||||
|
if (IrisUtil.irisShadowActive()) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
return this.viewportSelector.getViewport();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void addDebugInfo(List<String> debug) {
|
public void addDebugInfo(List<String> debug) {
|
||||||
debug.add("Buf/Tex [#/Mb]: [" + GlBuffer.getCount() + "/" + (GlBuffer.getTotalSize()/1_000_000) + "],[" + GlTexture.getCount() + "/" + (GlTexture.getEstimatedTotalSize()/1_000_000)+"]");
|
debug.add("Buf/Tex [#/Mb]: [" + GlBuffer.getCount() + "/" + (GlBuffer.getTotalSize()/1_000_000) + "],[" + GlTexture.getCount() + "/" + (GlTexture.getEstimatedTotalSize()/1_000_000)+"]");
|
||||||
this.renderer.addDebugData(debug);
|
{
|
||||||
|
this.modelService.addDebugData(debug);
|
||||||
|
this.renderGen.addDebugData(debug);
|
||||||
|
this.nodeManager.addDebug(debug);
|
||||||
|
this.pipeline.addDebug(debug);
|
||||||
|
}
|
||||||
{
|
{
|
||||||
TimingStatistics.update();
|
TimingStatistics.update();
|
||||||
debug.add("Voxy frame runtime (millis): " + TimingStatistics.dynamic.pVal() + ", " + TimingStatistics.main.pVal()+ ", " + TimingStatistics.postDynamic.pVal()+ ", " + TimingStatistics.all.pVal());
|
debug.add("Voxy frame runtime (millis): " + TimingStatistics.dynamic.pVal() + ", " + TimingStatistics.main.pVal()+ ", " + TimingStatistics.postDynamic.pVal()+ ", " + TimingStatistics.all.pVal());
|
||||||
debug.add("Extra time: " + TimingStatistics.A.pVal() + ", " + TimingStatistics.B.pVal() + ", " + TimingStatistics.C.pVal() + ", " + TimingStatistics.D.pVal());
|
debug.add("Extra time: " + TimingStatistics.A.pVal() + ", " + TimingStatistics.B.pVal() + ", " + TimingStatistics.C.pVal() + ", " + TimingStatistics.D.pVal());
|
||||||
debug.add("Extra 2 time: " + TimingStatistics.E.pVal() + ", " + TimingStatistics.F.pVal() + ", " + TimingStatistics.G.pVal() + ", " + TimingStatistics.H.pVal() + ", " + TimingStatistics.I.pVal());
|
debug.add("Extra 2 time: " + TimingStatistics.E.pVal() + ", " + TimingStatistics.F.pVal() + ", " + TimingStatistics.G.pVal() + ", " + TimingStatistics.H.pVal() + ", " + TimingStatistics.I.pVal());
|
||||||
}
|
}
|
||||||
|
debug.add(GPUTiming.INSTANCE.getDebug());
|
||||||
|
|
||||||
|
if (VoxyCommon.getInstance() instanceof VoxyClientInstance clientInstance) {
|
||||||
|
long lastUpdate = clientInstance.getLastLodUpdate();
|
||||||
|
long timeSince = System.currentTimeMillis() - lastUpdate;
|
||||||
|
debug.add("LOD Updates: " + clientInstance.getLodUpdatesReceived() + " (Last: " + timeSince + "ms ago)");
|
||||||
|
debug.add("Server View Dist: " + clientInstance.getServerViewDistance());
|
||||||
|
}
|
||||||
|
|
||||||
PrintfDebugUtil.addToOut(debug);
|
PrintfDebugUtil.addToOut(debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,11 +452,62 @@ public class VoxyRenderSystem {
|
|||||||
Logger.info("Flushing download stream");
|
Logger.info("Flushing download stream");
|
||||||
DownloadStream.INSTANCE.flushWaitClear();
|
DownloadStream.INSTANCE.flushWaitClear();
|
||||||
Logger.info("Shutting down rendering");
|
Logger.info("Shutting down rendering");
|
||||||
try {this.renderer.shutdown();this.chunkBoundRenderer.free();} catch (Exception e) {Logger.error("Error shutting down renderer", e);}
|
try {
|
||||||
Logger.info("Shutting down post processor");
|
//Cleanup callbacks
|
||||||
if (this.postProcessing!=null){try {this.postProcessing.shutdown();} catch (Exception e) {Logger.error("Error shutting down post processor", e);}}
|
this.worldIn.setDirtyCallback(null);
|
||||||
|
this.worldIn.getMapper().setBiomeCallback(null);
|
||||||
|
this.worldIn.getMapper().setStateCallback(null);
|
||||||
|
|
||||||
|
this.nodeManager.stop();
|
||||||
|
|
||||||
|
this.modelService.shutdown();
|
||||||
|
this.renderGen.shutdown();
|
||||||
|
this.traversal.free();
|
||||||
|
this.nodeCleaner.free();
|
||||||
|
|
||||||
|
this.geometryData.free();
|
||||||
|
this.chunkBoundRenderer.free();
|
||||||
|
|
||||||
|
this.viewportSelector.free();
|
||||||
|
} catch (Exception e) {Logger.error("Error shutting down renderer components", e);}
|
||||||
|
Logger.info("Shutting down render pipeline");
|
||||||
|
try {this.pipeline.free();} catch (Exception e){Logger.error("Error releasing render pipeline", e);}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Logger.info("Flushing download stream");
|
||||||
|
DownloadStream.INSTANCE.flushWaitClear();
|
||||||
|
|
||||||
//Release hold on the world
|
//Release hold on the world
|
||||||
this.worldIn.releaseRef();
|
this.worldIn.releaseRef();
|
||||||
|
Logger.info("Render shutdown completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long getGeometryBufferSize() {
|
||||||
|
long geometryCapacity = Math.min((1L<<(64-Long.numberOfLeadingZeros(Capabilities.INSTANCE.ssboMaxSize-1)))<<1, 1L<<32)-1024/*(1L<<32)-1024*/;
|
||||||
|
if (Capabilities.INSTANCE.isIntel) {
|
||||||
|
geometryCapacity = Math.max(geometryCapacity, 1L<<30);//intel moment, force min 1gb
|
||||||
|
}
|
||||||
|
|
||||||
|
//Limit to available dedicated memory if possible
|
||||||
|
if (Capabilities.INSTANCE.canQueryGpuMemory) {
|
||||||
|
//512mb less than avalible,
|
||||||
|
long limit = Capabilities.INSTANCE.getFreeDedicatedGpuMemory() - (long)(1.5*1024*1024*1024);//1.5gb vram buffer
|
||||||
|
// Give a minimum of 512 mb requirement
|
||||||
|
limit = Math.max(512*1024*1024, limit);
|
||||||
|
|
||||||
|
geometryCapacity = Math.min(geometryCapacity, limit);
|
||||||
|
}
|
||||||
|
//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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorldEngine getEngine() {
|
||||||
|
return this.worldIn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,30 @@
|
|||||||
package me.cortex.voxy.client.core.gl;
|
package me.cortex.voxy.client.core.gl;
|
||||||
|
|
||||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||||
|
import me.cortex.voxy.common.Logger;
|
||||||
import org.lwjgl.opengl.GL;
|
import org.lwjgl.opengl.GL;
|
||||||
|
import org.lwjgl.opengl.GL11C;
|
||||||
import org.lwjgl.opengl.GL20C;
|
import org.lwjgl.opengl.GL20C;
|
||||||
|
import org.lwjgl.opengl.GL30;
|
||||||
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
import static org.lwjgl.opengl.GL11.*;
|
import java.util.Locale;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.GL11.GL_NEAREST;
|
||||||
|
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
|
||||||
|
import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER;
|
||||||
|
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
|
||||||
|
import static org.lwjgl.opengl.GL15.glDeleteBuffers;
|
||||||
|
import static org.lwjgl.opengl.GL30.GL_DEPTH_STENCIL;
|
||||||
|
import static org.lwjgl.opengl.GL30C.GL_MAP_READ_BIT;
|
||||||
import static org.lwjgl.opengl.GL32.glGetInteger64;
|
import static org.lwjgl.opengl.GL32.glGetInteger64;
|
||||||
import static org.lwjgl.opengl.GL43C.GL_MAX_SHADER_STORAGE_BLOCK_SIZE;
|
import static org.lwjgl.opengl.GL43C.GL_MAX_SHADER_STORAGE_BLOCK_SIZE;
|
||||||
|
import static org.lwjgl.opengl.GL44.GL_DYNAMIC_STORAGE_BIT;
|
||||||
|
import static org.lwjgl.opengl.GL44.GL_MAP_COHERENT_BIT;
|
||||||
|
import static org.lwjgl.opengl.GL45.glClearNamedFramebufferfi;
|
||||||
|
import static org.lwjgl.opengl.GL45C.*;
|
||||||
|
import static org.lwjgl.opengl.GL45C.glCreateFramebuffers;
|
||||||
import static org.lwjgl.opengl.NVXGPUMemoryInfo.*;
|
import static org.lwjgl.opengl.NVXGPUMemoryInfo.*;
|
||||||
|
|
||||||
public class Capabilities {
|
public class Capabilities {
|
||||||
@@ -24,9 +42,16 @@ public class Capabilities {
|
|||||||
public final boolean compute;
|
public final boolean compute;
|
||||||
public final boolean indirectParameters;
|
public final boolean indirectParameters;
|
||||||
public final boolean isIntel;
|
public final boolean isIntel;
|
||||||
|
public final boolean subgroup;
|
||||||
|
public final boolean sparseBuffer;
|
||||||
|
public final boolean isNvidia;
|
||||||
|
public final boolean isAmd;
|
||||||
|
public final boolean nvBarryCoords;
|
||||||
|
public final boolean hasBrokenDepthSampler;
|
||||||
|
|
||||||
public Capabilities() {
|
public Capabilities() {
|
||||||
var cap = GL.getCapabilities();
|
var cap = GL.getCapabilities();
|
||||||
|
this.sparseBuffer = cap.GL_ARB_sparse_buffer;
|
||||||
this.compute = cap.glDispatchComputeIndirect != 0;
|
this.compute = cap.glDispatchComputeIndirect != 0;
|
||||||
this.indirectParameters = cap.glMultiDrawElementsIndirectCountARB != 0;
|
this.indirectParameters = cap.glMultiDrawElementsIndirectCountARB != 0;
|
||||||
this.repFragTest = cap.GL_NV_representative_fragment_test;
|
this.repFragTest = cap.GL_NV_representative_fragment_test;
|
||||||
@@ -42,11 +67,27 @@ public class Capabilities {
|
|||||||
uint64_t a = 1234;
|
uint64_t a = 1234;
|
||||||
}
|
}
|
||||||
""");
|
""");
|
||||||
|
if (cap.GL_KHR_shader_subgroup) {
|
||||||
|
this.subgroup = testShaderCompilesOk(ShaderType.COMPUTE, """
|
||||||
|
#version 430
|
||||||
|
#extension GL_KHR_shader_subgroup_basic : require
|
||||||
|
#extension GL_KHR_shader_subgroup_arithmetic : require
|
||||||
|
layout(local_size_x=32) in;
|
||||||
|
void main() {
|
||||||
|
uint a = subgroupExclusiveAdd(gl_LocalInvocationIndex);
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
} else {
|
||||||
|
this.subgroup = false;
|
||||||
|
}
|
||||||
|
|
||||||
this.ssboMaxSize = glGetInteger64(GL_MAX_SHADER_STORAGE_BLOCK_SIZE);
|
this.ssboMaxSize = glGetInteger64(GL_MAX_SHADER_STORAGE_BLOCK_SIZE);
|
||||||
|
|
||||||
this.isMesa = glGetString(GL_VERSION).toLowerCase().contains("mesa");
|
this.isMesa = glGetString(GL_VERSION).toLowerCase(Locale.ROOT).contains("mesa");
|
||||||
this.isIntel = glGetString(GL_VENDOR).toLowerCase().contains("intel");
|
var vendor = glGetString(GL_VENDOR).toLowerCase(Locale.ROOT);
|
||||||
|
this.isIntel = vendor.contains("intel");
|
||||||
|
this.isNvidia = vendor.contains("nvidia");
|
||||||
|
this.isAmd = vendor.contains("amd")||vendor.contains("radeon");
|
||||||
|
|
||||||
if (this.canQueryGpuMemory) {
|
if (this.canQueryGpuMemory) {
|
||||||
this.totalDedicatedMemory = glGetInteger64(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX)*1024;//Since its in Kb
|
this.totalDedicatedMemory = glGetInteger64(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX)*1024;//Since its in Kb
|
||||||
@@ -55,11 +96,106 @@ public class Capabilities {
|
|||||||
this.totalDedicatedMemory = -1;
|
this.totalDedicatedMemory = -1;
|
||||||
this.totalDynamicMemory = -1;
|
this.totalDynamicMemory = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.nvBarryCoords = cap.GL_NV_fragment_shader_barycentric;
|
||||||
|
|
||||||
|
if (this.compute&&this.isAmd) {
|
||||||
|
this.hasBrokenDepthSampler = testDepthSampler();
|
||||||
|
if (this.hasBrokenDepthSampler) {
|
||||||
|
throw new IllegalStateException("it bork, amd is bork");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.hasBrokenDepthSampler = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void init() {
|
public static void init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean testDepthSampler() {
|
||||||
|
String src = """
|
||||||
|
#version 460 core
|
||||||
|
layout(local_size_x=16,local_size_y=16) in;
|
||||||
|
|
||||||
|
layout(binding = 0) uniform sampler2D depthSampler;
|
||||||
|
layout(binding = 1) buffer OutData {
|
||||||
|
float[] outData;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout(location = 2) uniform int dynamicSampleThing;
|
||||||
|
layout(location = 3) uniform float sampleData;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
if (abs(texelFetch(depthSampler, ivec2(gl_GlobalInvocationID.xy), dynamicSampleThing).r-sampleData)>0.000001f) {
|
||||||
|
outData[0] = 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
int program = GL20C.glCreateProgram();
|
||||||
|
{
|
||||||
|
int shader = GL20C.glCreateShader(ShaderType.COMPUTE.gl);
|
||||||
|
GL20C.glShaderSource(shader, src);
|
||||||
|
GL20C.glCompileShader(shader);
|
||||||
|
if (GL20C.glGetShaderi(shader, GL20C.GL_COMPILE_STATUS) != 1) {
|
||||||
|
GL20C.glDeleteShader(shader);
|
||||||
|
throw new IllegalStateException("Shader compile fail");
|
||||||
|
}
|
||||||
|
GL20C.glAttachShader(program, shader);
|
||||||
|
GL20C.glLinkProgram(program);
|
||||||
|
glDeleteShader(shader);
|
||||||
|
}
|
||||||
|
|
||||||
|
int buffer = glCreateBuffers();
|
||||||
|
glNamedBufferStorage(buffer, 4096, GL_DYNAMIC_STORAGE_BIT|GL_MAP_READ_BIT);
|
||||||
|
|
||||||
|
int tex = glCreateTextures(GL_TEXTURE_2D);
|
||||||
|
glTextureStorage2D(tex, 2, GL_DEPTH24_STENCIL8, 256, 256);
|
||||||
|
glTextureParameteri(tex, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
|
glTextureParameteri(tex, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
|
||||||
|
int fb = glCreateFramebuffers();
|
||||||
|
boolean isCorrect = true;
|
||||||
|
for (int lvl = 0; lvl <= 1; lvl++) {
|
||||||
|
glNamedFramebufferTexture(fb, GL_DEPTH_STENCIL_ATTACHMENT, tex, lvl);
|
||||||
|
|
||||||
|
for (int i = 0; i <= 10; i++) {
|
||||||
|
float value = (float) (i / 10.0);
|
||||||
|
|
||||||
|
nglClearNamedBufferSubData(buffer, GL_R32F, 0, 4096, GL_RED, GL_FLOAT, 0);//Zero the buffer
|
||||||
|
glClearNamedFramebufferfi(fb, GL_DEPTH_STENCIL, 0, value, 1);//Set the depth texture
|
||||||
|
|
||||||
|
glUseProgram(program);
|
||||||
|
glUniform1i(2, lvl);
|
||||||
|
glUniform1f(3, value);
|
||||||
|
glBindTextureUnit(0, tex);
|
||||||
|
GL30.glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, buffer);
|
||||||
|
|
||||||
|
glDispatchCompute(256>>(lvl+4), 256>>(lvl+4), 1);
|
||||||
|
glFinish();
|
||||||
|
|
||||||
|
long ptr = nglMapNamedBuffer(buffer, GL_READ_ONLY);
|
||||||
|
float gottenValue = MemoryUtil.memGetFloat(ptr);
|
||||||
|
glUnmapNamedBuffer(buffer);
|
||||||
|
|
||||||
|
glUseProgram(0);
|
||||||
|
glBindTextureUnit(0, 0);
|
||||||
|
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
|
||||||
|
|
||||||
|
boolean localCorrect = gottenValue==0.0f;
|
||||||
|
if (!localCorrect) {
|
||||||
|
Logger.error("Depth read test failed at value: " + value);
|
||||||
|
}
|
||||||
|
isCorrect &= localCorrect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glDeleteFramebuffers(fb);
|
||||||
|
glDeleteTextures(tex);
|
||||||
|
glDeleteBuffers(buffer);
|
||||||
|
glDeleteProgram(program);
|
||||||
|
return !isCorrect;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean testShaderCompilesOk(ShaderType type, String src) {
|
private static boolean testShaderCompilesOk(ShaderType type, String src) {
|
||||||
int shader = GL20C.glCreateShader(type.gl);
|
int shader = GL20C.glCreateShader(type.gl);
|
||||||
GL20C.glShaderSource(shader, src);
|
GL20C.glShaderSource(shader, src);
|
||||||
|
|||||||
@@ -2,15 +2,17 @@ package me.cortex.voxy.client.core.gl;
|
|||||||
|
|
||||||
import me.cortex.voxy.common.util.TrackedObject;
|
import me.cortex.voxy.common.util.TrackedObject;
|
||||||
import org.lwjgl.opengl.GL11;
|
import org.lwjgl.opengl.GL11;
|
||||||
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.ARBSparseBuffer.GL_SPARSE_STORAGE_BIT_ARB;
|
||||||
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
|
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
|
||||||
import static org.lwjgl.opengl.GL15.glDeleteBuffers;
|
import static org.lwjgl.opengl.GL15.glDeleteBuffers;
|
||||||
import static org.lwjgl.opengl.GL44C.glBufferStorage;
|
|
||||||
import static org.lwjgl.opengl.GL45C.*;
|
import static org.lwjgl.opengl.GL45C.*;
|
||||||
|
|
||||||
public class GlBuffer extends TrackedObject {
|
public class GlBuffer extends TrackedObject {
|
||||||
public final int id;
|
public final int id;
|
||||||
private final long size;
|
private final long size;
|
||||||
|
private final int flags;
|
||||||
|
|
||||||
private static int COUNT;
|
private static int COUNT;
|
||||||
private static long TOTAL_SIZE;
|
private static long TOTAL_SIZE;
|
||||||
@@ -18,12 +20,22 @@ public class GlBuffer extends TrackedObject {
|
|||||||
public GlBuffer(long size) {
|
public GlBuffer(long size) {
|
||||||
this(size, 0);
|
this(size, 0);
|
||||||
}
|
}
|
||||||
|
public GlBuffer(long size, boolean zero) {
|
||||||
|
this(size, 0, zero);
|
||||||
|
}
|
||||||
|
|
||||||
public GlBuffer(long size, int flags) {
|
public GlBuffer(long size, int flags) {
|
||||||
|
this(size, flags, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlBuffer(long size, int flags, boolean zero) {
|
||||||
|
this.flags = flags;
|
||||||
this.id = glCreateBuffers();
|
this.id = glCreateBuffers();
|
||||||
this.size = size;
|
this.size = size;
|
||||||
glNamedBufferStorage(this.id, size, flags);
|
glNamedBufferStorage(this.id, size, flags);
|
||||||
|
if ((flags&GL_SPARSE_STORAGE_BIT_ARB)==0 && zero) {
|
||||||
this.zero();
|
this.zero();
|
||||||
|
}
|
||||||
|
|
||||||
COUNT++;
|
COUNT++;
|
||||||
TOTAL_SIZE += size;
|
TOTAL_SIZE += size;
|
||||||
@@ -38,6 +50,10 @@ public class GlBuffer extends TrackedObject {
|
|||||||
TOTAL_SIZE -= this.size;
|
TOTAL_SIZE -= this.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSparse() {
|
||||||
|
return (this.flags&GL_SPARSE_STORAGE_BIT_ARB)!=0;
|
||||||
|
}
|
||||||
|
|
||||||
public long size() {
|
public long size() {
|
||||||
return this.size;
|
return this.size;
|
||||||
}
|
}
|
||||||
@@ -58,7 +74,8 @@ public class GlBuffer extends TrackedObject {
|
|||||||
glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 0);
|
glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 0);
|
||||||
glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, 0);
|
glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, 0);
|
||||||
|
|
||||||
glClearNamedBufferData(this.id, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{data});
|
MemoryUtil.memPutInt(SCRATCH, data);
|
||||||
|
nglClearNamedBufferData(this.id, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, SCRATCH);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,4 +90,6 @@ public class GlBuffer extends TrackedObject {
|
|||||||
public GlBuffer name(String name) {
|
public GlBuffer name(String name) {
|
||||||
return GlDebug.name(name, this);
|
return GlDebug.name(name, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final long SCRATCH = MemoryUtil.nmemAlloc(4);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package me.cortex.voxy.client.core.gl;
|
package me.cortex.voxy.client.core.gl;
|
||||||
|
|
||||||
import me.cortex.voxy.common.util.TrackedObject;
|
import me.cortex.voxy.common.util.TrackedObject;
|
||||||
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
import static org.lwjgl.opengl.GL32.*;
|
import static org.lwjgl.opengl.GL32.*;
|
||||||
|
|
||||||
@@ -12,13 +13,24 @@ public class GlFence extends TrackedObject {
|
|||||||
this.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
this.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final long SCRATCH = MemoryUtil.nmemCalloc(1,4);
|
||||||
|
|
||||||
public boolean signaled() {
|
public boolean signaled() {
|
||||||
if (!this.signaled) {
|
if (!this.signaled) {
|
||||||
|
/*
|
||||||
int ret = glClientWaitSync(this.fence, 0, 0);
|
int ret = glClientWaitSync(this.fence, 0, 0);
|
||||||
if (ret == GL_ALREADY_SIGNALED || ret == GL_CONDITION_SATISFIED) {
|
if (ret == GL_ALREADY_SIGNALED || ret == GL_CONDITION_SATISFIED) {
|
||||||
this.signaled = true;
|
this.signaled = true;
|
||||||
} else if (ret != GL_TIMEOUT_EXPIRED) {
|
} else if (ret != GL_TIMEOUT_EXPIRED) {
|
||||||
throw new IllegalStateException("Poll for fence failed, glError: " + glGetError());
|
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;
|
return this.signaled;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package me.cortex.voxy.client.core.gl;
|
|||||||
import me.cortex.voxy.common.util.TrackedObject;
|
import me.cortex.voxy.common.util.TrackedObject;
|
||||||
|
|
||||||
import static org.lwjgl.opengl.GL45C.*;
|
import static org.lwjgl.opengl.GL45C.*;
|
||||||
|
import static org.lwjgl.opengl.GL45C.glNamedFramebufferDrawBuffers;
|
||||||
|
|
||||||
public class GlFramebuffer extends TrackedObject {
|
public class GlFramebuffer extends TrackedObject {
|
||||||
public final int id;
|
public final int id;
|
||||||
@@ -24,6 +25,11 @@ public class GlFramebuffer extends TrackedObject {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GlFramebuffer setDrawBuffers(int... buffers) {
|
||||||
|
glNamedFramebufferDrawBuffers(this.id, buffers);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void free() {
|
public void free() {
|
||||||
super.free0();
|
super.free0();
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package me.cortex.voxy.client.core.gl;
|
|||||||
|
|
||||||
import me.cortex.voxy.common.util.TrackedObject;
|
import me.cortex.voxy.common.util.TrackedObject;
|
||||||
|
|
||||||
import static org.lwjgl.opengl.GL11C.*;
|
|
||||||
import static org.lwjgl.opengl.GL45C.*;
|
import static org.lwjgl.opengl.GL45C.*;
|
||||||
|
|
||||||
public class GlRenderBuffer extends TrackedObject {
|
public class GlRenderBuffer extends TrackedObject {
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package me.cortex.voxy.client.core.gl;
|
|||||||
|
|
||||||
import me.cortex.voxy.common.util.TrackedObject;
|
import me.cortex.voxy.common.util.TrackedObject;
|
||||||
|
|
||||||
import static org.lwjgl.opengl.ARBFramebufferObject.glDeleteFramebuffers;
|
|
||||||
import static org.lwjgl.opengl.ARBFramebufferObject.glGenFramebuffers;
|
|
||||||
import static org.lwjgl.opengl.GL11.GL_RGBA8;
|
import static org.lwjgl.opengl.GL11.GL_RGBA8;
|
||||||
import static org.lwjgl.opengl.GL11C.*;
|
import static org.lwjgl.opengl.GL11C.*;
|
||||||
import static org.lwjgl.opengl.GL30.GL_DEPTH24_STENCIL8;
|
import static org.lwjgl.opengl.GL30.GL_DEPTH24_STENCIL8;
|
||||||
@@ -98,11 +96,18 @@ public class GlTexture extends TrackedObject {
|
|||||||
return this.levels;
|
return this.levels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getFormat() {
|
||||||
|
this.assertAllocated();
|
||||||
|
return this.format;
|
||||||
|
}
|
||||||
|
|
||||||
private long getEstimatedSize() {
|
private long getEstimatedSize() {
|
||||||
this.assertAllocated();
|
this.assertAllocated();
|
||||||
long elemSize = switch (this.format) {
|
long elemSize = switch (this.format) {
|
||||||
case GL_RGBA8, GL_DEPTH24_STENCIL8 -> 4;
|
case GL_R32UI, GL_RGBA8, GL_DEPTH24_STENCIL8, GL_R32F -> 4;
|
||||||
case GL_DEPTH_COMPONENT24 -> 4;//TODO: check this is right????
|
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");
|
default -> throw new IllegalStateException("Unknown element size");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ import me.cortex.voxy.common.util.TrackedObject;
|
|||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.GL30.glGenVertexArrays;
|
||||||
import static org.lwjgl.opengl.GL45C.*;
|
import static org.lwjgl.opengl.GL45C.*;
|
||||||
|
|
||||||
public class GlVertexArray extends TrackedObject {
|
public class GlVertexArray extends TrackedObject {
|
||||||
|
public static final int STATIC_VAO = glGenVertexArrays();
|
||||||
|
|
||||||
public final int id;
|
public final int id;
|
||||||
private int[] indices = new int[0];
|
private int[] indices = new int[0];
|
||||||
private int stride;
|
private int stride;
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ public class AutoBindingShader extends Shader {
|
|||||||
private final List<BufferBinding> bindings = new ArrayList<>();
|
private final List<BufferBinding> bindings = new ArrayList<>();
|
||||||
private final List<TextureBinding> textureBindings = new ArrayList<>();
|
private final List<TextureBinding> textureBindings = new ArrayList<>();
|
||||||
|
|
||||||
|
private boolean rebuild = true;
|
||||||
|
|
||||||
AutoBindingShader(Shader.Builder<AutoBindingShader> builder, int program) {
|
AutoBindingShader(Shader.Builder<AutoBindingShader> builder, int program) {
|
||||||
super(program);
|
super(program);
|
||||||
this.defines = builder.defines;
|
this.defines = builder.defines;
|
||||||
@@ -70,6 +72,8 @@ public class AutoBindingShader extends Shader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void insertOrReplaceBinding(BufferBinding binding) {
|
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
|
//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++) {
|
for (int i = 0; i < this.bindings.size(); i++) {
|
||||||
var entry = this.bindings.get(i);
|
var entry = this.bindings.get(i);
|
||||||
@@ -92,6 +96,16 @@ public class AutoBindingShader extends Shader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public AutoBindingShader texture(int unit, int sampler, GlTexture texture) {
|
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));
|
this.textureBindings.add(new TextureBinding(unit, sampler, texture));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -99,6 +113,13 @@ public class AutoBindingShader extends Shader {
|
|||||||
@Override
|
@Override
|
||||||
public void bind() {
|
public void bind() {
|
||||||
super.bind();
|
super.bind();
|
||||||
|
//TODO: replace with multibind and use the invalidate flag
|
||||||
|
/*
|
||||||
|
glBindSamplers();
|
||||||
|
glBindTextures();
|
||||||
|
glBindBuffersBase();
|
||||||
|
glBindBuffersRange();
|
||||||
|
*/
|
||||||
if (!this.bindings.isEmpty()) {
|
if (!this.bindings.isEmpty()) {
|
||||||
for (var binding : this.bindings) {
|
for (var binding : this.bindings) {
|
||||||
binding.buffer.assertNotFreed();
|
binding.buffer.assertNotFreed();
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
package me.cortex.voxy.client.core.gl.shader;
|
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class GenericsProcessor implements IShaderProcessor {
|
|
||||||
private static final Pattern GENERIC_DEFINE = Pattern.compile("#defineGen (?<name>[A-Za-z0-9]+)<(?<generic>[A-Za-z0-9]*)>");
|
|
||||||
private static final Pattern GENERIC_USE = Pattern.compile("(?<type>[A-Za-z0-9]+)<(?<generic>[A-Za-z0-9]*)>");
|
|
||||||
@Override
|
|
||||||
public String process(ShaderType type, String source) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package me.cortex.voxy.client.core.gl.shader;
|
package me.cortex.voxy.client.core.gl.shader;
|
||||||
|
|
||||||
import me.cortex.voxy.client.core.gl.Capabilities;
|
import me.cortex.voxy.client.core.gl.Capabilities;
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
|
||||||
import me.cortex.voxy.client.core.gl.GlDebug;
|
import me.cortex.voxy.client.core.gl.GlDebug;
|
||||||
import me.cortex.voxy.common.Logger;
|
import me.cortex.voxy.common.Logger;
|
||||||
|
import me.cortex.voxy.common.util.ThreadUtils;
|
||||||
import me.cortex.voxy.common.util.TrackedObject;
|
import me.cortex.voxy.common.util.TrackedObject;
|
||||||
import org.lwjgl.opengl.GL20C;
|
import org.lwjgl.opengl.GL20C;
|
||||||
|
import org.lwjgl.system.MemoryStack;
|
||||||
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -67,6 +69,7 @@ public class Shader extends TrackedObject {
|
|||||||
J make(Builder<J> builder, int program);
|
J make(Builder<J> builder, int program);
|
||||||
}
|
}
|
||||||
final Map<String, String> defines = new HashMap<>();
|
final Map<String, String> defines = new HashMap<>();
|
||||||
|
final Map<String, String> replacements = new LinkedHashMap<>();
|
||||||
private final Map<ShaderType, String> sources = new HashMap<>();
|
private final Map<ShaderType, String> sources = new HashMap<>();
|
||||||
private final IShaderProcessor processor;
|
private final IShaderProcessor processor;
|
||||||
private final IShaderObjectConstructor<T> constructor;
|
private final IShaderObjectConstructor<T> constructor;
|
||||||
@@ -75,6 +78,13 @@ public class Shader extends TrackedObject {
|
|||||||
this.processor = processor;
|
this.processor = processor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder<T> clone() {
|
||||||
|
var clone = new Builder<>(this.constructor, this.processor);
|
||||||
|
clone.defines.putAll(this.defines);
|
||||||
|
clone.sources.putAll(this.sources);
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder<T> define(String name) {
|
public Builder<T> define(String name) {
|
||||||
this.defines.put(name, "");
|
this.defines.put(name, "");
|
||||||
return this;
|
return this;
|
||||||
@@ -100,11 +110,21 @@ public class Shader extends TrackedObject {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder<T> define(String name, float value) {
|
||||||
|
this.defines.put(name, Float.toString(value)+"f");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder<T> define(String name, String value) {
|
public Builder<T> define(String name, String value) {
|
||||||
this.defines.put(name, value);
|
this.defines.put(name, value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder<T> replace(String value, String replacement) {
|
||||||
|
this.defines.put(value, replacement);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder<T> add(ShaderType type, String id) {
|
public Builder<T> add(ShaderType type, String id) {
|
||||||
this.addSource(type, ShaderLoader.parse(id));
|
this.addSource(type, ShaderLoader.parse(id));
|
||||||
return this;
|
return this;
|
||||||
@@ -130,6 +150,10 @@ public class Shader extends TrackedObject {
|
|||||||
defs
|
defs
|
||||||
+ src.substring(src.indexOf('\n')+1);
|
+ src.substring(src.indexOf('\n')+1);
|
||||||
|
|
||||||
|
for (var replacement : this.replacements.entrySet()) {
|
||||||
|
src = src.replace(replacement.getKey(), replacement.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
shaders[i++] = createShader(entry.getKey(), src);
|
shaders[i++] = createShader(entry.getKey(), src);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,6 +173,7 @@ public class Shader extends TrackedObject {
|
|||||||
|
|
||||||
public T compile() {
|
public T compile() {
|
||||||
this.defineIf("IS_INTEL", Capabilities.INSTANCE.isIntel);
|
this.defineIf("IS_INTEL", Capabilities.INSTANCE.isIntel);
|
||||||
|
this.defineIf("IS_WINDOWS", ThreadUtils.isWindows);
|
||||||
return this.constructor.make(this, this.compileToProgram());
|
return this.constructor.make(this, this.compileToProgram());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +195,13 @@ public class Shader extends TrackedObject {
|
|||||||
|
|
||||||
private static int createShader(ShaderType type, String src) {
|
private static int createShader(ShaderType type, String src) {
|
||||||
int shader = GL20C.glCreateShader(type.gl);
|
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);
|
GL20C.glCompileShader(shader);
|
||||||
String log = GL20C.glGetShaderInfoLog(shader);
|
String log = GL20C.glGetShaderInfoLog(shader);
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import net.caffeinemc.mods.sodium.client.gl.shader.ShaderParser;
|
|||||||
|
|
||||||
public class ShaderLoader {
|
public class ShaderLoader {
|
||||||
public static String parse(String id) {
|
public static String parse(String id) {
|
||||||
return ShaderParser.parseShader("#import <" + id + ">", ShaderConstants.builder().build());
|
return "#version 460 core\n"+ShaderParser.parseShader("\n#import <" + id + ">\n//beans", ShaderConstants.builder().build()).src().replaceAll("\r\n", "\n").replaceFirst("\n#version .+\n", "\n");
|
||||||
//return me.jellysquid.mods.sodium.client.gl.shader.ShaderLoader.getShaderSource(new Identifier(id));
|
//return me.jellysquid.mods.sodium.client.gl.shader.ShaderLoader.getShaderSource(new Identifier(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
128
src/main/java/me/cortex/voxy/client/core/model/MipGen.java
Normal file
128
src/main/java/me/cortex/voxy/client/core/model/MipGen.java
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package me.cortex.voxy.client.core.model;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue;
|
||||||
|
import me.cortex.voxy.common.util.MemoryBuffer;
|
||||||
|
import net.caffeinemc.mods.sodium.client.util.color.ColorSRGB;
|
||||||
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static me.cortex.voxy.client.core.model.ModelFactory.LAYERS;
|
||||||
|
import static me.cortex.voxy.client.core.model.ModelFactory.MODEL_TEXTURE_SIZE;
|
||||||
|
|
||||||
|
public class MipGen {
|
||||||
|
static {
|
||||||
|
if (MODEL_TEXTURE_SIZE>16) throw new IllegalStateException("TODO: THIS MUST BE UPDATED, IT CURRENTLY ASSUMES 16 OR SMALLER SIZE");
|
||||||
|
}
|
||||||
|
private static final short[] SCRATCH = new short[MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE];
|
||||||
|
private static final ByteArrayFIFOQueue QUEUE = new ByteArrayFIFOQueue(MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE);
|
||||||
|
|
||||||
|
private static long getOffset(int bx, int by, int i) {
|
||||||
|
bx += i&(MODEL_TEXTURE_SIZE-1);
|
||||||
|
by += i/MODEL_TEXTURE_SIZE;
|
||||||
|
return bx+by*MODEL_TEXTURE_SIZE*3;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void solidify(long baseAddr, byte msk) {
|
||||||
|
for (int idx = 0; idx < 6; idx++) {
|
||||||
|
if (((msk>>idx)&1)==0) continue;
|
||||||
|
int bx = (idx>>1)*MODEL_TEXTURE_SIZE;
|
||||||
|
int by = (idx&1)*MODEL_TEXTURE_SIZE;
|
||||||
|
long cAddr = baseAddr + (long)(bx+by*MODEL_TEXTURE_SIZE*3)*4;
|
||||||
|
Arrays.fill(SCRATCH, (short) -1);
|
||||||
|
for (int y = 0; y<MODEL_TEXTURE_SIZE;y++) {
|
||||||
|
for (int x = 0; x<MODEL_TEXTURE_SIZE;x++) {
|
||||||
|
int colour = MemoryUtil.memGetInt(cAddr+(x+y*MODEL_TEXTURE_SIZE*3)*4);
|
||||||
|
if ((colour&0xFF000000)!=0) {
|
||||||
|
int pos = x+y*MODEL_TEXTURE_SIZE;
|
||||||
|
SCRATCH[pos] = ((short)pos);
|
||||||
|
QUEUE.enqueue((byte) pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!QUEUE.isEmpty()) {
|
||||||
|
int pos = Byte.toUnsignedInt(QUEUE.dequeueByte());
|
||||||
|
int x = pos&(MODEL_TEXTURE_SIZE-1);
|
||||||
|
int y = pos/MODEL_TEXTURE_SIZE;//this better be turned into a bitshift
|
||||||
|
short newVal = (short) (SCRATCH[pos]+(short) 0x0100);
|
||||||
|
for (int D = 3; D!=-1; D--) {
|
||||||
|
int d = 2*(D&1)-1;
|
||||||
|
int x2 = x+(((D&2)==2)?d:0);
|
||||||
|
int y2 = y+(((D&2)==0)?d:0);
|
||||||
|
if (x2<0||x2>=MODEL_TEXTURE_SIZE||y2<0||y2>=MODEL_TEXTURE_SIZE) continue;
|
||||||
|
int pos2 = x2+y2*MODEL_TEXTURE_SIZE;
|
||||||
|
if ((newVal&0xFF00)<(SCRATCH[pos2]&0xFF00)) {
|
||||||
|
SCRATCH[pos2] = newVal;
|
||||||
|
QUEUE.enqueue((byte) pos2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE; i++) {
|
||||||
|
int d = Short.toUnsignedInt(SCRATCH[i]);
|
||||||
|
if ((d&0xFF00)!=0) {
|
||||||
|
int c = MemoryUtil.memGetInt(baseAddr+getOffset(bx, by, d&0xFF)*4)&0x00FFFFFF;
|
||||||
|
MemoryUtil.memPutInt(baseAddr+getOffset(bx, by, i)*4, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void putTextures(boolean darkened, ColourDepthTextureData[] textures, MemoryBuffer into) {
|
||||||
|
//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
|
||||||
|
|
||||||
|
final long addr = into.address;
|
||||||
|
final int LENGTH_B = MODEL_TEXTURE_SIZE*3;
|
||||||
|
byte solidMsk = 0;
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
int x = (i>>1)*MODEL_TEXTURE_SIZE;
|
||||||
|
int y = (i&1)*MODEL_TEXTURE_SIZE;
|
||||||
|
int j = 0;
|
||||||
|
boolean anyTransparent = false;
|
||||||
|
for (int t : textures[i].colour()) {
|
||||||
|
int o = ((y+(j>>LAYERS))*LENGTH_B + ((j&(MODEL_TEXTURE_SIZE-1))+x))*4; j++;//LAYERS here is just cause faster
|
||||||
|
//t = ((t&0xFF000000)==0)?0x00_FF_00_FF:t;//great for testing
|
||||||
|
MemoryUtil.memPutInt(addr+o, t);
|
||||||
|
anyTransparent |= ((t&0xFF000000)==0);
|
||||||
|
}
|
||||||
|
solidMsk |= (anyTransparent?1:0)<<i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!darkened) {
|
||||||
|
solidify(addr, solidMsk);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Mip the scratch
|
||||||
|
long dAddr = addr;
|
||||||
|
for (int i = 0; i < LAYERS-1; i++) {
|
||||||
|
long sAddr = dAddr;
|
||||||
|
dAddr += (MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*3*2*4)>>(i<<1);//is.. i*2 because shrink both MODEL_TEXTURE_SIZE by >>i so is 2*i total shift
|
||||||
|
int width = (MODEL_TEXTURE_SIZE*3)>>(i+1);
|
||||||
|
int sWidth = (MODEL_TEXTURE_SIZE*3)>>i;
|
||||||
|
int height = (MODEL_TEXTURE_SIZE*2)>>(i+1);
|
||||||
|
//TODO: OPTIMZIE THIS
|
||||||
|
for (int px = 0; px < width; px++) {
|
||||||
|
for (int py = 0; py < height; py++) {
|
||||||
|
long bp = sAddr + (px*2 + py*2*sWidth)*4;
|
||||||
|
int C00 = MemoryUtil.memGetInt(bp);
|
||||||
|
int C01 = MemoryUtil.memGetInt(bp+sWidth*4);
|
||||||
|
int C10 = MemoryUtil.memGetInt(bp+4);
|
||||||
|
int C11 = MemoryUtil.memGetInt(bp+sWidth*4+4);
|
||||||
|
MemoryUtil.memPutInt(dAddr + (px+py*width) * 4L, TextureUtils.mipColours(darkened, C00, C01, C10, C11));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void generateMipmaps(long[] textures, int size) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,33 +1,18 @@
|
|||||||
package me.cortex.voxy.client.core.model;
|
package me.cortex.voxy.client.core.model;
|
||||||
|
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet;
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||||
import me.cortex.voxy.client.TimingStatistics;
|
import me.cortex.voxy.common.Logger;
|
||||||
import me.cortex.voxy.client.core.gl.GlFramebuffer;
|
|
||||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
|
||||||
import me.cortex.voxy.client.core.rendering.util.RawDownloadStream;
|
|
||||||
import me.cortex.voxy.common.world.other.Mapper;
|
import me.cortex.voxy.common.world.other.Mapper;
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.registry.RegistryKeys;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
import org.lwjgl.opengl.GL11;
|
|
||||||
|
|
||||||
import java.lang.invoke.VarHandle;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.locks.StampedLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
import static org.lwjgl.opengl.ARBFramebufferObject.GL_COLOR_ATTACHMENT0;
|
|
||||||
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
|
|
||||||
import static org.lwjgl.opengl.GL11.glGetInteger;
|
import static org.lwjgl.opengl.GL11.glGetInteger;
|
||||||
import static org.lwjgl.opengl.GL11C.GL_NEAREST;
|
|
||||||
import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER;
|
import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER;
|
||||||
import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_BINDING;
|
import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_BINDING;
|
||||||
import static org.lwjgl.opengl.GL30C.GL_DRAW_FRAMEBUFFER_BINDING;
|
|
||||||
import static org.lwjgl.opengl.GL30C.glBindFramebuffer;
|
import static org.lwjgl.opengl.GL30C.glBindFramebuffer;
|
||||||
import static org.lwjgl.opengl.GL45.glBlitNamedFramebuffer;
|
|
||||||
|
|
||||||
public class ModelBakerySubsystem {
|
public class ModelBakerySubsystem {
|
||||||
//Redo to just make it request the block faces with the async texture download stream which
|
//Redo to just make it request the block faces with the async texture download stream which
|
||||||
@@ -35,50 +20,34 @@ public class ModelBakerySubsystem {
|
|||||||
|
|
||||||
private final ModelStore storage = new ModelStore();
|
private final ModelStore storage = new ModelStore();
|
||||||
public final ModelFactory factory;
|
public final ModelFactory factory;
|
||||||
|
private final Mapper mapper;
|
||||||
private final AtomicInteger blockIdCount = new AtomicInteger();
|
private final AtomicInteger blockIdCount = new AtomicInteger();
|
||||||
private final ConcurrentLinkedDeque<Integer> blockIdQueue = new ConcurrentLinkedDeque<>();//TODO: replace with custom DS
|
private final ConcurrentLinkedDeque<Integer> blockIdQueue = new ConcurrentLinkedDeque<>();//TODO: replace with custom DS
|
||||||
private final ConcurrentLinkedDeque<Mapper.BiomeEntry> biomeQueue = new ConcurrentLinkedDeque<>();
|
|
||||||
|
|
||||||
|
private final Thread processingThread;
|
||||||
|
private volatile boolean isRunning = true;
|
||||||
public ModelBakerySubsystem(Mapper mapper) {
|
public ModelBakerySubsystem(Mapper mapper) {
|
||||||
|
this.mapper = mapper;
|
||||||
this.factory = new ModelFactory(mapper, this.storage);
|
this.factory = new ModelFactory(mapper, this.storage);
|
||||||
|
this.processingThread = new Thread(()->{//TODO replace this with something good/integrate it into the async processor so that we just have less threads overall
|
||||||
|
while (this.isRunning) {
|
||||||
|
this.factory.processAllThings();
|
||||||
|
try {
|
||||||
|
Thread.sleep(10);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, "Model factory processor");
|
||||||
|
this.processingThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void tick(long totalBudget) {
|
public void tick(long totalBudget) {
|
||||||
//Upload all biomes
|
long start = System.nanoTime();
|
||||||
while (!this.biomeQueue.isEmpty()) {
|
this.factory.tickAndProcessUploads();
|
||||||
var biome = this.biomeQueue.poll();
|
|
||||||
var biomeReg = MinecraftClient.getInstance().world.getRegistryManager().getOrThrow(RegistryKeys.BIOME);
|
|
||||||
this.factory.addBiome(biome.id, biomeReg.get(Identifier.of(biome.biome)));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
//There should be a method to access the frame time IIRC, if the user framecap is unlimited lock it to like 60 fps for computation
|
|
||||||
int BUDGET = 16;//TODO: make this computed based on the remaining free time in a frame (and like div by 2 to reduce overhead) (with a min of 1)
|
|
||||||
if (!this.blockIdQueue.isEmpty()) {
|
|
||||||
int[] est = new int[Math.min(this.blockIdQueue.size(), BUDGET)];
|
|
||||||
int i = 0;
|
|
||||||
synchronized (this.blockIdQueue) {
|
|
||||||
for (;i < est.length && !this.blockIdQueue.isEmpty(); i++) {
|
|
||||||
int blockId = this.blockIdQueue.removeFirstInt();
|
|
||||||
if (blockId == -1) {
|
|
||||||
i--;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
est[i] = blockId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int j = 0; j < i; j++) {
|
|
||||||
this.factory.addEntry(est[j]);
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
//TimingStatistics.modelProcess.start();
|
|
||||||
if (this.blockIdCount.get() != 0) {
|
|
||||||
long budget = Math.min(totalBudget-150_000, totalBudget-(this.factory.resultJobs.size()*10_000L))-150_000;
|
|
||||||
|
|
||||||
//Always do 1 iteration minimum
|
//Always do 1 iteration minimum
|
||||||
Integer i = this.blockIdQueue.poll();
|
Integer i = this.blockIdQueue.poll();
|
||||||
|
if (i != null) {
|
||||||
int j = 0;
|
int j = 0;
|
||||||
if (i != null) {
|
if (i != null) {
|
||||||
int fbBinding = glGetInteger(GL_FRAMEBUFFER_BINDING);
|
int fbBinding = glGetInteger(GL_FRAMEBUFFER_BINDING);
|
||||||
@@ -86,7 +55,7 @@ public class ModelBakerySubsystem {
|
|||||||
do {
|
do {
|
||||||
this.factory.addEntry(i);
|
this.factory.addEntry(i);
|
||||||
j++;
|
j++;
|
||||||
if (24<j)//budget<(System.nanoTime() - start)+1000
|
if (4<j&&(totalBudget<(System.nanoTime() - start)+50_000))//20<j||
|
||||||
break;
|
break;
|
||||||
i = this.blockIdQueue.poll();
|
i = this.blockIdQueue.poll();
|
||||||
} while (i != null);
|
} while (i != null);
|
||||||
@@ -96,38 +65,41 @@ public class ModelBakerySubsystem {
|
|||||||
this.blockIdCount.addAndGet(-j);
|
this.blockIdCount.addAndGet(-j);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.factory.tick();
|
|
||||||
|
|
||||||
long start = System.nanoTime();
|
|
||||||
while (!this.factory.resultJobs.isEmpty()) {
|
|
||||||
this.factory.resultJobs.poll().run();
|
|
||||||
if (totalBudget<(System.nanoTime()-start))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
//TimingStatistics.modelProcess.stop();
|
//TimingStatistics.modelProcess.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
|
this.isRunning = false;
|
||||||
|
try {
|
||||||
|
this.processingThread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
this.factory.free();
|
this.factory.free();
|
||||||
this.storage.free();
|
this.storage.free();
|
||||||
}
|
}
|
||||||
|
|
||||||
//This is on this side only and done like this as only worker threads call this code
|
//This is on this side only and done like this as only worker threads call this code
|
||||||
private final StampedLock seenIdsLock = new StampedLock();
|
private final ReentrantLock seenIdsLock = new ReentrantLock();
|
||||||
private final IntOpenHashSet seenIds = new IntOpenHashSet(6000);
|
private final IntOpenHashSet seenIds = new IntOpenHashSet(6000);//TODO: move to a lock free concurrent hashmap
|
||||||
public void requestBlockBake(int blockId) {
|
public void requestBlockBake(int blockId) {
|
||||||
long stamp = this.seenIdsLock.writeLock();
|
if (this.mapper.getBlockStateCount() < blockId) {
|
||||||
if (!this.seenIds.add(blockId)) {
|
Logger.error("Error, got bakeing request for out of range state id. StateId: " + blockId + " max id: " + this.mapper.getBlockStateCount(), new Exception());
|
||||||
this.seenIdsLock.unlockWrite(stamp);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.seenIdsLock.unlockWrite(stamp);
|
this.seenIdsLock.lock();
|
||||||
|
if (!this.seenIds.add(blockId)) {
|
||||||
|
this.seenIdsLock.unlock();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.seenIdsLock.unlock();
|
||||||
this.blockIdQueue.add(blockId);
|
this.blockIdQueue.add(blockId);
|
||||||
this.blockIdCount.incrementAndGet();
|
this.blockIdCount.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addBiome(Mapper.BiomeEntry biomeEntry) {
|
public void addBiome(Mapper.BiomeEntry biomeEntry) {
|
||||||
this.biomeQueue.add(biomeEntry);
|
this.factory.addBiome(biomeEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addDebugData(List<String> debug) {
|
public void addDebugData(List<String> debug) {
|
||||||
@@ -139,7 +111,7 @@ public class ModelBakerySubsystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean areQueuesEmpty() {
|
public boolean areQueuesEmpty() {
|
||||||
return this.blockIdCount.get()==0 && this.factory.getInflightCount() == 0 && this.biomeQueue.isEmpty();
|
return this.blockIdCount.get()==0 && this.factory.getInflightCount() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getProcessingCount() {
|
public int getProcessingCount() {
|
||||||
|
|||||||
@@ -1,48 +1,53 @@
|
|||||||
package me.cortex.voxy.client.core.model;
|
package me.cortex.voxy.client.core.model;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectSet;
|
import it.unimi.dsi.fastutil.objects.ObjectSet;
|
||||||
import me.cortex.voxy.client.core.gl.Capabilities;
|
import me.cortex.voxy.client.core.gl.Capabilities;
|
||||||
|
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||||
|
import me.cortex.voxy.client.core.gl.GlTexture;
|
||||||
import me.cortex.voxy.client.core.model.bakery.ModelTextureBakery;
|
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.RawDownloadStream;
|
||||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||||
import me.cortex.voxy.common.Logger;
|
import me.cortex.voxy.common.Logger;
|
||||||
import me.cortex.voxy.common.util.MemoryBuffer;
|
import me.cortex.voxy.common.util.MemoryBuffer;
|
||||||
|
import me.cortex.voxy.common.util.Pair;
|
||||||
import me.cortex.voxy.common.world.other.Mapper;
|
import me.cortex.voxy.common.world.other.Mapper;
|
||||||
import net.minecraft.block.Block;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.block.BlockState;
|
import net.minecraft.client.color.block.BlockColor;
|
||||||
import net.minecraft.block.FluidBlock;
|
import net.minecraft.client.renderer.ItemBlockRenderTypes;
|
||||||
import net.minecraft.block.LeavesBlock;
|
import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
|
||||||
import net.minecraft.block.entity.BlockEntity;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.core.Direction;
|
||||||
import net.minecraft.client.color.block.BlockColorProvider;
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
import net.minecraft.client.render.RenderLayer;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.client.render.RenderLayers;
|
import net.minecraft.resources.Identifier;
|
||||||
import net.minecraft.fluid.FluidState;
|
import net.minecraft.world.level.BlockAndTintGetter;
|
||||||
import net.minecraft.registry.Registries;
|
import net.minecraft.world.level.ColorResolver;
|
||||||
import net.minecraft.registry.RegistryKeys;
|
import net.minecraft.world.level.LightLayer;
|
||||||
import net.minecraft.util.Pair;
|
import net.minecraft.world.level.biome.Biome;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.world.level.biome.Biomes;
|
||||||
import net.minecraft.util.math.Direction;
|
import net.minecraft.world.level.block.Block;
|
||||||
import net.minecraft.world.BlockRenderView;
|
import net.minecraft.world.level.block.Blocks;
|
||||||
import net.minecraft.world.LightType;
|
import net.minecraft.world.level.block.LeavesBlock;
|
||||||
import net.minecraft.world.biome.Biome;
|
import net.minecraft.world.level.block.LiquidBlock;
|
||||||
import net.minecraft.world.biome.BiomeKeys;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
import net.minecraft.world.biome.ColorResolver;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.minecraft.world.chunk.light.LightingProvider;
|
import net.minecraft.world.level.lighting.LevelLightEngine;
|
||||||
|
import net.minecraft.world.level.material.FluidState;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.lwjgl.system.MemoryUtil;
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
|
import java.lang.invoke.VarHandle;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
import static me.cortex.voxy.client.core.model.ModelStore.MODEL_SIZE;
|
import static me.cortex.voxy.client.core.model.ModelStore.MODEL_SIZE;
|
||||||
import static org.lwjgl.opengl.ARBDirectStateAccess.nglTextureSubImage2D;
|
import static org.lwjgl.opengl.ARBDirectStateAccess.nglTextureSubImage2D;
|
||||||
import static org.lwjgl.opengl.GL11.*;
|
import static org.lwjgl.opengl.GL11.*;
|
||||||
import static org.lwjgl.opengl.GL33.glDeleteSamplers;
|
|
||||||
import static org.lwjgl.opengl.GL33.glGenSamplers;
|
|
||||||
import static org.lwjgl.opengl.GL45C.glTextureSubImage2D;
|
|
||||||
|
|
||||||
//Manages the storage and updating of model states, textures and colours
|
//Manages the storage and updating of model states, textures and colours
|
||||||
|
|
||||||
@@ -58,16 +63,17 @@ import static org.lwjgl.opengl.GL45C.glTextureSubImage2D;
|
|||||||
// this _quarters_ the memory requirements for the texture atlas!!! WHICH IS HUGE saving
|
// this _quarters_ the memory requirements for the texture atlas!!! WHICH IS HUGE saving
|
||||||
public class ModelFactory {
|
public class ModelFactory {
|
||||||
public static final int MODEL_TEXTURE_SIZE = 16;
|
public static final int MODEL_TEXTURE_SIZE = 16;
|
||||||
|
public static final int LAYERS = Integer.numberOfTrailingZeros(MODEL_TEXTURE_SIZE);
|
||||||
|
|
||||||
//TODO: replace the fluid BlockState with a client model id integer of the fluidState, requires looking up
|
//TODO: replace the fluid BlockState with a client model id integer of the fluidState, requires looking up
|
||||||
// the fluid state in the mipper
|
// the fluid state in the mipper
|
||||||
private record ModelEntry(ColourDepthTextureData down, ColourDepthTextureData up, ColourDepthTextureData north, ColourDepthTextureData south, ColourDepthTextureData west, ColourDepthTextureData east, int fluidBlockStateId) {
|
private record ModelEntry(ColourDepthTextureData down, ColourDepthTextureData up, ColourDepthTextureData north, ColourDepthTextureData south, ColourDepthTextureData west, ColourDepthTextureData east, int fluidBlockStateId, int tintingColour) {
|
||||||
public ModelEntry(ColourDepthTextureData[] textures, int fluidBlockStateId) {
|
public ModelEntry(ColourDepthTextureData[] textures, int fluidBlockStateId, int tintingColour) {
|
||||||
this(textures[0], textures[1], textures[2], textures[3], textures[4], textures[5], fluidBlockStateId);
|
this(textures[0], textures[1], textures[2], textures[3], textures[4], textures[5], fluidBlockStateId, tintingColour);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Biome DEFAULT_BIOME = MinecraftClient.getInstance().world.getRegistryManager().getOrThrow(RegistryKeys.BIOME).get(BiomeKeys.PLAINS);
|
private final Biome DEFAULT_BIOME = Minecraft.getInstance().level.registryAccess().lookupOrThrow(Registries.BIOME).getValue(Biomes.PLAINS);
|
||||||
|
|
||||||
public final ModelTextureBakery bakery;
|
public final ModelTextureBakery bakery;
|
||||||
|
|
||||||
@@ -109,6 +115,7 @@ public class ModelFactory {
|
|||||||
//Contains the set of all block ids that are currently inflight/being baked
|
//Contains the set of all block ids that are currently inflight/being baked
|
||||||
// this is required due to "async" nature of gpu feedback
|
// this is required due to "async" nature of gpu feedback
|
||||||
private final IntOpenHashSet blockStatesInFlight = new IntOpenHashSet();
|
private final IntOpenHashSet blockStatesInFlight = new IntOpenHashSet();
|
||||||
|
private final ReentrantLock blockStatesInFlightLock = new ReentrantLock();
|
||||||
|
|
||||||
private final List<Biome> biomes = new ArrayList<>();
|
private final List<Biome> biomes = new ArrayList<>();
|
||||||
private final List<Pair<Integer, BlockState>> modelsRequiringBiomeColours = new ArrayList<>();
|
private final List<Pair<Integer, BlockState>> modelsRequiringBiomeColours = new ArrayList<>();
|
||||||
@@ -119,8 +126,11 @@ public class ModelFactory {
|
|||||||
private final ModelStore storage;
|
private final ModelStore storage;
|
||||||
private final RawDownloadStream downstream = new RawDownloadStream(8*1024*1024);//8mb downstream
|
private final RawDownloadStream downstream = new RawDownloadStream(8*1024*1024);//8mb downstream
|
||||||
|
|
||||||
public final Deque<Runnable> resultJobs = new ArrayDeque<>();
|
private final ConcurrentLinkedDeque<RawBakeResult> rawBakeResults = new ConcurrentLinkedDeque<>();
|
||||||
|
|
||||||
|
private final ConcurrentLinkedDeque<ResultUploader> uploadResults = new ConcurrentLinkedDeque<>();
|
||||||
|
|
||||||
|
private Object2IntMap<BlockState> customBlockStateIdMapping;
|
||||||
|
|
||||||
//TODO: NOTE!!! is it worth even uploading as a 16x16 texture, since automatic lod selection... doing 8x8 textures might be perfectly ok!!!
|
//TODO: NOTE!!! is it worth even uploading as a 16x16 texture, since automatic lod selection... doing 8x8 textures might be perfectly ok!!!
|
||||||
// this _quarters_ the memory requirements for the texture atlas!!! WHICH IS HUGE saving
|
// this _quarters_ the memory requirements for the texture atlas!!! WHICH IS HUGE saving
|
||||||
@@ -139,9 +149,32 @@ public class ModelFactory {
|
|||||||
this.addEntry(0);//Add air as the first entry
|
this.addEntry(0);//Add air as the first entry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCustomBlockStateMapping(Object2IntMap<BlockState> mapping) {
|
||||||
|
this.customBlockStateIdMapping = mapping;
|
||||||
|
}
|
||||||
|
|
||||||
public void tick() {
|
private static final class RawBakeResult {
|
||||||
this.downstream.tick();
|
private final int blockId;
|
||||||
|
private final BlockState blockState;
|
||||||
|
private final MemoryBuffer rawData;
|
||||||
|
|
||||||
|
public boolean isShaded;
|
||||||
|
public boolean hasDarkenedTextures;
|
||||||
|
|
||||||
|
public RawBakeResult(int blockId, BlockState blockState, MemoryBuffer rawData) {
|
||||||
|
this.blockId = blockId;
|
||||||
|
this.blockState = blockState;
|
||||||
|
this.rawData = rawData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RawBakeResult(int blockId, BlockState blockState) {
|
||||||
|
this(blockId, blockState, new MemoryBuffer(MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*2*4*6));
|
||||||
|
}
|
||||||
|
|
||||||
|
public RawBakeResult cpyBuf(long ptr) {
|
||||||
|
this.rawData.cpyFrom(ptr);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean addEntry(int blockId) {
|
public boolean addEntry(int blockId) {
|
||||||
@@ -151,19 +184,29 @@ public class ModelFactory {
|
|||||||
//We are (probably) going to be baking the block id
|
//We are (probably) going to be baking the block id
|
||||||
// check that it is currently not inflight, if it is, return as its already being baked
|
// check that it is currently not inflight, if it is, return as its already being baked
|
||||||
// else add it to the flight as it is going to be baked
|
// else add it to the flight as it is going to be baked
|
||||||
|
this.blockStatesInFlightLock.lock();
|
||||||
if (!this.blockStatesInFlight.add(blockId)) {
|
if (!this.blockStatesInFlight.add(blockId)) {
|
||||||
|
this.blockStatesInFlightLock.unlock();
|
||||||
//Block baking is already in-flight
|
//Block baking is already in-flight
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
this.blockStatesInFlightLock.unlock();
|
||||||
|
|
||||||
|
VarHandle.loadLoadFence();
|
||||||
|
|
||||||
|
//We need to get it twice cause of threading
|
||||||
|
if (this.idMappings[blockId] != -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var blockState = this.mapper.getBlockStateFromBlockId(blockId);
|
var blockState = this.mapper.getBlockStateFromBlockId(blockId);
|
||||||
|
|
||||||
//Before we enqueue the baking of this blockstate, we must check if it has a fluid state associated with it
|
//Before we enqueue the baking of this blockstate, we must check if it has a fluid state associated with it
|
||||||
// if it does, we must ensure that it is (effectivly) baked BEFORE we bake this blockstate
|
// if it does, we must ensure that it is (effectivly) baked BEFORE we bake this blockstate
|
||||||
boolean isFluid = blockState.getBlock() instanceof FluidBlock;
|
boolean isFluid = blockState.getBlock() instanceof LiquidBlock;
|
||||||
if ((!isFluid) && (!blockState.getFluidState().isEmpty())) {
|
if ((!isFluid) && (!blockState.getFluidState().isEmpty())) {
|
||||||
//Insert into the fluid LUT
|
//Insert into the fluid LUT
|
||||||
var fluidState = blockState.getFluidState().getBlockState();
|
var fluidState = blockState.getFluidState().createLegacyBlock();
|
||||||
|
|
||||||
int fluidStateId = this.mapper.getIdForBlockState(fluidState);
|
int fluidStateId = this.mapper.getIdForBlockState(fluidState);
|
||||||
|
|
||||||
@@ -176,9 +219,20 @@ public class ModelFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int TOTAL_FACES_TEXTURE_SIZE = MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*2*4*6;// since both depth and colour are packed together, 6 faces, 4 bytes per pixel
|
RawBakeResult result = new RawBakeResult(blockId, blockState);
|
||||||
int allocation = this.downstream.download(TOTAL_FACES_TEXTURE_SIZE, ptr -> {
|
int allocation = this.downstream.download(MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*2*4*6, ptr -> this.rawBakeResults.add(result.cpyBuf(ptr)));
|
||||||
|
int flags = this.bakery.renderToStream(blockState, this.downstream.getBufferId(), allocation);
|
||||||
|
result.hasDarkenedTextures = (flags&2)!=0;
|
||||||
|
result.isShaded = (flags&1)!=0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean processModelResult() {
|
||||||
|
var result = this.rawBakeResults.poll();
|
||||||
|
if (result == null) return false;
|
||||||
ColourDepthTextureData[] textureData = new ColourDepthTextureData[6];
|
ColourDepthTextureData[] textureData = new ColourDepthTextureData[6];
|
||||||
|
{//Create texture data
|
||||||
|
long ptr = result.rawData.address;
|
||||||
final int FACE_SIZE = MODEL_TEXTURE_SIZE * MODEL_TEXTURE_SIZE;
|
final int FACE_SIZE = MODEL_TEXTURE_SIZE * MODEL_TEXTURE_SIZE;
|
||||||
for (int face = 0; face < 6; face++) {
|
for (int face = 0; face < 6; face++) {
|
||||||
long faceDataPtr = ptr + (FACE_SIZE * 4) * face * 2;
|
long faceDataPtr = ptr + (FACE_SIZE * 4) * face * 2;
|
||||||
@@ -191,29 +245,118 @@ public class ModelFactory {
|
|||||||
colour[i] = MemoryUtil.memGetInt(faceDataPtr + (i * 4 * 2));
|
colour[i] = MemoryUtil.memGetInt(faceDataPtr + (i * 4 * 2));
|
||||||
depth[i] = MemoryUtil.memGetInt(faceDataPtr + (i * 4 * 2) + 4);
|
depth[i] = MemoryUtil.memGetInt(faceDataPtr + (i * 4 * 2) + 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
textureData[face] = new ColourDepthTextureData(colour, depth, MODEL_TEXTURE_SIZE, MODEL_TEXTURE_SIZE);
|
textureData[face] = new ColourDepthTextureData(colour, depth, MODEL_TEXTURE_SIZE, MODEL_TEXTURE_SIZE);
|
||||||
}
|
}
|
||||||
this.resultJobs.add(()->processTextureBakeResult(blockId, blockState, textureData));
|
}
|
||||||
});
|
result.rawData.free();
|
||||||
this.bakery.renderToStream(blockState, this.downstream.getBufferId(), allocation);
|
var bakeResult = this.processTextureBakeResult(result.blockId, result.blockState, textureData, result.isShaded, result.hasDarkenedTextures);
|
||||||
return true;
|
if (bakeResult!=null) {
|
||||||
|
this.uploadResults.add(bakeResult);
|
||||||
|
}
|
||||||
|
return !this.rawBakeResults.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final ConcurrentLinkedDeque<Mapper.BiomeEntry> biomeQueue = new ConcurrentLinkedDeque<>();
|
||||||
|
public void addBiome(Mapper.BiomeEntry biome) {
|
||||||
|
this.biomeQueue.add(biome);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processAllThings() {
|
||||||
|
var biomeEntry = this.biomeQueue.poll();
|
||||||
|
while (biomeEntry != null) {
|
||||||
|
var biomeRegistry = Minecraft.getInstance().level.registryAccess().lookupOrThrow(Registries.BIOME);
|
||||||
|
var res = this.addBiome0(biomeEntry.id, biomeRegistry.getValue(Identifier.parse(biomeEntry.biome)));
|
||||||
|
if (res != null) {
|
||||||
|
this.uploadResults.add(res);
|
||||||
|
}
|
||||||
|
biomeEntry = this.biomeQueue.poll();
|
||||||
|
}
|
||||||
|
|
||||||
//This is
|
while (this.processModelResult());
|
||||||
private void processTextureBakeResult(int blockId, BlockState blockState, ColourDepthTextureData[] textureData) {
|
}
|
||||||
|
|
||||||
|
public void tickAndProcessUploads() {
|
||||||
|
this.downstream.tick();
|
||||||
|
|
||||||
|
var upload = this.uploadResults.poll();
|
||||||
|
if (upload==null) return;
|
||||||
|
|
||||||
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||||
|
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
|
||||||
|
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
|
||||||
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||||
|
do {
|
||||||
|
upload.upload(this.storage);
|
||||||
|
upload.free();
|
||||||
|
upload = this.uploadResults.poll();
|
||||||
|
} while (upload != null);
|
||||||
|
UploadStream.INSTANCE.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface ResultUploader {
|
||||||
|
void upload(ModelStore store);
|
||||||
|
void free();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ModelBakeResultUpload implements ResultUploader {
|
||||||
|
private final MemoryBuffer model = new MemoryBuffer(MODEL_SIZE).zero();
|
||||||
|
private final MemoryBuffer texture = new MemoryBuffer((2L*3*computeSizeWithMips(MODEL_TEXTURE_SIZE))*4);
|
||||||
|
|
||||||
|
public int modelId = -1;
|
||||||
|
|
||||||
|
public int biomeUploadIndex = -1;
|
||||||
|
public @Nullable MemoryBuffer biomeUpload;
|
||||||
|
|
||||||
|
public void upload(ModelStore store) {//Uploads and resets for reuse
|
||||||
|
this.upload(store.modelBuffer, store.modelColourBuffer, store.textures);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void upload(GlBuffer modelBuffer, GlBuffer colourBuffer, GlTexture atlas) {//Uploads and resets for reuse
|
||||||
|
this.model.cpyTo(UploadStream.INSTANCE.upload(modelBuffer, (long) this.modelId * MODEL_SIZE, MODEL_SIZE));
|
||||||
|
if (this.biomeUploadIndex != -1) {
|
||||||
|
this.biomeUpload.cpyTo(UploadStream.INSTANCE.upload(colourBuffer, this.biomeUploadIndex * 4L, this.biomeUpload.size));
|
||||||
|
this.biomeUploadIndex = -1;
|
||||||
|
this.biomeUpload.free();
|
||||||
|
this.biomeUpload = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int X = (this.modelId&0xFF) * MODEL_TEXTURE_SIZE*3;
|
||||||
|
int Y = ((this.modelId>>8)&0xFF) * MODEL_TEXTURE_SIZE*2;
|
||||||
|
|
||||||
|
long cAddr = this.texture.address;
|
||||||
|
for (int lvl = 0; lvl < LAYERS; lvl++) {
|
||||||
|
nglTextureSubImage2D(atlas.id, lvl, X >> lvl, Y >> lvl, (MODEL_TEXTURE_SIZE*3) >> lvl, (MODEL_TEXTURE_SIZE*2) >> lvl, GL_RGBA, GL_UNSIGNED_BYTE, cAddr);
|
||||||
|
cAddr += (MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*3*2*4)>>(lvl<<1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.modelId = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void free() {
|
||||||
|
this.model.free();
|
||||||
|
this.texture.free();
|
||||||
|
if (this.biomeUpload != null) {
|
||||||
|
this.biomeUpload.free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModelBakeResultUpload processTextureBakeResult(int blockId, BlockState blockState, ColourDepthTextureData[] textureData, boolean isShaded, boolean darkenedTinting) {
|
||||||
if (this.idMappings[blockId] != -1) {
|
if (this.idMappings[blockId] != -1) {
|
||||||
//This should be impossible to reach as it means that multiple bakes for the same blockId happened and where inflight at the same time!
|
//This should be impossible to reach as it means that multiple bakes for the same blockId happened and where inflight at the same time!
|
||||||
throw new IllegalStateException("Block id already added: " + blockId + " for state: " + blockState);
|
throw new IllegalStateException("Block id already added: " + blockId + " for state: " + blockState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.blockStatesInFlightLock.lock();
|
||||||
if (!this.blockStatesInFlight.contains(blockId)) {
|
if (!this.blockStatesInFlight.contains(blockId)) {
|
||||||
|
this.blockStatesInFlightLock.unlock();
|
||||||
throw new IllegalStateException("processing a texture bake result but the block state was not in flight!!");
|
throw new IllegalStateException("processing a texture bake result but the block state was not in flight!!");
|
||||||
}
|
}
|
||||||
|
this.blockStatesInFlightLock.unlock();
|
||||||
|
|
||||||
boolean isFluid = blockState.getBlock() instanceof FluidBlock;
|
//TODO: add thing for `blockState.hasEmissiveLighting()` and `blockState.getLuminance()`
|
||||||
|
|
||||||
|
boolean isFluid = blockState.getBlock() instanceof LiquidBlock;
|
||||||
int modelId = -1;
|
int modelId = -1;
|
||||||
|
|
||||||
|
|
||||||
@@ -221,7 +364,7 @@ public class ModelFactory {
|
|||||||
|
|
||||||
if ((!isFluid) && (!blockState.getFluidState().isEmpty())) {
|
if ((!isFluid) && (!blockState.getFluidState().isEmpty())) {
|
||||||
//Insert into the fluid LUT
|
//Insert into the fluid LUT
|
||||||
var fluidState = blockState.getFluidState().getBlockState();
|
var fluidState = blockState.getFluidState().createLegacyBlock();
|
||||||
|
|
||||||
int fluidStateId = this.mapper.getIdForBlockState(fluidState);
|
int fluidStateId = this.mapper.getIdForBlockState(fluidState);
|
||||||
|
|
||||||
@@ -231,17 +374,28 @@ public class ModelFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var colourProvider = getColourProvider(blockState.getBlock());
|
||||||
|
|
||||||
|
boolean isBiomeColourDependent = false;
|
||||||
|
if (colourProvider != null) {
|
||||||
|
isBiomeColourDependent = isBiomeDependentColour(colourProvider, blockState);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelEntry entry;
|
||||||
{//Deduplicate same entries
|
{//Deduplicate same entries
|
||||||
var entry = new ModelEntry(textureData, clientFluidStateId);
|
entry = new ModelEntry(textureData, clientFluidStateId, isBiomeColourDependent||colourProvider==null?-1:captureColourConstant(colourProvider, blockState, DEFAULT_BIOME)|0xFF000000);
|
||||||
int possibleDuplicate = this.modelTexture2id.getInt(entry);
|
int possibleDuplicate = this.modelTexture2id.getInt(entry);
|
||||||
if (possibleDuplicate != -1) {//Duplicate found
|
if (possibleDuplicate != -1) {//Duplicate found
|
||||||
this.idMappings[blockId] = possibleDuplicate;
|
this.idMappings[blockId] = possibleDuplicate;
|
||||||
modelId = possibleDuplicate;
|
modelId = possibleDuplicate;
|
||||||
//Remove from flight
|
//Remove from flight
|
||||||
|
this.blockStatesInFlightLock.lock();
|
||||||
if (!this.blockStatesInFlight.remove(blockId)) {
|
if (!this.blockStatesInFlight.remove(blockId)) {
|
||||||
|
this.blockStatesInFlightLock.unlock();
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
return;
|
this.blockStatesInFlightLock.unlock();
|
||||||
|
return null;
|
||||||
} else {//Not a duplicate so create a new entry
|
} else {//Not a duplicate so create a new entry
|
||||||
modelId = this.modelTexture2id.size();
|
modelId = this.modelTexture2id.size();
|
||||||
//NOTE: we set the mapping at the very end so that race conditions with this and getMetadata dont occur
|
//NOTE: we set the mapping at the very end so that race conditions with this and getMetadata dont occur
|
||||||
@@ -256,39 +410,30 @@ public class ModelFactory {
|
|||||||
this.fluidStateLUT[modelId] = clientFluidStateId;
|
this.fluidStateLUT[modelId] = clientFluidStateId;
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderLayer blockRenderLayer = null;
|
ChunkSectionLayer blockRenderLayer = null;
|
||||||
if (blockState.getBlock() instanceof FluidBlock) {
|
if (blockState.getBlock() instanceof LiquidBlock) {
|
||||||
blockRenderLayer = RenderLayers.getFluidLayer(blockState.getFluidState());
|
blockRenderLayer = ItemBlockRenderTypes.getRenderLayer(blockState.getFluidState());
|
||||||
} else {
|
} else {
|
||||||
if (blockState.getBlock() instanceof LeavesBlock) {
|
if (blockState.getBlock() instanceof LeavesBlock) {
|
||||||
blockRenderLayer = RenderLayer.getSolid();
|
blockRenderLayer = ChunkSectionLayer.SOLID;
|
||||||
} else {
|
} else {
|
||||||
blockRenderLayer = RenderLayers.getBlockLayer(blockState);
|
blockRenderLayer = ItemBlockRenderTypes.getChunkRenderType(blockState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int checkMode = blockRenderLayer==RenderLayer.getSolid()?TextureUtils.WRITE_CHECK_STENCIL:TextureUtils.WRITE_CHECK_ALPHA;
|
int checkMode = blockRenderLayer==ChunkSectionLayer.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
|
|
||||||
// the sampler in the compute shader always reads zero even when stencil is guarenteed not to be zero
|
|
||||||
// (e.g. clearing with stencil 10)
|
|
||||||
checkMode = TextureUtils.WRITE_CHECK_ALPHA;
|
|
||||||
}
|
|
||||||
|
|
||||||
var colourProvider = getColourProvider(blockState.getBlock());
|
|
||||||
|
|
||||||
|
|
||||||
long uploadPtr = UploadStream.INSTANCE.upload(this.storage.modelBuffer, (long) modelId * MODEL_SIZE, MODEL_SIZE);
|
|
||||||
|
|
||||||
|
|
||||||
|
ModelBakeResultUpload uploadResult = new ModelBakeResultUpload();
|
||||||
|
uploadResult.modelId = modelId;
|
||||||
|
long uploadPtr = uploadResult.model.address;
|
||||||
|
|
||||||
//TODO: implement;
|
//TODO: implement;
|
||||||
// TODO: if it has a constant colour instead... idk why (apparently for things like spruce leaves)?? but premultiply the texture data by the constant colour
|
// TODO: if it has a constant colour instead... idk why (apparently for things like spruce leaves)?? but premultiply the texture data by the constant colour
|
||||||
boolean isBiomeColourDependent = false;
|
|
||||||
if (colourProvider != null) {
|
|
||||||
isBiomeColourDependent = isBiomeDependentColour(colourProvider, blockState);
|
|
||||||
}
|
|
||||||
//If it contains fluid but isnt a fluid
|
//If it contains fluid but isnt a fluid
|
||||||
if ((!isFluid) && (!blockState.getFluidState().isEmpty()) && clientFluidStateId != -1) {
|
if ((!isFluid) && (!blockState.getFluidState().isEmpty()) && clientFluidStateId != -1) {
|
||||||
|
|
||||||
@@ -301,12 +446,12 @@ public class ModelFactory {
|
|||||||
//TODO: special case stuff like vines and glow lichen, where it can be represented by a single double sided quad
|
//TODO: special case stuff like vines and glow lichen, where it can be represented by a single double sided quad
|
||||||
// since that would help alot with perf of lots of vines, can be done by having one of the faces just not exist and the other be in no occlusion mode
|
// since that would help alot with perf of lots of vines, can be done by having one of the faces just not exist and the other be in no occlusion mode
|
||||||
|
|
||||||
var sizes = this.computeModelDepth(textureData, checkMode);
|
var depths = this.computeModelDepth(textureData, checkMode);
|
||||||
|
|
||||||
//TODO: THIS, note this can be tested for in 2 ways, re render the model with quad culling disabled and see if the result
|
//TODO: THIS, note this can be tested for in 2 ways, re render the model with quad culling disabled and see if the result
|
||||||
// is the same, (if yes then needs double sided quads)
|
// is the same, (if yes then needs double sided quads)
|
||||||
// another way to test it is if e.g. up and down havent got anything rendered but the sides do (e.g. all plants etc)
|
// another way to test it is if e.g. up and down havent got anything rendered but the sides do (e.g. all plants etc)
|
||||||
boolean needsDoubleSidedQuads = (sizes[0] < -0.1 && sizes[1] < -0.1) || (sizes[2] < -0.1 && sizes[3] < -0.1) || (sizes[4] < -0.1 && sizes[5] < -0.1);
|
boolean needsDoubleSidedQuads = (depths[0] < -0.1 && depths[1] < -0.1) || (depths[2] < -0.1 && depths[3] < -0.1) || (depths[4] < -0.1 && depths[5] < -0.1);
|
||||||
|
|
||||||
|
|
||||||
boolean cullsSame = false;
|
boolean cullsSame = false;
|
||||||
@@ -317,7 +462,7 @@ public class ModelFactory {
|
|||||||
boolean allFalse = true;
|
boolean allFalse = true;
|
||||||
//Guestimation test for if the block culls itself
|
//Guestimation test for if the block culls itself
|
||||||
for (var dir : Direction.values()) {
|
for (var dir : Direction.values()) {
|
||||||
if (blockState.isSideInvisible(blockState, dir)) {
|
if (blockState.skipRendering(blockState, dir)) {
|
||||||
allFalse = false;
|
allFalse = false;
|
||||||
} else {
|
} else {
|
||||||
allTrue = false;
|
allTrue = false;
|
||||||
@@ -339,7 +484,7 @@ public class ModelFactory {
|
|||||||
//Each face gets 1 byte, with the top 2 bytes being for whatever
|
//Each face gets 1 byte, with the top 2 bytes being for whatever
|
||||||
long metadata = 0;
|
long metadata = 0;
|
||||||
metadata |= isBiomeColourDependent?1:0;
|
metadata |= isBiomeColourDependent?1:0;
|
||||||
metadata |= blockRenderLayer == RenderLayer.getTranslucent()?2:0;
|
metadata |= blockRenderLayer == ChunkSectionLayer.TRANSLUCENT?2:0;
|
||||||
metadata |= needsDoubleSidedQuads?4: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) && !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
|
metadata |= isFluid?16:0;//Is a fluid
|
||||||
@@ -348,11 +493,13 @@ public class ModelFactory {
|
|||||||
|
|
||||||
boolean fullyOpaque = true;
|
boolean fullyOpaque = true;
|
||||||
|
|
||||||
|
//TODO: FIXME faces that have the same "alignment depth" e.g. (sizes[0]+sizes[1])~=1 can be merged into a double faced single quad
|
||||||
|
|
||||||
//TODO: add a bunch of control config options for overriding/setting options of metadata for each face of each type
|
//TODO: add a bunch of control config options for overriding/setting options of metadata for each face of each type
|
||||||
for (int face = 5; face != -1; face--) {//In reverse order to make indexing into the metadata long easier
|
for (int face = 5; face != -1; face--) {//In reverse order to make indexing into the metadata long easier
|
||||||
long faceUploadPtr = uploadPtr + 4L * face;//Each face gets 4 bytes worth of data
|
long faceUploadPtr = uploadPtr + 4L * face;//Each face gets 4 bytes worth of data
|
||||||
metadata <<= 8;
|
metadata <<= 8;
|
||||||
float offset = sizes[face];
|
float offset = depths[face];
|
||||||
if (offset < -0.1) {//Face is empty, so ignore
|
if (offset < -0.1) {//Face is empty, so ignore
|
||||||
metadata |= 0xFF;//Mark the face as non-existent
|
metadata |= 0xFF;//Mark the face as non-existent
|
||||||
//Set to -1 as safepoint
|
//Set to -1 as safepoint
|
||||||
@@ -373,7 +520,7 @@ public class ModelFactory {
|
|||||||
|
|
||||||
//TODO: add alot of config options for the following
|
//TODO: add alot of config options for the following
|
||||||
boolean occludesFace = true;
|
boolean occludesFace = true;
|
||||||
occludesFace &= blockRenderLayer != RenderLayer.getTranslucent();//If its translucent, it doesnt occlude
|
occludesFace &= blockRenderLayer != ChunkSectionLayer.TRANSLUCENT;//If its translucent, it doesnt occlude
|
||||||
|
|
||||||
//TODO: make this an option, basicly if the face is really close, it occludes otherwise it doesnt
|
//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
|
occludesFace &= offset < 0.1;//If the face is rendered far away from the other face, then it doesnt occlude
|
||||||
@@ -393,31 +540,44 @@ public class ModelFactory {
|
|||||||
metadata |= canBeOccluded?4:0;
|
metadata |= canBeOccluded?4:0;
|
||||||
|
|
||||||
//Face uses its own lighting if its not flat against the adjacent block & isnt traslucent
|
//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 == ChunkSectionLayer.TRANSLUCENT)?0b1000:0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (MODEL_TEXTURE_SIZE-1 != 15) {
|
||||||
//Scale face size from 0->this.modelTextureSize-1 to 0->15
|
//Scale face size from 0->this.modelTextureSize-1 to 0->15
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
faceSize[i] = Math.round((((float) faceSize[i]) / (MODEL_TEXTURE_SIZE - 1)) * 15);
|
faceSize[i] = Math.round((((float) faceSize[i]) / (MODEL_TEXTURE_SIZE - 1)) * 15);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int faceModelData = 0;
|
int faceModelData = 0;
|
||||||
faceModelData |= faceSize[0] | (faceSize[1]<<4) | (faceSize[2]<<8) | (faceSize[3]<<12);
|
faceModelData |= faceSize[0] | (faceSize[1]<<4) | (faceSize[2]<<8) | (faceSize[3]<<12);
|
||||||
faceModelData |= Math.round(offset*63)<<16;//Change the scale from 0->1 (ends inclusive) float to 0->63 (6 bits) NOTE! that 63 == 1.0f meaning its shifted all the way to the other side of the model
|
//Change the scale from 0->1 (ends inclusive)
|
||||||
|
// this is cursed also warning stuff at 63 (i.e half a pixel from the end will be clamped to the end)
|
||||||
|
int enc = Math.round(offset*64);
|
||||||
|
faceModelData |= Math.min(enc,62)<<16;
|
||||||
//Still have 11 bits free
|
//Still have 11 bits free
|
||||||
|
|
||||||
//Stuff like fences are solid, however they have extra side piece that mean it needs to have discard on
|
//Stuff like fences are solid, however they have extra side piece that mean it needs to have discard on
|
||||||
int area = (faceSize[1]-faceSize[0]+1) * (faceSize[3]-faceSize[2]+1);
|
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
|
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 != ChunkSectionLayer.SOLID;
|
||||||
needsAlphaDiscard &= blockRenderLayer != RenderLayer.getTranslucent();//Translucent doesnt have alpha discard
|
needsAlphaDiscard &= blockRenderLayer != ChunkSectionLayer.TRANSLUCENT;//Translucent doesnt have alpha discard
|
||||||
faceModelData |= needsAlphaDiscard?1<<22:0;
|
faceModelData |= needsAlphaDiscard?1<<22:0;
|
||||||
|
|
||||||
faceModelData |= ((!faceCoversFullBlock)&&blockRenderLayer != RenderLayer.getTranslucent())?1<<23:0;//Alpha discard override, translucency doesnt have alpha discard
|
faceModelData |= ((!faceCoversFullBlock)&&blockRenderLayer != ChunkSectionLayer.TRANSLUCENT)?1<<23:0;//Alpha discard override, translucency doesnt have alpha discard
|
||||||
|
|
||||||
|
|
||||||
|
//Bits 24,25 are tint metadata
|
||||||
|
if (colourProvider!=null) {//We have a tint
|
||||||
|
int tintState = TextureUtils.computeFaceTint(textureData[face], checkMode);
|
||||||
|
if (tintState == 2) {//Partial tint
|
||||||
|
faceModelData |= 1<<24;
|
||||||
|
} else if (tintState == 3) {//Full tint
|
||||||
|
faceModelData |= 2<<24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MemoryUtil.memPutInt(faceUploadPtr, faceModelData);
|
MemoryUtil.memPutInt(faceUploadPtr, faceModelData);
|
||||||
}
|
}
|
||||||
@@ -435,53 +595,103 @@ public class ModelFactory {
|
|||||||
int modelFlags = 0;
|
int modelFlags = 0;
|
||||||
modelFlags |= colourProvider != null?1: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 |= 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 == ChunkSectionLayer.TRANSLUCENT?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)
|
|
||||||
|
|
||||||
|
//TODO: THIS
|
||||||
|
modelFlags |= isShaded?8:0;//model has AO and shade
|
||||||
|
|
||||||
//modelFlags |= blockRenderLayer == RenderLayer.getSolid()?0:1;// should discard alpha
|
//modelFlags |= blockRenderLayer == RenderLayer.getSolid()?0:1;// should discard alpha
|
||||||
MemoryUtil.memPutInt(uploadPtr, modelFlags);
|
MemoryUtil.memPutInt(uploadPtr, modelFlags); uploadPtr += 4;
|
||||||
|
|
||||||
|
|
||||||
//Temporary override to always be non biome specific
|
//Temporary override to always be non biome specific
|
||||||
if (colourProvider == null) {
|
if (colourProvider == null) {
|
||||||
MemoryUtil.memPutInt(uploadPtr + 4, -1);//Set the default to nothing so that its faster on the gpu
|
MemoryUtil.memPutInt(uploadPtr, -1);//Set the default to nothing so that its faster on the gpu
|
||||||
} else if (!isBiomeColourDependent) {
|
} else if (!isBiomeColourDependent) {
|
||||||
MemoryUtil.memPutInt(uploadPtr + 4, captureColourConstant(colourProvider, blockState, DEFAULT_BIOME)|0xFF000000);
|
MemoryUtil.memPutInt(uploadPtr, entry.tintingColour);
|
||||||
} else if (!this.biomes.isEmpty()) {
|
} else if (!this.biomes.isEmpty()) {
|
||||||
//Populate the list of biomes for the model state
|
//Populate the list of biomes for the model state
|
||||||
int biomeIndex = this.modelsRequiringBiomeColours.size() * this.biomes.size();
|
int biomeIndex = this.modelsRequiringBiomeColours.size() * this.biomes.size();
|
||||||
MemoryUtil.memPutInt(uploadPtr + 4, biomeIndex);
|
MemoryUtil.memPutInt(uploadPtr, biomeIndex);
|
||||||
this.modelsRequiringBiomeColours.add(new Pair<>(modelId, blockState));
|
this.modelsRequiringBiomeColours.add(new Pair<>(modelId, blockState));
|
||||||
//NOTE: UploadStream.INSTANCE is called _after_ uploadPtr is finished being used, this is cause the upload pointer
|
|
||||||
// may be invalidated as soon as another upload stream is invoked
|
uploadResult.biomeUploadIndex = biomeIndex;
|
||||||
long clrUploadPtr = UploadStream.INSTANCE.upload(this.storage.modelColourBuffer, biomeIndex * 4L, 4L * this.biomes.size());
|
long clrUploadPtr = (uploadResult.biomeUpload = new MemoryBuffer(4L * this.biomes.size())).address;
|
||||||
for (var biome : this.biomes) {
|
for (var biome : this.biomes) {
|
||||||
MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, blockState, biome)|0xFF000000); clrUploadPtr += 4;
|
MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, blockState, biome)|0xFF000000); clrUploadPtr += 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
uploadPtr += 4;
|
||||||
|
|
||||||
|
//have 32 bytes of free space after here
|
||||||
|
|
||||||
|
//install the custom mapping id if it exists
|
||||||
|
if (this.customBlockStateIdMapping != null && this.customBlockStateIdMapping.containsKey(blockState)) {
|
||||||
|
MemoryUtil.memPutInt(uploadPtr, this.customBlockStateIdMapping.getInt(blockState));
|
||||||
|
} else {
|
||||||
|
MemoryUtil.memPutInt(uploadPtr, 0);
|
||||||
|
} uploadPtr += 4;
|
||||||
|
|
||||||
|
|
||||||
//Note: if the layer isSolid then need to fill all the points in the texture where alpha == 0 with the average colour
|
//Note: if the layer isSolid then need to fill all the points in the texture where alpha == 0 with the average colour
|
||||||
// of the surrounding blocks but only within the computed face size bounds
|
// of the surrounding blocks but only within the computed face size bounds
|
||||||
//TODO
|
|
||||||
|
//TODO callback to inject extra data into the model data
|
||||||
|
|
||||||
|
|
||||||
this.putTextures(modelId, textureData);
|
MipGen.putTextures(darkenedTinting, textureData, uploadResult.texture);
|
||||||
|
|
||||||
//glGenerateTextureMipmap(this.textures.id);
|
//glGenerateTextureMipmap(this.textures.id);
|
||||||
|
|
||||||
//Set the mapping at the very end
|
//Set the mapping at the very end
|
||||||
this.idMappings[blockId] = modelId;
|
this.idMappings[blockId] = modelId;
|
||||||
|
|
||||||
|
this.blockStatesInFlightLock.lock();
|
||||||
if (!this.blockStatesInFlight.remove(blockId)) {
|
if (!this.blockStatesInFlight.remove(blockId)) {
|
||||||
|
this.blockStatesInFlightLock.unlock();
|
||||||
throw new IllegalStateException("processing a texture bake result but the block state was not in flight!!");
|
throw new IllegalStateException("processing a texture bake result but the block state was not in flight!!");
|
||||||
}
|
}
|
||||||
|
this.blockStatesInFlightLock.unlock();
|
||||||
|
|
||||||
//Upload/commit stream
|
return uploadResult;
|
||||||
//TODO maybe dont do it for every uploaded block?? try to batch it
|
|
||||||
UploadStream.INSTANCE.commit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addBiome(int id, Biome biome) {
|
private static final class BiomeUploadResult implements ResultUploader {
|
||||||
|
private final MemoryBuffer biomeColourBuffer;
|
||||||
|
private final MemoryBuffer modelBiomeIndexPairs;
|
||||||
|
private BiomeUploadResult(int biomes, int models) {
|
||||||
|
this.biomeColourBuffer = new MemoryBuffer(biomes*models*4);
|
||||||
|
this.modelBiomeIndexPairs = new MemoryBuffer(models*8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void upload(ModelStore store) {
|
||||||
|
this.upload(store.modelBuffer, store.modelColourBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void upload(GlBuffer modelBuffer, GlBuffer modelColourBuffer) {
|
||||||
|
this.biomeColourBuffer.cpyTo(UploadStream.INSTANCE.upload(modelColourBuffer, 0, this.biomeColourBuffer.size));
|
||||||
|
|
||||||
|
//TODO: optimize this to like a compute scatter update or something
|
||||||
|
long ptr = this.modelBiomeIndexPairs.address;
|
||||||
|
for (long offset = 0; offset < this.modelBiomeIndexPairs.size; offset += 8) {
|
||||||
|
long v = MemoryUtil.memGetLong(ptr);ptr += 8;
|
||||||
|
MemoryUtil.memPutInt(UploadStream.INSTANCE.upload(modelBuffer, (MODEL_SIZE*(v&((1L<<32)-1)))+ 4*6 + 4, 4), (int) (v>>>32));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.biomeColourBuffer.free();
|
||||||
|
this.modelBiomeIndexPairs.free();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void free() {
|
||||||
|
if (!this.biomeColourBuffer.isFreed()) {
|
||||||
|
this.biomeColourBuffer.free();
|
||||||
|
this.modelBiomeIndexPairs.free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BiomeUploadResult addBiome0(int id, Biome biome) {
|
||||||
for (int i = this.biomes.size(); i <= id; i++) {
|
for (int i = this.biomes.size(); i <= id; i++) {
|
||||||
this.biomes.add(null);
|
this.biomes.add(null);
|
||||||
}
|
}
|
||||||
@@ -494,53 +704,58 @@ public class ModelFactory {
|
|||||||
Logger.error("Biome added was a duplicate");
|
Logger.error("Biome added was a duplicate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.modelsRequiringBiomeColours.isEmpty()) return null;
|
||||||
|
|
||||||
|
var result = new BiomeUploadResult(this.biomes.size(), this.modelsRequiringBiomeColours.size());
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
long modelUpPtr = result.modelBiomeIndexPairs.address;
|
||||||
for (var entry : this.modelsRequiringBiomeColours) {
|
for (var entry : this.modelsRequiringBiomeColours) {
|
||||||
var colourProvider = MinecraftClient.getInstance().getBlockColors().providers.get(Registries.BLOCK.getRawId(entry.getRight().getBlock()));
|
var colourProvider = getColourProvider(entry.right().getBlock());
|
||||||
if (colourProvider == null) {
|
if (colourProvider == null) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
//Populate the list of biomes for the model state
|
//Populate the list of biomes for the model state
|
||||||
int biomeIndex = (i++) * this.biomes.size();
|
int biomeIndex = (i++) * this.biomes.size();
|
||||||
MemoryUtil.memPutInt(UploadStream.INSTANCE.upload(this.storage.modelBuffer, (entry.getLeft()* MODEL_SIZE)+ 4*6 + 4, 4), biomeIndex);
|
MemoryUtil.memPutLong(modelUpPtr, Integer.toUnsignedLong(entry.left())|(Integer.toUnsignedLong(biomeIndex)<<32));modelUpPtr+=8;
|
||||||
long clrUploadPtr = UploadStream.INSTANCE.upload(this.storage.modelColourBuffer, biomeIndex * 4L, 4L * this.biomes.size());
|
long clrUploadPtr = result.biomeColourBuffer.address + biomeIndex * 4L;
|
||||||
for (var biomeE : this.biomes) {
|
for (var biomeE : this.biomes) {
|
||||||
if (biomeE == null) {
|
if (biomeE == null) {
|
||||||
continue;//If null, ignore
|
continue;//If null, ignore
|
||||||
}
|
}
|
||||||
MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, entry.getRight(), biomeE)|0xFF000000); clrUploadPtr += 4;
|
MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, entry.right(), biomeE)|0xFF000000); clrUploadPtr += 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UploadStream.INSTANCE.commit();
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BlockColorProvider getColourProvider(Block block) {
|
private static BlockColor getColourProvider(Block block) {
|
||||||
return MinecraftClient.getInstance().getBlockColors().providers.get(Registries.BLOCK.getRawId(block));
|
return Minecraft.getInstance().getBlockColors().blockColors.byId(BuiltInRegistries.BLOCK.getId(block));
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: add a method to detect biome dependent colours (can do by detecting if getColor is ever called)
|
//TODO: add a method to detect biome dependent colours (can do by detecting if getColor is ever called)
|
||||||
// if it is, need to add it to a list and mark it as biome colour dependent or something then the shader
|
// if it is, need to add it to a list and mark it as biome colour dependent or something then the shader
|
||||||
// will either use the uint as an index or a direct colour multiplier
|
// will either use the uint as an index or a direct colour multiplier
|
||||||
private static int captureColourConstant(BlockColorProvider colorProvider, BlockState state, Biome biome) {
|
private static int captureColourConstant(BlockColor colorProvider, BlockState state, Biome biome) {
|
||||||
return colorProvider.getColor(state, new BlockRenderView() {
|
var getter = new BlockAndTintGetter() {
|
||||||
@Override
|
@Override
|
||||||
public float getBrightness(Direction direction, boolean shaded) {
|
public float getShade(Direction direction, boolean shaded) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLightLevel(LightType type, BlockPos pos) {
|
public int getBrightness(LightLayer type, BlockPos pos) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LightingProvider getLightingProvider() {
|
public LevelLightEngine getLightEngine() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getColor(BlockPos pos, ColorResolver colorResolver) {
|
public int getBlockTint(BlockPos pos, ColorResolver colorResolver) {
|
||||||
return colorResolver.getColor(biome, 0, 0);
|
return colorResolver.getColor(biome, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,32 +781,36 @@ public class ModelFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getBottomY() {
|
public int getMinY() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}, BlockPos.ORIGIN, 0);
|
};
|
||||||
|
//Multiple layer bs to do with flower beds
|
||||||
|
int c = colorProvider.getColor(state, getter, BlockPos.ZERO, 0);
|
||||||
|
if (c!=-1) return c;
|
||||||
|
return colorProvider.getColor(state, getter, BlockPos.ZERO, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isBiomeDependentColour(BlockColorProvider colorProvider, BlockState state) {
|
private static boolean isBiomeDependentColour(BlockColor colorProvider, BlockState state) {
|
||||||
boolean[] biomeDependent = new boolean[1];
|
boolean[] biomeDependent = new boolean[1];
|
||||||
colorProvider.getColor(state, new BlockRenderView() {
|
var getter = new BlockAndTintGetter() {
|
||||||
@Override
|
@Override
|
||||||
public float getBrightness(Direction direction, boolean shaded) {
|
public float getShade(Direction direction, boolean shaded) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLightLevel(LightType type, BlockPos pos) {
|
public int getBrightness(LightLayer type, BlockPos pos) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LightingProvider getLightingProvider() {
|
public LevelLightEngine getLightEngine() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getColor(BlockPos pos, ColorResolver colorResolver) {
|
public int getBlockTint(BlockPos pos, ColorResolver colorResolver) {
|
||||||
biomeDependent[0] = true;
|
biomeDependent[0] = true;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -618,17 +837,19 @@ public class ModelFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getBottomY() {
|
public int getMinY() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}, BlockPos.ORIGIN, 0);
|
};
|
||||||
|
colorProvider.getColor(state, getter, BlockPos.ZERO, 0);
|
||||||
|
colorProvider.getColor(state, getter, BlockPos.ZERO, 1);
|
||||||
return biomeDependent[0];
|
return biomeDependent[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
private float[] computeModelDepth(ColourDepthTextureData[] textures, int checkMode) {
|
private static float[] computeModelDepth(ColourDepthTextureData[] textures, int checkMode) {
|
||||||
float[] res = new float[6];
|
float[] res = new float[6];
|
||||||
for (var dir : Direction.values()) {
|
for (var dir : Direction.values()) {
|
||||||
var data = textures[dir.getIndex()];
|
var data = textures[dir.get3DDataValue()];
|
||||||
float fd = TextureUtils.computeDepth(data, TextureUtils.DEPTH_MODE_AVG, checkMode);//Compute the min float depth, smaller means closer to the camera, range 0-1
|
float fd = TextureUtils.computeDepth(data, TextureUtils.DEPTH_MODE_AVG, checkMode);//Compute the min float depth, smaller means closer to the camera, range 0-1
|
||||||
//int depth = Math.round(fd * MODEL_TEXTURE_SIZE);
|
//int depth = Math.round(fd * MODEL_TEXTURE_SIZE);
|
||||||
//If fd is -1, it means that there was nothing rendered on that face and it should be discarded
|
//If fd is -1, it means that there was nothing rendered on that face and it should be discarded
|
||||||
@@ -670,73 +891,15 @@ public class ModelFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static int computeSizeWithMips(int size) {
|
|
||||||
int total = 0;
|
|
||||||
for (;size!=0;size>>=1) total += size*size;
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
private static final MemoryBuffer SCRATCH_TEX = new MemoryBuffer((2L*3*computeSizeWithMips(MODEL_TEXTURE_SIZE))*4);
|
|
||||||
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");}
|
|
||||||
|
|
||||||
//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
|
|
||||||
|
|
||||||
//Copy all textures into scratch
|
|
||||||
final long addr = SCRATCH_TEX.address;
|
|
||||||
final int LENGTH_B = MODEL_TEXTURE_SIZE*3;
|
|
||||||
for (int i = 0; i < 6; i++) {
|
|
||||||
int x = (i>>1)*MODEL_TEXTURE_SIZE;
|
|
||||||
int y = (i&1)*MODEL_TEXTURE_SIZE;
|
|
||||||
int j = 0;
|
|
||||||
for (int t : textures[i].colour()) {
|
|
||||||
int o = ((y+(j>>LAYERS))*LENGTH_B + ((j&(MODEL_TEXTURE_SIZE-1))+x))*4; j++;//LAYERS here is just cause faster
|
|
||||||
MemoryUtil.memPutInt(addr+o, t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Mip the scratch
|
|
||||||
long dAddr = addr;
|
|
||||||
for (int i = 0; i < LAYERS-1; i++) {
|
|
||||||
long sAddr = dAddr;
|
|
||||||
dAddr += (MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*3*2*4)>>(i<<1);//is.. i*2 because shrink both MODEL_TEXTURE_SIZE by >>i so is 2*i total shift
|
|
||||||
int width = (MODEL_TEXTURE_SIZE*3)>>(i+1);
|
|
||||||
int sWidth = (MODEL_TEXTURE_SIZE*3)>>i;
|
|
||||||
int height = (MODEL_TEXTURE_SIZE*2)>>(i+1);
|
|
||||||
//TODO: OPTIMZIE THIS
|
|
||||||
for (int px = 0; px < width; px++) {
|
|
||||||
for (int py = 0; py < height; py++) {
|
|
||||||
long bp = sAddr + (px*2 + py*2*sWidth)*4;
|
|
||||||
int C00 = MemoryUtil.memGetInt(bp);
|
|
||||||
int C01 = MemoryUtil.memGetInt(bp+sWidth*4);
|
|
||||||
int C10 = MemoryUtil.memGetInt(bp+4);
|
|
||||||
int C11 = MemoryUtil.memGetInt(bp+sWidth*4+4);
|
|
||||||
MemoryUtil.memPutInt(dAddr + (px+py*width) * 4L, TextureUtils.mipColours(C00, C01, C10, C11));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int X = (id&0xFF) * MODEL_TEXTURE_SIZE*3;
|
|
||||||
int Y = ((id>>8)&0xFF) * MODEL_TEXTURE_SIZE*2;
|
|
||||||
|
|
||||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
|
||||||
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
|
|
||||||
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
|
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
||||||
|
|
||||||
long cAddr = addr;
|
|
||||||
for (int lvl = 0; lvl < LAYERS; lvl++) {
|
|
||||||
nglTextureSubImage2D(this.storage.textures.id, lvl, X >> lvl, Y >> lvl, (MODEL_TEXTURE_SIZE*3) >> lvl, (MODEL_TEXTURE_SIZE*2) >> lvl, GL_RGBA, GL_UNSIGNED_BYTE, cAddr);
|
|
||||||
cAddr += (MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*3*2*4)>>(lvl<<1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void free() {
|
public void free() {
|
||||||
this.downstream.free();
|
|
||||||
this.bakery.free();
|
this.bakery.free();
|
||||||
|
this.downstream.free();
|
||||||
|
while (!this.rawBakeResults.isEmpty()) {
|
||||||
|
this.rawBakeResults.poll().rawData.free();
|
||||||
|
}
|
||||||
|
while (!this.uploadResults.isEmpty()) {
|
||||||
|
this.uploadResults.poll().free();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getBakedCount() {
|
public int getBakedCount() {
|
||||||
@@ -744,6 +907,17 @@ public class ModelFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getInflightCount() {
|
public int getInflightCount() {
|
||||||
return this.blockStatesInFlight.size();
|
//TODO replace all of this with an atomic?
|
||||||
|
int size = this.blockStatesInFlight.size();
|
||||||
|
size += this.uploadResults.size();
|
||||||
|
size += this.biomeQueue.size();
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static int computeSizeWithMips(int size) {
|
||||||
|
int total = 0;
|
||||||
|
for (;size!=0;size>>=1) total += size*size;
|
||||||
|
return total;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package me.cortex.voxy.client.core.model;
|
|||||||
|
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
import me.cortex.voxy.client.core.gl.GlTexture;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.renderer.texture.TextureAtlas;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
import static org.lwjgl.opengl.GL11.*;
|
import static org.lwjgl.opengl.GL11.*;
|
||||||
import static org.lwjgl.opengl.GL11C.GL_NEAREST;
|
import static org.lwjgl.opengl.GL11C.GL_NEAREST;
|
||||||
@@ -24,14 +27,18 @@ public class ModelStore {
|
|||||||
public ModelStore() {
|
public ModelStore() {
|
||||||
this.modelBuffer = new GlBuffer(MODEL_SIZE * (1<<16)).name("ModelData");
|
this.modelBuffer = new GlBuffer(MODEL_SIZE * (1<<16)).name("ModelData");
|
||||||
this.modelColourBuffer = new GlBuffer(4 * (1<<16)).name("ModelColour");
|
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");
|
||||||
|
|
||||||
|
|
||||||
|
//Limit the mips of the texture to match that of the terrain atlas
|
||||||
|
int mipLvl = ((TextureAtlas) Minecraft.getInstance().getTextureManager()
|
||||||
|
.getTexture(Identifier.fromNamespaceAndPath("minecraft", "textures/atlas/blocks.png")))
|
||||||
|
.maxMipLevel;
|
||||||
|
|
||||||
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
|
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_MAG_FILTER, GL_NEAREST);
|
||||||
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_LOD, 0);
|
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_LOD, 0);
|
||||||
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAX_LOD, 4);
|
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAX_LOD, mipLvl);//Integer.numberOfTrailingZeros(ModelFactory.MODEL_TEXTURE_SIZE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package me.cortex.voxy.client.core.model;
|
package me.cortex.voxy.client.core.model;
|
||||||
|
|
||||||
import net.caffeinemc.mods.sodium.client.util.color.ColorSRGB;
|
import net.caffeinemc.mods.sodium.client.util.color.ColorSRGB;
|
||||||
import net.minecraft.util.math.ColorHelper;
|
import net.minecraft.client.renderer.texture.MipmapGenerator;
|
||||||
|
import net.minecraft.util.ARGB;
|
||||||
|
|
||||||
//Texturing utils to manipulate data from the model bakery
|
//Texturing utils to manipulate data from the model bakery
|
||||||
public class TextureUtils {
|
public class TextureUtils {
|
||||||
@@ -26,6 +27,7 @@ public class TextureUtils {
|
|||||||
public static final int WRITE_CHECK_STENCIL = 1;
|
public static final int WRITE_CHECK_STENCIL = 1;
|
||||||
public static final int WRITE_CHECK_DEPTH = 2;
|
public static final int WRITE_CHECK_DEPTH = 2;
|
||||||
public static final int WRITE_CHECK_ALPHA = 3;
|
public static final int WRITE_CHECK_ALPHA = 3;
|
||||||
|
|
||||||
private static boolean wasPixelWritten(ColourDepthTextureData data, int mode, int index) {
|
private static boolean wasPixelWritten(ColourDepthTextureData data, int mode, int index) {
|
||||||
if (mode == WRITE_CHECK_STENCIL) {
|
if (mode == WRITE_CHECK_STENCIL) {
|
||||||
return (data.depth()[index] & 0xFF) != 0;
|
return (data.depth()[index] & 0xFF) != 0;
|
||||||
@@ -38,6 +40,37 @@ public class TextureUtils {
|
|||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//0: nothing written
|
||||||
|
//1: none tinted
|
||||||
|
//2: some tinted
|
||||||
|
//3: all tinted
|
||||||
|
public static int computeFaceTint(ColourDepthTextureData texture, int checkMode) {
|
||||||
|
boolean allTinted = true;
|
||||||
|
boolean someTinted = false;
|
||||||
|
boolean wasWriten = false;
|
||||||
|
|
||||||
|
final var colourData = texture.colour();
|
||||||
|
final var depthData = texture.depth();
|
||||||
|
for (int i = 0; i < colourData.length; i++) {
|
||||||
|
if (!wasPixelWritten(texture, checkMode, i)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((colourData[i] & 0xFFFFFF) == 0 || (colourData[i] >>> 24) == 0) {//If the pixel is fully black (or translucent)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
boolean pixelTinited = (depthData[i] & (1 << 7)) != 0;
|
||||||
|
wasWriten |= true;
|
||||||
|
allTinted &= pixelTinited;
|
||||||
|
someTinted |= pixelTinited;
|
||||||
|
|
||||||
|
}
|
||||||
|
if (!wasWriten) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return someTinted ? (allTinted ? 3 : 2) : 1;
|
||||||
|
}
|
||||||
|
|
||||||
public static final int DEPTH_MODE_AVG = 1;
|
public static final int DEPTH_MODE_AVG = 1;
|
||||||
public static final int DEPTH_MODE_MAX = 2;
|
public static final int DEPTH_MODE_MAX = 2;
|
||||||
public static final int DEPTH_MODE_MIN = 3;
|
public static final int DEPTH_MODE_MIN = 3;
|
||||||
@@ -106,7 +139,6 @@ public class TextureUtils {
|
|||||||
|
|
||||||
//NOTE: data goes from bottom left to top right (x first then y)
|
//NOTE: data goes from bottom left to top right (x first then y)
|
||||||
public static int[] computeBounds(ColourDepthTextureData data, int checkMode) {
|
public static int[] computeBounds(ColourDepthTextureData data, int checkMode) {
|
||||||
final var depth = data.depth();
|
|
||||||
//Compute x bounds first
|
//Compute x bounds first
|
||||||
int minX = 0;
|
int minX = 0;
|
||||||
minXCheck:
|
minXCheck:
|
||||||
@@ -165,59 +197,42 @@ public class TextureUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static int mipColours(boolean darkend, int C00, int C01, int C10, int C11) {
|
||||||
|
darkend = !darkend;//Invert to make it easier
|
||||||
|
float r = 0.0f;
|
||||||
|
float g = 0.0f;
|
||||||
|
float b = 0.0f;
|
||||||
|
float a = 0.0f;
|
||||||
|
if (darkend || (C00 >>> 24) != 0) {
|
||||||
|
r += ColorSRGB.srgbToLinear((C00 >> 0) & 0xFF);
|
||||||
|
g += ColorSRGB.srgbToLinear((C00 >> 8) & 0xFF);
|
||||||
|
b += ColorSRGB.srgbToLinear((C00 >> 16) & 0xFF);
|
||||||
|
a += darkend ? (C00 >>> 24) : ColorSRGB.srgbToLinear(C00 >>> 24);
|
||||||
|
}
|
||||||
public static int mipColours(int one, int two, int three, int four) {
|
if (darkend || (C01 >>> 24) != 0) {
|
||||||
return weightedAverageColor(weightedAverageColor(one, two), weightedAverageColor(three, four));
|
r += ColorSRGB.srgbToLinear((C01 >> 0) & 0xFF);
|
||||||
|
g += ColorSRGB.srgbToLinear((C01 >> 8) & 0xFF);
|
||||||
|
b += ColorSRGB.srgbToLinear((C01 >> 16) & 0xFF);
|
||||||
|
a += darkend ? (C01 >>> 24) : ColorSRGB.srgbToLinear(C01 >>> 24);
|
||||||
|
}
|
||||||
|
if (darkend || (C10 >>> 24) != 0) {
|
||||||
|
r += ColorSRGB.srgbToLinear((C10 >> 0) & 0xFF);
|
||||||
|
g += ColorSRGB.srgbToLinear((C10 >> 8) & 0xFF);
|
||||||
|
b += ColorSRGB.srgbToLinear((C10 >> 16) & 0xFF);
|
||||||
|
a += darkend ? (C10 >>> 24) : ColorSRGB.srgbToLinear(C10 >>> 24);
|
||||||
|
}
|
||||||
|
if (darkend || (C11 >>> 24) != 0) {
|
||||||
|
r += ColorSRGB.srgbToLinear((C11 >> 0) & 0xFF);
|
||||||
|
g += ColorSRGB.srgbToLinear((C11 >> 8) & 0xFF);
|
||||||
|
b += ColorSRGB.srgbToLinear((C11 >> 16) & 0xFF);
|
||||||
|
a += darkend ? (C11 >>> 24) : ColorSRGB.srgbToLinear(C11 >>> 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: FIXME!!! ITS READING IT AS ABGR??? isnt the format RGBA??
|
|
||||||
private static int weightedAverageColor(int a, int b) {
|
|
||||||
//We specifically want the entire other component if the alpha is zero
|
|
||||||
// this prevents black mips from generating due to A) non filled colours, and B) when the sampler samples everything it doesnt detonate
|
|
||||||
if ((a&0xFF000000) == 0) {
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
if ((b&0xFF000000) == 0) {
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (((a^b)&0xFF000000)==0) {
|
|
||||||
return ColorSRGB.linearToSrgb(
|
return ColorSRGB.linearToSrgb(
|
||||||
addHalfLinear(0, a,b),
|
r / 4,
|
||||||
addHalfLinear(8, a,b),
|
g / 4,
|
||||||
addHalfLinear(16, a,b),
|
b / 4,
|
||||||
a>>>24);
|
darkend ? ((int) a) / 4 : ARGB.linearToSrgbChannel(a / 4)
|
||||||
}
|
);
|
||||||
|
|
||||||
{
|
|
||||||
int A = (a>>>24);
|
|
||||||
int B = (a>>>24);
|
|
||||||
float mul = 1.0F / (float)(A+B);
|
|
||||||
float wA = A * mul;
|
|
||||||
float wB = B * mul;
|
|
||||||
return ColorSRGB.linearToSrgb(
|
|
||||||
addMulLinear(0, a,b,wA,wB),
|
|
||||||
addMulLinear(8, a,b,wA,wB),
|
|
||||||
addMulLinear(16, a,b,wA,wB)
|
|
||||||
, (A + B)/2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float addHalfLinear(int shift, int a, int b) {
|
|
||||||
return addMulLinear(shift, a, b, 0.5f, 0.5f);
|
|
||||||
}
|
|
||||||
private static float addMulLinear(int shift, int a, int b, float mulA, float mulB) {
|
|
||||||
return Math.fma(ColorSRGB.srgbToLinear((a>>shift)&0xFF),mulA, ColorSRGB.srgbToLinear((b>>shift)&0xFF)*mulB);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
|
/*
|
||||||
package me.cortex.voxy.client.core.model.bakery;
|
package me.cortex.voxy.client.core.model.bakery;
|
||||||
|
|
||||||
import com.mojang.blaze3d.textures.GpuTexture;
|
|
||||||
import me.cortex.voxy.common.Logger;
|
import me.cortex.voxy.common.Logger;
|
||||||
import net.minecraft.block.BlockEntityProvider;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.block.BlockState;
|
import net.minecraft.client.model.Model;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.renderer.SubmitNodeStorage;
|
||||||
import net.minecraft.client.gl.GlGpuBuffer;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.client.render.*;
|
import net.minecraft.resources.Identifier;
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import net.minecraft.util.math.Vec3d;
|
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
|
import org.joml.Quaternionf;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -19,7 +19,8 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class BakedBlockEntityModel {
|
public class BakedBlockEntityModel {
|
||||||
private record LayerConsumer(RenderLayer layer, ReuseVertexConsumer consumer) {}
|
|
||||||
|
private record LayerConsumer(RenderType layer, ReuseVertexConsumer consumer) {}
|
||||||
private final List<LayerConsumer> layers;
|
private final List<LayerConsumer> layers;
|
||||||
private BakedBlockEntityModel(List<LayerConsumer> layers) {
|
private BakedBlockEntityModel(List<LayerConsumer> layers) {
|
||||||
this.layers = layers;
|
this.layers = layers;
|
||||||
@@ -28,12 +29,12 @@ public class BakedBlockEntityModel {
|
|||||||
public void render(Matrix4f matrix, int texId) {
|
public void render(Matrix4f matrix, int texId) {
|
||||||
for (var layer : this.layers) {
|
for (var layer : this.layers) {
|
||||||
if (layer.consumer.isEmpty()) continue;
|
if (layer.consumer.isEmpty()) continue;
|
||||||
if (layer.layer instanceof RenderLayer.MultiPhase mp) {
|
if (layer.layer instanceof RenderType.CompositeRenderType mp) {
|
||||||
Identifier textureId = mp.phases.texture.getId().orElse(null);
|
Identifier textureId = mp.state.textureState.cutoutTexture().orElse(null);
|
||||||
if (textureId == null) {
|
if (textureId == null) {
|
||||||
Logger.error("ERROR: Empty texture id for layer: " + layer);
|
Logger.error("ERROR: Empty texture id for layer: " + layer);
|
||||||
} else {
|
} else {
|
||||||
texId = ((net.minecraft.client.texture.GlTexture)MinecraftClient.getInstance().getTextureManager().getTexture(textureId).getGlTexture()).getGlId();
|
texId = ((com.mojang.blaze3d.opengl.GlTexture)Minecraft.getInstance().getTextureManager().getTexture(textureId).getTexture()).glId();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (texId == 0) continue;
|
if (texId == 0) continue;
|
||||||
@@ -46,22 +47,46 @@ public class BakedBlockEntityModel {
|
|||||||
this.layers.forEach(layer->layer.consumer.free());
|
this.layers.forEach(layer->layer.consumer.free());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int getMetaFromLayer(RenderType layer) {
|
||||||
|
boolean hasDiscard = layer == RenderType.cutout() ||
|
||||||
|
layer == RenderType.cutoutMipped() ||
|
||||||
|
layer == RenderType.tripwire();
|
||||||
|
|
||||||
|
boolean isMipped = layer == RenderType.cutoutMipped() ||
|
||||||
|
layer == RenderType.solid() ||
|
||||||
|
layer.sortOnUpload() ||
|
||||||
|
layer == RenderType.tripwire();
|
||||||
|
|
||||||
|
int meta = hasDiscard?1:0;
|
||||||
|
meta |= isMipped?2:0;
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
|
||||||
public static BakedBlockEntityModel bake(BlockState state) {
|
public static BakedBlockEntityModel bake(BlockState state) {
|
||||||
Map<RenderLayer, LayerConsumer> map = new HashMap<>();
|
Map<RenderType, LayerConsumer> map = new HashMap<>();
|
||||||
var entity = ((BlockEntityProvider)state.getBlock()).createBlockEntity(BlockPos.ORIGIN, state);
|
var entity = ((EntityBlock)state.getBlock()).newBlockEntity(BlockPos.ZERO, state);
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
var renderer = MinecraftClient.getInstance().getBlockEntityRenderDispatcher().get(entity);
|
var renderer = Minecraft.getInstance().getBlockEntityRenderDispatcher().getRenderer(entity);
|
||||||
entity.setWorld(MinecraftClient.getInstance().world);
|
entity.setLevel(Minecraft.getInstance().level);
|
||||||
if (renderer != null) {
|
if (renderer != null) {
|
||||||
try {
|
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));
|
var rt = renderer.createRenderState();
|
||||||
|
renderer.extractRenderState(entity, rt, 0.0f, new Vec3d(0,0,0), null);
|
||||||
|
|
||||||
|
//TODO: FIXME: FINISH
|
||||||
|
var cstate = new CameraRenderState();
|
||||||
|
var queue = new SubmitNodeStorage();
|
||||||
|
renderer.submit(rt, new MatrixStack(), queue, cstate);
|
||||||
|
var qq = queue.order(0);
|
||||||
|
qq.
|
||||||
|
//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) {
|
} catch (Exception e) {
|
||||||
Logger.error("Unable to bake block entity: " + entity, e);
|
Logger.error("Unable to bake block entity: " + entity, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entity.markRemoved();
|
entity.setRemoved();
|
||||||
if (map.isEmpty()) {
|
if (map.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -77,3 +102,4 @@ public class BakedBlockEntityModel {
|
|||||||
return new BakedBlockEntityModel(new ArrayList<>(map.values()));
|
return new BakedBlockEntityModel(new ArrayList<>(map.values()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
@@ -2,14 +2,13 @@ package me.cortex.voxy.client.core.model.bakery;
|
|||||||
|
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
import com.mojang.blaze3d.textures.GpuTexture;
|
import com.mojang.blaze3d.textures.GpuTexture;
|
||||||
|
import com.mojang.blaze3d.vertex.MeshData;
|
||||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||||
import me.cortex.voxy.client.core.gl.GlVertexArray;
|
import me.cortex.voxy.client.core.gl.GlVertexArray;
|
||||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||||
import net.minecraft.client.gl.GlGpuBuffer;
|
|
||||||
import net.minecraft.client.render.BuiltBuffer;
|
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.lwjgl.system.MemoryUtil;
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
@@ -26,11 +25,12 @@ public class BudgetBufferRenderer {
|
|||||||
.compile();
|
.compile();
|
||||||
|
|
||||||
|
|
||||||
|
public static void init(){}
|
||||||
private static final GlBuffer indexBuffer;
|
private static final GlBuffer indexBuffer;
|
||||||
static {
|
static {
|
||||||
var i = RenderSystem.getSequentialBuffer(VertexFormat.DrawMode.QUADS);
|
var i = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS);
|
||||||
int id = ((GlGpuBuffer) i.getIndexBuffer(4096*3*2)).id;
|
int id = ((com.mojang.blaze3d.opengl.GlBuffer) i.getBuffer(4096*3*2)).handle;
|
||||||
if (i.getIndexType() != VertexFormat.IndexType.SHORT) {
|
if (i.type() != VertexFormat.IndexType.SHORT) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
indexBuffer = new GlBuffer(3*2*2*4096);
|
indexBuffer = new GlBuffer(3*2*2*4096);
|
||||||
@@ -46,18 +46,18 @@ public class BudgetBufferRenderer {
|
|||||||
|
|
||||||
private static GlBuffer immediateBuffer;
|
private static GlBuffer immediateBuffer;
|
||||||
private static int quadCount;
|
private static int quadCount;
|
||||||
public static void drawFast(BuiltBuffer buffer, GpuTexture tex, Matrix4f matrix) {
|
public static void drawFast(MeshData buffer, GpuTexture tex, Matrix4f matrix) {
|
||||||
if (buffer.getDrawParameters().mode() != VertexFormat.DrawMode.QUADS) {
|
if (buffer.drawState().mode() != VertexFormat.Mode.QUADS) {
|
||||||
throw new IllegalStateException("Fast only supports quads");
|
throw new IllegalStateException("Fast only supports quads");
|
||||||
}
|
}
|
||||||
|
|
||||||
var buff = buffer.getBuffer();
|
var buff = buffer.vertexBuffer();
|
||||||
int size = buff.remaining();
|
int size = buff.remaining();
|
||||||
if (size%STRIDE != 0) throw new IllegalStateException();
|
if (size%STRIDE != 0) throw new IllegalStateException();
|
||||||
size /= STRIDE;
|
size /= STRIDE;
|
||||||
if (size%4 != 0) throw new IllegalStateException();
|
if (size%4 != 0) throw new IllegalStateException();
|
||||||
size /= 4;
|
size /= 4;
|
||||||
setup(MemoryUtil.memAddress(buff), size, ((net.minecraft.client.texture.GlTexture)tex).getGlId());
|
setup(MemoryUtil.memAddress(buff), size, ((com.mojang.blaze3d.opengl.GlTexture)tex).glId());
|
||||||
buffer.close();
|
buffer.close();
|
||||||
|
|
||||||
render(matrix);
|
render(matrix);
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
package me.cortex.voxy.client.core.model.bakery;
|
package me.cortex.voxy.client.core.model.bakery;
|
||||||
|
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
|
||||||
import me.cortex.voxy.client.core.gl.GlFramebuffer;
|
import me.cortex.voxy.client.core.gl.GlFramebuffer;
|
||||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
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.Shader;
|
||||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||||
|
import org.lwjgl.system.MemoryStack;
|
||||||
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
import static org.lwjgl.opengl.ARBDirectStateAccess.glClearNamedFramebufferfv;
|
import static org.lwjgl.opengl.ARBDirectStateAccess.*;
|
||||||
import static org.lwjgl.opengl.ARBDirectStateAccess.glTextureParameteri;
|
|
||||||
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_FRAMEBUFFER_BARRIER_BIT;
|
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_FRAMEBUFFER_BARRIER_BIT;
|
||||||
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_PIXEL_BUFFER_BARRIER_BIT;
|
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_PIXEL_BUFFER_BARRIER_BIT;
|
||||||
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT;
|
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT;
|
||||||
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_TEXTURE_UPDATE_BARRIER_BIT;
|
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_TEXTURE_UPDATE_BARRIER_BIT;
|
||||||
import static org.lwjgl.opengl.ARBShaderImageLoadStore.glMemoryBarrier;
|
import static org.lwjgl.opengl.ARBShaderImageLoadStore.glMemoryBarrier;
|
||||||
import static org.lwjgl.opengl.GL11.*;
|
import static org.lwjgl.opengl.GL11.*;
|
||||||
import static org.lwjgl.opengl.GL11.GL_STENCIL_INDEX;
|
|
||||||
import static org.lwjgl.opengl.GL30.*;
|
import static org.lwjgl.opengl.GL30.*;
|
||||||
import static org.lwjgl.opengl.GL43.*;
|
import static org.lwjgl.opengl.GL43.*;
|
||||||
import static org.lwjgl.opengl.GL45.glClearNamedFramebufferfi;
|
import static org.lwjgl.opengl.GL45.glClearNamedFramebufferfi;
|
||||||
@@ -25,12 +24,14 @@ public class GlViewCapture {
|
|||||||
private final GlTexture colourTex;
|
private final GlTexture colourTex;
|
||||||
private final GlTexture depthTex;
|
private final GlTexture depthTex;
|
||||||
private final GlTexture stencilTex;
|
private final GlTexture stencilTex;
|
||||||
|
private final GlTexture metaTex;
|
||||||
final GlFramebuffer framebuffer;
|
final GlFramebuffer framebuffer;
|
||||||
private final Shader copyOutShader;
|
private final Shader copyOutShader;
|
||||||
|
|
||||||
public GlViewCapture(int width, int height) {
|
public GlViewCapture(int width, int height) {
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
|
this.metaTex = new GlTexture().store(GL_R32UI, 1, width*3, height*2).name("ModelBakeryMetadata");
|
||||||
this.colourTex = new GlTexture().store(GL_RGBA8, 1, width*3, height*2).name("ModelBakeryColour");
|
this.colourTex = new GlTexture().store(GL_RGBA8, 1, width*3, height*2).name("ModelBakeryColour");
|
||||||
this.depthTex = new GlTexture().store(GL_DEPTH24_STENCIL8, 1, width*3, height*2).name("ModelBakeryDepth");
|
this.depthTex = new GlTexture().store(GL_DEPTH24_STENCIL8, 1, width*3, height*2).name("ModelBakeryDepth");
|
||||||
//TODO: FIXME: Mesa is broken when trying to read from a sampler of GL_STENCIL_INDEX
|
//TODO: FIXME: Mesa is broken when trying to read from a sampler of GL_STENCIL_INDEX
|
||||||
@@ -39,9 +40,17 @@ public class GlViewCapture {
|
|||||||
this.stencilTex = this.depthTex.createView();
|
this.stencilTex = this.depthTex.createView();
|
||||||
glTextureParameteri(this.depthTex.id, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_DEPTH_COMPONENT);
|
glTextureParameteri(this.depthTex.id, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_DEPTH_COMPONENT);
|
||||||
|
|
||||||
this.framebuffer = new GlFramebuffer().bind(GL_COLOR_ATTACHMENT0, this.colourTex).bind(GL_DEPTH_STENCIL_ATTACHMENT, this.depthTex).verify().name("ModelFramebuffer");
|
this.framebuffer = new GlFramebuffer().bind(GL_COLOR_ATTACHMENT0, this.colourTex).bind(GL_COLOR_ATTACHMENT1, this.metaTex).setDrawBuffers(GL_COLOR_ATTACHMENT0,GL_COLOR_ATTACHMENT1).bind(GL_DEPTH_STENCIL_ATTACHMENT, this.depthTex).verify().name("ModelFramebuffer");
|
||||||
|
|
||||||
glTextureParameteri(this.stencilTex.id, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
|
glTextureParameteri(this.stencilTex.id, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
|
||||||
|
glTextureParameteri(this.stencilTex.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
glTextureParameteri(this.stencilTex.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
|
|
||||||
|
glTextureParameteri(this.metaTex.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
glTextureParameteri(this.metaTex.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
|
|
||||||
|
glTextureParameteri(this.depthTex.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
glTextureParameteri(this.depthTex.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
|
|
||||||
this.copyOutShader = Shader.makeAuto()
|
this.copyOutShader = Shader.makeAuto()
|
||||||
.define("WIDTH", width)
|
.define("WIDTH", width)
|
||||||
@@ -49,10 +58,12 @@ public class GlViewCapture {
|
|||||||
.define("COLOUR_IN_BINDING", 0)
|
.define("COLOUR_IN_BINDING", 0)
|
||||||
.define("DEPTH_IN_BINDING", 1)
|
.define("DEPTH_IN_BINDING", 1)
|
||||||
.define("STENCIL_IN_BINDING", 2)
|
.define("STENCIL_IN_BINDING", 2)
|
||||||
.define("BUFFER_OUT_BINDING", 3)
|
.define("META_IN_BINDING", 3)
|
||||||
|
.define("BUFFER_OUT_BINDING", 4)
|
||||||
.add(ShaderType.COMPUTE, "voxy:bakery/bufferreorder.comp")
|
.add(ShaderType.COMPUTE, "voxy:bakery/bufferreorder.comp")
|
||||||
.compile()
|
.compile()
|
||||||
.name("ModelBakeryOut")
|
.name("ModelBakeryOut")
|
||||||
|
.texture("META_IN_BINDING", 0, this.metaTex)
|
||||||
.texture("COLOUR_IN_BINDING", 0, this.colourTex)
|
.texture("COLOUR_IN_BINDING", 0, this.colourTex)
|
||||||
.texture("DEPTH_IN_BINDING", 0, this.depthTex)
|
.texture("DEPTH_IN_BINDING", 0, this.depthTex)
|
||||||
.texture("STENCIL_IN_BINDING", 0, this.stencilTex);
|
.texture("STENCIL_IN_BINDING", 0, this.stencilTex);
|
||||||
@@ -60,13 +71,22 @@ public class GlViewCapture {
|
|||||||
|
|
||||||
public void emitToStream(int buffer, int offset) {
|
public void emitToStream(int buffer, int offset) {
|
||||||
this.copyOutShader.bind();
|
this.copyOutShader.bind();
|
||||||
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 3, buffer, offset, (this.width*3L)*(this.height*2L)*4L*2);//its 2*4 because colour + depth stencil
|
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 4, buffer, offset, (this.width*3L)*(this.height*2L)*4L*2);//its 2*4 because colour + depth stencil
|
||||||
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT|GL_TEXTURE_UPDATE_BARRIER_BIT|GL_PIXEL_BUFFER_BARRIER_BIT|GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);//Am not sure if barriers are right
|
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT|GL_TEXTURE_UPDATE_BARRIER_BIT|GL_PIXEL_BUFFER_BARRIER_BIT|GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);//Am not sure if barriers are right
|
||||||
glDispatchCompute(3, 2, 1);
|
glDispatchCompute(3, 2, 1);
|
||||||
|
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 4, 0, 0, 4);//WHY DOES THIS FIX FUCKING BINDING ISSUES HERE WHEN DOING THIS IN THE RENDER SYSTEM DOESNT
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
glClearNamedFramebufferfv(this.framebuffer.id, GL_COLOR, 0, new float[]{0,0,0,0});
|
try (var stack = MemoryStack.stackPush()) {
|
||||||
|
long ptr = stack.nmalloc(4*4);
|
||||||
|
MemoryUtil.memPutLong(ptr, 0);
|
||||||
|
MemoryUtil.memPutLong(ptr+8, 0);
|
||||||
|
nglClearNamedFramebufferfv(this.framebuffer.id, GL_COLOR, 0, ptr);
|
||||||
|
nglClearNamedFramebufferuiv(this.framebuffer.id, GL_COLOR, 1, ptr);
|
||||||
|
//TODO: fix the draw buffer thing maybe? it might need todo multiple clears
|
||||||
|
//nglClearNamedFramebufferfv(this.framebuffer.id, GL_COLOR, 0, ptr);
|
||||||
|
}
|
||||||
glClearNamedFramebufferfi(this.framebuffer.id, GL_DEPTH_STENCIL, 0, 1.0f, 0);
|
glClearNamedFramebufferfi(this.framebuffer.id, GL_DEPTH_STENCIL, 0, 1.0f, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +95,7 @@ public class GlViewCapture {
|
|||||||
this.colourTex.free();
|
this.colourTex.free();
|
||||||
this.stencilTex.free();
|
this.stencilTex.free();
|
||||||
this.depthTex.free();
|
this.depthTex.free();
|
||||||
|
this.metaTex.free();
|
||||||
this.copyOutShader.free();
|
this.copyOutShader.free();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,38 @@
|
|||||||
package me.cortex.voxy.client.core.model.bakery;
|
package me.cortex.voxy.client.core.model.bakery;
|
||||||
|
|
||||||
import net.minecraft.block.BlockState;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.block.Blocks;
|
import net.minecraft.client.renderer.ItemBlockRenderTypes;
|
||||||
import net.minecraft.block.FluidBlock;
|
import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
|
||||||
import net.minecraft.block.LeavesBlock;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.block.entity.BlockEntity;
|
import net.minecraft.core.Direction;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.resources.Identifier;
|
||||||
import net.minecraft.client.render.RenderLayer;
|
import net.minecraft.world.level.BlockAndTintGetter;
|
||||||
import net.minecraft.client.render.RenderLayers;
|
import net.minecraft.world.level.ColorResolver;
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
import net.minecraft.world.level.LightLayer;
|
||||||
import net.minecraft.fluid.FluidState;
|
import net.minecraft.world.level.block.Blocks;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.world.level.block.LeavesBlock;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.world.level.block.LiquidBlock;
|
||||||
import net.minecraft.util.math.Direction;
|
import net.minecraft.world.level.block.RenderShape;
|
||||||
import net.minecraft.util.math.RotationAxis;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
import net.minecraft.util.math.random.LocalRandom;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.minecraft.world.BlockRenderView;
|
import net.minecraft.world.level.levelgen.SingleThreadedRandomSource;
|
||||||
import net.minecraft.world.LightType;
|
import net.minecraft.world.level.lighting.LevelLightEngine;
|
||||||
import net.minecraft.world.biome.ColorResolver;
|
import net.minecraft.world.level.material.FluidState;
|
||||||
import net.minecraft.world.chunk.light.LightingProvider;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
|
import org.joml.Quaternionf;
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
import org.lwjgl.opengl.ARBDrawBuffersBlend;
|
||||||
import org.lwjgl.opengl.GL14;
|
import org.lwjgl.opengl.GL14;
|
||||||
|
|
||||||
import static org.lwjgl.opengl.GL11.*;
|
import static org.lwjgl.opengl.GL11.*;
|
||||||
import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate;
|
import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate;
|
||||||
import static org.lwjgl.opengl.GL30.*;
|
import static org.lwjgl.opengl.GL30.*;
|
||||||
|
import static org.lwjgl.opengl.GL40.glBlendFuncSeparatei;
|
||||||
import static org.lwjgl.opengl.GL45.glTextureBarrier;
|
import static org.lwjgl.opengl.GL45.glTextureBarrier;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
|
||||||
public class ModelTextureBakery {
|
public class ModelTextureBakery {
|
||||||
//Note: the first bit of metadata is if alpha discard is enabled
|
//Note: the first bit of metadata is if alpha discard is enabled
|
||||||
private static final Matrix4f[] VIEWS = new Matrix4f[6];
|
private static final Matrix4f[] VIEWS = new Matrix4f[6];
|
||||||
@@ -43,62 +48,69 @@ public class ModelTextureBakery {
|
|||||||
this.height = height;
|
this.height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getMetaFromLayer(RenderLayer layer) {
|
public static int getMetaFromLayer(ChunkSectionLayer layer) {
|
||||||
boolean hasDiscard = layer == RenderLayer.getCutout() ||
|
boolean hasDiscard = layer == ChunkSectionLayer.CUTOUT ||
|
||||||
layer == RenderLayer.getCutoutMipped() ||
|
layer == ChunkSectionLayer.TRANSLUCENT||
|
||||||
layer == RenderLayer.getTripwire();
|
layer == ChunkSectionLayer.TRIPWIRE;
|
||||||
|
|
||||||
boolean isMipped = layer == RenderLayer.getCutoutMipped() ||
|
boolean isMipped = layer == ChunkSectionLayer.SOLID ||
|
||||||
layer == RenderLayer.getSolid() ||
|
layer == ChunkSectionLayer.TRANSLUCENT ||
|
||||||
layer == RenderLayer.getTranslucent() ||
|
layer == ChunkSectionLayer.TRIPWIRE;
|
||||||
layer == RenderLayer.getTripwire();
|
|
||||||
|
|
||||||
int meta = hasDiscard?1:0;
|
int meta = hasDiscard?1:0;
|
||||||
meta |= isMipped?2:0;
|
meta |= true?2:0;
|
||||||
return meta;
|
return meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bakeBlockModel(BlockState state, RenderLayer layer) {
|
private void bakeBlockModel(BlockState state, ChunkSectionLayer layer) {
|
||||||
var model = MinecraftClient.getInstance()
|
if (state.getRenderShape() == RenderShape.INVISIBLE) {
|
||||||
.getBakedModelManager()
|
return;//Dont bake if invisible
|
||||||
.getBlockModels()
|
}
|
||||||
.getModel(state);
|
var model = Minecraft.getInstance()
|
||||||
|
.getModelManager()
|
||||||
|
.getBlockModelShaper()
|
||||||
|
.getBlockModel(state);
|
||||||
|
|
||||||
int meta = getMetaFromLayer(layer);
|
int meta = getMetaFromLayer(layer);
|
||||||
|
|
||||||
|
for (var part : model.collectParts(new SingleThreadedRandomSource(42L))) {
|
||||||
for (Direction direction : new Direction[]{Direction.DOWN, Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST, null}) {
|
for (Direction direction : new Direction[]{Direction.DOWN, Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST, null}) {
|
||||||
for (var part : model.getParts(new LocalRandom(42L))) {
|
|
||||||
var quads = part.getQuads(direction);
|
var quads = part.getQuads(direction);
|
||||||
for (var quad : quads) {
|
for (var quad : quads) {
|
||||||
//TODO: add meta specifiying quad has a tint
|
this.vc.quad(quad, meta|(quad.isTinted()?4:0));
|
||||||
|
|
||||||
this.vc.quad(quad, meta);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void bakeFluidState(BlockState state, RenderLayer layer, int face) {
|
private void bakeFluidState(BlockState state, ChunkSectionLayer layer, int face) {
|
||||||
this.vc.setDefaultMeta(getMetaFromLayer(layer));//Set the meta while baking
|
{
|
||||||
MinecraftClient.getInstance().getBlockRenderManager().renderFluid(BlockPos.ORIGIN, new BlockRenderView() {
|
//TODO: somehow set the tint flag per quad or something?
|
||||||
|
int metadata = getMetaFromLayer(layer);
|
||||||
|
//Just assume all fluids are tinted, if they arnt it should be implicitly culled in the model baking phase
|
||||||
|
// since it wont have the colour provider
|
||||||
|
metadata |= 4;//Has tint
|
||||||
|
this.vc.setDefaultMeta(metadata);//Set the meta while baking
|
||||||
|
}
|
||||||
|
Minecraft.getInstance().getBlockRenderer().renderLiquid(BlockPos.ZERO, new BlockAndTintGetter() {
|
||||||
@Override
|
@Override
|
||||||
public float getBrightness(Direction direction, boolean shaded) {
|
public float getShade(Direction direction, boolean shaded) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LightingProvider getLightingProvider() {
|
public LevelLightEngine getLightEngine() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLightLevel(LightType type, BlockPos pos) {
|
public int getBrightness(LightLayer type, BlockPos pos) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getColor(BlockPos pos, ColorResolver colorResolver) {
|
public int getBlockTint(BlockPos pos, ColorResolver colorResolver) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +123,7 @@ public class ModelTextureBakery {
|
|||||||
@Override
|
@Override
|
||||||
public BlockState getBlockState(BlockPos pos) {
|
public BlockState getBlockState(BlockPos pos) {
|
||||||
if (shouldReturnAirForFluid(pos, face)) {
|
if (shouldReturnAirForFluid(pos, face)) {
|
||||||
return Blocks.AIR.getDefaultState();
|
return Blocks.AIR.defaultBlockState();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Fixme:
|
//Fixme:
|
||||||
@@ -129,7 +141,7 @@ public class ModelTextureBakery {
|
|||||||
@Override
|
@Override
|
||||||
public FluidState getFluidState(BlockPos pos) {
|
public FluidState getFluidState(BlockPos pos) {
|
||||||
if (shouldReturnAirForFluid(pos, face)) {
|
if (shouldReturnAirForFluid(pos, face)) {
|
||||||
return Blocks.AIR.getDefaultState().getFluidState();
|
return Blocks.AIR.defaultBlockState().getFluidState();
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.getFluidState();
|
return state.getFluidState();
|
||||||
@@ -141,7 +153,7 @@ public class ModelTextureBakery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getBottomY() {
|
public int getMinY() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}, this.vc, state, state.getFluidState());
|
}, this.vc, state, state.getFluidState());
|
||||||
@@ -149,7 +161,7 @@ public class ModelTextureBakery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean shouldReturnAirForFluid(BlockPos pos, int face) {
|
private static boolean shouldReturnAirForFluid(BlockPos pos, int face) {
|
||||||
var fv = Direction.byIndex(face).getVector();
|
var fv = Direction.from3DDataValue(face).getUnitVec3i();
|
||||||
int dot = fv.getX()*pos.getX() + fv.getY()*pos.getY() + fv.getZ()*pos.getZ();
|
int dot = fv.getX()*pos.getX() + fv.getY()*pos.getY() + fv.getZ()*pos.getZ();
|
||||||
return dot >= 1;
|
return dot >= 1;
|
||||||
}
|
}
|
||||||
@@ -160,25 +172,25 @@ public class ModelTextureBakery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void renderToStream(BlockState state, int streamBuffer, int streamOffset) {
|
public int renderToStream(BlockState state, int streamBuffer, int streamOffset) {
|
||||||
this.capture.clear();
|
this.capture.clear();
|
||||||
boolean isBlock = true;
|
boolean isBlock = true;
|
||||||
RenderLayer layer;
|
ChunkSectionLayer layer;
|
||||||
if (state.getBlock() instanceof FluidBlock) {
|
if (state.getBlock() instanceof LiquidBlock) {
|
||||||
layer = RenderLayers.getFluidLayer(state.getFluidState());
|
layer = ItemBlockRenderTypes.getRenderLayer(state.getFluidState());
|
||||||
isBlock = false;
|
isBlock = false;
|
||||||
} else {
|
} else {
|
||||||
if (state.getBlock() instanceof LeavesBlock) {
|
if (state.getBlock() instanceof LeavesBlock) {
|
||||||
layer = RenderLayer.getSolid();
|
layer = ChunkSectionLayer.SOLID;
|
||||||
} else {
|
} else {
|
||||||
layer = RenderLayers.getBlockLayer(state);
|
layer = ItemBlockRenderTypes.getChunkRenderType(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: support block model entities
|
//TODO: support block model entities
|
||||||
BakedBlockEntityModel bbem = null;
|
//BakedBlockEntityModel bbem = null;
|
||||||
if (state.hasBlockEntity()) {
|
if (state.hasBlockEntity()) {
|
||||||
bbem = BakedBlockEntityModel.bake(state);
|
//bbem = BakedBlockEntityModel.bake(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Setup GL state
|
//Setup GL state
|
||||||
@@ -189,9 +201,10 @@ public class ModelTextureBakery {
|
|||||||
glEnable(GL_STENCIL_TEST);
|
glEnable(GL_STENCIL_TEST);
|
||||||
glEnable(GL_DEPTH_TEST);
|
glEnable(GL_DEPTH_TEST);
|
||||||
glEnable(GL_CULL_FACE);
|
glEnable(GL_CULL_FACE);
|
||||||
if (layer == RenderLayer.getTranslucent()) {
|
if (layer == ChunkSectionLayer.TRANSLUCENT) {
|
||||||
glEnable(GL_BLEND);
|
glEnablei(GL_BLEND, 0);
|
||||||
glBlendFuncSeparate(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
glDisablei(GL_BLEND, 1);
|
||||||
|
ARBDrawBuffersBlend.glBlendFuncSeparateiARB(0, GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
} else {
|
} else {
|
||||||
glDisable(GL_BLEND);//FUCK YOU INTEL (screams), for _some reason_ discard or something... JUST DOESNT WORK??
|
glDisable(GL_BLEND);//FUCK YOU INTEL (screams), for _some reason_ discard or something... JUST DOESNT WORK??
|
||||||
//glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ONE);
|
//glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ONE);
|
||||||
@@ -206,14 +219,17 @@ public class ModelTextureBakery {
|
|||||||
//Bind the capture framebuffer
|
//Bind the capture framebuffer
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, this.capture.framebuffer.id);
|
glBindFramebuffer(GL_FRAMEBUFFER, this.capture.framebuffer.id);
|
||||||
|
|
||||||
var tex = MinecraftClient.getInstance().getTextureManager().getTexture(Identifier.of("minecraft", "textures/atlas/blocks.png")).getGlTexture();
|
var tex = Minecraft.getInstance().getTextureManager().getTexture(Identifier.fromNamespaceAndPath("minecraft", "textures/atlas/blocks.png")).getTexture();
|
||||||
blockTextureId = ((net.minecraft.client.texture.GlTexture)tex).getGlId();
|
blockTextureId = ((com.mojang.blaze3d.opengl.GlTexture)tex).glId();
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: fastpath for blocks
|
boolean isAnyShaded = false;
|
||||||
|
boolean isAnyDarkend = false;
|
||||||
if (isBlock) {
|
if (isBlock) {
|
||||||
this.vc.reset();
|
this.vc.reset();
|
||||||
this.bakeBlockModel(state, layer);
|
this.bakeBlockModel(state, layer);
|
||||||
|
isAnyShaded |= this.vc.anyShaded;
|
||||||
|
isAnyDarkend |= this.vc.anyDarkendTex;
|
||||||
if (!this.vc.isEmpty()) {//only render if there... is shit to render
|
if (!this.vc.isEmpty()) {//only render if there... is shit to render
|
||||||
|
|
||||||
//Setup for continual emission
|
//Setup for continual emission
|
||||||
@@ -241,7 +257,8 @@ public class ModelTextureBakery {
|
|||||||
}
|
}
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
} else {//Is fluid, slow path :(
|
} else {//Is fluid, slow path :(
|
||||||
if (!(state.getBlock() instanceof FluidBlock)) throw new IllegalStateException();
|
|
||||||
|
if (!(state.getBlock() instanceof LiquidBlock)) throw new IllegalStateException();
|
||||||
|
|
||||||
var mat = new Matrix4f();
|
var mat = new Matrix4f();
|
||||||
for (int i = 0; i < VIEWS.length; i++) {
|
for (int i = 0; i < VIEWS.length; i++) {
|
||||||
@@ -254,6 +271,8 @@ public class ModelTextureBakery {
|
|||||||
this.vc.reset();
|
this.vc.reset();
|
||||||
this.bakeFluidState(state, layer, i);
|
this.bakeFluidState(state, layer, i);
|
||||||
if (this.vc.isEmpty()) continue;
|
if (this.vc.isEmpty()) continue;
|
||||||
|
isAnyShaded |= this.vc.anyShaded;
|
||||||
|
isAnyDarkend |= this.vc.anyDarkendTex;
|
||||||
BudgetBufferRenderer.setup(this.vc.getAddress(), this.vc.quadCount(), blockTextureId);
|
BudgetBufferRenderer.setup(this.vc.getAddress(), this.vc.quadCount(), blockTextureId);
|
||||||
|
|
||||||
glViewport((i % 3) * this.width, (i / 3) * this.height, this.width, this.height);
|
glViewport((i % 3) * this.width, (i / 3) * this.height, this.width, this.height);
|
||||||
@@ -271,6 +290,7 @@ public class ModelTextureBakery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Render block model entity data if it exists
|
//Render block model entity data if it exists
|
||||||
|
/*
|
||||||
if (bbem != null) {
|
if (bbem != null) {
|
||||||
//Rerender everything again ;-; but is ok (is not)
|
//Rerender everything again ;-; but is ok (is not)
|
||||||
|
|
||||||
@@ -296,7 +316,8 @@ public class ModelTextureBakery {
|
|||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
|
|
||||||
bbem.release();
|
bbem.release();
|
||||||
}
|
}*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//"Restore" gl state
|
//"Restore" gl state
|
||||||
@@ -311,10 +332,12 @@ public class ModelTextureBakery {
|
|||||||
glBindFramebuffer(GL_FRAMEBUFFER, this.capture.framebuffer.id);
|
glBindFramebuffer(GL_FRAMEBUFFER, this.capture.framebuffer.id);
|
||||||
glClearDepth(1);
|
glClearDepth(1);
|
||||||
glClear(GL_DEPTH_BUFFER_BIT);
|
glClear(GL_DEPTH_BUFFER_BIT);
|
||||||
if (layer == RenderLayer.getTranslucent()) {
|
if (layer == ChunkSectionLayer.TRANSLUCENT) {
|
||||||
//reset the blend func
|
//reset the blend func
|
||||||
GL14.glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
GL14.glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (isAnyShaded?1:0)|(isAnyDarkend?2:0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -333,13 +356,24 @@ public class ModelTextureBakery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void addView(int i, float pitch, float yaw, float rotation, int flip) {
|
private static void addView(int i, float pitch, float yaw, float rotation, int flip) {
|
||||||
var stack = new MatrixStack();
|
var stack = new PoseStack();
|
||||||
stack.translate(0.5f,0.5f,0.5f);
|
stack.translate(0.5f,0.5f,0.5f);
|
||||||
stack.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(rotation));
|
stack.mulPose(makeQuatFromAxisExact(new Vector3f(0,0,1), rotation));
|
||||||
stack.multiply(RotationAxis.POSITIVE_X.rotationDegrees(pitch));
|
stack.mulPose(makeQuatFromAxisExact(new Vector3f(1,0,0), pitch));
|
||||||
stack.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw));
|
stack.mulPose(makeQuatFromAxisExact(new Vector3f(0,1,0), yaw));
|
||||||
stack.multiplyPositionMatrix(new Matrix4f().scale(1-2*(flip&1), 1-(flip&2), 1-((flip>>1)&2)));
|
stack.mulPose(new Matrix4f().scale(1-2*(flip&1), 1-(flip&2), 1-((flip>>1)&2)));
|
||||||
stack.translate(-0.5f,-0.5f,-0.5f);
|
stack.translate(-0.5f,-0.5f,-0.5f);
|
||||||
VIEWS[i] = new Matrix4f(stack.peek().getPositionMatrix());
|
VIEWS[i] = new Matrix4f(stack.last().pose());
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,24 @@ package me.cortex.voxy.client.core.model.bakery;
|
|||||||
|
|
||||||
|
|
||||||
import me.cortex.voxy.common.util.MemoryBuffer;
|
import me.cortex.voxy.common.util.MemoryBuffer;
|
||||||
import net.minecraft.client.render.VertexConsumer;
|
import net.minecraft.client.model.geom.builders.UVPair;
|
||||||
import net.minecraft.client.render.model.BakedQuad;
|
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||||
|
import net.minecraft.client.renderer.texture.MipmapStrategy;
|
||||||
import org.lwjgl.system.MemoryUtil;
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
import static me.cortex.voxy.client.core.model.bakery.BudgetBufferRenderer.VERTEX_FORMAT_SIZE;
|
import static me.cortex.voxy.client.core.model.bakery.BudgetBufferRenderer.VERTEX_FORMAT_SIZE;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
|
|
||||||
public final class ReuseVertexConsumer implements VertexConsumer {
|
public final class ReuseVertexConsumer implements VertexConsumer {
|
||||||
private MemoryBuffer buffer = new MemoryBuffer(8192);
|
private MemoryBuffer buffer = new MemoryBuffer(8192);
|
||||||
private long ptr;
|
private long ptr;
|
||||||
private int count;
|
private int count;
|
||||||
private int defaultMeta;
|
private int defaultMeta;
|
||||||
|
|
||||||
|
public boolean anyShaded;
|
||||||
|
public boolean anyDarkendTex;
|
||||||
|
|
||||||
public ReuseVertexConsumer() {
|
public ReuseVertexConsumer() {
|
||||||
this.reset();
|
this.reset();
|
||||||
}
|
}
|
||||||
@@ -24,7 +30,7 @@ public final class ReuseVertexConsumer implements VertexConsumer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReuseVertexConsumer vertex(float x, float y, float z) {
|
public ReuseVertexConsumer addVertex(float x, float y, float z) {
|
||||||
this.ensureCanPut();
|
this.ensureCanPut();
|
||||||
this.ptr += VERTEX_FORMAT_SIZE; this.count++; //Goto next vertex
|
this.ptr += VERTEX_FORMAT_SIZE; this.count++; //Goto next vertex
|
||||||
this.meta(this.defaultMeta);
|
this.meta(this.defaultMeta);
|
||||||
@@ -40,43 +46,51 @@ public final class ReuseVertexConsumer implements VertexConsumer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReuseVertexConsumer color(int red, int green, int blue, int alpha) {
|
public ReuseVertexConsumer setColor(int red, int green, int blue, int alpha) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReuseVertexConsumer texture(float u, float v) {
|
public VertexConsumer setColor(int i) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReuseVertexConsumer setUv(float u, float v) {
|
||||||
MemoryUtil.memPutFloat(this.ptr + 16, u);
|
MemoryUtil.memPutFloat(this.ptr + 16, u);
|
||||||
MemoryUtil.memPutFloat(this.ptr + 20, v);
|
MemoryUtil.memPutFloat(this.ptr + 20, v);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReuseVertexConsumer overlay(int u, int v) {
|
public ReuseVertexConsumer setUv1(int u, int v) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReuseVertexConsumer light(int u, int v) {
|
public ReuseVertexConsumer setUv2(int u, int v) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReuseVertexConsumer normal(float x, float y, float z) {
|
public ReuseVertexConsumer setNormal(float x, float y, float z) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VertexConsumer setLineWidth(float f) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public ReuseVertexConsumer quad(BakedQuad quad, int metadata) {
|
public ReuseVertexConsumer quad(BakedQuad quad, int metadata) {
|
||||||
|
this.anyShaded |= quad.shade();
|
||||||
|
this.anyDarkendTex |= quad.sprite().contents().mipmapStrategy == MipmapStrategy.DARK_CUTOUT;
|
||||||
this.ensureCanPut();
|
this.ensureCanPut();
|
||||||
int[] data = quad.vertexData();
|
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
float x = Float.intBitsToFloat(data[i * 8]);
|
var pos = quad.position(i);
|
||||||
float y = Float.intBitsToFloat(data[i * 8 + 1]);
|
this.addVertex(pos.x(), pos.y(), pos.z());
|
||||||
float z = Float.intBitsToFloat(data[i * 8 + 2]);
|
long puv = quad.packedUV(i);
|
||||||
this.vertex(x,y,z);
|
this.setUv(UVPair.unpackU(puv),UVPair.unpackV(puv));
|
||||||
float u = Float.intBitsToFloat(data[i * 8 + 4]);
|
|
||||||
float v = Float.intBitsToFloat(data[i * 8 + 5]);
|
|
||||||
this.texture(u,v);
|
|
||||||
|
|
||||||
this.meta(metadata);
|
this.meta(metadata);
|
||||||
}
|
}
|
||||||
@@ -97,6 +111,8 @@ public final class ReuseVertexConsumer implements VertexConsumer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ReuseVertexConsumer reset() {
|
public ReuseVertexConsumer reset() {
|
||||||
|
this.anyShaded = false;
|
||||||
|
this.anyDarkendTex = false;
|
||||||
this.defaultMeta = 0;//RESET THE DEFAULT META
|
this.defaultMeta = 0;//RESET THE DEFAULT META
|
||||||
this.count = 0;
|
this.count = 0;
|
||||||
this.ptr = this.buffer.address - VERTEX_FORMAT_SIZE;//the thing is first time this gets incremented by FORMAT_STRIDE
|
this.ptr = this.buffer.address - VERTEX_FORMAT_SIZE;//the thing is first time this gets incremented by FORMAT_STRIDE
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
package me.cortex.voxy.client.core.rendering;
|
package me.cortex.voxy.client.core.rendering;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
|
|
||||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||||
|
import me.cortex.voxy.client.core.AbstractRenderPipeline;
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||||
import me.cortex.voxy.client.core.gl.GlFramebuffer;
|
import me.cortex.voxy.client.core.gl.GlVertexArray;
|
||||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
|
||||||
import me.cortex.voxy.client.core.gl.shader.AutoBindingShader;
|
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.Shader;
|
||||||
|
import me.cortex.voxy.client.core.gl.shader.ShaderLoader;
|
||||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||||
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
|
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
|
||||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||||
import me.cortex.voxy.common.Logger;
|
import me.cortex.voxy.common.Logger;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.util.math.MathHelper;
|
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
import org.joml.Vector3i;
|
import org.joml.Vector3i;
|
||||||
@@ -26,10 +25,8 @@ import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
|
|||||||
import static org.lwjgl.opengl.GL15.glBindBuffer;
|
import static org.lwjgl.opengl.GL15.glBindBuffer;
|
||||||
import static org.lwjgl.opengl.GL30.glBindVertexArray;
|
import static org.lwjgl.opengl.GL30.glBindVertexArray;
|
||||||
import static org.lwjgl.opengl.GL30C.*;
|
import static org.lwjgl.opengl.GL30C.*;
|
||||||
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
|
|
||||||
import static org.lwjgl.opengl.GL31.glDrawElementsInstanced;
|
import static org.lwjgl.opengl.GL31.glDrawElementsInstanced;
|
||||||
import static org.lwjgl.opengl.GL42.glDrawElementsInstancedBaseInstance;
|
import static org.lwjgl.opengl.GL42.glDrawElementsInstancedBaseInstance;
|
||||||
import static org.lwjgl.opengl.GL45.glClearNamedFramebufferfv;
|
|
||||||
|
|
||||||
//This is a render subsystem, its very simple in what it does
|
//This is a render subsystem, its very simple in what it does
|
||||||
// it renders an AABB around loaded chunks, thats it
|
// it renders an AABB around loaded chunks, thats it
|
||||||
@@ -39,20 +36,28 @@ public class ChunkBoundRenderer {
|
|||||||
private final GlBuffer uniformBuffer = new GlBuffer(128);
|
private final GlBuffer uniformBuffer = new GlBuffer(128);
|
||||||
private final Long2IntOpenHashMap chunk2idx = new Long2IntOpenHashMap(INIT_MAX_CHUNK_COUNT);
|
private final Long2IntOpenHashMap chunk2idx = new Long2IntOpenHashMap(INIT_MAX_CHUNK_COUNT);
|
||||||
private long[] idx2chunk = new long[INIT_MAX_CHUNK_COUNT];
|
private long[] idx2chunk = new long[INIT_MAX_CHUNK_COUNT];
|
||||||
private final Shader rasterShader = Shader.makeAuto()
|
private final Shader rasterShader;
|
||||||
.add(ShaderType.VERTEX, "voxy:chunkoutline/outline.vsh")
|
|
||||||
|
private final LongOpenHashSet addQueue = new LongOpenHashSet();
|
||||||
|
private final LongOpenHashSet remQueue = new LongOpenHashSet();
|
||||||
|
|
||||||
|
private final AbstractRenderPipeline pipeline;
|
||||||
|
public ChunkBoundRenderer(AbstractRenderPipeline pipeline) {
|
||||||
|
this.chunk2idx.defaultReturnValue(-1);
|
||||||
|
this.pipeline = pipeline;
|
||||||
|
|
||||||
|
String vert = ShaderLoader.parse("voxy:chunkoutline/outline.vsh");
|
||||||
|
String taa = pipeline.taaFunction("getTAA");
|
||||||
|
if (taa != null) {
|
||||||
|
vert = vert+"\n\n\n"+taa;
|
||||||
|
}
|
||||||
|
this.rasterShader = Shader.makeAuto()
|
||||||
|
.addSource(ShaderType.VERTEX, vert)
|
||||||
|
.defineIf("TAA", taa != null)
|
||||||
.add(ShaderType.FRAGMENT, "voxy:chunkoutline/outline.fsh")
|
.add(ShaderType.FRAGMENT, "voxy:chunkoutline/outline.fsh")
|
||||||
.compile()
|
.compile()
|
||||||
.ubo(0, this.uniformBuffer)
|
.ubo(0, this.uniformBuffer)
|
||||||
.ssbo(1, this.chunkPosBuffer);
|
.ssbo(1, this.chunkPosBuffer);
|
||||||
|
|
||||||
private GlTexture depthBuffer = new GlTexture().store(GL_DEPTH_COMPONENT24, 1, 128, 128);
|
|
||||||
private final GlFramebuffer frameBuffer = new GlFramebuffer().bind(GL_DEPTH_ATTACHMENT, this.depthBuffer).verify();
|
|
||||||
private final LongOpenHashSet addQueue = new LongOpenHashSet();
|
|
||||||
private final LongOpenHashSet remQueue = new LongOpenHashSet();
|
|
||||||
|
|
||||||
public ChunkBoundRenderer() {
|
|
||||||
this.chunk2idx.defaultReturnValue(-1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addSection(long pos) {
|
public void addSection(long pos) {
|
||||||
@@ -74,37 +79,33 @@ public class ChunkBoundRenderer {
|
|||||||
this.remQueue.forEach(this::_remPos);//TODO: REPLACE WITH SCATTER COMPUTE
|
this.remQueue.forEach(this::_remPos);//TODO: REPLACE WITH SCATTER COMPUTE
|
||||||
this.remQueue.clear();
|
this.remQueue.clear();
|
||||||
if (this.chunk2idx.isEmpty()&&!wasEmpty) {//When going from stuff to nothing need to clear the depth buffer
|
if (this.chunk2idx.isEmpty()&&!wasEmpty) {//When going from stuff to nothing need to clear the depth buffer
|
||||||
glClearNamedFramebufferfv(this.frameBuffer.id, GL_DEPTH, 0, new float[]{0});
|
viewport.depthBoundingBuffer.clear(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.depthBuffer.getWidth() != viewport.width || this.depthBuffer.getHeight() != viewport.height) {
|
|
||||||
this.depthBuffer.free();
|
|
||||||
this.depthBuffer = new GlTexture().store(GL_DEPTH_COMPONENT24, 1, viewport.width, viewport.height);
|
|
||||||
this.frameBuffer.bind(GL_DEPTH_ATTACHMENT, this.depthBuffer).verify();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.chunk2idx.isEmpty() && this.addQueue.isEmpty()) return;
|
if (this.chunk2idx.isEmpty() && this.addQueue.isEmpty()) return;
|
||||||
|
|
||||||
|
viewport.depthBoundingBuffer.clear(0);
|
||||||
|
|
||||||
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, 128);
|
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, 128);
|
||||||
long matPtr = ptr; ptr += 4*4*4;
|
long matPtr = ptr; ptr += 4*4*4;
|
||||||
|
|
||||||
final float renderDistance = MinecraftClient.getInstance().options.getClampedViewDistance()*16;//In blocks
|
final float renderDistance = Minecraft.getInstance().options.getEffectiveRenderDistance()*16;//In blocks
|
||||||
|
|
||||||
{//This is recomputed to be in chunk section space not worldsection
|
{//This is recomputed to be in chunk section space not worldsection
|
||||||
int sx = MathHelper.floor(viewport.cameraX) >> 4;
|
int sx = (int)(viewport.cameraX);
|
||||||
int sy = MathHelper.floor(viewport.cameraY) >> 4;
|
int sy = (int)(viewport.cameraY);
|
||||||
int sz = MathHelper.floor(viewport.cameraZ) >> 4;
|
int sz = (int)(viewport.cameraZ);
|
||||||
new Vector3i(sx, sy, sz).getToAddress(ptr); ptr += 4*4;
|
new Vector3i(sx, sy, sz).getToAddress(ptr); ptr += 4*4;
|
||||||
|
|
||||||
var negInnerSec = new Vector3f(
|
var negInnerSec = new Vector3f(
|
||||||
-(float) (viewport.cameraX - (sx << 4)),
|
(float) (viewport.cameraX - sx),
|
||||||
-(float) (viewport.cameraY - (sy << 4)),
|
(float) (viewport.cameraY - sy),
|
||||||
-(float) (viewport.cameraZ - (sz << 4)));
|
(float) (viewport.cameraZ - sz));
|
||||||
|
|
||||||
viewport.MVP.translate(negInnerSec, new Matrix4f()).getToAddress(matPtr);
|
|
||||||
|
|
||||||
negInnerSec.getToAddress(ptr); ptr += 4*3;
|
negInnerSec.getToAddress(ptr); ptr += 4*3;
|
||||||
|
viewport.MVP.translate(negInnerSec.negate(), new Matrix4f()).getToAddress(matPtr);
|
||||||
MemoryUtil.memPutFloat(ptr, renderDistance); ptr += 4;
|
MemoryUtil.memPutFloat(ptr, renderDistance); ptr += 4;
|
||||||
}
|
}
|
||||||
UploadStream.INSTANCE.commit();
|
UploadStream.INSTANCE.commit();
|
||||||
@@ -116,20 +117,20 @@ public class ChunkBoundRenderer {
|
|||||||
glFrontFace(GL_CW);//Reverse winding order
|
glFrontFace(GL_CW);//Reverse winding order
|
||||||
|
|
||||||
//"reverse depth buffer" it goes from 0->1 where 1 is far away
|
//"reverse depth buffer" it goes from 0->1 where 1 is far away
|
||||||
glClearNamedFramebufferfv(this.frameBuffer.id, GL_DEPTH, 0, new float[]{0});
|
|
||||||
glEnable(GL_CULL_FACE);
|
glEnable(GL_CULL_FACE);
|
||||||
glEnable(GL_DEPTH_TEST);
|
glEnable(GL_DEPTH_TEST);
|
||||||
glDepthFunc(GL_GREATER);
|
glDepthFunc(GL_GREATER);
|
||||||
}
|
}
|
||||||
|
|
||||||
glBindVertexArray(RenderService.STATIC_VAO);
|
glBindVertexArray(GlVertexArray.STATIC_VAO);
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, this.frameBuffer.id);
|
viewport.depthBoundingBuffer.bind();
|
||||||
this.rasterShader.bind();
|
this.rasterShader.bind();
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE_BB_BYTE.id());
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE_BB_BYTE.id());
|
||||||
|
this.pipeline.bindUniforms();
|
||||||
|
|
||||||
//Batch the draws into groups of size 32
|
//Batch the draws into groups of size 32
|
||||||
int count = this.chunk2idx.size();
|
int count = this.chunk2idx.size();
|
||||||
if (count > 32) {
|
if (count >= 32) {
|
||||||
glDrawElementsInstanced(GL_TRIANGLES, 6 * 2 * 3 * 32, GL_UNSIGNED_BYTE, 0, count/32);
|
glDrawElementsInstanced(GL_TRIANGLES, 6 * 2 * 3 * 32, GL_UNSIGNED_BYTE, 0, count/32);
|
||||||
}
|
}
|
||||||
if (count%32 != 0) {
|
if (count%32 != 0) {
|
||||||
@@ -224,15 +225,8 @@ public class ChunkBoundRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void free() {
|
public void free() {
|
||||||
this.depthBuffer.free();
|
|
||||||
this.frameBuffer.free();
|
|
||||||
|
|
||||||
this.rasterShader.free();
|
this.rasterShader.free();
|
||||||
this.uniformBuffer.free();
|
this.uniformBuffer.free();
|
||||||
this.chunkPosBuffer.free();
|
this.chunkPosBuffer.free();
|
||||||
}
|
}
|
||||||
|
|
||||||
public GlTexture getDepthBoundTexture() {
|
|
||||||
return this.depthBuffer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
|||||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
||||||
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.concurrent.locks.StampedLock;
|
|
||||||
|
|
||||||
//CPU side cache for section geometry, not thread safe
|
//CPU side cache for section geometry, not thread safe
|
||||||
public class GeometryCache {
|
public class GeometryCache {
|
||||||
|
|||||||
@@ -1,247 +0,0 @@
|
|||||||
package me.cortex.voxy.client.core.rendering;
|
|
||||||
|
|
||||||
import me.cortex.voxy.client.RenderStatistics;
|
|
||||||
import me.cortex.voxy.client.TimingStatistics;
|
|
||||||
import me.cortex.voxy.client.VoxyClient;
|
|
||||||
import me.cortex.voxy.client.core.gl.Capabilities;
|
|
||||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
|
||||||
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
|
|
||||||
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.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;
|
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
|
||||||
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static org.lwjgl.opengl.GL42.*;
|
|
||||||
|
|
||||||
public class RenderService<T extends AbstractSectionRenderer<J, Q>, J extends Viewport<J>, Q extends IGeometryData> {
|
|
||||||
public static final int STATIC_VAO = glGenVertexArrays();
|
|
||||||
|
|
||||||
private final ViewportSelector<?> viewportSelector;
|
|
||||||
private final Q geometryData;
|
|
||||||
private final AbstractSectionRenderer<J, Q> sectionRenderer;
|
|
||||||
|
|
||||||
private final AsyncNodeManager nodeManager;
|
|
||||||
private final NodeCleaner nodeCleaner;
|
|
||||||
private final HierarchicalOcclusionTraverser traversal;
|
|
||||||
private final ModelBakerySubsystem modelService;
|
|
||||||
private final RenderGenerationService renderGen;
|
|
||||||
|
|
||||||
private final WorldEngine world;
|
|
||||||
|
|
||||||
private static long getGeometryBufferSize() {
|
|
||||||
long geometryCapacity = Math.min((1L<<(64-Long.numberOfLeadingZeros(Capabilities.INSTANCE.ssboMaxSize-1)))<<1, 1L<<32)-1024/*(1L<<32)-1024*/;
|
|
||||||
if (Capabilities.INSTANCE.isIntel) {
|
|
||||||
geometryCapacity = Math.max(geometryCapacity, 1L<<30);//intel moment, force min 1gb
|
|
||||||
}
|
|
||||||
|
|
||||||
//Limit to available dedicated memory if possible
|
|
||||||
if (Capabilities.INSTANCE.canQueryGpuMemory) {
|
|
||||||
//512mb less than avalible,
|
|
||||||
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<<28;
|
|
||||||
//geometryCapacity = 1<<30;//1GB test
|
|
||||||
return geometryCapacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public RenderService(WorldEngine world, ServiceThreadPool serviceThreadPool) {
|
|
||||||
this.world = world;
|
|
||||||
this.modelService = new ModelBakerySubsystem(world.getMapper());
|
|
||||||
|
|
||||||
long geometryCapacity = getGeometryBufferSize();
|
|
||||||
|
|
||||||
this.geometryData = (Q) new BasicSectionGeometryData(1<<20, geometryCapacity);
|
|
||||||
|
|
||||||
//Max sections: ~500k
|
|
||||||
this.sectionRenderer = (T) new MDICSectionRenderer(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
|
|
||||||
|
|
||||||
this.viewportSelector = new ViewportSelector<>(this.sectionRenderer::createViewport);
|
|
||||||
this.renderGen = new RenderGenerationService(world, this.modelService, serviceThreadPool,
|
|
||||||
this.sectionRenderer.getGeometryManager() instanceof IUsesMeshlets,
|
|
||||||
()->true);
|
|
||||||
|
|
||||||
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.renderGen);
|
|
||||||
|
|
||||||
world.setDirtyCallback(this.nodeManager::worldEvent);
|
|
||||||
|
|
||||||
Arrays.stream(world.getMapper().getBiomeEntries()).forEach(this.modelService::addBiome);
|
|
||||||
world.getMapper().setBiomeCallback(this.modelService::addBiome);
|
|
||||||
|
|
||||||
this.nodeManager.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addTopLevelNode(long pos) {
|
|
||||||
this.nodeManager.addTopLevel(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeTopLevelNode(long pos) {
|
|
||||||
this.nodeManager.removeTopLevel(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void tickModelService(long budget) {
|
|
||||||
this.modelService.tick(budget);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
//Execute the hieracial selector
|
|
||||||
// render delta sections
|
|
||||||
|
|
||||||
//Hieracial is not an abstract thing but
|
|
||||||
// the section renderer is as it might have different backends, but they all accept a buffer containing the section list
|
|
||||||
|
|
||||||
|
|
||||||
TimingStatistics.G.start();
|
|
||||||
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
|
|
||||||
{
|
|
||||||
TimingStatistics.main.stop();
|
|
||||||
TimingStatistics.dynamic.start();
|
|
||||||
|
|
||||||
/*
|
|
||||||
this.sectionUpdateQueue.consume(128);
|
|
||||||
|
|
||||||
//if (this.modelService.getProcessingCount() < 750)
|
|
||||||
{//Very bad hack to try control things
|
|
||||||
this.geometryUpdateQueue.consumeNano(Math.max(3_000_000 - (System.nanoTime() - frameStart), 50_000));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.nodeManager.writeChanges(this.traversal.getNodeBuffer())) {//TODO: maybe move the node buffer out of the traversal class
|
|
||||||
UploadStream.INSTANCE.commit();
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
|
||||||
TimingStatistics.D.start();
|
|
||||||
//Tick download stream
|
|
||||||
DownloadStream.INSTANCE.tick();
|
|
||||||
TimingStatistics.D.stop();
|
|
||||||
|
|
||||||
this.nodeManager.tick(this.traversal.getNodeBuffer(), this.nodeCleaner);
|
|
||||||
//glFlush();
|
|
||||||
|
|
||||||
this.nodeCleaner.tick(this.traversal.getNodeBuffer());//Probably do this here??
|
|
||||||
|
|
||||||
TimingStatistics.dynamic.stop();
|
|
||||||
TimingStatistics.main.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT | GL_PIXEL_BUFFER_BARRIER_BIT);
|
|
||||||
|
|
||||||
TimingStatistics.I.start();
|
|
||||||
this.traversal.doTraversal(viewport);
|
|
||||||
TimingStatistics.I.stop();
|
|
||||||
|
|
||||||
} while (this.frexStillHasWork());
|
|
||||||
|
|
||||||
|
|
||||||
TimingStatistics.H.start();
|
|
||||||
this.sectionRenderer.buildDrawCalls(viewport);
|
|
||||||
TimingStatistics.H.stop();
|
|
||||||
|
|
||||||
TimingStatistics.G.start();
|
|
||||||
this.sectionRenderer.renderTemporal(viewport, depthBoundTexture);
|
|
||||||
TimingStatistics.G.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void renderFarAwayTranslucent(J viewport, GlTexture depthBoundTexture) {
|
|
||||||
this.sectionRenderer.renderTranslucent(viewport, depthBoundTexture);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addDebugData(List<String> debug) {
|
|
||||||
this.modelService.addDebugData(debug);
|
|
||||||
this.renderGen.addDebugData(debug);
|
|
||||||
this.sectionRenderer.addDebug(debug);
|
|
||||||
this.nodeManager.addDebug(debug);
|
|
||||||
|
|
||||||
if (RenderStatistics.enabled) {
|
|
||||||
debug.add("HTC: [" + Arrays.stream(flipCopy(RenderStatistics.hierarchicalTraversalCounts)).mapToObj(Integer::toString).collect(Collectors.joining(", "))+"]");
|
|
||||||
debug.add("HRS: [" + Arrays.stream(flipCopy(RenderStatistics.hierarchicalRenderSections)).mapToObj(Integer::toString).collect(Collectors.joining(", "))+"]");
|
|
||||||
debug.add("VS: [" + Arrays.stream(flipCopy(RenderStatistics.visibleSections)).mapToObj(Integer::toString).collect(Collectors.joining(", "))+"]");
|
|
||||||
debug.add("QC: [" + Arrays.stream(flipCopy(RenderStatistics.quadCount)).mapToObj(Integer::toString).collect(Collectors.joining(", "))+"]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int[] flipCopy(int[] array) {
|
|
||||||
int[] ret = new int[array.length];
|
|
||||||
int i = ret.length;
|
|
||||||
for (int j : array) {
|
|
||||||
ret[--i] = j;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void shutdown() {
|
|
||||||
//Cleanup callbacks
|
|
||||||
this.world.setDirtyCallback(null);
|
|
||||||
this.world.getMapper().setBiomeCallback(null);
|
|
||||||
this.world.getMapper().setStateCallback(null);
|
|
||||||
|
|
||||||
this.nodeManager.stop();
|
|
||||||
|
|
||||||
this.modelService.shutdown();
|
|
||||||
this.renderGen.shutdown();
|
|
||||||
this.viewportSelector.free();
|
|
||||||
this.sectionRenderer.free();
|
|
||||||
this.traversal.free();
|
|
||||||
this.nodeCleaner.free();
|
|
||||||
|
|
||||||
this.geometryData.free();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Viewport<?> getViewport() {
|
|
||||||
return this.viewportSelector.getViewport();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMeshQueueCount() {
|
|
||||||
return this.renderGen.getTaskCount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -69,7 +69,7 @@ public class SectionUpdateRouter implements ISectionWatcher {
|
|||||||
}
|
}
|
||||||
lock.unlock(stamp);
|
lock.unlock(stamp);
|
||||||
}
|
}
|
||||||
if ((delta&UPDATE_TYPE_BLOCK_BIT)!=0) {
|
if (((delta&types)&UPDATE_TYPE_BLOCK_BIT)!=0) {
|
||||||
//If we added it, immediately invoke for an update
|
//If we added it, immediately invoke for an update
|
||||||
this.initialRenderMeshGen.accept(position);
|
this.initialRenderMeshGen.accept(position);
|
||||||
}
|
}
|
||||||
@@ -138,19 +138,32 @@ public class SectionUpdateRouter implements ISectionWatcher {
|
|||||||
var lock = this.locks[idx];
|
var lock = this.locks[idx];
|
||||||
|
|
||||||
long stamp = lock.readLock();
|
long stamp = lock.readLock();
|
||||||
byte types = set.getOrDefault(position, (byte) 0);
|
byte types = (byte) (set.getOrDefault(position, (byte) 0)&type);
|
||||||
lock.unlockRead(stamp);
|
lock.unlockRead(stamp);
|
||||||
|
|
||||||
if (types!=0) {
|
if (types!=0) {
|
||||||
if ((type&WorldEngine.UPDATE_TYPE_CHILD_EXISTENCE_BIT)!=0) {
|
if ((types&WorldEngine.UPDATE_TYPE_CHILD_EXISTENCE_BIT)!=0) {
|
||||||
this.childUpdateCallback.accept(section);
|
this.childUpdateCallback.accept(section);
|
||||||
}
|
}
|
||||||
if ((type& UPDATE_TYPE_BLOCK_BIT)!=0) {
|
if ((types&UPDATE_TYPE_BLOCK_BIT)!=0) {
|
||||||
this.renderMeshGen.accept(section.key);
|
this.renderMeshGen.accept(section.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void triggerRemesh(long position) {
|
||||||
|
int idx = getSliceIndex(position);
|
||||||
|
var set = this.slices[idx];
|
||||||
|
var lock = this.locks[idx];
|
||||||
|
|
||||||
|
long stamp = lock.readLock();
|
||||||
|
byte types = set.getOrDefault(position, (byte) 0);
|
||||||
|
lock.unlockRead(stamp);
|
||||||
|
if ((types&UPDATE_TYPE_BLOCK_BIT)!=0) {
|
||||||
|
this.renderMeshGen.accept(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static int getSliceIndex(long value) {
|
private static int getSliceIndex(long value) {
|
||||||
value = (value ^ value >>> 30) * -4658895280553007687L;
|
value = (value ^ value >>> 30) * -4658895280553007687L;
|
||||||
value = (value ^ value >>> 27) * -7723592293110705685L;
|
value = (value ^ value >>> 27) * -7723592293110705685L;
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
package me.cortex.voxy.client.core.rendering;
|
package me.cortex.voxy.client.core.rendering;
|
||||||
|
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||||
|
import me.cortex.voxy.client.core.rendering.util.DepthFramebuffer;
|
||||||
import me.cortex.voxy.client.core.rendering.util.HiZBuffer;
|
import me.cortex.voxy.client.core.rendering.util.HiZBuffer;
|
||||||
import net.minecraft.util.math.MathHelper;
|
import net.caffeinemc.mods.sodium.client.util.FogParameters;
|
||||||
|
import net.minecraft.util.Mth;
|
||||||
import org.joml.*;
|
import org.joml.*;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
public abstract class Viewport <A extends Viewport<A>> {
|
public abstract class Viewport <A extends Viewport<A>> {
|
||||||
|
//public final HiZBuffer2 hiZBuffer = new HiZBuffer2();
|
||||||
public final HiZBuffer hiZBuffer = new HiZBuffer();
|
public final HiZBuffer hiZBuffer = new HiZBuffer();
|
||||||
|
public final DepthFramebuffer depthBoundingBuffer = new DepthFramebuffer();
|
||||||
|
|
||||||
private static final Field planesField;
|
private static final Field planesField;
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
@@ -22,13 +27,15 @@ public abstract class Viewport <A extends Viewport<A>> {
|
|||||||
public int width;
|
public int width;
|
||||||
public int height;
|
public int height;
|
||||||
public int frameId;
|
public int frameId;
|
||||||
public Matrix4f projection;
|
public Matrix4f vanillaProjection = new Matrix4f();
|
||||||
public Matrix4f modelView;
|
public Matrix4f projection = new Matrix4f();
|
||||||
|
public Matrix4f modelView = new Matrix4f();
|
||||||
public final FrustumIntersection frustum = new FrustumIntersection();
|
public final FrustumIntersection frustum = new FrustumIntersection();
|
||||||
public final Vector4f[] frustumPlanes;
|
public final Vector4f[] frustumPlanes;
|
||||||
public double cameraX;
|
public double cameraX;
|
||||||
public double cameraY;
|
public double cameraY;
|
||||||
public double cameraZ;
|
public double cameraZ;
|
||||||
|
public FogParameters fogParameters;
|
||||||
|
|
||||||
public final Matrix4f MVP = new Matrix4f();
|
public final Matrix4f MVP = new Matrix4f();
|
||||||
public final Vector3i section = new Vector3i();
|
public final Vector3i section = new Vector3i();
|
||||||
@@ -50,6 +57,12 @@ public abstract class Viewport <A extends Viewport<A>> {
|
|||||||
|
|
||||||
protected void delete0() {
|
protected void delete0() {
|
||||||
this.hiZBuffer.free();
|
this.hiZBuffer.free();
|
||||||
|
this.depthBoundingBuffer.free();
|
||||||
|
}
|
||||||
|
|
||||||
|
public A setVanillaProjection(Matrix4fc projection) {
|
||||||
|
this.vanillaProjection.set(projection);
|
||||||
|
return (A) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public A setProjection(Matrix4f projection) {
|
public A setProjection(Matrix4f projection) {
|
||||||
@@ -75,6 +88,11 @@ public abstract class Viewport <A extends Viewport<A>> {
|
|||||||
return (A) this;
|
return (A) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public A setFogParameters(FogParameters fogParameters) {
|
||||||
|
this.fogParameters = fogParameters;
|
||||||
|
return (A) this;
|
||||||
|
}
|
||||||
|
|
||||||
public A update() {
|
public A update() {
|
||||||
//MVP
|
//MVP
|
||||||
this.projection.mul(this.modelView, this.MVP);
|
this.projection.mul(this.modelView, this.MVP);
|
||||||
@@ -83,9 +101,9 @@ public abstract class Viewport <A extends Viewport<A>> {
|
|||||||
this.frustum.set(this.MVP, false);
|
this.frustum.set(this.MVP, false);
|
||||||
|
|
||||||
//Translation vectors
|
//Translation vectors
|
||||||
int sx = MathHelper.floor(this.cameraX)>>5;
|
int sx = Mth.floor(this.cameraX)>>5;
|
||||||
int sy = MathHelper.floor(this.cameraY)>>5;
|
int sy = Mth.floor(this.cameraY)>>5;
|
||||||
int sz = MathHelper.floor(this.cameraZ)>>5;
|
int sz = Mth.floor(this.cameraZ)>>5;
|
||||||
this.section.set(sx, sy, sz);
|
this.section.set(sx, sy, sz);
|
||||||
|
|
||||||
this.innerTranslation.set(
|
this.innerTranslation.set(
|
||||||
@@ -93,6 +111,10 @@ public abstract class Viewport <A extends Viewport<A>> {
|
|||||||
(float) (this.cameraY-(sy<<5)),
|
(float) (this.cameraY-(sy<<5)),
|
||||||
(float) (this.cameraZ-(sz<<5)));
|
(float) (this.cameraZ-(sz<<5)));
|
||||||
|
|
||||||
|
if (this.depthBoundingBuffer.resize(this.width, this.height)) {
|
||||||
|
this.depthBoundingBuffer.clear(0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
return (A) this;
|
return (A) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,54 @@
|
|||||||
package me.cortex.voxy.client.core.rendering;
|
package me.cortex.voxy.client.core.rendering;
|
||||||
|
|
||||||
import me.cortex.voxy.client.core.rendering.Viewport;
|
import me.cortex.voxy.client.core.util.IrisUtil;
|
||||||
import net.fabricmc.loader.api.FabricLoader;
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
import org.vivecraft.client_vr.ClientDataHolderVR;
|
import org.vivecraft.api.client.VRRenderingAPI;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import static org.vivecraft.api.client.data.RenderPass.VANILLA;
|
||||||
|
|
||||||
public class ViewportSelector <T extends Viewport<?>> {
|
public class ViewportSelector <T extends Viewport<?>> {
|
||||||
public static final boolean VIVECRAFT_INSTALLED = FabricLoader.getInstance().isModLoaded("vivecraft");
|
public static final boolean VIVECRAFT_INSTALLED = FabricLoader.getInstance().isModLoaded("vivecraft");
|
||||||
|
|
||||||
private final Supplier<T> creator;
|
private final Supplier<T> creator;
|
||||||
private final T defaultViewport;
|
private final T defaultViewport;
|
||||||
private final Map<Object, T> extraViewports = new HashMap<>();
|
private final Map<Object, T> extraViewports = new HashMap<>();//TODO should maybe be a weak hashmap with value cleanup queue thing?
|
||||||
|
|
||||||
public ViewportSelector(Supplier<T> viewportCreator) {
|
public ViewportSelector(Supplier<T> viewportCreator) {
|
||||||
this.creator = viewportCreator;
|
this.creator = viewportCreator;
|
||||||
this.defaultViewport = viewportCreator.get();
|
this.defaultViewport = viewportCreator.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
private T getVivecraftViewport() {
|
private T getOrCreate(Object holder) {
|
||||||
var cdh = ClientDataHolderVR.getInstance();
|
return this.extraViewports.computeIfAbsent(holder, a->this.creator.get());
|
||||||
var pass = cdh.currentPass;
|
|
||||||
if (pass == null) {
|
|
||||||
return this.defaultViewport;
|
|
||||||
}
|
|
||||||
return this.extraViewports.computeIfAbsent(pass, a->this.creator.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public T getViewport() {
|
private T getVivecraftViewport() {
|
||||||
if (VIVECRAFT_INSTALLED) {
|
var pass = VRRenderingAPI.instance().getCurrentRenderPass();
|
||||||
return getVivecraftViewport();
|
if (pass == null || pass == VANILLA) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return this.defaultViewport;
|
return this.getOrCreate(pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Object IRIS_SHADOW_OBJECT = new Object();
|
||||||
|
public T getViewport() {
|
||||||
|
T viewport = null;
|
||||||
|
if (viewport == null && VIVECRAFT_INSTALLED) {
|
||||||
|
viewport = getVivecraftViewport();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viewport == null && IrisUtil.irisShadowActive()) {
|
||||||
|
viewport = this.getOrCreate(IRIS_SHADOW_OBJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viewport == null) {
|
||||||
|
viewport = this.defaultViewport;
|
||||||
|
}
|
||||||
|
return viewport;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void free() {
|
public void free() {
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ public final class BuiltSection {
|
|||||||
public final int aabb;
|
public final int aabb;
|
||||||
public final MemoryBuffer geometryBuffer;
|
public final MemoryBuffer geometryBuffer;
|
||||||
public final int[] offsets;
|
public final int[] offsets;
|
||||||
|
public final MemoryBuffer occupancy;
|
||||||
|
|
||||||
private BuiltSection(long position, byte children) {
|
private BuiltSection(long position, byte children) {
|
||||||
this(position, children, -1, null, null);
|
this(position, children, -1, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BuiltSection empty(long position) {
|
public static BuiltSection empty(long position) {
|
||||||
@@ -25,7 +26,7 @@ public final class BuiltSection {
|
|||||||
return new BuiltSection(position, children);
|
return new BuiltSection(position, children);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BuiltSection(long position, byte childExistence, int aabb, MemoryBuffer geometryBuffer, int[] offsets) {
|
public BuiltSection(long position, byte childExistence, int aabb, MemoryBuffer geometryBuffer, int[] offsets, MemoryBuffer occupancy) {
|
||||||
this.position = position;
|
this.position = position;
|
||||||
this.childExistence = childExistence;
|
this.childExistence = childExistence;
|
||||||
this.aabb = aabb;
|
this.aabb = aabb;
|
||||||
@@ -39,16 +40,20 @@ public final class BuiltSection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.occupancy = occupancy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BuiltSection clone() {
|
public BuiltSection clone() {
|
||||||
return new BuiltSection(this.position, this.childExistence, this.aabb, this.geometryBuffer!=null?this.geometryBuffer.copy():null, this.offsets!=null?Arrays.copyOf(this.offsets, this.offsets.length):null);
|
return new BuiltSection(this.position, this.childExistence, this.aabb, this.geometryBuffer!=null?this.geometryBuffer.copy():null, this.offsets!=null?Arrays.copyOf(this.offsets, this.offsets.length):null, this.occupancy!=null?this.occupancy.copy():null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void free() {
|
public void free() {
|
||||||
if (this.geometryBuffer != null) {
|
if (this.geometryBuffer != null) {
|
||||||
this.geometryBuffer.free();
|
this.geometryBuffer.free();
|
||||||
}
|
}
|
||||||
|
if (this.occupancy != null) {
|
||||||
|
this.occupancy.free();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package me.cortex.voxy.client.core.rendering.building;
|
||||||
|
|
||||||
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.BitSet;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
//Block occupancy, 2 lvl compacted bitset for occluding block existance
|
||||||
|
// TODO: need to add neighboring chunk data aswell? or somehow do a linking thing where this is stored in a secondary storage
|
||||||
|
// where we can link them together (or store the neighbor faces seperately or something) might be out of scope for this class
|
||||||
|
public class OccupancySet {
|
||||||
|
private long topLvl;//4x4x4
|
||||||
|
private final long[] bottomLvl = new long[(4*4*4)*8];
|
||||||
|
public void set(final int pos) {
|
||||||
|
final long topBit = 1L<<Integer.compress(pos, 0b11000_11000_11000);
|
||||||
|
final int botIdx = Integer.compress(pos, 0b00111_00111_00111);
|
||||||
|
|
||||||
|
int baseBotIdx = Long.bitCount(this.topLvl&(topBit-1))*8;
|
||||||
|
if ((this.topLvl & topBit) == 0) {
|
||||||
|
//we need to shuffle up all the bottomlvl
|
||||||
|
long toMove = this.topLvl & (~((topBit << 1) - 1));
|
||||||
|
if (toMove != 0) {
|
||||||
|
int base = baseBotIdx+8;//+8 cause were bubbling
|
||||||
|
int count = Long.bitCount(toMove);
|
||||||
|
for (int i = base+count*8-1; base<=i; i--) {
|
||||||
|
this.bottomLvl[i] = this.bottomLvl[i-8];
|
||||||
|
}
|
||||||
|
for (int i = baseBotIdx; i<baseBotIdx+8; i++) {
|
||||||
|
this.bottomLvl[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.topLvl |= topBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bottomLvl[baseBotIdx+(botIdx>>6)] |= 1L<<(botIdx&63);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean get(int pos) {
|
||||||
|
final long topBit = 1L<<Integer.compress(pos, 0b11000_11000_11000);
|
||||||
|
final int botIdx = Integer.compress(pos, 0b00111_00111_00111);
|
||||||
|
if ((this.topLvl & topBit) == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int baseBotIdx = Long.bitCount(this.topLvl&(topBit-1))*8;
|
||||||
|
|
||||||
|
return (this.bottomLvl[baseBotIdx+(botIdx>>6)]&(1L<<(botIdx&63)))!=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
if (this.topLvl != 0) {
|
||||||
|
Arrays.fill(this.bottomLvl, 0);
|
||||||
|
}
|
||||||
|
this.topLvl = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int writeSize() {
|
||||||
|
return 8+Long.bitCount(this.topLvl)*8*8;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return this.topLvl == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(long ptr, boolean asLongs) {
|
||||||
|
if (asLongs) {
|
||||||
|
MemoryUtil.memPutLong(ptr, this.topLvl); ptr += 8;
|
||||||
|
int cnt = Long.bitCount(this.topLvl);
|
||||||
|
for (int i = 0; i < cnt; i++) {
|
||||||
|
for (int j = 0; j < 8; j++) {
|
||||||
|
MemoryUtil.memPutLong(ptr, this.bottomLvl[i*8+j]); ptr += 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MemoryUtil.memPutInt(ptr, (int) (this.topLvl>>>32)); ptr += 4;
|
||||||
|
MemoryUtil.memPutInt(ptr, (int) this.topLvl); ptr += 4;
|
||||||
|
int cnt = Long.bitCount(this.topLvl);
|
||||||
|
for (int i = 0; i < cnt; i++) {
|
||||||
|
for (int j = 0; j < 8; j++) {
|
||||||
|
long v = this.bottomLvl[i*8+j];
|
||||||
|
MemoryUtil.memPutInt(ptr, (int) (v>>>32)); ptr += 4;
|
||||||
|
MemoryUtil.memPutInt(ptr, (int) v); ptr += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
for (int q = 0; q < 1000; q++) {
|
||||||
|
var o = new OccupancySet();
|
||||||
|
var r = new Random(12523532643L*q);
|
||||||
|
var bs = new BitSet(32 * 32 * 32);
|
||||||
|
for (int i = 0; i < 5000; i++) {
|
||||||
|
int p = r.nextInt(32 * 32 * 32);
|
||||||
|
o.set(p);
|
||||||
|
bs.set(p);
|
||||||
|
|
||||||
|
for (int j = 0; j < 32 * 32 * 32; j++) {
|
||||||
|
if (o.get(j) != bs.get(j)) throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.ModelFactory;
|
||||||
import me.cortex.voxy.client.core.model.ModelQueries;
|
import me.cortex.voxy.client.core.model.ModelQueries;
|
||||||
import me.cortex.voxy.client.core.util.ScanMesher2D;
|
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.MemoryBuffer;
|
||||||
import me.cortex.voxy.common.util.UnsafeUtil;
|
import me.cortex.voxy.common.util.UnsafeUtil;
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
@@ -16,7 +17,10 @@ import java.util.Arrays;
|
|||||||
|
|
||||||
|
|
||||||
public class RenderDataFactory {
|
public class RenderDataFactory {
|
||||||
|
private static final boolean BUILD_OCCUPANCY_SET = false;
|
||||||
|
|
||||||
private static final boolean CHECK_NEIGHBOR_FACE_OCCLUSION = true;
|
private static final boolean CHECK_NEIGHBOR_FACE_OCCLUSION = true;
|
||||||
|
private static final boolean DISABLE_CULL_SAME_OCCLUDES = false;//TODO: FIX TRANSLUCENTS (e.g. stained glass) breaking on chunk boarders with this set to false (it might be something else????)
|
||||||
|
|
||||||
private static final boolean VERIFY_MESHING = VoxyCommon.isVerificationFlagOn("verifyMeshing");
|
private static final boolean VERIFY_MESHING = VoxyCommon.isVerificationFlagOn("verifyMeshing");
|
||||||
|
|
||||||
@@ -58,6 +62,8 @@ public class RenderDataFactory {
|
|||||||
|
|
||||||
private int quadCount = 0;
|
private int quadCount = 0;
|
||||||
|
|
||||||
|
private final OccupancySet occupancy = new OccupancySet();
|
||||||
|
|
||||||
//Wont work for double sided quads
|
//Wont work for double sided quads
|
||||||
private final class Mesher extends ScanMesher2D {
|
private final class Mesher extends ScanMesher2D {
|
||||||
public int auxiliaryPosition = 0;
|
public int auxiliaryPosition = 0;
|
||||||
@@ -189,15 +195,16 @@ public class RenderDataFactory {
|
|||||||
boolean a = ModelQueries.isTranslucent(metadata);
|
boolean a = ModelQueries.isTranslucent(metadata);
|
||||||
boolean b = ModelQueries.isDoubleSided(metadata);
|
boolean b = ModelQueries.isDoubleSided(metadata);
|
||||||
//Pre shift by 1
|
//Pre shift by 1
|
||||||
type = a|b?0:4;
|
//type = a|b?0:4;
|
||||||
type |= b?2:0;
|
//type |= b&!a?2:0;
|
||||||
|
type = a?0:(b?2:4);
|
||||||
}
|
}
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long packPartialQuadData(int modelId, long state, long metadata) {
|
private static long packPartialQuadData(int modelId, long state, long metadata) {
|
||||||
//This uses hardcoded data to shuffle things
|
//This uses hardcoded data to shuffle things
|
||||||
long lightAndBiome = (state&((0x1FFL<<47)|(0xFFL<<56)))>>1;
|
long lightAndBiome = (state&((0x1FFL<<47)|(0xFFL<<56)))>>>1;
|
||||||
lightAndBiome &= ModelQueries.isBiomeColoured(metadata)?-1:~(0x1FFL<<46);//46 not 47 because is already shifted by 1 THIS WASTED 4 HOURS ;-; aaaaaAAAAAA
|
lightAndBiome &= ModelQueries.isBiomeColoured(metadata)?-1:~(0x1FFL<<46);//46 not 47 because is already shifted by 1 THIS WASTED 4 HOURS ;-; aaaaaAAAAAA
|
||||||
lightAndBiome &= ModelQueries.isFullyOpaque(metadata)?~(0xFFL<<55):-1;//If its fully opaque it always uses neighbor light?
|
lightAndBiome &= ModelQueries.isFullyOpaque(metadata)?~(0xFFL<<55):-1;//If its fully opaque it always uses neighbor light?
|
||||||
|
|
||||||
@@ -215,11 +222,11 @@ public class RenderDataFactory {
|
|||||||
long pureFluid = 0;
|
long pureFluid = 0;
|
||||||
long partialFluid = 0;
|
long partialFluid = 0;
|
||||||
|
|
||||||
int neighborAcquireMsk = 0;//-+x, -+z, -+y
|
int neighborAcquireMskAndFlags = 0;//-+x, -+z, -+y
|
||||||
for (int i = 0; i < 32*32*32;) {
|
for (int i = 0; i < 32*32*32;) {
|
||||||
long block = rawSectionData[i];//Get the block mapping
|
long block = rawSectionData[i];//Get the block mapping
|
||||||
if (Mapper.isAir(block)) {//If it is air, just emit lighting
|
if (Mapper.isAir(block)) {//If it is air, just emit lighting
|
||||||
sectionData[i * 2] = (block&(0xFFL<<56))>>1;
|
sectionData[i * 2] = (block&(0xFFL<<56))>>>1;
|
||||||
sectionData[i * 2 + 1] = 0;
|
sectionData[i * 2 + 1] = 0;
|
||||||
} else {
|
} else {
|
||||||
int modelId = rawModelIds[Mapper.getBlockId(block)];
|
int modelId = rawModelIds[Mapper.getBlockId(block)];
|
||||||
@@ -255,17 +262,17 @@ public class RenderDataFactory {
|
|||||||
|
|
||||||
int neighborMsk = 0;
|
int neighborMsk = 0;
|
||||||
//-+x
|
//-+x
|
||||||
neighborMsk |= packedEmpty&1;//-x
|
neighborMsk += packedEmpty&1;//-x
|
||||||
neighborMsk |= (packedEmpty>>>30)&0b10;//+x
|
neighborMsk += (packedEmpty>>>30)&0b10;//+x
|
||||||
|
|
||||||
//notEmpty = (notEmpty != 0)?1:0;
|
//notEmpty = (notEmpty != 0)?1:0;
|
||||||
neighborMsk |= ((((i - 1) >> 10) == 0) ? 0b100 : 0)*(packedEmpty!=0?1:0);//-y
|
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 - 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 - 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
|
neighborMsk += (((((i - 1) >> 5) & 0x1F) == 31) ? 0b100000 : 0)*((notEmpty>>>32)!=0?1:0);//+z
|
||||||
|
|
||||||
neighborAcquireMsk |= neighborMsk;
|
|
||||||
|
|
||||||
|
neighborAcquireMskAndFlags |= neighborMsk;
|
||||||
|
neighborAcquireMskAndFlags |= opaque!=0?(1<<6):0;
|
||||||
|
|
||||||
opaque = 0;
|
opaque = 0;
|
||||||
notEmpty = 0;
|
notEmpty = 0;
|
||||||
@@ -273,7 +280,7 @@ public class RenderDataFactory {
|
|||||||
partialFluid = 0;
|
partialFluid = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return neighborAcquireMsk;
|
return neighborAcquireMskAndFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void acquireNeighborData(WorldSection section, int msk) {
|
private void acquireNeighborData(WorldSection section, int msk) {
|
||||||
@@ -339,9 +346,9 @@ public class RenderDataFactory {
|
|||||||
private static final long LM = (0xFFL<<55);
|
private static final long LM = (0xFFL<<55);
|
||||||
|
|
||||||
private static boolean shouldMeshNonOpaqueBlockFace(int face, long quad, long meta, long neighborQuad, long neighborMeta) {
|
private static boolean shouldMeshNonOpaqueBlockFace(int face, long quad, long meta, long neighborQuad, long neighborMeta) {
|
||||||
if (((quad^neighborQuad)&(0xFFFFL<<26))==0) return false;//This is a hack, if the neigbor and this are the same, dont mesh the face
|
if (((quad^neighborQuad)&(0xFFFFL<<26))==0 && (DISABLE_CULL_SAME_OCCLUDES || (ModelQueries.cullsSame(meta)||ModelQueries.faceOccludes(meta, face)))) return false;//This is a hack, if the neigbor and this are the same, dont mesh the face// TODO: FIXME
|
||||||
if (!ModelQueries.faceExists(meta, face)) return false;//Dont mesh if no face
|
if (!ModelQueries.faceExists(meta, face)) return false;//Dont mesh if no face
|
||||||
//if (ModelQueries.faceCanBeOccluded(meta, face)) //TODO: maybe enable this
|
if (ModelQueries.faceCanBeOccluded(meta, face)) //TODO: maybe enable this
|
||||||
if (ModelQueries.faceOccludes(neighborMeta, face^1)) return false;
|
if (ModelQueries.faceOccludes(neighborMeta, face^1)) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -395,16 +402,21 @@ public class RenderDataFactory {
|
|||||||
int iA = idx * 2 + (facingForward == 1 ? 0 : shift);
|
int iA = idx * 2 + (facingForward == 1 ? 0 : shift);
|
||||||
int iB = idx * 2 + (facingForward == 1 ? shift : 0);
|
int iB = idx * 2 + (facingForward == 1 ? shift : 0);
|
||||||
|
|
||||||
|
long selfModel = this.sectionData[iA];
|
||||||
|
long nextModel = this.sectionData[iB];
|
||||||
|
|
||||||
//Check if next culls this face
|
//Check if next culls this face
|
||||||
if (CHECK_NEIGHBOR_FACE_OCCLUSION) {
|
if (CHECK_NEIGHBOR_FACE_OCCLUSION) {
|
||||||
if (ModelQueries.faceOccludes(this.sectionData[iB + 1], (axis << 1) | (1 - facingForward))) {
|
long neighbor = this.sectionData[iB + 1];
|
||||||
|
boolean culls = false;
|
||||||
|
culls |= ((selfModel^nextModel)&(0xFFFFL<<26))==0&&ModelQueries.cullsSame(neighbor);
|
||||||
|
culls |= ModelQueries.faceOccludes(neighbor, (axis << 1) | (1 - facingForward));
|
||||||
|
if (culls) {
|
||||||
this.blockMesher.skip(1);
|
this.blockMesher.skip(1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
long selfModel = this.sectionData[iA];
|
|
||||||
long nextModel = this.sectionData[iB];
|
|
||||||
this.blockMesher.putNext(((long) facingForward) |//Facing
|
this.blockMesher.putNext(((long) facingForward) |//Facing
|
||||||
(selfModel&~LM) |
|
(selfModel&~LM) |
|
||||||
(nextModel&LM)//Apply lighting
|
(nextModel&LM)//Apply lighting
|
||||||
@@ -450,9 +462,12 @@ public class RenderDataFactory {
|
|||||||
|
|
||||||
int neighborIdx = ((axis+1)*32*32 * 2)+(side)*32*32;
|
int neighborIdx = ((axis+1)*32*32 * 2)+(side)*32*32;
|
||||||
long neighborId = this.neighboringFaces[neighborIdx + (other*32) + index];
|
long neighborId = this.neighboringFaces[neighborIdx + (other*32) + index];
|
||||||
|
long A = this.sectionData[idx * 2];
|
||||||
|
|
||||||
if (Mapper.getBlockId(neighborId) != 0) {//Not air
|
int nib = Mapper.getBlockId(neighborId);
|
||||||
long meta = this.modelMan.getModelMetadataFromClientId(this.modelMan.getModelId(Mapper.getBlockId(neighborId)));
|
if (nib != 0) {//Not air
|
||||||
|
int cid = this.modelMan.getModelId(nib);
|
||||||
|
long meta = this.modelMan.getModelMetadataFromClientId(cid);
|
||||||
if (ModelQueries.isFullyOpaque(meta)) {//Dont mesh this face
|
if (ModelQueries.isFullyOpaque(meta)) {//Dont mesh this face
|
||||||
this.blockMesher.skip(1);
|
this.blockMesher.skip(1);
|
||||||
continue;
|
continue;
|
||||||
@@ -461,7 +476,10 @@ public class RenderDataFactory {
|
|||||||
//This very funnily causes issues when not combined with meshing non full opaque geometry
|
//This very funnily causes issues when not combined with meshing non full opaque geometry
|
||||||
//TODO:FIXME, when non opaque geometry is added
|
//TODO:FIXME, when non opaque geometry is added
|
||||||
if (CHECK_NEIGHBOR_FACE_OCCLUSION) {
|
if (CHECK_NEIGHBOR_FACE_OCCLUSION) {
|
||||||
if (ModelQueries.faceOccludes(meta, (axis << 1) | (1 - side))) {
|
boolean culls = false;
|
||||||
|
culls |= cid==((A>>26)&0xFFFF)&&ModelQueries.cullsSame(meta);
|
||||||
|
culls |= ModelQueries.faceOccludes(meta, (axis << 1) | (1 - side));
|
||||||
|
if (culls) {
|
||||||
this.blockMesher.skip(1);
|
this.blockMesher.skip(1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -469,11 +487,10 @@ public class RenderDataFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
long A = this.sectionData[idx * 2];
|
|
||||||
|
|
||||||
this.blockMesher.putNext(((side == 0) ? 0L : 1L) |
|
this.blockMesher.putNext(((side == 0) ? 0L : 1L) |
|
||||||
(A&~LM) |
|
(A&~LM) |
|
||||||
((neighborId & (0xFFL << 56)) >> 1)
|
((neighborId & (0xFFL << 56)) >>> 1)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -529,7 +546,7 @@ public class RenderDataFactory {
|
|||||||
int bi = facingForward == 1 ? b : a;
|
int bi = facingForward == 1 ? b : a;
|
||||||
|
|
||||||
//TODO: check if must cull against next entries face
|
//TODO: check if must cull against next entries face
|
||||||
if (CHECK_NEIGHBOR_FACE_OCCLUSION) {
|
if (CHECK_NEIGHBOR_FACE_OCCLUSION) {//TODO:SELF OCCLUSION
|
||||||
if (ModelQueries.faceOccludes(this.sectionData[bi + 1], (axis << 1) | (1 - facingForward))) {
|
if (ModelQueries.faceOccludes(this.sectionData[bi + 1], (axis << 1) | (1 - facingForward))) {
|
||||||
this.blockMesher.skip(1);
|
this.blockMesher.skip(1);
|
||||||
continue;
|
continue;
|
||||||
@@ -550,7 +567,7 @@ public class RenderDataFactory {
|
|||||||
A &= ~0b110L; A |= getQuadTyping(Am);
|
A &= ~0b110L; A |= getQuadTyping(Am);
|
||||||
}
|
}
|
||||||
|
|
||||||
long lighter = A;
|
long lighter = this.sectionData[bi];
|
||||||
//if (!ModelQueries.faceUsesSelfLighting(Am, facingForward|(axis*2))) {//TODO: check this is right
|
//if (!ModelQueries.faceUsesSelfLighting(Am, facingForward|(axis*2))) {//TODO: check this is right
|
||||||
// lighter = this.sectionData[bi];
|
// lighter = this.sectionData[bi];
|
||||||
//}
|
//}
|
||||||
@@ -639,7 +656,7 @@ public class RenderDataFactory {
|
|||||||
|
|
||||||
this.blockMesher.putNext((side == 0 ? 0L : 1L) |
|
this.blockMesher.putNext((side == 0 ? 0L : 1L) |
|
||||||
(A&~LM) |
|
(A&~LM) |
|
||||||
((neighborId&(0xFFL<<56))>>1)
|
((neighborId&(0xFFL<<56))>>>1)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -751,7 +768,10 @@ public class RenderDataFactory {
|
|||||||
//Check and test if can cull W.R.T neighbor
|
//Check and test if can cull W.R.T neighbor
|
||||||
if (Mapper.getBlockId(neighborId) != 0) {//Not air
|
if (Mapper.getBlockId(neighborId) != 0) {//Not air
|
||||||
int modelId = this.modelMan.getModelId(Mapper.getBlockId(neighborId));
|
int modelId = this.modelMan.getModelId(Mapper.getBlockId(neighborId));
|
||||||
if (modelId == ((A>>26)&0xFFFF)) {
|
|
||||||
|
|
||||||
|
if (ModelQueries.cullsSame(B) && modelId == ((A>>26)&0xFFFF)) {//TODO: FIXME, this technically isnt correct as need to check self occulsion, thinks?
|
||||||
|
//TODO: check self occlsuion in the if statment
|
||||||
fail = true;
|
fail = true;
|
||||||
} else {
|
} else {
|
||||||
long meta = this.modelMan.getModelMetadataFromClientId(modelId);
|
long meta = this.modelMan.getModelMetadataFromClientId(modelId);
|
||||||
@@ -765,7 +785,10 @@ public class RenderDataFactory {
|
|||||||
long nA = this.sectionData[(idx+skipAmount) * 2];
|
long nA = this.sectionData[(idx+skipAmount) * 2];
|
||||||
long nB = this.sectionData[(idx+skipAmount) * 2 + 1];
|
long nB = this.sectionData[(idx+skipAmount) * 2 + 1];
|
||||||
boolean failB = false;
|
boolean failB = false;
|
||||||
if ((nA&(0xFFFFL<<26)) == (A&(0xFFFFL<<26))) {
|
//TODO: check self occlusion
|
||||||
|
|
||||||
|
if (ModelQueries.cullsSame(nB) && (nA&(0xFFFFL<<26)) == (A&(0xFFFFL<<26))) {//TODO: FIXME, this technically isnt correct as need to check self occulsion, thinks?
|
||||||
|
//TODO: check self occlsuion in the if statment
|
||||||
failB = true;
|
failB = true;
|
||||||
} else {
|
} else {
|
||||||
if (ModelQueries.faceOccludes(nB, (axis << 1) | (side))) {
|
if (ModelQueries.faceOccludes(nB, (axis << 1) | (side))) {
|
||||||
@@ -928,6 +951,7 @@ public class RenderDataFactory {
|
|||||||
|
|
||||||
//Check if next culls this face
|
//Check if next culls this face
|
||||||
if (CHECK_NEIGHBOR_FACE_OCCLUSION) {
|
if (CHECK_NEIGHBOR_FACE_OCCLUSION) {
|
||||||
|
//TODO: check self occlsuion
|
||||||
if (ModelQueries.faceOccludes(this.sectionData[iB + 1], (2 << 1) | (1 - facingForward))) {
|
if (ModelQueries.faceOccludes(this.sectionData[iB + 1], (2 << 1) | (1 - facingForward))) {
|
||||||
mesher.skip(1);
|
mesher.skip(1);
|
||||||
continue;
|
continue;
|
||||||
@@ -994,6 +1018,7 @@ public class RenderDataFactory {
|
|||||||
if (ModelQueries.isFullyOpaque(meta)) {
|
if (ModelQueries.isFullyOpaque(meta)) {
|
||||||
oki = false;
|
oki = false;
|
||||||
} else if (CHECK_NEIGHBOR_FACE_OCCLUSION && ModelQueries.faceOccludes(meta, (2 << 1) | (1 - 1))) {
|
} else if (CHECK_NEIGHBOR_FACE_OCCLUSION && ModelQueries.faceOccludes(meta, (2 << 1) | (1 - 1))) {
|
||||||
|
//TODO check self occlsion
|
||||||
oki = false;
|
oki = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1002,7 +1027,7 @@ public class RenderDataFactory {
|
|||||||
long A = this.sectionData[(i<<5) * 2];
|
long A = this.sectionData[(i<<5) * 2];
|
||||||
ma.putNext(0L |
|
ma.putNext(0L |
|
||||||
(A&~LM) |
|
(A&~LM) |
|
||||||
((neighborId&(0xFFL<<56))>>1)
|
((neighborId&(0xFFL<<56))>>>1)
|
||||||
);
|
);
|
||||||
} else {skipA++;}
|
} else {skipA++;}
|
||||||
} else {skipA++;}
|
} else {skipA++;}
|
||||||
@@ -1015,6 +1040,7 @@ public class RenderDataFactory {
|
|||||||
if (ModelQueries.isFullyOpaque(meta)) {
|
if (ModelQueries.isFullyOpaque(meta)) {
|
||||||
oki = false;
|
oki = false;
|
||||||
} else if (CHECK_NEIGHBOR_FACE_OCCLUSION && ModelQueries.faceOccludes(meta, (2 << 1) | (1 - 0))) {
|
} else if (CHECK_NEIGHBOR_FACE_OCCLUSION && ModelQueries.faceOccludes(meta, (2 << 1) | (1 - 0))) {
|
||||||
|
//TODO check self occlsion
|
||||||
oki = false;
|
oki = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1023,7 +1049,7 @@ public class RenderDataFactory {
|
|||||||
long A = this.sectionData[(i*32+31) * 2];
|
long A = this.sectionData[(i*32+31) * 2];
|
||||||
mb.putNext(1L |
|
mb.putNext(1L |
|
||||||
(A&~LM) |
|
(A&~LM) |
|
||||||
((neighborId&(0xFFL<<56))>>1)
|
((neighborId&(0xFFL<<56))>>>1)
|
||||||
);
|
);
|
||||||
} else {skipB++;}
|
} else {skipB++;}
|
||||||
} else {skipB++;}
|
} else {skipB++;}
|
||||||
@@ -1120,7 +1146,8 @@ public class RenderDataFactory {
|
|||||||
|
|
||||||
if (CHECK_NEIGHBOR_FACE_OCCLUSION) {
|
if (CHECK_NEIGHBOR_FACE_OCCLUSION) {
|
||||||
if (ModelQueries.faceOccludes(this.sectionData[bi + 1], (2 << 1) | (1 - facingForward))) {
|
if (ModelQueries.faceOccludes(this.sectionData[bi + 1], (2 << 1) | (1 - facingForward))) {
|
||||||
this.blockMesher.skip(1);
|
//TODO check self occlsion
|
||||||
|
mesher.skip(1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1140,7 +1167,7 @@ public class RenderDataFactory {
|
|||||||
A &= ~0b110L; A |= getQuadTyping(Am);
|
A &= ~0b110L; A |= getQuadTyping(Am);
|
||||||
}
|
}
|
||||||
|
|
||||||
long lighter = A;
|
long lighter = this.sectionData[bi];
|
||||||
//if (!ModelQueries.faceUsesSelfLighting(Am, facingForward|(axis*2))) {//TODO: check this is right
|
//if (!ModelQueries.faceUsesSelfLighting(Am, facingForward|(axis*2))) {//TODO: check this is right
|
||||||
// lighter = this.sectionData[bi];
|
// lighter = this.sectionData[bi];
|
||||||
//}
|
//}
|
||||||
@@ -1244,7 +1271,7 @@ public class RenderDataFactory {
|
|||||||
ma.skip(skipA); skipA = 0;
|
ma.skip(skipA); skipA = 0;
|
||||||
|
|
||||||
//TODO: LIGHTING
|
//TODO: LIGHTING
|
||||||
long lightData = ((neighborId&(0xFFL<<56))>>1);//A;
|
long lightData = ((neighborId&(0xFFL<<56))>>>1);//A;
|
||||||
//if (!ModelQueries.faceUsesSelfLighting(Am, facingForward|(axis*2))) {//TODO: check this is right
|
//if (!ModelQueries.faceUsesSelfLighting(Am, facingForward|(axis*2))) {//TODO: check this is right
|
||||||
// lighter = this.sectionData[bi];
|
// lighter = this.sectionData[bi];
|
||||||
//}
|
//}
|
||||||
@@ -1305,7 +1332,7 @@ public class RenderDataFactory {
|
|||||||
mb.skip(skipB); skipB = 0;
|
mb.skip(skipB); skipB = 0;
|
||||||
|
|
||||||
//TODO: LIGHTING
|
//TODO: LIGHTING
|
||||||
long lightData = ((neighborId&(0xFFL<<56))>>1);//A;
|
long lightData = ((neighborId&(0xFFL<<56))>>>1);//A;
|
||||||
//if (!ModelQueries.faceUsesSelfLighting(Am, facingForward|(axis*2))) {//TODO: check this is right
|
//if (!ModelQueries.faceUsesSelfLighting(Am, facingForward|(axis*2))) {//TODO: check this is right
|
||||||
// lighter = this.sectionData[bi];
|
// lighter = this.sectionData[bi];
|
||||||
//}
|
//}
|
||||||
@@ -1470,6 +1497,7 @@ public class RenderDataFactory {
|
|||||||
int msk = this.nonOpaqueMasks[i];
|
int msk = this.nonOpaqueMasks[i];
|
||||||
if ((msk & 1) != 0) {//-x
|
if ((msk & 1) != 0) {//-x
|
||||||
long neighborId = this.neighboringFaces[i];
|
long neighborId = this.neighboringFaces[i];
|
||||||
|
//TODO also check self occlusion
|
||||||
|
|
||||||
int sidx = (i<<5) * 2;
|
int sidx = (i<<5) * 2;
|
||||||
long A = this.sectionData[sidx];
|
long A = this.sectionData[sidx];
|
||||||
@@ -1490,6 +1518,8 @@ public class RenderDataFactory {
|
|||||||
|
|
||||||
if ((msk & (1<<31)) != 0) {//+x
|
if ((msk & (1<<31)) != 0) {//+x
|
||||||
long neighborId = this.neighboringFaces[i+32*32];
|
long neighborId = this.neighboringFaces[i+32*32];
|
||||||
|
//TODO also check self occlusion
|
||||||
|
|
||||||
|
|
||||||
int sidx = (i*32+31) * 2;
|
int sidx = (i*32+31) * 2;
|
||||||
long A = this.sectionData[sidx];
|
long A = this.sectionData[sidx];
|
||||||
@@ -1542,6 +1572,29 @@ public class RenderDataFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Build the occupancy set (used for AO) from the set of fully opaque blocks (atm, this can change in the future if needed to a special occupancy bitset)
|
||||||
|
private final void buildOccupancy() {
|
||||||
|
//We basicly want to record all the points where we go from air to solid or solid to air (this is to just get better compression)
|
||||||
|
for (int i = 0; i < 32*32; i++) {
|
||||||
|
int occ = 0;
|
||||||
|
int msk = this.opaqueMasks[i];
|
||||||
|
//x
|
||||||
|
occ |= msk^(msk>>1);
|
||||||
|
occ |= msk^(msk<<1);
|
||||||
|
//y
|
||||||
|
occ |= i<32*31?msk^this.opaqueMasks[i+32]:0;
|
||||||
|
occ |= 31<i ?msk^this.opaqueMasks[i-32]:0;
|
||||||
|
//z
|
||||||
|
occ |= (i&31)<31?msk^this.opaqueMasks[i+1]:0;
|
||||||
|
occ |= 0< (i&31)?msk^this.opaqueMasks[i-1]:0;
|
||||||
|
|
||||||
|
//We now have our occlusion mask, fill in our occupancy set
|
||||||
|
for (;occ!=0;occ&=~Integer.lowestOneBit(occ)) {
|
||||||
|
this.occupancy.set(i*32+Integer.numberOfTrailingZeros(occ));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//section is already acquired and gets released by the parent
|
//section is already acquired and gets released by the parent
|
||||||
public BuiltSection generateMesh(WorldSection section) {
|
public BuiltSection generateMesh(WorldSection section) {
|
||||||
//TODO: FIXME: because of the exceptions that are thrown when aquiring modelId
|
//TODO: FIXME: because of the exceptions that are thrown when aquiring modelId
|
||||||
@@ -1572,6 +1625,8 @@ public class RenderDataFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.occupancy.reset();
|
||||||
|
|
||||||
this.minX = Integer.MAX_VALUE;
|
this.minX = Integer.MAX_VALUE;
|
||||||
this.minY = Integer.MAX_VALUE;
|
this.minY = Integer.MAX_VALUE;
|
||||||
this.minZ = Integer.MAX_VALUE;
|
this.minZ = Integer.MAX_VALUE;
|
||||||
@@ -1585,11 +1640,15 @@ public class RenderDataFactory {
|
|||||||
Arrays.fill(this.fluidMasks, 0);
|
Arrays.fill(this.fluidMasks, 0);
|
||||||
|
|
||||||
//Prepare everything
|
//Prepare everything
|
||||||
int neighborMsk = this.prepareSectionData(section._unsafeGetRawDataArray());
|
int neighborMskAndFlags = this.prepareSectionData(section._unsafeGetRawDataArray());
|
||||||
if (neighborMsk>>31!=0) {//We failed to get everything so throw exception
|
if ((neighborMskAndFlags&(1<<31))!=0) {//We failed to get everything so throw exception
|
||||||
throw new IdNotYetComputedException(neighborMsk&(~(1<<31)), true);
|
throw new IdNotYetComputedException(neighborMskAndFlags&((1<<20)-1), true);
|
||||||
}
|
}
|
||||||
|
int neighborMsk = neighborMskAndFlags&0b11_11_11;
|
||||||
|
int flags = neighborMskAndFlags>>>6;
|
||||||
|
if (CHECK_NEIGHBOR_FACE_OCCLUSION) {
|
||||||
this.acquireNeighborData(section, neighborMsk);
|
this.acquireNeighborData(section, neighborMsk);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.generateYZFaces();
|
this.generateYZFaces();
|
||||||
@@ -1600,6 +1659,11 @@ public class RenderDataFactory {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//We only care if we have quads
|
||||||
|
if (BUILD_OCCUPANCY_SET && this.quadCount != 0 && (flags&1) != 0) {
|
||||||
|
this.buildOccupancy();
|
||||||
|
}
|
||||||
|
|
||||||
//TODO:NOTE! when doing face culling of translucent blocks,
|
//TODO:NOTE! when doing face culling of translucent blocks,
|
||||||
// if the connecting type of the translucent block is the same AND the face is full, discard it
|
// if the connecting type of the translucent block is the same AND the face is full, discard it
|
||||||
// this stops e.g. multiple layers of glass (and ocean) from having 3000 layers of quads etc
|
// this stops e.g. multiple layers of glass (and ocean) from having 3000 layers of quads etc
|
||||||
@@ -1607,6 +1671,10 @@ public class RenderDataFactory {
|
|||||||
return BuiltSection.emptyWithChildren(section.key, section.getNonEmptyChildren());
|
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) {
|
if (this.minX<0 || this.minY<0 || this.minZ<0 || 32<this.maxX || 32<this.maxY || 32<this.maxZ) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
@@ -1630,7 +1698,13 @@ public class RenderDataFactory {
|
|||||||
aabb |= (this.maxY-this.minY-1)<<20;
|
aabb |= (this.maxY-this.minY-1)<<20;
|
||||||
aabb |= (this.maxZ-this.minZ-1)<<25;
|
aabb |= (this.maxZ-this.minZ-1)<<25;
|
||||||
|
|
||||||
return new BuiltSection(section.key, section.getNonEmptyChildren(), aabb, buff, offsets);
|
MemoryBuffer occupancy = null;
|
||||||
|
if (BUILD_OCCUPANCY_SET && !this.occupancy.isEmpty()) {
|
||||||
|
occupancy = new MemoryBuffer(this.occupancy.writeSize());
|
||||||
|
this.occupancy.write(occupancy.address, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BuiltSection(section.key, section.getNonEmptyChildren(), aabb, buff, offsets, occupancy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void free() {
|
public void free() {
|
||||||
|
|||||||
@@ -4,18 +4,17 @@ import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
|||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
import me.cortex.voxy.client.core.model.IdNotYetComputedException;
|
import me.cortex.voxy.client.core.model.IdNotYetComputedException;
|
||||||
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
|
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
|
||||||
|
import me.cortex.voxy.common.thread.Service;
|
||||||
|
import me.cortex.voxy.common.thread.ServiceManager;
|
||||||
import me.cortex.voxy.common.util.Pair;
|
import me.cortex.voxy.common.util.Pair;
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
import me.cortex.voxy.common.world.WorldSection;
|
import me.cortex.voxy.common.world.WorldSection;
|
||||||
import me.cortex.voxy.common.world.other.Mapper;
|
import me.cortex.voxy.common.world.other.Mapper;
|
||||||
import me.cortex.voxy.common.thread.ServiceSlice;
|
|
||||||
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.PriorityBlockingQueue;
|
import java.util.concurrent.PriorityBlockingQueue;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.locks.StampedLock;
|
import java.util.concurrent.locks.StampedLock;
|
||||||
import java.util.function.BooleanSupplier;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
//TODO: Add a render cache
|
//TODO: Add a render cache
|
||||||
@@ -51,35 +50,36 @@ public class RenderGenerationService {
|
|||||||
private final AtomicInteger holdingSectionCount = new AtomicInteger();//Used to limit section holding
|
private final AtomicInteger holdingSectionCount = new AtomicInteger();//Used to limit section holding
|
||||||
|
|
||||||
private final AtomicInteger taskQueueCount = new AtomicInteger();
|
private final AtomicInteger taskQueueCount = new AtomicInteger();
|
||||||
private final PriorityBlockingQueue<BuildTask> taskQueue = new PriorityBlockingQueue<>(320000, (a,b)-> Long.compareUnsigned(a.priority, b.priority));
|
private final PriorityBlockingQueue<BuildTask> taskQueue = new PriorityBlockingQueue<>(5000, (a,b)-> Long.compareUnsigned(a.priority, b.priority));
|
||||||
private final StampedLock taskMapLock = new StampedLock();
|
private final StampedLock taskMapLock = new StampedLock();
|
||||||
private final Long2ObjectOpenHashMap<BuildTask> taskMap = new Long2ObjectOpenHashMap<>(320000);
|
private final Long2ObjectOpenHashMap<BuildTask> taskMap = new Long2ObjectOpenHashMap<>(5000);
|
||||||
|
|
||||||
private final WorldEngine world;
|
private final WorldEngine world;
|
||||||
private final ModelBakerySubsystem modelBakery;
|
private final ModelBakerySubsystem modelBakery;
|
||||||
private Consumer<BuiltSection> resultConsumer;
|
private Consumer<BuiltSection> resultConsumer;
|
||||||
private final boolean emitMeshlets;
|
private final boolean emitMeshlets;
|
||||||
|
|
||||||
private final ServiceSlice threads;
|
private final Service service;
|
||||||
|
|
||||||
|
|
||||||
public RenderGenerationService(WorldEngine world, ModelBakerySubsystem modelBakery, ServiceThreadPool serviceThreadPool, boolean emitMeshlets) {
|
/*
|
||||||
this(world, modelBakery, serviceThreadPool, emitMeshlets, ()->true);
|
public RenderGenerationService(WorldEngine world, ModelBakerySubsystem modelBakery, ServiceManager sm, boolean emitMeshlets) {
|
||||||
}
|
this(world, modelBakery, sm, emitMeshlets, ()->true);
|
||||||
|
}*/
|
||||||
|
|
||||||
public RenderGenerationService(WorldEngine world, ModelBakerySubsystem modelBakery, ServiceThreadPool serviceThreadPool, boolean emitMeshlets, BooleanSupplier taskLimiter) {
|
public RenderGenerationService(WorldEngine world, ModelBakerySubsystem modelBakery, ServiceManager sm, boolean emitMeshlets) {
|
||||||
this.emitMeshlets = emitMeshlets;
|
this.emitMeshlets = emitMeshlets;
|
||||||
this.world = world;
|
this.world = world;
|
||||||
this.modelBakery = modelBakery;
|
this.modelBakery = modelBakery;
|
||||||
|
|
||||||
this.threads = serviceThreadPool.createService("Section mesh generation service", 100, ()->{
|
this.service = sm.createService(()->{
|
||||||
//Thread local instance of the factory
|
//Thread local instance of the factory
|
||||||
var factory = new RenderDataFactory(this.world, this.modelBakery.factory, this.emitMeshlets);
|
var factory = new RenderDataFactory(this.world, this.modelBakery.factory, this.emitMeshlets);
|
||||||
IntOpenHashSet seenMissed = new IntOpenHashSet(128);
|
IntOpenHashSet seenMissed = new IntOpenHashSet(128);
|
||||||
return new Pair<>(() -> {
|
return new Pair<>(() -> {
|
||||||
this.processJob(factory, seenMissed);
|
this.processJob(factory, seenMissed);
|
||||||
}, factory::free);
|
}, factory::free);
|
||||||
}, taskLimiter);
|
}, 10, "Section mesh generation service", ()->modelBakery.getProcessingCount()<400||RenderGenerationService.MESH_FAILED_COUNTER.get()<500);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setResultConsumer(Consumer<BuiltSection> consumer) {
|
public void setResultConsumer(Consumer<BuiltSection> consumer) {
|
||||||
@@ -141,6 +141,17 @@ public class RenderGenerationService {
|
|||||||
section = task.section;
|
section = task.section;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{//Remove the task from the map, this is done before we check for null sections as well the task map needs to be correct
|
||||||
|
long stamp = this.taskMapLock.writeLock();
|
||||||
|
var rtask = this.taskMap.remove(task.position);
|
||||||
|
if (rtask != task) {
|
||||||
|
this.taskMapLock.unlockWrite(stamp);
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
this.taskMapLock.unlockWrite(stamp);
|
||||||
|
}
|
||||||
|
|
||||||
if (section == null) {
|
if (section == null) {
|
||||||
if (this.resultConsumer != null) {
|
if (this.resultConsumer != null) {
|
||||||
this.resultConsumer.accept(BuiltSection.empty(task.position));
|
this.resultConsumer.accept(BuiltSection.empty(task.position));
|
||||||
@@ -150,15 +161,6 @@ public class RenderGenerationService {
|
|||||||
section.assertNotFree();
|
section.assertNotFree();
|
||||||
BuiltSection mesh = null;
|
BuiltSection mesh = null;
|
||||||
|
|
||||||
{
|
|
||||||
long stamp = this.taskMapLock.writeLock();
|
|
||||||
var rtask = this.taskMap.remove(task.position);
|
|
||||||
if (rtask != task) {
|
|
||||||
this.taskMapLock.unlockWrite(stamp);
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
this.taskMapLock.unlockWrite(stamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mesh = factory.generateMesh(section);
|
mesh = factory.generateMesh(section);
|
||||||
@@ -256,8 +258,8 @@ public class RenderGenerationService {
|
|||||||
this.taskQueue.add(task);
|
this.taskQueue.add(task);
|
||||||
this.taskQueueCount.incrementAndGet();
|
this.taskQueueCount.incrementAndGet();
|
||||||
|
|
||||||
if (this.threads.isAlive()) {//Only execute if were not dead
|
if (this.service.isLive()) {//Only execute if were not dead
|
||||||
this.threads.execute();//Since we put in queue, release permit
|
this.service.execute();//Since we put in queue, release permit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,7 +282,7 @@ public class RenderGenerationService {
|
|||||||
|
|
||||||
|
|
||||||
public void enqueueTask(long pos) {
|
public void enqueueTask(long pos) {
|
||||||
if (!this.threads.isAlive()) {
|
if (!this.service.isLive()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
boolean[] isOurs = new boolean[1];
|
boolean[] isOurs = new boolean[1];
|
||||||
@@ -296,7 +298,7 @@ public class RenderGenerationService {
|
|||||||
task.updatePriority();
|
task.updatePriority();
|
||||||
this.taskQueue.add(task);
|
this.taskQueue.add(task);
|
||||||
this.taskQueueCount.incrementAndGet();
|
this.taskQueueCount.incrementAndGet();
|
||||||
this.threads.execute();
|
this.service.execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,8 +310,8 @@ public class RenderGenerationService {
|
|||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
//Steal and free as much work as possible
|
//Steal and free as much work as possible
|
||||||
while (this.threads.hasJobs()) {
|
while (this.service.numJobs() != 0) {
|
||||||
int i = this.threads.drain();
|
int i = this.service.drain();
|
||||||
if (i == 0) break;
|
if (i == 0) break;
|
||||||
{
|
{
|
||||||
long stamp = this.taskMapLock.writeLock();
|
long stamp = this.taskMapLock.writeLock();
|
||||||
@@ -329,7 +331,7 @@ public class RenderGenerationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Shutdown the threads
|
//Shutdown the threads
|
||||||
this.threads.shutdown();
|
this.service.shutdown();
|
||||||
|
|
||||||
//Cleanup any remaining data
|
//Cleanup any remaining data
|
||||||
while (!this.taskQueue.isEmpty()) {
|
while (!this.taskQueue.isEmpty()) {
|
||||||
@@ -352,14 +354,12 @@ public class RenderGenerationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private long lastChangedTime = 0;
|
private long lastChangedTime = 0;
|
||||||
private int failedCounter = 0;
|
|
||||||
public void addDebugData(List<String> debug) {
|
public void addDebugData(List<String> debug) {
|
||||||
if (System.currentTimeMillis()-this.lastChangedTime > 1000) {
|
if (System.currentTimeMillis()-this.lastChangedTime > 100) {
|
||||||
this.failedCounter = 0;
|
MESH_FAILED_COUNTER.set(0);
|
||||||
this.lastChangedTime = System.currentTimeMillis();
|
this.lastChangedTime = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
this.failedCounter += MESH_FAILED_COUNTER.getAndSet(0);
|
debug.add("RSSQ/TFC: " + this.taskQueueCount.get() + "/" + MESH_FAILED_COUNTER.get());//render section service queue, Task Fail Counter
|
||||||
debug.add("RSSQ/TFC: " + this.taskQueueCount.get() + "/" + this.failedCounter);//render section service queue, Task Fail Counter
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.hierachical;
|
package me.cortex.voxy.client.core.rendering.hierachical;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.*;
|
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntConsumer;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||||
import me.cortex.voxy.client.TimingStatistics;
|
import me.cortex.voxy.client.TimingStatistics;
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||||
@@ -17,6 +19,7 @@ import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
|||||||
import me.cortex.voxy.common.Logger;
|
import me.cortex.voxy.common.Logger;
|
||||||
import me.cortex.voxy.common.util.AllocationArena;
|
import me.cortex.voxy.common.util.AllocationArena;
|
||||||
import me.cortex.voxy.common.util.MemoryBuffer;
|
import me.cortex.voxy.common.util.MemoryBuffer;
|
||||||
|
import me.cortex.voxy.common.util.UnsafeUtil;
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
import me.cortex.voxy.common.world.WorldSection;
|
import me.cortex.voxy.common.world.WorldSection;
|
||||||
import org.lwjgl.system.MemoryUtil;
|
import org.lwjgl.system.MemoryUtil;
|
||||||
@@ -186,7 +189,7 @@ public class AsyncNodeManager {
|
|||||||
}
|
}
|
||||||
//This is a funny thing, wait a bit, this allows for better batching, but this thread is independent of everything else so waiting a bit should be mostly ok
|
//This is a funny thing, wait a bit, this allows for better batching, but this thread is independent of everything else so waiting a bit should be mostly ok
|
||||||
try {
|
try {
|
||||||
Thread.sleep(25);
|
Thread.sleep(10);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@@ -246,7 +249,7 @@ public class AsyncNodeManager {
|
|||||||
|
|
||||||
//Limit uploading as well as by geometry capacity being available
|
//Limit uploading as well as by geometry capacity being available
|
||||||
// must have 50 mb of free geometry space to upload
|
// 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 < 300 && ((this.geometryCapacity-this.geometryManager.getGeometryUsedBytes())>50_000_000L); limit++) {
|
||||||
var job = this.geometryUpdateQueue.poll();
|
var job = this.geometryUpdateQueue.poll();
|
||||||
if (job == null)
|
if (job == null)
|
||||||
break;
|
break;
|
||||||
@@ -475,7 +478,10 @@ public class AsyncNodeManager {
|
|||||||
results.usedGeometry = this.geometryManager.getGeometryUsedBytes();
|
results.usedGeometry = this.geometryManager.getGeometryUsedBytes();
|
||||||
results.currentMaxNodeId = this.manager.getCurrentMaxNodeId();
|
results.currentMaxNodeId = this.manager.getCurrentMaxNodeId();
|
||||||
|
|
||||||
this.needsWaitForSync |= results.geometryUpload.currentElemCopyAmount*8L > 4L<<20;//4mb limit per frame
|
this.needsWaitForSync |= results.geometryUpload.currentElemCopyAmount*8L > 2L<<20;//2mb limit per frame
|
||||||
|
this.needsWaitForSync |= results.cleanerOperations.size() > 1024;
|
||||||
|
this.needsWaitForSync |= results.scatterWriteLocationMap.size() > 4096;
|
||||||
|
this.needsWaitForSync |= results.tlnDelta.size() > 10;
|
||||||
|
|
||||||
if (!RESULT_HANDLE.compareAndSet(this, null, results)) {
|
if (!RESULT_HANDLE.compareAndSet(this, null, results)) {
|
||||||
throw new IllegalArgumentException("Should always have null");
|
throw new IllegalArgumentException("Should always have null");
|
||||||
@@ -511,13 +517,14 @@ public class AsyncNodeManager {
|
|||||||
|
|
||||||
var upload = results.geometryUpload;
|
var upload = results.geometryUpload;
|
||||||
if (!upload.dataUploadPoints.isEmpty()) {
|
if (!upload.dataUploadPoints.isEmpty()) {
|
||||||
|
((BasicSectionGeometryData)this.geometryData).ensureAccessable(upload.maxElementAccess);
|
||||||
TimingStatistics.A.start();
|
TimingStatistics.A.start();
|
||||||
|
|
||||||
int copies = upload.dataUploadPoints.size();
|
int copies = upload.dataUploadPoints.size();
|
||||||
int scratchSize = (int) upload.arena.getSize() * 8;
|
int scratchSize = (int) upload.arena.getSize() * 8;
|
||||||
long ptr = UploadStream.INSTANCE.rawUploadAddress(scratchSize + copies * 16);
|
long ptr = UploadStream.INSTANCE.rawUploadAddress(scratchSize + copies * 16);
|
||||||
MemoryUtil.memCopy(upload.scratchHeaderBuffer.address, UploadStream.INSTANCE.getBaseAddress() + ptr, copies * 16L);
|
UnsafeUtil.memcpy(upload.scratchHeaderBuffer.address, UploadStream.INSTANCE.getBaseAddress() + ptr, copies * 16L);
|
||||||
MemoryUtil.memCopy(upload.scratchDataBuffer.address, UploadStream.INSTANCE.getBaseAddress() + ptr + copies * 16L, scratchSize);
|
UnsafeUtil.memcpy(upload.scratchDataBuffer.address, UploadStream.INSTANCE.getBaseAddress() + ptr + copies * 16L, scratchSize);
|
||||||
UploadStream.INSTANCE.commit();//Commit the buffer
|
UploadStream.INSTANCE.commit();//Commit the buffer
|
||||||
|
|
||||||
this.multiMemcpy.bind();
|
this.multiMemcpy.bind();
|
||||||
@@ -757,11 +764,20 @@ public class AsyncNodeManager {
|
|||||||
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) {
|
public void worldEvent(WorldSection section, int flags, int neighborMask) {
|
||||||
//If there is any change, we need to clear the geometry cache before emitting update
|
//If there is any change, we need to clear the geometry cache before emitting update
|
||||||
this.geometryCache.clear(section.key);
|
this.geometryCache.clear(section.key);
|
||||||
|
|
||||||
this.router.forwardEvent(section, flags);
|
this.router.forwardEvent(section, flags);
|
||||||
|
|
||||||
|
if (neighborMask != 0) {//trigger rebuilds for neighbors
|
||||||
|
if ((neighborMask&0b000001)!=0) this.router.triggerRemesh(WorldEngine.getWorldSectionId(section.lvl, section.x, section.y-1, section.z));//-y
|
||||||
|
if ((neighborMask&0b000010)!=0) this.router.triggerRemesh(WorldEngine.getWorldSectionId(section.lvl, section.x, section.y+1, section.z));//+y
|
||||||
|
if ((neighborMask&0b000100)!=0) this.router.triggerRemesh(WorldEngine.getWorldSectionId(section.lvl, section.x-1, section.y, section.z));//-x
|
||||||
|
if ((neighborMask&0b001000)!=0) this.router.triggerRemesh(WorldEngine.getWorldSectionId(section.lvl, section.x+1, section.y, section.z));//+x
|
||||||
|
if ((neighborMask&0b010000)!=0) this.router.triggerRemesh(WorldEngine.getWorldSectionId(section.lvl, section.x, section.y, section.z-1));//-z
|
||||||
|
if ((neighborMask&0b100000)!=0) this.router.triggerRemesh(WorldEngine.getWorldSectionId(section.lvl, section.x, section.y, section.z+1));//+z
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Results object, which is to be synced between the render thread and worker thread
|
//Results object, which is to be synced between the render thread and worker thread
|
||||||
@@ -846,6 +862,7 @@ public class AsyncNodeManager {
|
|||||||
|
|
||||||
private static class ComputeMemoryCopy {
|
private static class ComputeMemoryCopy {
|
||||||
public int currentElemCopyAmount;
|
public int currentElemCopyAmount;
|
||||||
|
public int maxElementAccess;
|
||||||
private MemoryBuffer scratchHeaderBuffer = new MemoryBuffer(1<<16);
|
private MemoryBuffer scratchHeaderBuffer = new MemoryBuffer(1<<16);
|
||||||
private MemoryBuffer scratchDataBuffer = new MemoryBuffer(1<<20);
|
private MemoryBuffer scratchDataBuffer = new MemoryBuffer(1<<20);
|
||||||
|
|
||||||
@@ -897,6 +914,7 @@ public class AsyncNodeManager {
|
|||||||
public void upload(int point, MemoryBuffer data) {
|
public void upload(int point, MemoryBuffer data) {
|
||||||
if ((data.size%8)!=0) throw new IllegalStateException("Data must be of size multiple 8");
|
if ((data.size%8)!=0) throw new IllegalStateException("Data must be of size multiple 8");
|
||||||
int elemSize = (int) (data.size / 8);
|
int elemSize = (int) (data.size / 8);
|
||||||
|
this.maxElementAccess = Math.max(this.maxElementAccess, point + elemSize);
|
||||||
int header = this.dataUploadPoints.get(point);
|
int header = this.dataUploadPoints.get(point);
|
||||||
if (header != -1) {
|
if (header != -1) {
|
||||||
//If we already have a header location, we just need to reallocate the data
|
//If we already have a header location, we just need to reallocate the data
|
||||||
@@ -971,6 +989,7 @@ public class AsyncNodeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
|
this.maxElementAccess = 0;
|
||||||
this.currentElemCopyAmount = 0;
|
this.currentElemCopyAmount = 0;
|
||||||
this.dataUploadPoints.clear();
|
this.dataUploadPoints.clear();
|
||||||
this.arena.reset();
|
this.arena.reset();
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.hierachical;
|
package me.cortex.voxy.client.core.rendering.hierachical;
|
||||||
|
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||||
|
import me.cortex.voxy.client.core.gl.GlVertexArray;
|
||||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||||
import me.cortex.voxy.client.core.rendering.RenderService;
|
|
||||||
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
|
|
||||||
import me.cortex.voxy.client.core.rendering.Viewport;
|
import me.cortex.voxy.client.core.rendering.Viewport;
|
||||||
|
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
|
||||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||||
import net.minecraft.util.math.MathHelper;
|
import net.minecraft.util.Mth;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
import org.lwjgl.opengl.GL15;
|
import org.lwjgl.opengl.GL15;
|
||||||
@@ -38,9 +38,9 @@ public class DebugRenderer {
|
|||||||
|
|
||||||
private void uploadUniform(Viewport<?> viewport) {
|
private void uploadUniform(Viewport<?> viewport) {
|
||||||
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, 1024);
|
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, 1024);
|
||||||
int sx = MathHelper.floor(viewport.cameraX)>>5;
|
int sx = Mth.floor(viewport.cameraX)>>5;
|
||||||
int sy = MathHelper.floor(viewport.cameraY)>>5;
|
int sy = Mth.floor(viewport.cameraY)>>5;
|
||||||
int sz = MathHelper.floor(viewport.cameraZ)>>5;
|
int sz = Mth.floor(viewport.cameraZ)>>5;
|
||||||
|
|
||||||
new Matrix4f(viewport.projection).mul(viewport.modelView).getToAddress(ptr); ptr += 4*4*4;
|
new Matrix4f(viewport.projection).mul(viewport.modelView).getToAddress(ptr); ptr += 4*4*4;
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ public class DebugRenderer {
|
|||||||
|
|
||||||
glEnable(GL_DEPTH_TEST);
|
glEnable(GL_DEPTH_TEST);
|
||||||
this.debugShader.bind();
|
this.debugShader.bind();
|
||||||
glBindVertexArray(RenderService.STATIC_VAO);
|
glBindVertexArray(GlVertexArray.STATIC_VAO);
|
||||||
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.drawBuffer.id);
|
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.drawBuffer.id);
|
||||||
GL15.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE_BYTE.id());
|
GL15.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE_BYTE.id());
|
||||||
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniformBuffer.id);
|
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniformBuffer.id);
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ package me.cortex.voxy.client.core.rendering.hierachical;
|
|||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||||
import me.cortex.voxy.client.RenderStatistics;
|
import me.cortex.voxy.client.RenderStatistics;
|
||||||
import me.cortex.voxy.client.config.VoxyConfig;
|
import me.cortex.voxy.common.config.VoxyConfig;
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
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.AutoBindingShader;
|
||||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||||
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
|
|
||||||
import me.cortex.voxy.client.core.rendering.util.PrintfDebugUtil;
|
|
||||||
import me.cortex.voxy.client.core.rendering.util.HiZBuffer;
|
|
||||||
import me.cortex.voxy.client.core.rendering.Viewport;
|
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.DownloadStream;
|
||||||
|
import me.cortex.voxy.client.core.rendering.util.PrintfDebugUtil;
|
||||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
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.util.MemoryBuffer;
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
import org.lwjgl.system.MemoryUtil;
|
import org.lwjgl.system.MemoryUtil;
|
||||||
@@ -131,7 +131,9 @@ public class HierarchicalOcclusionTraverser {
|
|||||||
|
|
||||||
//Use clear buffer, yes know is a bad idea, TODO: replace
|
//Use clear buffer, yes know is a bad idea, TODO: replace
|
||||||
//Add the new top level node to the queue
|
//Add the new top level node to the queue
|
||||||
glClearNamedBufferSubData(this.topNodeIds.id, GL_R32UI, aid*4L, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{id});
|
MemoryUtil.memPutInt(SCRATCH, id);
|
||||||
|
nglClearNamedBufferSubData(this.topNodeIds.id, GL_R32UI, aid * 4L, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, SCRATCH);
|
||||||
|
|
||||||
if (this.topNode2idxMapping.put(id, aid) != -1) {
|
if (this.topNode2idxMapping.put(id, aid) != -1) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
@@ -158,8 +160,10 @@ public class HierarchicalOcclusionTraverser {
|
|||||||
this.idx2topNodeMapping[idx] = endTLNId;//Set the old to the new
|
this.idx2topNodeMapping[idx] = endTLNId;//Set the old to the new
|
||||||
if (this.topNode2idxMapping.put(endTLNId, idx) == -1)
|
if (this.topNode2idxMapping.put(endTLNId, idx) == -1)
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
|
|
||||||
//Move it server side, from end to new idx
|
//Move it server side, from end to new idx
|
||||||
glClearNamedBufferSubData(this.topNodeIds.id, GL_R32UI, idx*4L, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{endTLNId});
|
MemoryUtil.memPutInt(SCRATCH, endTLNId);
|
||||||
|
nglClearNamedBufferSubData(this.topNodeIds.id, GL_R32UI, idx*4L, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, SCRATCH);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setFrustum(Viewport<?> viewport, long ptr) {
|
private static void setFrustum(Viewport<?> viewport, long ptr) {
|
||||||
@@ -321,7 +325,8 @@ public class HierarchicalOcclusionTraverser {
|
|||||||
private void forwardDownloadResult(long ptr, long size) {
|
private void forwardDownloadResult(long ptr, long size) {
|
||||||
int count = MemoryUtil.memGetInt(ptr);ptr += 8;//its 8 since we need to skip the second value (which is empty)
|
int count = MemoryUtil.memGetInt(ptr);ptr += 8;//its 8 since we need to skip the second value (which is empty)
|
||||||
if (count < 0 || count > 50000) {
|
if (count < 0 || count > 50000) {
|
||||||
throw new IllegalStateException("Count unexpected extreme value: " + count);
|
Logger.error(new IllegalStateException("Count unexpected extreme value: " + count + " things may get weird"));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (count > (this.requestBuffer.size()>>3)-1) {
|
if (count > (this.requestBuffer.size()>>3)-1) {
|
||||||
//This should not break the synchonization between gpu and cpu as in the traversal shader is
|
//This should not break the synchonization between gpu and cpu as in the traversal shader is
|
||||||
@@ -358,4 +363,6 @@ public class HierarchicalOcclusionTraverser {
|
|||||||
this.scratchQueueB.free();
|
this.scratchQueueB.free();
|
||||||
glDeleteSamplers(this.hizSampler);
|
glDeleteSamplers(this.hizSampler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final long SCRATCH = MemoryUtil.nmemAlloc(32);//32 bytes of scratch memory
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,12 @@ 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.AutoBindingShader;
|
||||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
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.DownloadStream;
|
||||||
|
import me.cortex.voxy.client.core.rendering.util.PrintfDebugUtil;
|
||||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||||
import org.lwjgl.opengl.ARBDirectStateAccess;
|
import org.lwjgl.opengl.ARBDirectStateAccess;
|
||||||
import org.lwjgl.system.MemoryUtil;
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
import static org.lwjgl.opengl.ARBDirectStateAccess.glCopyNamedBufferSubData;
|
|
||||||
import static org.lwjgl.opengl.GL30C.glBindBufferRange;
|
import static org.lwjgl.opengl.GL30C.glBindBufferRange;
|
||||||
import static org.lwjgl.opengl.GL42C.glMemoryBarrier;
|
import static org.lwjgl.opengl.GL42C.glMemoryBarrier;
|
||||||
import static org.lwjgl.opengl.GL43C.*;
|
import static org.lwjgl.opengl.GL43C.*;
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import me.cortex.voxy.common.world.WorldEngine;
|
|||||||
import org.lwjgl.system.MemoryUtil;
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import static me.cortex.voxy.common.world.WorldEngine.MAX_LOD_LAYER;
|
import static me.cortex.voxy.common.world.WorldEngine.MAX_LOD_LAYER;
|
||||||
import static me.cortex.voxy.common.world.WorldEngine.UPDATE_TYPE_BLOCK_BIT;
|
import static me.cortex.voxy.common.world.WorldEngine.UPDATE_TYPE_BLOCK_BIT;
|
||||||
@@ -137,7 +136,7 @@ public class NodeManager {
|
|||||||
z <<= lvl;
|
z <<= lvl;
|
||||||
long p2 = WorldEngine.getWorldSectionId(0, x, y, z);
|
long p2 = WorldEngine.getWorldSectionId(0, x, y, z);
|
||||||
if (WorldEngine.getLevel(p2) != 0 || WorldEngine.getX(p2) != x || WorldEngine.getY(p2) != y || WorldEngine.getZ(p2) != z) {
|
if (WorldEngine.getLevel(p2) != 0 || WorldEngine.getX(p2) != x || WorldEngine.getY(p2) != y || WorldEngine.getZ(p2) != z) {
|
||||||
throw new IllegalStateException("Position not valid at all levels");
|
throw new IllegalStateException("Position not valid at all levels: " + pos + "-"+WorldEngine.pprintPos(pos) + ":"+WorldEngine.pprintPos(p2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +154,7 @@ public class NodeManager {
|
|||||||
|
|
||||||
var request = new SingleNodeRequest(pos);
|
var request = new SingleNodeRequest(pos);
|
||||||
int id = this.singleRequests.put(request);
|
int id = this.singleRequests.put(request);
|
||||||
this.watcher.watch(pos, WorldEngine.UPDATE_FLAGS);
|
this.watcher.watch(pos, WorldEngine.DEFAULT_UPDATE_FLAGS);
|
||||||
this.activeSectionMap.put(pos, id|NODE_TYPE_REQUEST|REQUEST_TYPE_SINGLE);
|
this.activeSectionMap.put(pos, id|NODE_TYPE_REQUEST|REQUEST_TYPE_SINGLE);
|
||||||
this.topLevelNodes.add(pos);
|
this.topLevelNodes.add(pos);
|
||||||
}
|
}
|
||||||
@@ -354,7 +353,7 @@ public class NodeManager {
|
|||||||
throw new IllegalStateException("Child pos was in a request but not in active section map");
|
throw new IllegalStateException("Child pos was in a request but not in active section map");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.watcher.unwatch(cPos, WorldEngine.UPDATE_FLAGS)) {
|
if (!this.watcher.unwatch(cPos, WorldEngine.DEFAULT_UPDATE_FLAGS)) {
|
||||||
throw new IllegalStateException("Child pos was not being watched");
|
throw new IllegalStateException("Child pos was not being watched");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -372,7 +371,7 @@ public class NodeManager {
|
|||||||
if (this.activeSectionMap.put(cPos, requestId|NODE_TYPE_REQUEST|REQUEST_TYPE_CHILD) != -1) {
|
if (this.activeSectionMap.put(cPos, requestId|NODE_TYPE_REQUEST|REQUEST_TYPE_CHILD) != -1) {
|
||||||
throw new IllegalStateException("Child pos was already in active section tracker but was part of a request");
|
throw new IllegalStateException("Child pos was already in active section tracker but was part of a request");
|
||||||
}
|
}
|
||||||
if (!this.watcher.watch(cPos, WorldEngine.UPDATE_FLAGS)) {
|
if (!this.watcher.watch(cPos, WorldEngine.DEFAULT_UPDATE_FLAGS)) {
|
||||||
throw new IllegalStateException("Child pos update router issue");
|
throw new IllegalStateException("Child pos update router issue");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -429,7 +428,7 @@ public class NodeManager {
|
|||||||
if (this.activeSectionMap.put(cPos, requestId|NODE_TYPE_REQUEST|REQUEST_TYPE_CHILD) != -1) {
|
if (this.activeSectionMap.put(cPos, requestId|NODE_TYPE_REQUEST|REQUEST_TYPE_CHILD) != -1) {
|
||||||
throw new IllegalStateException("Child pos was already in active section tracker but was part of a request");
|
throw new IllegalStateException("Child pos was already in active section tracker but was part of a request");
|
||||||
}
|
}
|
||||||
if (!this.watcher.watch(cPos, WorldEngine.UPDATE_FLAGS)) {
|
if (!this.watcher.watch(cPos, WorldEngine.DEFAULT_UPDATE_FLAGS)) {
|
||||||
throw new IllegalStateException("Child pos update router issue");
|
throw new IllegalStateException("Child pos update router issue");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -468,7 +467,7 @@ public class NodeManager {
|
|||||||
if (cnid == -1 || (cnid&NODE_TYPE_MSK) != NODE_TYPE_REQUEST) {//TODO: verify the removed section is a request type of child and the request id matches this
|
if (cnid == -1 || (cnid&NODE_TYPE_MSK) != NODE_TYPE_REQUEST) {//TODO: verify the removed section is a request type of child and the request id matches this
|
||||||
throw new IllegalStateException("Child pos was in a request but not in active section map");
|
throw new IllegalStateException("Child pos was in a request but not in active section map");
|
||||||
}
|
}
|
||||||
if (!this.watcher.unwatch(cPos, WorldEngine.UPDATE_FLAGS)) {
|
if (!this.watcher.unwatch(cPos, WorldEngine.DEFAULT_UPDATE_FLAGS)) {
|
||||||
throw new IllegalStateException("Child pos was not being watched");
|
throw new IllegalStateException("Child pos was not being watched");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -664,7 +663,7 @@ public class NodeManager {
|
|||||||
if ((cId&NODE_TYPE_MSK) != NODE_TYPE_REQUEST || (cId&REQUEST_TYPE_MSK) != REQUEST_TYPE_CHILD || (cId&NODE_ID_MSK) != reqId) {
|
if ((cId&NODE_TYPE_MSK) != NODE_TYPE_REQUEST || (cId&REQUEST_TYPE_MSK) != REQUEST_TYPE_CHILD || (cId&NODE_ID_MSK) != reqId) {
|
||||||
throw new IllegalStateException("Invalid child active state map: " + cId);
|
throw new IllegalStateException("Invalid child active state map: " + cId);
|
||||||
}
|
}
|
||||||
if (!this.watcher.unwatch(childPos, WorldEngine.UPDATE_FLAGS)) {
|
if (!this.watcher.unwatch(childPos, WorldEngine.DEFAULT_UPDATE_FLAGS)) {
|
||||||
throw new IllegalStateException("Pos was not being watched");
|
throw new IllegalStateException("Pos was not being watched");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -786,7 +785,7 @@ public class NodeManager {
|
|||||||
this.invalidateNode(nodeId);
|
this.invalidateNode(nodeId);
|
||||||
|
|
||||||
//Unwatch position
|
//Unwatch position
|
||||||
if (!this.watcher.unwatch(pos, WorldEngine.UPDATE_FLAGS)) {
|
if (!this.watcher.unwatch(pos, WorldEngine.DEFAULT_UPDATE_FLAGS)) {
|
||||||
throw new IllegalStateException("Pos was not being watched");
|
throw new IllegalStateException("Pos was not being watched");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -796,7 +795,7 @@ public class NodeManager {
|
|||||||
this.invalidateNode(nodeId);
|
this.invalidateNode(nodeId);
|
||||||
}
|
}
|
||||||
} else if (type == NODE_TYPE_REQUEST) {
|
} else if (type == NODE_TYPE_REQUEST) {
|
||||||
if (!this.watcher.unwatch(pos, WorldEngine.UPDATE_FLAGS)) {
|
if (!this.watcher.unwatch(pos, WorldEngine.DEFAULT_UPDATE_FLAGS)) {
|
||||||
throw new IllegalStateException("Pos was not being watched");
|
throw new IllegalStateException("Pos was not being watched");
|
||||||
}
|
}
|
||||||
if ((nodeId&REQUEST_TYPE_MSK) == REQUEST_TYPE_SINGLE) {
|
if ((nodeId&REQUEST_TYPE_MSK) == REQUEST_TYPE_SINGLE) {
|
||||||
@@ -1073,7 +1072,8 @@ public class NodeManager {
|
|||||||
public void processRequest(long pos) {
|
public void processRequest(long pos) {
|
||||||
int nodeId = this.activeSectionMap.get(pos);
|
int nodeId = this.activeSectionMap.get(pos);
|
||||||
if (nodeId == -1) {
|
if (nodeId == -1) {
|
||||||
Logger.warn("Got request for pos " + WorldEngine.pprintPos(pos) + " but it was not in active map, ignoring!");
|
//TODO: make into timing thing
|
||||||
|
//Logger.warn("Got request for pos " + WorldEngine.pprintPos(pos) + " but it was not in active map, ignoring!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int nodeType = nodeId&NODE_TYPE_MSK;
|
int nodeType = nodeId&NODE_TYPE_MSK;
|
||||||
@@ -1184,7 +1184,7 @@ public class NodeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Watch and request the child node at the given position
|
//Watch and request the child node at the given position
|
||||||
if (!this.watcher.watch(childPos, WorldEngine.UPDATE_FLAGS)) {
|
if (!this.watcher.watch(childPos, WorldEngine.DEFAULT_UPDATE_FLAGS)) {
|
||||||
throw new IllegalStateException("Failed to watch childPos");
|
throw new IllegalStateException("Failed to watch childPos");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1238,7 +1238,8 @@ public class NodeManager {
|
|||||||
int nodeType = nodeId&NODE_TYPE_MSK;
|
int nodeType = nodeId&NODE_TYPE_MSK;
|
||||||
nodeId &= NODE_ID_MSK;
|
nodeId &= NODE_ID_MSK;
|
||||||
if (nodeType == NODE_TYPE_REQUEST) {
|
if (nodeType == NODE_TYPE_REQUEST) {
|
||||||
Logger.warn("Tried removing geometry for pos: " + WorldEngine.pprintPos(pos) + " but its type was a request, ignoring!");
|
//TODO: only log a specific number of times
|
||||||
|
//Logger.warn("Tried removing geometry for pos: " + WorldEngine.pprintPos(pos) + " but its type was a request, ignoring!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//this.clearId(nodeId);
|
//this.clearId(nodeId);
|
||||||
|
|||||||
@@ -39,9 +39,15 @@ public class TestNodeManager {
|
|||||||
this.removeSection(oldId);
|
this.removeSection(oldId);
|
||||||
}
|
}
|
||||||
int newId = this.allocation.allocateNext();
|
int newId = this.allocation.allocateNext();
|
||||||
|
if (newId == -1) {
|
||||||
|
Logger.error("Allocator full: "+this.allocation.getCount()+" " +section, new Throwable());
|
||||||
|
section.free();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
var entry = new Entry(section.position, section.geometryBuffer.size);
|
var entry = new Entry(section.position, section.geometryBuffer.size);
|
||||||
if (this.sections.put(newId, entry) != null) {
|
var old = this.sections.put(newId, entry);
|
||||||
throw new IllegalStateException();
|
if (old != null) {
|
||||||
|
throw new IllegalStateException(oldId + ","+newId+" "+old+","+entry);
|
||||||
}
|
}
|
||||||
this.memoryInUse += entry.size;
|
this.memoryInUse += entry.size;
|
||||||
section.free();
|
section.free();
|
||||||
@@ -139,7 +145,7 @@ public class TestNodeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String[] getPrettyTypes(int msk) {
|
private static String[] getPrettyTypes(int msk) {
|
||||||
if ((msk&~UPDATE_FLAGS)!=0) {
|
if ((msk&~(DEFAULT_UPDATE_FLAGS|UPDATE_TYPE_DONT_SAVE))!=0) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
String[] types = new String[Integer.bitCount(msk)];
|
String[] types = new String[Integer.bitCount(msk)];
|
||||||
@@ -150,6 +156,9 @@ public class TestNodeManager {
|
|||||||
if ((msk&UPDATE_TYPE_CHILD_EXISTENCE_BIT)!=0) {
|
if ((msk&UPDATE_TYPE_CHILD_EXISTENCE_BIT)!=0) {
|
||||||
types[i++] = "CHILD";
|
types[i++] = "CHILD";
|
||||||
}
|
}
|
||||||
|
if ((msk&UPDATE_TYPE_DONT_SAVE)!=0) {
|
||||||
|
types[i++] = "DONT_SAVE";
|
||||||
|
}
|
||||||
return types;
|
return types;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,7 +192,7 @@ public class TestNodeManager {
|
|||||||
if (geometrySize != 0) {
|
if (geometrySize != 0) {
|
||||||
buff = new MemoryBuffer(geometrySize);
|
buff = new MemoryBuffer(geometrySize);
|
||||||
}
|
}
|
||||||
var builtGeometry = new BuiltSection(pos, (byte) childExistence, -2, buff, null);
|
var builtGeometry = new BuiltSection(pos, (byte) childExistence, -2, buff, null, null);
|
||||||
this.nodeManager.processGeometryResult(builtGeometry);
|
this.nodeManager.processGeometryResult(builtGeometry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,22 +288,24 @@ public class TestNodeManager {
|
|||||||
int ITER_COUNT = 5_000;
|
int ITER_COUNT = 5_000;
|
||||||
int INNER_ITER_COUNT = 1_000_000;
|
int INNER_ITER_COUNT = 1_000_000;
|
||||||
boolean GEO_REM = true;
|
boolean GEO_REM = true;
|
||||||
|
boolean LIMIT_REQUEST_SEC_ALLOCATION = true;
|
||||||
|
|
||||||
AtomicInteger finished = new AtomicInteger();
|
AtomicInteger finished = new AtomicInteger();
|
||||||
HashSet<List<StackTraceElement>> seenTraces = new HashSet<>();
|
HashSet<List<StackTraceElement>> seenTraces = new HashSet<>();
|
||||||
|
|
||||||
|
Logger.SHUTUP_INFO = true;
|
||||||
Logger.SHUTUP = true;
|
Logger.SHUTUP = true;
|
||||||
|
|
||||||
if (true) {
|
if (false) {
|
||||||
for (int q = 0; q < ITER_COUNT; q++) {
|
for (int q = 0; q < ITER_COUNT; q++) {
|
||||||
//Logger.info("Iteration "+ q);
|
//Logger.info("Iteration "+ q);
|
||||||
if (runTest(INNER_ITER_COUNT, q, seenTraces, GEO_REM)) {
|
if (runTest(INNER_ITER_COUNT, q, seenTraces, GEO_REM, LIMIT_REQUEST_SEC_ALLOCATION)) {
|
||||||
finished.incrementAndGet();
|
finished.incrementAndGet();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
IntStream.range(0, ITER_COUNT).parallel().forEach(i->{
|
IntStream.range(0, ITER_COUNT).parallel().forEach(i->{
|
||||||
if (runTest(INNER_ITER_COUNT, i, seenTraces, GEO_REM)) {
|
if (runTest(INNER_ITER_COUNT, i, seenTraces, GEO_REM, LIMIT_REQUEST_SEC_ALLOCATION)) {
|
||||||
finished.incrementAndGet();
|
finished.incrementAndGet();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -311,7 +322,7 @@ public class TestNodeManager {
|
|||||||
return WorldEngine.getWorldSectionId(lvl, r.nextInt(bound)+(WorldEngine.getX(top)<<4), r.nextInt(bound)+(WorldEngine.getY(top)<<4), r.nextInt(bound)+(WorldEngine.getZ(top)<<4));
|
return WorldEngine.getWorldSectionId(lvl, r.nextInt(bound)+(WorldEngine.getX(top)<<4), r.nextInt(bound)+(WorldEngine.getY(top)<<4), r.nextInt(bound)+(WorldEngine.getZ(top)<<4));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean runTest(int ITERS, int testIdx, Set<List<StackTraceElement>> traces, boolean geoRemoval) {
|
private static boolean runTest(int ITERS, int testIdx, Set<List<StackTraceElement>> traces, boolean geoRemoval, boolean requestLimiter) {
|
||||||
Random r = new Random(testIdx * 1234L);
|
Random r = new Random(testIdx * 1234L);
|
||||||
try {
|
try {
|
||||||
var test = new TestBase();
|
var test = new TestBase();
|
||||||
@@ -331,7 +342,7 @@ public class TestNodeManager {
|
|||||||
//Fuzzy bruteforce everything
|
//Fuzzy bruteforce everything
|
||||||
for (int x = -R; x<=R; x++) {
|
for (int x = -R; x<=R; x++) {
|
||||||
for (int z = -R; z<=R; z++) {
|
for (int z = -R; z<=R; z++) {
|
||||||
for (int y = -8; y<=7; y++) {
|
for (int y = -1; y<=0; y++) {
|
||||||
tops.add(WorldEngine.getWorldSectionId(4, x, y, z));
|
tops.add(WorldEngine.getWorldSectionId(4, x, y, z));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -348,28 +359,29 @@ public class TestNodeManager {
|
|||||||
long pos = rPos(r, tops);
|
long pos = rPos(r, tops);
|
||||||
int op = r.nextInt(5);
|
int op = r.nextInt(5);
|
||||||
int extra = r.nextInt(256);
|
int extra = r.nextInt(256);
|
||||||
|
boolean geoAddOk = ((!requestLimiter)||(test.geometryManager.allocation.getLimit()-test.geometryManager.allocation.getCount())>1000);
|
||||||
boolean hasGeometry = r.nextBoolean();
|
boolean hasGeometry = r.nextBoolean();
|
||||||
boolean addRemTLN = r.nextInt(64) == 0;
|
boolean addRemTLN = r.nextInt(64) == 0;
|
||||||
boolean extraBool = r.nextBoolean();
|
boolean extraBool = r.nextBoolean();
|
||||||
if (op == 0 && addRemTLN) {
|
if (op == 0 && addRemTLN) {
|
||||||
pos = WorldEngine.getWorldSectionId(4, r.nextInt(5)-2, r.nextInt(32)-16, r.nextInt(5)-2);
|
pos = WorldEngine.getWorldSectionId(4, r.nextInt(5)-2, r.nextInt(2)-1, r.nextInt(5)-2);//r.nextInt(16)-8//for y
|
||||||
boolean cont = tops.contains(pos);
|
boolean cont = tops.contains(pos);
|
||||||
if (cont&&extraBool&&tops.size()>1) {
|
if (cont&&extraBool&&tops.size()>1) {
|
||||||
extraBool = true;
|
extraBool = true;
|
||||||
test.remTopPos(pos);
|
test.remTopPos(pos);
|
||||||
tops.rem(pos);
|
tops.rem(pos);
|
||||||
} else if (!cont) {
|
} else if ((!cont)&&geoAddOk) {
|
||||||
extraBool = false;
|
extraBool = false;
|
||||||
test.putTopPos(pos);
|
test.putTopPos(pos);
|
||||||
tops.add(pos);
|
tops.add(pos);
|
||||||
}
|
}
|
||||||
} else if (op == 0) {
|
} else if (op == 0&&geoAddOk) {
|
||||||
test.request(pos);
|
test.request(pos);
|
||||||
}
|
}
|
||||||
if (op == 1) {
|
if (op == 1) {
|
||||||
test.childUpdate(pos, extra);
|
test.childUpdate(pos, extra);
|
||||||
}
|
}
|
||||||
if (op == 2) {
|
if (op == 2&&((!hasGeometry)||geoAddOk)) {
|
||||||
test.meshUpdate(pos, extra, hasGeometry ? 100 : 0);
|
test.meshUpdate(pos, extra, hasGeometry ? 100 : 0);
|
||||||
}
|
}
|
||||||
if (op == 3 && geoRemoval) {
|
if (op == 3 && geoRemoval) {
|
||||||
@@ -589,10 +601,9 @@ public class TestNodeManager {
|
|||||||
test.printNodeChanges();
|
test.printNodeChanges();
|
||||||
Logger.info("\n\n");
|
Logger.info("\n\n");
|
||||||
|
|
||||||
var positions = new ArrayList<>(aa.keySet().stream().filter(k->{
|
var positions = new ArrayList<>(aa.keySet().longStream().filter(k->{
|
||||||
return WorldEngine.getLevel(k)!=0;
|
return WorldEngine.getLevel(k)!=0;
|
||||||
}).toList());
|
}).sorted().mapToObj(Long::valueOf).toList());
|
||||||
positions.sort(Long::compareTo);
|
|
||||||
Collections.shuffle(positions, r);
|
Collections.shuffle(positions, r);
|
||||||
|
|
||||||
Logger.info("Removing", WorldEngine.pprintPos(positions.get(0)));
|
Logger.info("Removing", WorldEngine.pprintPos(positions.get(0)));
|
||||||
|
|||||||
@@ -2,9 +2,13 @@ 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.Shader;
|
||||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||||
|
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
|
||||||
|
|
||||||
import static org.lwjgl.opengl.GL11C.GL_TRIANGLES;
|
import java.util.function.Function;
|
||||||
import static org.lwjgl.opengl.GL11C.glDrawArrays;
|
|
||||||
|
import static org.lwjgl.opengl.GL11C.*;
|
||||||
|
import static org.lwjgl.opengl.GL15C.GL_ELEMENT_ARRAY_BUFFER;
|
||||||
|
import static org.lwjgl.opengl.GL15C.glBindBuffer;
|
||||||
import static org.lwjgl.opengl.GL30C.glBindVertexArray;
|
import static org.lwjgl.opengl.GL30C.glBindVertexArray;
|
||||||
import static org.lwjgl.opengl.GL45C.glCreateVertexArrays;
|
import static org.lwjgl.opengl.GL45C.glCreateVertexArrays;
|
||||||
|
|
||||||
@@ -13,9 +17,21 @@ public class FullscreenBlit {
|
|||||||
|
|
||||||
private final Shader shader;
|
private final Shader shader;
|
||||||
public FullscreenBlit(String fragId) {
|
public FullscreenBlit(String fragId) {
|
||||||
this.shader = Shader.make()
|
this(fragId, (a)->a);
|
||||||
.add(ShaderType.VERTEX, "voxy:post/fullscreen.vert")
|
}
|
||||||
.add(ShaderType.FRAGMENT, fragId)
|
|
||||||
|
public FullscreenBlit(String vertId, String fragId) {
|
||||||
|
this(vertId, fragId, (a)->a);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends Shader> FullscreenBlit(String fragId, Function<Shader.Builder<T>, Shader.Builder<T>> builder) {
|
||||||
|
this("voxy:post/fullscreen.vert", fragId, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends Shader> FullscreenBlit(String vertId, String fragId, Function<Shader.Builder<T>, Shader.Builder<T>> builder) {
|
||||||
|
this.shader = builder.apply((Shader.Builder<T>) Shader.make()
|
||||||
|
.add(ShaderType.VERTEX, vertId)
|
||||||
|
.add(ShaderType.FRAGMENT, fragId))
|
||||||
.compile();
|
.compile();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +42,8 @@ public class FullscreenBlit {
|
|||||||
public void blit() {
|
public void blit() {
|
||||||
glBindVertexArray(EMPTY_VAO);
|
glBindVertexArray(EMPTY_VAO);
|
||||||
this.shader.bind();
|
this.shader.bind();
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 6);
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE_BYTE.id());
|
||||||
|
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, 0);
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,195 +0,0 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.post;
|
|
||||||
|
|
||||||
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.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;
|
|
||||||
import static org.lwjgl.opengl.GL15.GL_READ_WRITE;
|
|
||||||
import static org.lwjgl.opengl.GL20.glUniformMatrix4fv;
|
|
||||||
import static org.lwjgl.opengl.GL30C.*;
|
|
||||||
import static org.lwjgl.opengl.GL30C.GL_DEPTH24_STENCIL8;
|
|
||||||
import static org.lwjgl.opengl.GL43.GL_DEPTH_STENCIL_TEXTURE_MODE;
|
|
||||||
import static org.lwjgl.opengl.GL45C.*;
|
|
||||||
|
|
||||||
public class PostProcessing {
|
|
||||||
private final GlFramebuffer framebuffer;
|
|
||||||
private final GlFramebuffer framebufferSSAO;
|
|
||||||
private int width;
|
|
||||||
private int height;
|
|
||||||
private GlTexture colour;
|
|
||||||
private GlTexture colourSSAO;
|
|
||||||
private GlTexture depthStencil;
|
|
||||||
private boolean didSSAO;
|
|
||||||
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 Shader ssaoComp = Shader.make()
|
|
||||||
.add(ShaderType.COMPUTE, "voxy:post/ssao.comp")
|
|
||||||
.compile();
|
|
||||||
private final GlStateCapture glStateCapture = GlStateCapture.make()
|
|
||||||
.addCapability(GL_STENCIL_TEST)
|
|
||||||
.addCapability(GL_DEPTH_TEST)
|
|
||||||
.addTexture(GL_TEXTURE0)
|
|
||||||
.addTexture(GL_TEXTURE1)
|
|
||||||
.addTexture(GL_TEXTURE2)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
public PostProcessing() {
|
|
||||||
this.framebuffer = new GlFramebuffer();
|
|
||||||
this.framebufferSSAO = new GlFramebuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSize(int width, int height) {
|
|
||||||
if (this.width != width || this.height != height) {
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
if (this.colour != null) {
|
|
||||||
if (this.colourSSAO != null) {
|
|
||||||
this.colourSSAO.free();
|
|
||||||
}
|
|
||||||
this.colour.free();
|
|
||||||
this.depthStencil.free();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.colour = new GlTexture().store(GL_RGBA8, 1, width, height);
|
|
||||||
this.colourSSAO = new GlTexture().store(GL_RGBA8, 1, width, height);
|
|
||||||
this.depthStencil = new GlTexture().store(GL_DEPTH24_STENCIL8, 1, width, height);
|
|
||||||
|
|
||||||
glTextureParameterf(this.colour.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
||||||
glTextureParameterf(this.colour.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
||||||
glTextureParameterf(this.colourSSAO.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
||||||
glTextureParameterf(this.colourSSAO.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
||||||
glTextureParameterf(this.depthStencil.id, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_DEPTH_COMPONENT);
|
|
||||||
|
|
||||||
this.framebuffer.bind(GL_COLOR_ATTACHMENT0, this.colour)
|
|
||||||
.bind(GL_DEPTH_STENCIL_ATTACHMENT, this.depthStencil)
|
|
||||||
.verify();
|
|
||||||
|
|
||||||
this.framebufferSSAO.bind(GL_COLOR_ATTACHMENT0, this.colourSSAO)
|
|
||||||
.bind(GL_DEPTH_STENCIL_ATTACHMENT, this.depthStencil)
|
|
||||||
.verify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void shutdown() {
|
|
||||||
this.framebuffer.free();
|
|
||||||
this.framebufferSSAO.free();
|
|
||||||
if (this.colourSSAO != null) this.colourSSAO.free();
|
|
||||||
if (this.colour != null) this.colour.free();
|
|
||||||
if (this.depthStencil != null) this.depthStencil.free();
|
|
||||||
this.emptyBlit.delete();
|
|
||||||
this.setDepth0.delete();
|
|
||||||
this.blitTexture.delete();
|
|
||||||
this.ssaoComp.free();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setup(int width, int height, int sourceFB) {
|
|
||||||
//TODO: use the raw depth texture instead
|
|
||||||
//TODO: when blitting, also set the depth value of where the mask is created to 0 (I.E. closest to the camera)
|
|
||||||
// cause of hiz computing, it makes alot of sections visible
|
|
||||||
this.didSSAO = false;
|
|
||||||
this.glStateCapture.capture();
|
|
||||||
|
|
||||||
this.setSize(width, height);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, this.framebuffer.id);
|
|
||||||
|
|
||||||
//glClearColor(0,0,0,0);//TODO: RESTORE THIS TO THE ORIGINAL VALUE
|
|
||||||
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
|
||||||
glBlitNamedFramebuffer(sourceFB, this.framebuffer.id, 0,0, width, height, 0,0, width, height, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
|
|
||||||
|
|
||||||
//Create a stencil mask of terrain generated by minecraft
|
|
||||||
glEnable(GL_STENCIL_TEST);
|
|
||||||
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
|
|
||||||
glStencilFunc(GL_ALWAYS, 1, 0xFF);
|
|
||||||
glStencilMask(0xFF);
|
|
||||||
|
|
||||||
glEnable(GL_DEPTH_TEST);
|
|
||||||
glDepthMask(false);
|
|
||||||
glColorMask(false,false,false,false);
|
|
||||||
this.emptyBlit.blit();
|
|
||||||
glDepthMask(true);
|
|
||||||
|
|
||||||
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
|
|
||||||
//Set depth to 0 w.r.t mask
|
|
||||||
glStencilFunc(GL_EQUAL, 0, 0xFF);
|
|
||||||
this.setDepth0.blit();
|
|
||||||
glDisable(GL_DEPTH_TEST);
|
|
||||||
glColorMask(true,true,true,true);
|
|
||||||
|
|
||||||
//Make voxy terrain render only where there isnt mc terrain
|
|
||||||
glStencilFunc(GL_EQUAL, 1, 0xFF);
|
|
||||||
|
|
||||||
|
|
||||||
//TODO: need to figure out how to do translucency cause doing it normally will cause water to have double translucency (i think)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Computes ssao on the current framebuffer data and updates it
|
|
||||||
// this means that translucency wont be effected etc
|
|
||||||
public void computeSSAO(Matrix4f mvp) {
|
|
||||||
this.didSSAO = true;
|
|
||||||
|
|
||||||
this.ssaoComp.bind();
|
|
||||||
float[] data = new float[4*4];
|
|
||||||
mvp.get(data);
|
|
||||||
glUniformMatrix4fv(3, false, data);//MVP
|
|
||||||
mvp.invert(new Matrix4f()).get(data);
|
|
||||||
glUniformMatrix4fv(4, false, data);//invMVP
|
|
||||||
|
|
||||||
glBindImageTexture(0, this.colourSSAO.id, 0, false,0, GL_READ_WRITE, GL_RGBA8);
|
|
||||||
glBindTextureUnit(1, this.depthStencil.id);
|
|
||||||
glBindTextureUnit(2, this.colour.id);
|
|
||||||
|
|
||||||
glDispatchCompute((this.width+31)/32, (this.height+31)/32, 1);
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, this.framebufferSSAO.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//Executes the post processing and emits to whatever framebuffer is currently bound via a blit
|
|
||||||
public void renderPost(Matrix4f fromProjection, Matrix4fc tooProjection, int outputFB) {
|
|
||||||
glDisable(GL_STENCIL_TEST);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, outputFB);
|
|
||||||
|
|
||||||
//This will need to be replaced with a blit shader to raster with respect to the stencil
|
|
||||||
//glBlitNamedFramebuffer(this.framebuffer.id, outputFB, 0,0, this.width, this.height, 0,0, this.width, this.height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
|
||||||
|
|
||||||
|
|
||||||
this.blitTexture.bind();
|
|
||||||
|
|
||||||
float[] data = new float[4*4];
|
|
||||||
var mat = new Matrix4f(fromProjection).invert();
|
|
||||||
mat.get(data);
|
|
||||||
glUniformMatrix4fv(2, false, data);//inverse fromProjection
|
|
||||||
tooProjection.get(data);
|
|
||||||
glUniformMatrix4fv(3, false, data);//tooProjection
|
|
||||||
|
|
||||||
|
|
||||||
glBindTextureUnit(0, this.didSSAO?this.colourSSAO.id:this.colour.id);
|
|
||||||
|
|
||||||
glBindTextureUnit(1, this.depthStencil.id);
|
|
||||||
//glTextureParameteri(this.depthStencil.id, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_DEPTH_COMPONENT);
|
|
||||||
|
|
||||||
glEnable(GL_DEPTH_TEST);
|
|
||||||
glDepthMask(true);
|
|
||||||
this.blitTexture.blit();
|
|
||||||
glDisable(GL_DEPTH_TEST);
|
|
||||||
glDepthMask(true);
|
|
||||||
|
|
||||||
this.glStateCapture.restore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.section;
|
|
||||||
|
|
||||||
|
|
||||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
|
||||||
import me.cortex.voxy.client.core.model.ModelStore;
|
|
||||||
import me.cortex.voxy.client.core.rendering.Viewport;
|
|
||||||
import me.cortex.voxy.client.core.rendering.section.geometry.IGeometryData;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
//Takes in mesh ids from the hierachical traversal and may perform more culling then renders it
|
|
||||||
public abstract class AbstractSectionRenderer <T extends Viewport<T>, J extends IGeometryData> {
|
|
||||||
protected final J geometryManager;
|
|
||||||
protected final ModelStore modelStore;
|
|
||||||
protected AbstractSectionRenderer(ModelStore modelStore, J geometryManager) {
|
|
||||||
this.geometryManager = geometryManager;
|
|
||||||
this.modelStore = modelStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void renderOpaque(T viewport, GlTexture depthBoundTexture);
|
|
||||||
public abstract void buildDrawCalls(T viewport);
|
|
||||||
public abstract void renderTemporal(T viewport, GlTexture depthBoundTexture);
|
|
||||||
public abstract void renderTranslucent(T viewport, GlTexture depthBoundTexture);
|
|
||||||
public abstract T createViewport();
|
|
||||||
public abstract void free();
|
|
||||||
|
|
||||||
public J getGeometryManager() {
|
|
||||||
return this.geometryManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addDebug(List<String> lines) {}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package me.cortex.voxy.client.core.rendering.section.backend;
|
||||||
|
|
||||||
|
|
||||||
|
import me.cortex.voxy.client.core.AbstractRenderPipeline;
|
||||||
|
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.Viewport;
|
||||||
|
import me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData;
|
||||||
|
import me.cortex.voxy.client.core.rendering.section.geometry.IGeometryData;
|
||||||
|
import me.cortex.voxy.common.Logger;
|
||||||
|
import net.minecraft.client.multiplayer.ClientLevel;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.world.level.dimension.DimensionType;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
//Takes in mesh ids from the hierachical traversal and may perform more culling then renders it
|
||||||
|
public abstract class AbstractSectionRenderer <T extends Viewport<T>, J extends IGeometryData> {
|
||||||
|
public interface FactoryConstructor<VIEWPORT extends Viewport<VIEWPORT>, GEODATA extends IGeometryData> {
|
||||||
|
AbstractSectionRenderer<VIEWPORT, GEODATA> create(AbstractRenderPipeline pipeline, ModelStore modelStore, GEODATA geometryData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Factory<VIEWPORT extends Viewport<VIEWPORT>, GEODATA extends IGeometryData>(Class<? extends AbstractSectionRenderer<VIEWPORT, GEODATA>> clz, FactoryConstructor<VIEWPORT, GEODATA> constructor) {
|
||||||
|
public AbstractSectionRenderer<VIEWPORT, GEODATA> create(AbstractRenderPipeline pipeline, ModelStore store, IGeometryData geometryData) {
|
||||||
|
return this.constructor.create(pipeline, store, (GEODATA) geometryData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <VIEWPORT2 extends Viewport<VIEWPORT2>, GEODATA2 extends IGeometryData> Factory<VIEWPORT2, GEODATA2> create(Class<? extends AbstractSectionRenderer<VIEWPORT2, GEODATA2>> clz) {
|
||||||
|
var constructors = clz.getConstructors();
|
||||||
|
if (constructors.length != 1) {
|
||||||
|
Logger.error("Render backend " + clz.getCanonicalName() + " had more then 1 constructor");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var constructor = constructors[0];
|
||||||
|
var params = constructor.getParameterTypes();
|
||||||
|
if (params.length != 3 || params[0] != AbstractRenderPipeline.class || params[1] != ModelStore.class || !IGeometryData.class.isAssignableFrom(params[2])) {
|
||||||
|
Logger.error("Render backend " + clz.getCanonicalName() + " had invalid constructor");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Factory<>(clz, (a,b,c)-> {
|
||||||
|
try {
|
||||||
|
return (AbstractSectionRenderer<VIEWPORT2, GEODATA2>) constructor.newInstance(a,b,c);
|
||||||
|
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected final J geometryManager;
|
||||||
|
protected final ModelStore modelStore;
|
||||||
|
protected AbstractSectionRenderer(ModelStore modelStore, J geometryManager) {
|
||||||
|
this.geometryManager = geometryManager;
|
||||||
|
this.modelStore = modelStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void renderOpaque(T viewport);
|
||||||
|
public abstract void buildDrawCalls(T viewport);
|
||||||
|
public abstract void renderTemporal(T viewport);
|
||||||
|
public abstract void renderTranslucent(T viewport);
|
||||||
|
public abstract T createViewport();
|
||||||
|
public abstract void free();
|
||||||
|
|
||||||
|
public J getGeometryManager() {
|
||||||
|
return this.geometryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDebug(List<String> lines) {}
|
||||||
|
|
||||||
|
protected static void addDirectionalFaceTint(Shader.Builder<?> builder, ClientLevel cl) {
|
||||||
|
builder.define("NO_SHADE_FACE_TINT", cl.getShade(Direction.UP, false));
|
||||||
|
builder.define("UP_FACE_TINT", cl.getShade(Direction.UP, true));
|
||||||
|
builder.define("DOWN_FACE_TINT", cl.getShade(Direction.DOWN, true));
|
||||||
|
builder.define("Z_AXIS_FACE_TINT", cl.getShade(Direction.NORTH, true));//assumed here that Direction.SOUTH returns the same value
|
||||||
|
builder.define("X_AXIS_FACE_TINT", cl.getShade(Direction.EAST, true));//assumed here that Direction.WEST returns the same value
|
||||||
|
/*
|
||||||
|
//TODO: generate the tinting table here and use the replacement feature
|
||||||
|
float[] tints = new float[7];
|
||||||
|
tints[6] = cl.getShade(Direction.UP, false);
|
||||||
|
for (Direction direction : Direction.values()) {
|
||||||
|
tints[direction.get3DDataValue()] = cl.getShade(direction, true);
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Shader tryCompilePatchedOrNormal(Shader.Builder<?> builder, String shader, String original) {
|
||||||
|
boolean patched = shader != original;//This is the correct comparison type (reference)
|
||||||
|
try {
|
||||||
|
return builder.clone()
|
||||||
|
.defineIf("PATCHED_SHADER", patched)
|
||||||
|
.addSource(ShaderType.FRAGMENT, shader)
|
||||||
|
.compile();
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
if (patched) {
|
||||||
|
Logger.error("Failed to compile shader patch, using normal pipeline to prevent errors", e);
|
||||||
|
return tryCompilePatchedOrNormal(builder, original, original);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,26 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.section;
|
package me.cortex.voxy.client.core.rendering.section.backend.mdic;
|
||||||
|
|
||||||
|
|
||||||
import me.cortex.voxy.client.RenderStatistics;
|
import me.cortex.voxy.client.RenderStatistics;
|
||||||
|
import me.cortex.voxy.client.VoxyClient;
|
||||||
|
import me.cortex.voxy.client.core.AbstractRenderPipeline;
|
||||||
import me.cortex.voxy.client.core.gl.Capabilities;
|
import me.cortex.voxy.client.core.gl.Capabilities;
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
import me.cortex.voxy.client.core.gl.GlVertexArray;
|
||||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||||
|
import me.cortex.voxy.client.core.gl.shader.ShaderLoader;
|
||||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||||
import me.cortex.voxy.client.core.model.ModelStore;
|
import me.cortex.voxy.client.core.model.ModelStore;
|
||||||
|
import me.cortex.voxy.client.core.rendering.section.backend.AbstractSectionRenderer;
|
||||||
import me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData;
|
import me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData;
|
||||||
import me.cortex.voxy.client.core.rendering.util.LightMapHelper;
|
|
||||||
import me.cortex.voxy.client.core.rendering.RenderService;
|
|
||||||
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
|
|
||||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
||||||
|
import me.cortex.voxy.client.core.rendering.util.LightMapHelper;
|
||||||
|
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
|
||||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||||
import me.cortex.voxy.common.Logger;
|
import me.cortex.voxy.common.Logger;
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.lwjgl.system.MemoryUtil;
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
@@ -34,19 +39,17 @@ import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER;
|
|||||||
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
|
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
|
||||||
import static org.lwjgl.opengl.GL43.*;
|
import static org.lwjgl.opengl.GL43.*;
|
||||||
import static org.lwjgl.opengl.GL45.glBindTextureUnit;
|
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;
|
import static org.lwjgl.opengl.NVRepresentativeFragmentTest.GL_REPRESENTATIVE_FRAGMENT_TEST_NV;
|
||||||
|
|
||||||
//Uses MDIC to render the sections
|
//Uses MDIC to render the sections
|
||||||
public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, BasicSectionGeometryData> {
|
public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, BasicSectionGeometryData> {
|
||||||
|
public static final Factory<MDICViewport, BasicSectionGeometryData> FACTORY = AbstractSectionRenderer.Factory.create(MDICSectionRenderer.class);
|
||||||
|
|
||||||
private static final int TRANSLUCENT_OFFSET = 400_000;//in draw calls
|
private static final int TRANSLUCENT_OFFSET = 400_000;//in draw calls
|
||||||
private static final int TEMPORAL_OFFSET = 500_000;//in draw calls
|
private static final int TEMPORAL_OFFSET = 500_000;//in draw calls
|
||||||
private static final int STATISTICS_BUFFER_BINDING = 8;
|
private static final int STATISTICS_BUFFER_BINDING = 8;
|
||||||
private final Shader terrainShader = Shader.make()
|
private final Shader terrainShader;
|
||||||
.defineIf("DEBUG_RENDER", false)
|
private final Shader translucentTerrainShader;
|
||||||
.add(ShaderType.VERTEX, "voxy:lod/gl46/quads2.vert")
|
|
||||||
.add(ShaderType.FRAGMENT, "voxy:lod/gl46/quads.frag")
|
|
||||||
.compile();
|
|
||||||
|
|
||||||
private final Shader commandGenShader = Shader.make()
|
private final Shader commandGenShader = Shader.make()
|
||||||
.define("TRANSLUCENT_WRITE_BASE", 1024)
|
.define("TRANSLUCENT_WRITE_BASE", 1024)
|
||||||
@@ -70,7 +73,8 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, B
|
|||||||
.compile();
|
.compile();
|
||||||
|
|
||||||
private final Shader prefixSumShader = Shader.make()
|
private final Shader prefixSumShader = Shader.make()
|
||||||
.add(ShaderType.COMPUTE, "voxy:util/prefixsum/inital3.comp")
|
//Use subgroup prefix sum if possible otherwise use dodgy... slow prefix sum
|
||||||
|
.add(ShaderType.COMPUTE, Capabilities.INSTANCE.subgroup?"voxy:util/prefixsum/inital3.comp":"voxy:util/prefixsum/simple.comp")
|
||||||
.define("IO_BUFFER", 0)
|
.define("IO_BUFFER", 0)
|
||||||
.compile();
|
.compile();
|
||||||
|
|
||||||
@@ -90,8 +94,40 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, B
|
|||||||
//Statistics
|
//Statistics
|
||||||
private final GlBuffer statisticsBuffer = new GlBuffer(1024).zero();
|
private final GlBuffer statisticsBuffer = new GlBuffer(1024).zero();
|
||||||
|
|
||||||
public MDICSectionRenderer(ModelStore modelStore, BasicSectionGeometryData geometryData) {
|
private final AbstractRenderPipeline pipeline;
|
||||||
|
public MDICSectionRenderer(AbstractRenderPipeline pipeline, ModelStore modelStore, BasicSectionGeometryData geometryData) {
|
||||||
super(modelStore, geometryData);
|
super(modelStore, geometryData);
|
||||||
|
this.pipeline = pipeline;
|
||||||
|
//The pipeline can be used to transform the renderer in abstract ways
|
||||||
|
|
||||||
|
String vertex = ShaderLoader.parse("voxy:lod/gl46/quads3.vert");
|
||||||
|
String taa = pipeline.taaFunction("taaShift");
|
||||||
|
if (taa != null) {
|
||||||
|
vertex += "\n"+taa;//inject it at the end
|
||||||
|
}
|
||||||
|
var builder = Shader.make()
|
||||||
|
.defineIf("TAA_PATCH", taa != null)
|
||||||
|
.defineIf("DEBUG_RENDER", false)
|
||||||
|
|
||||||
|
//.defineIf("USE_NV_BARRY", Capabilities.INSTANCE.nvBarryCoords)
|
||||||
|
|
||||||
|
.addSource(ShaderType.VERTEX, vertex);
|
||||||
|
|
||||||
|
//Apply per face tinting
|
||||||
|
addDirectionalFaceTint(builder, Minecraft.getInstance().level);
|
||||||
|
|
||||||
|
String frag = ShaderLoader.parse("voxy:lod/gl46/quads.frag");
|
||||||
|
|
||||||
|
String opaqueFrag = pipeline.patchOpaqueShader(this, frag);
|
||||||
|
opaqueFrag = opaqueFrag==null?frag:opaqueFrag;
|
||||||
|
|
||||||
|
//TODO: find a more robust/nicer way todo this
|
||||||
|
this.terrainShader = tryCompilePatchedOrNormal(builder, opaqueFrag, frag);
|
||||||
|
|
||||||
|
String translucentFrag = pipeline.patchTranslucentShader(this, frag);
|
||||||
|
translucentFrag = translucentFrag==null?frag:translucentFrag;
|
||||||
|
|
||||||
|
this.translucentTerrainShader = tryCompilePatchedOrNormal(builder.define("TRANSLUCENT"), translucentFrag, frag);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void uploadUniformBuffer(MDICViewport viewport) {
|
private void uploadUniformBuffer(MDICViewport viewport) {
|
||||||
@@ -114,32 +150,42 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, B
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void bindRenderingBuffers(MDICViewport viewport, GlTexture depthBoundTexture) {
|
private void bindRenderingBuffers(MDICViewport viewport) {
|
||||||
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniform.id);
|
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniform.id);
|
||||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.geometryManager.getGeometryBuffer().id);
|
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.geometryManager.getGeometryBuffer().id);
|
||||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.geometryManager.getMetadataBuffer().id);
|
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.geometryManager.getMetadataBuffer().id);
|
||||||
this.modelStore.bind(3, 4, 0);
|
this.modelStore.bind(3, 4, 0);
|
||||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, viewport.positionScratchBuffer.id);
|
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, viewport.positionScratchBuffer.id);
|
||||||
LightMapHelper.bind(1);
|
LightMapHelper.bind(1);
|
||||||
glBindTextureUnit(2, depthBoundTexture.id);
|
glBindTextureUnit(2, viewport.depthBoundingBuffer.getDepthTex().id);
|
||||||
|
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE.id());
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE.id());
|
||||||
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, viewport.drawCallBuffer.id);
|
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, viewport.drawCallBuffer.id);
|
||||||
glBindBuffer(GL_PARAMETER_BUFFER_ARB, viewport.drawCountCallBuffer.id);
|
glBindBuffer(GL_PARAMETER_BUFFER_ARB, viewport.drawCountCallBuffer.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderTerrain(MDICViewport viewport, GlTexture depthBoundTexture, long indirectOffset, long drawCountOffset, int maxDrawCount) {
|
private void renderTerrain(MDICViewport viewport, long indirectOffset, long drawCountOffset, int maxDrawCount) {
|
||||||
//RenderLayer.getCutoutMipped().startDrawing();
|
//RenderLayer.getCutoutMipped().startDrawing();
|
||||||
|
|
||||||
|
|
||||||
glDisable(GL_CULL_FACE);
|
glDisable(GL_CULL_FACE);
|
||||||
|
glDisable(GL_BLEND);
|
||||||
glEnable(GL_DEPTH_TEST);
|
glEnable(GL_DEPTH_TEST);
|
||||||
this.terrainShader.bind();
|
this.terrainShader.bind();
|
||||||
glBindVertexArray(RenderService.STATIC_VAO);//Needs to be before binding
|
glBindVertexArray(GlVertexArray.STATIC_VAO);//Needs to be before binding
|
||||||
this.bindRenderingBuffers(viewport, depthBoundTexture);
|
this.pipeline.setupAndBindOpaque(viewport);
|
||||||
|
this.bindRenderingBuffers(viewport);
|
||||||
|
|
||||||
glMemoryBarrier(GL_COMMAND_BARRIER_BIT|GL_SHADER_STORAGE_BARRIER_BIT);//Barrier everything is needed
|
glMemoryBarrier(GL_COMMAND_BARRIER_BIT|GL_SHADER_STORAGE_BARRIER_BIT);//Barrier everything is needed
|
||||||
glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
|
glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
|
||||||
|
|
||||||
|
if (VoxyClient.getOcclusionDebugState()==3) {
|
||||||
|
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
||||||
|
}
|
||||||
glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, indirectOffset, drawCountOffset, maxDrawCount, 0);
|
glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, indirectOffset, drawCountOffset, maxDrawCount, 0);
|
||||||
|
if (VoxyClient.getOcclusionDebugState()==3) {
|
||||||
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
||||||
|
}
|
||||||
|
|
||||||
glEnable(GL_CULL_FACE);
|
glEnable(GL_CULL_FACE);
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
@@ -152,25 +198,27 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, B
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void renderOpaque(MDICViewport viewport, GlTexture dbt) {
|
public void renderOpaque(MDICViewport viewport) {
|
||||||
if (this.geometryManager.getSectionCount() == 0) return;
|
if (this.geometryManager.getSectionCount() == 0) return;
|
||||||
|
|
||||||
this.uploadUniformBuffer(viewport);
|
this.uploadUniformBuffer(viewport);
|
||||||
|
|
||||||
this.renderTerrain(viewport, dbt, 0, 4*3, Math.min((int)(this.geometryManager.getSectionCount()*4.4+128), 400_000));
|
this.renderTerrain(viewport, 0, 4*3, Math.min((int)(this.geometryManager.getSectionCount()*4.4+128), 400_000));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void renderTranslucent(MDICViewport viewport, GlTexture depthBoundTexture) {
|
public void renderTranslucent(MDICViewport viewport) {
|
||||||
if (this.geometryManager.getSectionCount() == 0) return;
|
if (this.geometryManager.getSectionCount() == 0) return;
|
||||||
|
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
glDisable(GL_CULL_FACE);
|
glDisable(GL_CULL_FACE);
|
||||||
glEnable(GL_DEPTH_TEST);
|
glEnable(GL_DEPTH_TEST);
|
||||||
this.terrainShader.bind();
|
this.translucentTerrainShader.bind();
|
||||||
glBindVertexArray(RenderService.STATIC_VAO);//Needs to be before binding
|
glBindVertexArray(GlVertexArray.STATIC_VAO);//Needs to be before binding
|
||||||
this.bindRenderingBuffers(viewport, depthBoundTexture);
|
this.pipeline.setupAndBindTranslucent(viewport);
|
||||||
|
this.bindRenderingBuffers(viewport);
|
||||||
|
|
||||||
glMemoryBarrier(GL_COMMAND_BARRIER_BIT|GL_SHADER_STORAGE_BARRIER_BIT);//Barrier everything is needed
|
glMemoryBarrier(GL_COMMAND_BARRIER_BIT|GL_SHADER_STORAGE_BARRIER_BIT);//Barrier everything is needed
|
||||||
glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
|
glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
|
||||||
@@ -209,7 +257,7 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, B
|
|||||||
if (Capabilities.INSTANCE.repFragTest) {
|
if (Capabilities.INSTANCE.repFragTest) {
|
||||||
glEnable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
|
glEnable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
|
||||||
}
|
}
|
||||||
glBindVertexArray(RenderService.STATIC_VAO);
|
glBindVertexArray(GlVertexArray.STATIC_VAO);
|
||||||
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniform.id);
|
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniform.id);
|
||||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.geometryManager.getMetadataBuffer().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, 2, viewport.visibilityBuffer.id);
|
||||||
@@ -290,10 +338,10 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, B
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void renderTemporal(MDICViewport viewport, GlTexture dbt) {
|
public void renderTemporal(MDICViewport viewport) {
|
||||||
if (this.geometryManager.getSectionCount() == 0) return;
|
if (this.geometryManager.getSectionCount() == 0) return;
|
||||||
//Render temporal
|
//Render temporal
|
||||||
this.renderTerrain(viewport, dbt, TEMPORAL_OFFSET*5*4, 4*5, Math.min(this.geometryManager.getSectionCount(), 100_000));
|
this.renderTerrain(viewport, TEMPORAL_OFFSET*5*4, 4*5, Math.min(this.geometryManager.getSectionCount(), 100_000));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -311,6 +359,7 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, B
|
|||||||
public void free() {
|
public void free() {
|
||||||
this.uniform.free();
|
this.uniform.free();
|
||||||
this.distanceCountBuffer.free();
|
this.distanceCountBuffer.free();
|
||||||
|
this.translucentTerrainShader.free();
|
||||||
this.terrainShader.free();
|
this.terrainShader.free();
|
||||||
this.commandGenShader.free();
|
this.commandGenShader.free();
|
||||||
this.cullShader.free();
|
this.cullShader.free();
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.section;
|
package me.cortex.voxy.client.core.rendering.section.backend.mdic;
|
||||||
|
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||||
import me.cortex.voxy.client.core.rendering.Viewport;
|
import me.cortex.voxy.client.core.rendering.Viewport;
|
||||||
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser;
|
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 class MDICViewport extends Viewport<MDICViewport> {
|
||||||
public final GlBuffer drawCountCallBuffer = new GlBuffer(1024).zero();
|
public final GlBuffer drawCountCallBuffer = new GlBuffer(1024).zero();
|
||||||
public final GlBuffer drawCallBuffer = new GlBuffer(5*4*(400_000+100_000+100_000)).zero();//400k draw calls
|
public final GlBuffer drawCallBuffer = new GlBuffer(5*4*(400_000+100_000+100_000)).zero();//400k draw calls
|
||||||
public final GlBuffer positionScratchBuffer = new GlBuffer(8*400000).zero();//400k positions
|
public final GlBuffer positionScratchBuffer = new GlBuffer(8*400000).zero();//400k positions
|
||||||
public final GlBuffer indirectLookupBuffer = new GlBuffer(HierarchicalOcclusionTraverser.MAX_QUEUE_SIZE *4+4);
|
public final GlBuffer indirectLookupBuffer = new GlBuffer(HierarchicalOcclusionTraverser.MAX_QUEUE_SIZE *4+4);//In theory, this could be global/not unique to the viewport
|
||||||
public final GlBuffer visibilityBuffer;
|
public final GlBuffer visibilityBuffer;
|
||||||
|
|
||||||
public MDICViewport(int maxSectionCount) {
|
public MDICViewport(int maxSectionCount) {
|
||||||
@@ -11,11 +11,12 @@ import org.lwjgl.system.MemoryUtil;
|
|||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryManager.SECTION_METADATA_SIZE;
|
|
||||||
|
|
||||||
//Is basicly the manager for an "undefined" data store, the underlying store is irrelevant
|
//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
|
// 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 {
|
public class BasicAsyncGeometryManager implements IGeometryManager {
|
||||||
|
public static final int SECTION_METADATA_SIZE = 32;
|
||||||
|
|
||||||
private static final long GEOMETRY_ELEMENT_SIZE = 8;
|
private static final long GEOMETRY_ELEMENT_SIZE = 8;
|
||||||
private final HierarchicalBitSet allocationSet;
|
private final HierarchicalBitSet allocationSet;
|
||||||
private final AllocationArena allocationHeap = new AllocationArena();
|
private final AllocationArena allocationHeap = new AllocationArena();
|
||||||
@@ -107,12 +108,14 @@ public class BasicAsyncGeometryManager implements IGeometryManager {
|
|||||||
private SectionMeta createMeta(BuiltSection section) {
|
private SectionMeta createMeta(BuiltSection section) {
|
||||||
if ((section.geometryBuffer.size%GEOMETRY_ELEMENT_SIZE)!=0) throw new IllegalStateException();
|
if ((section.geometryBuffer.size%GEOMETRY_ELEMENT_SIZE)!=0) throw new IllegalStateException();
|
||||||
int size = (int) (section.geometryBuffer.size/GEOMETRY_ELEMENT_SIZE);
|
int size = (int) (section.geometryBuffer.size/GEOMETRY_ELEMENT_SIZE);
|
||||||
|
//clamp size upwards
|
||||||
|
int upsized = (size+1023)&~1023;
|
||||||
//Address
|
//Address
|
||||||
int addr = (int)this.allocationHeap.alloc(size);
|
int addr = (int)this.allocationHeap.alloc(upsized);
|
||||||
if (addr == -1) {
|
if (addr == -1) {
|
||||||
throw new IllegalStateException("Geometry OOM");
|
throw new IllegalStateException("Geometry OOM. requested allocation size (in elements): " + size + ", Heap size at top remaining: " + (this.allocationHeap.getLimit()-this.allocationHeap.getSize()) + ", used elements: " + this.usedCapacity);
|
||||||
}
|
}
|
||||||
this.usedCapacity += size;
|
this.usedCapacity += upsized;
|
||||||
//Create upload
|
//Create upload
|
||||||
if (this.heapUploads.put(addr, section.geometryBuffer) != null) {
|
if (this.heapUploads.put(addr, section.geometryBuffer) != null) {
|
||||||
throw new IllegalStateException("Addr: " + addr);
|
throw new IllegalStateException("Addr: " + addr);
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.section.geometry;
|
package me.cortex.voxy.client.core.rendering.section.geometry;
|
||||||
|
|
||||||
|
import me.cortex.voxy.client.core.gl.Capabilities;
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||||
|
import me.cortex.voxy.common.Logger;
|
||||||
|
import me.cortex.voxy.common.util.ThreadUtils;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.ARBSparseBuffer.*;
|
||||||
|
import static org.lwjgl.opengl.GL11C.*;
|
||||||
|
import static org.lwjgl.opengl.GL15C.GL_ARRAY_BUFFER;
|
||||||
|
import static org.lwjgl.opengl.GL15C.glBindBuffer;
|
||||||
|
|
||||||
public class BasicSectionGeometryData implements IGeometryData {
|
public class BasicSectionGeometryData implements IGeometryData {
|
||||||
public static final int SECTION_METADATA_SIZE = 32;
|
public static final int SECTION_METADATA_SIZE = 32;
|
||||||
@@ -17,7 +25,58 @@ public class BasicSectionGeometryData implements IGeometryData {
|
|||||||
if ((geometryCapacity%8)!=0) {
|
if ((geometryCapacity%8)!=0) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
this.geometryBuffer = new GlBuffer(geometryCapacity);
|
long start = System.currentTimeMillis();
|
||||||
|
String msg = "Creating and zeroing " + (geometryCapacity/(1024*1024)) + "MB geometry buffer";
|
||||||
|
if (Capabilities.INSTANCE.canQueryGpuMemory) {
|
||||||
|
msg += " driver states " + (Capabilities.INSTANCE.getFreeDedicatedGpuMemory()/(1024*1024)) + "MB of free memory";
|
||||||
|
}
|
||||||
|
Logger.info(msg);
|
||||||
|
Logger.info("if your game crashes/exits here without any other log message, try manually decreasing the geometry capacity");
|
||||||
|
glGetError();//Clear any errors
|
||||||
|
GlBuffer buffer = null;
|
||||||
|
if (!(Capabilities.INSTANCE.isNvidia&&Capabilities.INSTANCE.sparseBuffer)) {//This hack makes it so it doesnt crash on renderdoc
|
||||||
|
buffer = new GlBuffer(geometryCapacity, false);//Only do this if we are not on nvidia
|
||||||
|
//TODO: FIXME: TEST, see if the issue is that we are trying to zero the entire buffer, try only zeroing increments
|
||||||
|
// or dont zero it at all
|
||||||
|
} else {
|
||||||
|
Logger.info("Running on nvidia, using workaround sparse buffer allocation");
|
||||||
|
}
|
||||||
|
int error = glGetError();
|
||||||
|
if (error != GL_NO_ERROR || buffer == null) {
|
||||||
|
if ((buffer == null || error == GL_OUT_OF_MEMORY) && Capabilities.INSTANCE.sparseBuffer) {
|
||||||
|
if (buffer != null) {
|
||||||
|
Logger.error("Failed to allocate geometry buffer, attempting workaround with sparse buffers");
|
||||||
|
buffer.free();
|
||||||
|
}
|
||||||
|
buffer = new GlBuffer(geometryCapacity, GL_SPARSE_STORAGE_BIT_ARB);
|
||||||
|
//buffer.zero();
|
||||||
|
error = glGetError();
|
||||||
|
if (error != GL_NO_ERROR) {
|
||||||
|
buffer.free();
|
||||||
|
throw new IllegalStateException("Unable to allocate geometry buffer using workaround, got gl error " + error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Unable to allocate geometry buffer, got gl error " + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.geometryBuffer = buffer;
|
||||||
|
long delta = System.currentTimeMillis() - start;
|
||||||
|
Logger.info("Successfully allocated the geometry buffer in " + delta + "ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
private long sparseCommitment = 0;//Tracks the current range of the allocated sparse buffer
|
||||||
|
public void ensureAccessable(int maxElementAccess) {
|
||||||
|
long size = (Integer.toUnsignedLong(maxElementAccess)*8L+65535L)&~65535L;
|
||||||
|
//If we are a sparse buffer, ensure the memory upto the requested size is allocated
|
||||||
|
if (this.geometryBuffer.isSparse()) {
|
||||||
|
if (this.sparseCommitment < size) {//if we try to access memory outside the allocation range, allocate it
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, this.geometryBuffer.id);
|
||||||
|
size += 65536L*1024;//increase size by 64mb to prevent driver allocation thrashing
|
||||||
|
glBufferPageCommitmentARB(GL_ARRAY_BUFFER, this.sparseCommitment, size-this.sparseCommitment, true);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||||
|
this.sparseCommitment = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public GlBuffer getGeometryBuffer() {
|
public GlBuffer getGeometryBuffer() {
|
||||||
@@ -47,6 +106,40 @@ public class BasicSectionGeometryData implements IGeometryData {
|
|||||||
@Override
|
@Override
|
||||||
public void free() {
|
public void free() {
|
||||||
this.sectionMetadataBuffer.free();
|
this.sectionMetadataBuffer.free();
|
||||||
|
|
||||||
|
long gpuMemory = 0;
|
||||||
|
if (Capabilities.INSTANCE.canQueryGpuMemory) {
|
||||||
|
glFinish();
|
||||||
|
gpuMemory = Capabilities.INSTANCE.getFreeDedicatedGpuMemory();
|
||||||
|
}
|
||||||
|
if (this.geometryBuffer.isSparse()) {
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, this.geometryBuffer.id);
|
||||||
|
glBufferPageCommitmentARB(GL_ARRAY_BUFFER, 0, this.sparseCommitment, false);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
glFinish();
|
||||||
this.geometryBuffer.free();
|
this.geometryBuffer.free();
|
||||||
|
glFinish();
|
||||||
|
if (Capabilities.INSTANCE.canQueryGpuMemory) {
|
||||||
|
long releaseSize = (long) (this.geometryBuffer.size()*0.75);//if gpu memory usage drops by 75% of the expected value assume we freed it
|
||||||
|
if (this.geometryBuffer.isSparse()) {//If we are using sparse buffers, use the commited size instead
|
||||||
|
releaseSize = (long)(this.sparseCommitment*0.75);
|
||||||
|
}
|
||||||
|
if (Capabilities.INSTANCE.getFreeDedicatedGpuMemory()-gpuMemory<=releaseSize) {
|
||||||
|
Logger.info("Attempting to wait for gpu memory to release");
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
|
long TIMEOUT = 2500;
|
||||||
|
|
||||||
|
while (System.currentTimeMillis() - start > TIMEOUT) {//Wait up to 2.5 seconds for memory to release
|
||||||
|
glFinish();
|
||||||
|
if (Capabilities.INSTANCE.getFreeDedicatedGpuMemory() - gpuMemory > releaseSize) break;
|
||||||
|
}
|
||||||
|
if (Capabilities.INSTANCE.getFreeDedicatedGpuMemory() - gpuMemory <= releaseSize) {
|
||||||
|
Logger.warn("Failed to wait for gpu memory to be freed, this could indicate an issue with the driver");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,7 +135,8 @@ public class BasicSectionGeometryManager extends AbstractSectionGeometryManager
|
|||||||
}
|
}
|
||||||
var oldMetadata = this.sectionMetadata.set(id, null);
|
var oldMetadata = this.sectionMetadata.set(id, null);
|
||||||
this.geometry.downloadRemove(oldMetadata.geometryPtr, buffer ->
|
this.geometry.downloadRemove(oldMetadata.geometryPtr, buffer ->
|
||||||
callback.accept(new BuiltSection(oldMetadata.position, oldMetadata.childExistence, oldMetadata.aabb, buffer.copy(), oldMetadata.offsets))
|
//TODO: occupancy
|
||||||
|
callback.accept(new BuiltSection(oldMetadata.position, oldMetadata.childExistence, oldMetadata.aabb, buffer.copy(), oldMetadata.offsets, null))
|
||||||
);
|
);
|
||||||
//this.geometry.free(oldMetadata.geometryPtr);
|
//this.geometry.free(oldMetadata.geometryPtr);
|
||||||
this.invalidatedSectionIds.add(id);
|
this.invalidatedSectionIds.add(id);
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.util;
|
|
||||||
|
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
|
||||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
|
||||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
|
||||||
import org.lwjgl.system.MemoryUtil;
|
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
import static org.lwjgl.opengl.GL15.glBindBuffer;
|
|
||||||
import static org.lwjgl.opengl.GL20.glUniform1i;
|
|
||||||
import static org.lwjgl.opengl.GL20.glUseProgram;
|
|
||||||
import static org.lwjgl.opengl.GL30.glBindBufferBase;
|
|
||||||
import static org.lwjgl.opengl.GL30.glUniform1ui;
|
|
||||||
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
|
|
||||||
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
|
|
||||||
import static org.lwjgl.opengl.GL43C.glDispatchCompute;
|
|
||||||
|
|
||||||
//Utilities for common operations not suited for basic gl functions
|
|
||||||
// such as sparse memory setting
|
|
||||||
|
|
||||||
//TODO CLEAN THIS SHIT UP
|
|
||||||
public class ComputeUtils {
|
|
||||||
private ComputeUtils() {}
|
|
||||||
public static ComputeUtils INSTANCE = new ComputeUtils();
|
|
||||||
private static final int SETTING_BUFFER_BINDING = 1;
|
|
||||||
private static final int ENTRY_BUFFER_BINDING = 2;
|
|
||||||
|
|
||||||
//TODO: FIXME! This should itself be just a raw streaming buffer/mapped ptr (probably)
|
|
||||||
private final GlBuffer SCRATCH = new GlBuffer(1<<20);//1 MB scratch buffer... this should be enough.. right?
|
|
||||||
|
|
||||||
private int maxCount;
|
|
||||||
private int count;
|
|
||||||
private long ptr;
|
|
||||||
|
|
||||||
private final Supplier<Shader> uintSetShader = makeCacheSetShader("uint");
|
|
||||||
public void prepSetUint(int maxCount) {
|
|
||||||
if (this.count != 0 || this.maxCount != 0 || this.ptr != 0) {
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
this.ptr = UploadStream.INSTANCE.upload(SCRATCH, 0, maxCount*8L);
|
|
||||||
this.maxCount = maxCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pushSetUint(int index, int value) {
|
|
||||||
//For uint it goes
|
|
||||||
// {uint value; uint index;}
|
|
||||||
if (this.maxCount <= this.count++) {
|
|
||||||
throw new IllegalStateException("Pushed to many values to prepared set");
|
|
||||||
}
|
|
||||||
MemoryUtil.memPutInt(this.ptr, value); this.ptr += 4;
|
|
||||||
MemoryUtil.memPutInt(this.ptr, index); this.ptr += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void finishSetUint(GlBuffer dst) {
|
|
||||||
UploadStream.INSTANCE.commit();
|
|
||||||
this.uintSetShader.get().bind();
|
|
||||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, SETTING_BUFFER_BINDING, dst.id);
|
|
||||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, ENTRY_BUFFER_BINDING, this.SCRATCH.id);
|
|
||||||
glUniform1i(0, this.count);
|
|
||||||
glDispatchCompute((this.count+127)/128, 1, 1);
|
|
||||||
this.ptr = 0;
|
|
||||||
this.maxCount = 0;
|
|
||||||
this.count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static Supplier<Shader> makeCacheSetShader(String type) {
|
|
||||||
return makeAndCache(()->makeSetShader(type));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Shader makeSetShader(String type) {
|
|
||||||
return Shader.make()
|
|
||||||
.define("TYPE", type)
|
|
||||||
.define("SETTING_BUFFER_BINDING", SETTING_BUFFER_BINDING)
|
|
||||||
.define("ENTRY_BUFFER_BINDING", ENTRY_BUFFER_BINDING)
|
|
||||||
.add(ShaderType.COMPUTE, "voxy:util/set.comp")
|
|
||||||
.compile();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T> Supplier<T> makeAndCache(Supplier<T> maker) {
|
|
||||||
Object[] value = new Object[1];
|
|
||||||
boolean[] hasSet = new boolean[1];
|
|
||||||
return ()->{
|
|
||||||
if (hasSet[0]) {
|
|
||||||
return (T) value[0];
|
|
||||||
} else {
|
|
||||||
var val = maker.get();
|
|
||||||
hasSet[0] = true;
|
|
||||||
value[0] = val;
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.util;
|
|
||||||
|
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
|
||||||
import me.cortex.voxy.common.util.UnsafeUtil;
|
|
||||||
import org.lwjgl.system.MemoryUtil;
|
|
||||||
|
|
||||||
//Just a utility for making a deferred upload (make on other thread then upload on render thread)
|
|
||||||
public final class DeferredUpload {
|
|
||||||
public final long ptr;
|
|
||||||
private final long size;
|
|
||||||
private final long offset;
|
|
||||||
private final GlBuffer buffer;
|
|
||||||
public DeferredUpload(GlBuffer buffer, long offset, long size) {
|
|
||||||
this.ptr = MemoryUtil.nmemAlloc(size);
|
|
||||||
this.offset = offset;
|
|
||||||
this.buffer = buffer;
|
|
||||||
this.size = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void upload() {
|
|
||||||
long upPtr = UploadStream.INSTANCE.upload(this.buffer, this.offset, this.size);
|
|
||||||
UnsafeUtil.memcpy(this.ptr, upPtr, this.size);
|
|
||||||
MemoryUtil.nmemFree(this.ptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
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 org.lwjgl.system.MemoryStack;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.ARBDirectStateAccess.nglClearNamedFramebufferfv;
|
||||||
|
import static org.lwjgl.opengl.GL11C.GL_DEPTH;
|
||||||
|
import static org.lwjgl.opengl.GL14.GL_DEPTH_COMPONENT24;
|
||||||
|
import static org.lwjgl.opengl.GL30C.*;
|
||||||
|
|
||||||
|
public class DepthFramebuffer {
|
||||||
|
private final int depthType;
|
||||||
|
private GlTexture depthBuffer;
|
||||||
|
public final GlFramebuffer framebuffer = new GlFramebuffer();
|
||||||
|
|
||||||
|
public DepthFramebuffer() {
|
||||||
|
this(GL_DEPTH_COMPONENT24);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DepthFramebuffer(int depthType) {
|
||||||
|
this.depthType = depthType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean resize(int width, int height) {
|
||||||
|
if (this.depthBuffer == null || this.depthBuffer.getWidth() != width || this.depthBuffer.getHeight() != height) {
|
||||||
|
if (this.depthBuffer != null) {
|
||||||
|
this.depthBuffer.free();
|
||||||
|
}
|
||||||
|
this.depthBuffer = new GlTexture().store(this.depthType, 1, width, height);
|
||||||
|
this.framebuffer.bind(this.getDepthAttachmentType(), this.depthBuffer).verify();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDepthAttachmentType() {
|
||||||
|
return this.depthType == GL_DEPTH24_STENCIL8?GL_DEPTH_STENCIL_ATTACHMENT: GL_DEPTH_ATTACHMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
this.clear(1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear(float depth) {
|
||||||
|
try (var stack = MemoryStack.stackPush()) {
|
||||||
|
nglClearNamedFramebufferfv(this.framebuffer.id, GL_DEPTH, 0, stack.nfloat(depth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlTexture getDepthTex() {
|
||||||
|
return this.depthBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void free() {
|
||||||
|
this.framebuffer.free();
|
||||||
|
if (this.depthBuffer != null) {
|
||||||
|
this.depthBuffer.free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind() {
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, this.framebuffer.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFormat() {
|
||||||
|
return this.depthType;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ import static org.lwjgl.opengl.GL11.glFinish;
|
|||||||
import static org.lwjgl.opengl.GL30C.GL_MAP_READ_BIT;
|
import static org.lwjgl.opengl.GL30C.GL_MAP_READ_BIT;
|
||||||
import static org.lwjgl.opengl.GL42.GL_BUFFER_UPDATE_BARRIER_BIT;
|
import static org.lwjgl.opengl.GL42.GL_BUFFER_UPDATE_BARRIER_BIT;
|
||||||
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
|
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
|
||||||
import static org.lwjgl.opengl.GL44.*;
|
import static org.lwjgl.opengl.GL44.GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT;
|
||||||
import static org.lwjgl.opengl.GL45.glCopyNamedBufferSubData;
|
import static org.lwjgl.opengl.GL45.glCopyNamedBufferSubData;
|
||||||
|
|
||||||
public class DownloadStream {
|
public class DownloadStream {
|
||||||
@@ -149,6 +149,21 @@ public class DownloadStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Synchonize force flushes everything
|
//Synchonize force flushes everything
|
||||||
|
public void waitDiscard() {
|
||||||
|
glFinish();
|
||||||
|
var fence = new GlFence();
|
||||||
|
glFinish();
|
||||||
|
while (!fence.signaled())
|
||||||
|
Thread.onSpinWait();
|
||||||
|
fence.free();
|
||||||
|
while (!this.frames.isEmpty()) {
|
||||||
|
var frame = this.frames.pop();
|
||||||
|
while (!frame.fence.signaled()) Thread.onSpinWait();
|
||||||
|
frame.allocations.forEach(this.allocationArena::free);
|
||||||
|
frame.fence.free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void flushWaitClear() {
|
public void flushWaitClear() {
|
||||||
glFinish();
|
glFinish();
|
||||||
this.tick();
|
this.tick();
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.util;
|
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
|
||||||
|
|
||||||
import static org.lwjgl.opengl.GL11.*;
|
|
||||||
import static org.lwjgl.opengl.GL13.GL_ACTIVE_TEXTURE;
|
|
||||||
import static org.lwjgl.opengl.GL13.glActiveTexture;
|
|
||||||
import static org.lwjgl.opengl.GL13C.GL_TEXTURE0;
|
|
||||||
import static org.lwjgl.opengl.GL33.glBindSampler;
|
|
||||||
|
|
||||||
public class GlStateCapture {
|
|
||||||
private final int[] capabilityIds;
|
|
||||||
private final boolean[] enabledCaps;
|
|
||||||
|
|
||||||
|
|
||||||
private final int[] textureUnits;
|
|
||||||
private final int[] textures;
|
|
||||||
private GlStateCapture(int[] caps, int[] textureUnits) {
|
|
||||||
this.capabilityIds = caps;
|
|
||||||
this.enabledCaps = new boolean[caps.length];
|
|
||||||
|
|
||||||
this.textureUnits = textureUnits;
|
|
||||||
this.textures = new int[textureUnits.length];
|
|
||||||
}
|
|
||||||
|
|
||||||
public void capture() {
|
|
||||||
this.textureUnits[0] = glGetInteger(GL_ACTIVE_TEXTURE);
|
|
||||||
//Capture all the texture data
|
|
||||||
for (int i = 0; i < this.textures.length; i++) {
|
|
||||||
glActiveTexture(this.textureUnits[i]);
|
|
||||||
this.textures[i] = glGetInteger(GL_TEXTURE_BINDING_2D);
|
|
||||||
}
|
|
||||||
//Reset the original active texture
|
|
||||||
glActiveTexture(this.textureUnits[0]);
|
|
||||||
|
|
||||||
for (int i = 0; i < this.capabilityIds.length; i++) {
|
|
||||||
this.enabledCaps[i] = glIsEnabled(this.capabilityIds[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void restore() {
|
|
||||||
//Capture all the texture data
|
|
||||||
for (int i = 1; i < this.textures.length; i++) {
|
|
||||||
glActiveTexture(this.textureUnits[i]);
|
|
||||||
//glBindSampler(this.textureUnits[i]-GL_TEXTURE0, 0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, this.textures[i]);
|
|
||||||
}
|
|
||||||
//Reset the original active texture
|
|
||||||
glActiveTexture(this.textureUnits[0]);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, this.textures[0]);
|
|
||||||
|
|
||||||
for (int i = 0; i < this.capabilityIds.length; i++) {
|
|
||||||
if (this.enabledCaps[i]) {
|
|
||||||
glEnable(this.capabilityIds[i]);
|
|
||||||
} else {
|
|
||||||
glDisable(this.capabilityIds[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder make() {
|
|
||||||
return new Builder();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
private final IntArrayList caps = new IntArrayList();
|
|
||||||
private final IntArrayList textures = new IntArrayList();
|
|
||||||
|
|
||||||
private Builder() {
|
|
||||||
this.addTexture(-1);//Special texture unit, used to capture the current texture unit
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addCapability(int cap) {
|
|
||||||
this.caps.add(cap);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addTexture(int unit) {
|
|
||||||
this.textures.add(unit);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GlStateCapture build() {
|
|
||||||
return new GlStateCapture(this.caps.toIntArray(), this.textures.toIntArray());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,9 @@ package me.cortex.voxy.client.core.rendering.util;
|
|||||||
|
|
||||||
import me.cortex.voxy.client.core.gl.GlFramebuffer;
|
import me.cortex.voxy.client.core.gl.GlFramebuffer;
|
||||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
import me.cortex.voxy.client.core.gl.GlTexture;
|
||||||
|
import me.cortex.voxy.client.core.gl.GlVertexArray;
|
||||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||||
import me.cortex.voxy.client.core.rendering.RenderService;
|
|
||||||
import org.lwjgl.opengl.GL11;
|
import org.lwjgl.opengl.GL11;
|
||||||
|
|
||||||
import static org.lwjgl.opengl.ARBDirectStateAccess.*;
|
import static org.lwjgl.opengl.ARBDirectStateAccess.*;
|
||||||
@@ -17,7 +17,6 @@ import static org.lwjgl.opengl.GL33C.glDeleteSamplers;
|
|||||||
import static org.lwjgl.opengl.GL33C.glSamplerParameteri;
|
import static org.lwjgl.opengl.GL33C.glSamplerParameteri;
|
||||||
import static org.lwjgl.opengl.GL42C.GL_FRAMEBUFFER_BARRIER_BIT;
|
import static org.lwjgl.opengl.GL42C.GL_FRAMEBUFFER_BARRIER_BIT;
|
||||||
import static org.lwjgl.opengl.GL42C.glMemoryBarrier;
|
import static org.lwjgl.opengl.GL42C.glMemoryBarrier;
|
||||||
import static org.lwjgl.opengl.GL43C.glCopyImageSubData;
|
|
||||||
import static org.lwjgl.opengl.GL45C.glTextureBarrier;
|
import static org.lwjgl.opengl.GL45C.glTextureBarrier;
|
||||||
|
|
||||||
public class HiZBuffer {
|
public class HiZBuffer {
|
||||||
@@ -76,7 +75,7 @@ public class HiZBuffer {
|
|||||||
}
|
}
|
||||||
this.alloc(Integer.highestOneBit(width), Integer.highestOneBit(height));
|
this.alloc(Integer.highestOneBit(width), Integer.highestOneBit(height));
|
||||||
}
|
}
|
||||||
glBindVertexArray(RenderService.STATIC_VAO);
|
glBindVertexArray(GlVertexArray.STATIC_VAO);
|
||||||
int boundFB = GL11.glGetInteger(GL_DRAW_FRAMEBUFFER_BINDING);
|
int boundFB = GL11.glGetInteger(GL_DRAW_FRAMEBUFFER_BINDING);
|
||||||
this.hiz.bind();
|
this.hiz.bind();
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, this.fb.id);
|
glBindFramebuffer(GL_FRAMEBUFFER, this.fb.id);
|
||||||
@@ -128,6 +127,6 @@ public class HiZBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getPackedLevels() {
|
public int getPackedLevels() {
|
||||||
return ((Integer.numberOfTrailingZeros(this.width))<<16)|(Integer.numberOfTrailingZeros(this.height));//+1
|
return (this.width<<16)|this.height;//+1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
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.GlVertexArray;
|
||||||
|
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||||
|
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||||
|
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.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 = Math.min(7,(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(GlVertexArray.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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,13 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.util;
|
package me.cortex.voxy.client.core.rendering.util;
|
||||||
|
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
import static org.lwjgl.opengl.GL33.glBindSampler;
|
||||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
|
||||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import org.lwjgl.system.MemoryUtil;
|
|
||||||
|
|
||||||
import static org.lwjgl.opengl.ARBUniformBufferObject.glBindBufferBase;
|
|
||||||
import static org.lwjgl.opengl.GL11.glBindTexture;
|
|
||||||
import static org.lwjgl.opengl.GL33.*;
|
|
||||||
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
|
|
||||||
import static org.lwjgl.opengl.GL45.glBindTextureUnit;
|
import static org.lwjgl.opengl.GL45.glBindTextureUnit;
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
|
||||||
public class LightMapHelper {
|
public class LightMapHelper {
|
||||||
public static void bind(int lightingIndex) {
|
public static void bind(int lightingIndex) {
|
||||||
glBindSampler(lightingIndex, 0);
|
glBindSampler(lightingIndex, 0);
|
||||||
glBindTextureUnit(lightingIndex, ((net.minecraft.client.texture.GlTexture)(MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().getGlTexture())).getGlId());
|
glBindTextureUnit(lightingIndex, ((com.mojang.blaze3d.opengl.GlTexture)(Minecraft.getInstance().gameRenderer.lightTexture().getTextureView().texture())).glId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,6 +80,17 @@ public class RawDownloadStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void free() {
|
public void free() {
|
||||||
|
glFinish();
|
||||||
|
this.tick();
|
||||||
|
GlFence fence = new GlFence();
|
||||||
|
while (!fence.signaled()) {
|
||||||
|
glFinish();
|
||||||
|
}
|
||||||
|
fence.free();
|
||||||
|
this.tick();
|
||||||
|
if (this.frames.size() != 0) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
this.frames.forEach(a->a.fence.free());
|
this.frames.forEach(a->a.fence.free());
|
||||||
this.downloadBuffer.free();
|
this.downloadBuffer.free();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,11 +15,9 @@ import static me.cortex.voxy.common.util.AllocationArena.SIZE_LIMIT;
|
|||||||
import static org.lwjgl.opengl.ARBDirectStateAccess.glCopyNamedBufferSubData;
|
import static org.lwjgl.opengl.ARBDirectStateAccess.glCopyNamedBufferSubData;
|
||||||
import static org.lwjgl.opengl.ARBMapBufferRange.*;
|
import static org.lwjgl.opengl.ARBMapBufferRange.*;
|
||||||
import static org.lwjgl.opengl.GL11.glFinish;
|
import static org.lwjgl.opengl.GL11.glFinish;
|
||||||
import static org.lwjgl.opengl.GL42.GL_UNIFORM_BARRIER_BIT;
|
|
||||||
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
|
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
|
||||||
import static org.lwjgl.opengl.GL42C.GL_BUFFER_UPDATE_BARRIER_BIT;
|
import static org.lwjgl.opengl.GL42C.GL_BUFFER_UPDATE_BARRIER_BIT;
|
||||||
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BARRIER_BIT;
|
import static org.lwjgl.opengl.GL44.GL_CLIENT_STORAGE_BIT;
|
||||||
import static org.lwjgl.opengl.GL44.GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT;
|
|
||||||
import static org.lwjgl.opengl.GL44.GL_MAP_COHERENT_BIT;
|
import static org.lwjgl.opengl.GL44.GL_MAP_COHERENT_BIT;
|
||||||
import static org.lwjgl.opengl.GL45C.glFlushMappedNamedBufferRange;
|
import static org.lwjgl.opengl.GL45C.glFlushMappedNamedBufferRange;
|
||||||
|
|
||||||
@@ -34,7 +32,7 @@ public class UploadStream {
|
|||||||
private static final boolean USE_COHERENT = false;
|
private static final boolean USE_COHERENT = false;
|
||||||
|
|
||||||
public UploadStream(long size) {
|
public UploadStream(long size) {
|
||||||
this.uploadBuffer = new GlPersistentMappedBuffer(size,GL_MAP_WRITE_BIT|GL_MAP_UNSYNCHRONIZED_BIT|(USE_COHERENT?GL_MAP_COHERENT_BIT:GL_MAP_FLUSH_EXPLICIT_BIT)).name("UploadStream");
|
this.uploadBuffer = new GlPersistentMappedBuffer(size,GL_CLIENT_STORAGE_BIT|GL_MAP_WRITE_BIT|GL_MAP_UNSYNCHRONIZED_BIT|(USE_COHERENT?GL_MAP_COHERENT_BIT:GL_MAP_FLUSH_EXPLICIT_BIT)).name("UploadStream");
|
||||||
this.allocationArena.setLimit(size);
|
this.allocationArena.setLimit(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,6 +42,10 @@ public class UploadStream {
|
|||||||
data.cpyTo(this.upload(buffer, destOffset, data.size));
|
data.cpyTo(this.upload(buffer, destOffset, data.size));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long uploadTo(GlBuffer buffer) {
|
||||||
|
return this.upload(buffer, 0, buffer.size());
|
||||||
|
}
|
||||||
|
|
||||||
public long upload(GlBuffer buffer, long destOffset, long size) {
|
public long upload(GlBuffer buffer, long destOffset, long size) {
|
||||||
long addr = this.rawUploadAddress((int) size);
|
long addr = this.rawUploadAddress((int) size);
|
||||||
|
|
||||||
@@ -105,14 +107,15 @@ public class UploadStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void commit() {
|
public void commit() {
|
||||||
if (this.uploadList.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ((!USE_COHERENT)&&this.caddr != -1) {
|
if ((!USE_COHERENT)&&this.caddr != -1) {
|
||||||
//Flush this allocation
|
//Flush this allocation
|
||||||
glFlushMappedNamedBufferRange(this.uploadBuffer.id, this.caddr, this.offset);
|
glFlushMappedNamedBufferRange(this.uploadBuffer.id, this.caddr, this.offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.uploadList.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
|
glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
|
||||||
//Execute all the copies
|
//Execute all the copies
|
||||||
for (var entry : this.uploadList) {
|
for (var entry : this.uploadList) {
|
||||||
|
|||||||
185
src/main/java/me/cortex/voxy/client/core/util/GPUTiming.java
Normal file
185
src/main/java/me/cortex/voxy/client/core/util/GPUTiming.java
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
package me.cortex.voxy.client.core.util;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue;
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue;
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||||
|
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||||
|
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
||||||
|
import me.cortex.voxy.common.util.MemoryBuffer;
|
||||||
|
import me.cortex.voxy.common.util.Pair;
|
||||||
|
import me.cortex.voxy.common.util.TrackedObject;
|
||||||
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.ARBTimerQuery.GL_TIMESTAMP;
|
||||||
|
import static org.lwjgl.opengl.ARBTimerQuery.glQueryCounter;
|
||||||
|
import static org.lwjgl.opengl.GL11.glFinish;
|
||||||
|
import static org.lwjgl.opengl.GL11.glFlush;
|
||||||
|
import static org.lwjgl.opengl.GL15.glDeleteQueries;
|
||||||
|
import static org.lwjgl.opengl.GL15.glGenQueries;
|
||||||
|
import static org.lwjgl.opengl.GL15C.*;
|
||||||
|
import static org.lwjgl.opengl.GL33.glGetQueryObjecti64;
|
||||||
|
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
|
||||||
|
import static org.lwjgl.opengl.GL44.GL_QUERY_RESULT_NO_WAIT;
|
||||||
|
import static org.lwjgl.opengl.GL45.glGetQueryBufferObjectui64v;
|
||||||
|
|
||||||
|
public class GPUTiming {
|
||||||
|
public static GPUTiming INSTANCE = new GPUTiming();
|
||||||
|
|
||||||
|
private final GlTimestampQuerySet timingSet = new GlTimestampQuerySet();
|
||||||
|
|
||||||
|
private float[] timings = new float[0];
|
||||||
|
|
||||||
|
public void marker() {
|
||||||
|
this.timingSet.capture(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDebug() {
|
||||||
|
StringBuilder str = new StringBuilder("GpuTime: [");
|
||||||
|
for (int i = 0; i < this.timings.length; i++) {
|
||||||
|
str.append(String.format("%.2f", this.timings[i]));
|
||||||
|
if (i!=this.timings.length-1) {
|
||||||
|
str.append(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
str.append(']');
|
||||||
|
return str.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tick() {
|
||||||
|
this.timingSet.download((meta,data)->{
|
||||||
|
long current = data[0];
|
||||||
|
|
||||||
|
if (data.length-1!=this.timings.length) {
|
||||||
|
this.timings = new float[data.length-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < meta.length; i++) {
|
||||||
|
long next = data[i];
|
||||||
|
long delta = next - current;
|
||||||
|
float time = (float) (((double)delta)/1_000_000);
|
||||||
|
this.timings[i-1] = Math.max(this.timings[i-1]*0.99f+time*0.01f, time);
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.timingSet.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void free() {
|
||||||
|
this.timingSet.free();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface TimingDataConsumer {
|
||||||
|
void accept(int[] metadata, long[] timings);
|
||||||
|
}
|
||||||
|
private static final class GlTimestampQuerySet extends TrackedObject {
|
||||||
|
private record InflightRequest(int[] queries, int[] meta, TimingDataConsumer callback) {
|
||||||
|
private boolean callbackIfReady(IntArrayFIFOQueue queryPool) {
|
||||||
|
boolean ready = glGetQueryObjecti(this.queries[this.queries.length-1], GL_QUERY_RESULT_AVAILABLE) == GL_TRUE;
|
||||||
|
if (!ready) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
long[] results = new long[this.queries.length];
|
||||||
|
for (int i = 0; i < this.queries.length; i++) {
|
||||||
|
results[i] = glGetQueryObjecti64(this.queries[i], GL_QUERY_RESULT);
|
||||||
|
queryPool.enqueue(this.queries[i]);
|
||||||
|
}
|
||||||
|
this.callback.accept(this.meta, results);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private final IntArrayFIFOQueue POOL = new IntArrayFIFOQueue();
|
||||||
|
private final ObjectArrayFIFOQueue<InflightRequest> INFLIGHT = new ObjectArrayFIFOQueue();
|
||||||
|
|
||||||
|
private final int[] queries = new int[64];
|
||||||
|
private final int[] metadata = new int[64];
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
|
||||||
|
public void capture(int metadata) {
|
||||||
|
if (this.index > this.metadata.length) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
int slot = this.index++;
|
||||||
|
this.metadata[slot] = metadata;
|
||||||
|
int query = this.getQuery();
|
||||||
|
glQueryCounter(query, GL_TIMESTAMP);
|
||||||
|
this.queries[slot] = query;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void download(TimingDataConsumer consumer) {
|
||||||
|
var queries = Arrays.copyOf(this.queries, this.index);
|
||||||
|
var metadata = Arrays.copyOf(this.metadata, this.index);
|
||||||
|
this.index = 0;
|
||||||
|
this.INFLIGHT.enqueue(new InflightRequest(queries, metadata, consumer));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tick() {
|
||||||
|
while (!INFLIGHT.isEmpty()) {
|
||||||
|
if (INFLIGHT.first().callbackIfReady(POOL)) {
|
||||||
|
INFLIGHT.dequeue();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getQuery() {
|
||||||
|
if (POOL.isEmpty()) {
|
||||||
|
return glGenQueries();
|
||||||
|
} else {
|
||||||
|
return POOL.dequeueInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void free() {
|
||||||
|
super.free0();
|
||||||
|
while (!POOL.isEmpty()) {
|
||||||
|
glDeleteQueries(POOL.dequeueInt());
|
||||||
|
}
|
||||||
|
while (!INFLIGHT.isEmpty()) {
|
||||||
|
glDeleteQueries(INFLIGHT.dequeue().queries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
private static final class GlTimestampQuerySet extends TrackedObject {
|
||||||
|
private final int query = glGenQueries();
|
||||||
|
public final GlBuffer store;
|
||||||
|
public final int[] metadata;
|
||||||
|
public int index;
|
||||||
|
public GlTimestampQuerySet(int maxCount) {
|
||||||
|
this.store = new GlBuffer(maxCount*8L);
|
||||||
|
this.metadata = new int[maxCount];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void capture(int metadata) {
|
||||||
|
if (this.index>this.metadata.length) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
int slot = this.index++;
|
||||||
|
this.metadata[slot] = metadata;
|
||||||
|
glQueryCounter(this.query, GL_TIMESTAMP);//This should be gpu side, so should be fast
|
||||||
|
glFinish();
|
||||||
|
glGetQueryBufferObjectui64v(this.query, this.store.id, GL_QUERY_RESULT_NO_WAIT, slot*8L);
|
||||||
|
glMemoryBarrier(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void download(TimingDataConsumer consumer) {
|
||||||
|
var meta = Arrays.copyOf(this.metadata, this.index);
|
||||||
|
this.index = 0;
|
||||||
|
//DownloadStream.INSTANCE.download(this.store, buffer->consumer.accept(meta, buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void free() {
|
||||||
|
super.free0();
|
||||||
|
glDeleteQueries(this.query);
|
||||||
|
this.store.free();
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
@@ -1,11 +1,29 @@
|
|||||||
package me.cortex.voxy.client.core.util;
|
package me.cortex.voxy.client.core.util;
|
||||||
|
|
||||||
|
import me.cortex.voxy.client.core.VoxyRenderSystem;
|
||||||
|
import me.cortex.voxy.client.core.rendering.Viewport;
|
||||||
|
import net.caffeinemc.mods.sodium.client.render.chunk.ChunkRenderMatrices;
|
||||||
|
import net.caffeinemc.mods.sodium.client.util.FogParameters;
|
||||||
import net.fabricmc.loader.api.FabricLoader;
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
import net.irisshaders.iris.Iris;
|
||||||
|
import net.irisshaders.iris.api.v0.IrisApi;
|
||||||
|
import net.irisshaders.iris.gl.IrisRenderSystem;
|
||||||
import net.irisshaders.iris.shadows.ShadowRenderer;
|
import net.irisshaders.iris.shadows.ShadowRenderer;
|
||||||
import org.spongepowered.asm.mixin.Unique;
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
public class IrisUtil {
|
public class IrisUtil {
|
||||||
private static final boolean IRIS_INSTALLED = FabricLoader.getInstance().isModLoaded("iris");
|
|
||||||
|
public record CapturedViewportParameters(ChunkRenderMatrices matrices, FogParameters parameters, double x, double y, double z) {
|
||||||
|
public Viewport<?> apply(VoxyRenderSystem vrs) {
|
||||||
|
return vrs.setupViewport(this.matrices, this.parameters, this.x, this.y, this.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CapturedViewportParameters CAPTURED_VIEWPORT_PARAMETERS;
|
||||||
|
|
||||||
|
public static final boolean IRIS_INSTALLED = FabricLoader.getInstance().isModLoaded("iris");
|
||||||
|
public static final boolean SHADER_SUPPORT = true;//System.getProperty("voxy.enableExperimentalIrisPipeline", "false").equalsIgnoreCase("true");
|
||||||
|
|
||||||
|
|
||||||
private static boolean irisShadowActive0() {
|
private static boolean irisShadowActive0() {
|
||||||
@@ -15,4 +33,41 @@ public class IrisUtil {
|
|||||||
public static boolean irisShadowActive() {
|
public static boolean irisShadowActive() {
|
||||||
return IRIS_INSTALLED && irisShadowActive0();
|
return IRIS_INSTALLED && irisShadowActive0();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void clearIrisSamplers() {
|
||||||
|
if (IRIS_INSTALLED) clearIrisSamplers0();
|
||||||
|
}
|
||||||
|
public static void reload() {
|
||||||
|
if (IRIS_INSTALLED) reload0();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void reload0() {
|
||||||
|
try {
|
||||||
|
if (IrisApi.getInstance().isShaderPackInUse()) {//Only reload if there is a shaderpack
|
||||||
|
Iris.reload();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void clearIrisSamplers0() {
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
IrisRenderSystem.bindSamplerToUnit(i, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean irisShaderPackEnabled0() {
|
||||||
|
return Iris.isPackInUseQuick();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean irisShaderPackEnabled() {
|
||||||
|
return IRIS_INSTALLED && irisShaderPackEnabled0();
|
||||||
|
}
|
||||||
|
public static void disableIrisShaders() {
|
||||||
|
if(IRIS_INSTALLED) disableIrisShaders0();
|
||||||
|
}
|
||||||
|
private static void disableIrisShaders0() {
|
||||||
|
IrisApi.getInstance().getConfig().setShadersEnabledAndApply(false);//Disable shaders
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package me.cortex.voxy.client.iris;
|
||||||
|
|
||||||
|
public interface IGetIrisVoxyPipelineData {
|
||||||
|
IrisVoxyRenderPipelineData voxy$getPipelineData();
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package me.cortex.voxy.client.iris;
|
||||||
|
|
||||||
|
public interface IGetVoxyPatchData {
|
||||||
|
IrisShaderPatch voxy$getPatchData();
|
||||||
|
}
|
||||||
384
src/main/java/me/cortex/voxy/client/iris/IrisShaderPatch.java
Normal file
384
src/main/java/me/cortex/voxy/client/iris/IrisShaderPatch.java
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
package me.cortex.voxy.client.iris;
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
import com.google.gson.annotations.JsonAdapter;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
|
||||||
|
import me.cortex.voxy.common.Logger;
|
||||||
|
import net.irisshaders.iris.shaderpack.ShaderPack;
|
||||||
|
import net.irisshaders.iris.shaderpack.include.AbsolutePackPath;
|
||||||
|
import org.lwjgl.opengl.ARBDrawBuffersBlend;
|
||||||
|
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.IntSupplier;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.GL11.*;
|
||||||
|
import static org.lwjgl.opengl.GL33.*;
|
||||||
|
|
||||||
|
public class IrisShaderPatch {
|
||||||
|
public static final int VERSION = ((IntSupplier)()->1).getAsInt();
|
||||||
|
|
||||||
|
public static final boolean IMPERSONATE_DISTANT_HORIZONS = System.getProperty("voxy.impersonateDHShader", "false").equalsIgnoreCase("true");
|
||||||
|
|
||||||
|
|
||||||
|
private static final class SSBODeserializer implements JsonDeserializer<Int2ObjectOpenHashMap<String>> {
|
||||||
|
@Override
|
||||||
|
public Int2ObjectOpenHashMap<String> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||||
|
Int2ObjectOpenHashMap<String> ret = new Int2ObjectOpenHashMap<>();
|
||||||
|
if (json==null) return null;
|
||||||
|
try {
|
||||||
|
for (var entry : json.getAsJsonObject().entrySet()) {
|
||||||
|
ret.put(Integer.parseInt(entry.getKey()), entry.getValue().getAsString());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.error(e);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static final class SamplerDeserializer implements JsonDeserializer<Object2ObjectLinkedOpenHashMap<String, String>> {
|
||||||
|
@Override
|
||||||
|
public Object2ObjectLinkedOpenHashMap<String, String> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||||
|
Object2ObjectLinkedOpenHashMap<String, String> ret = new Object2ObjectLinkedOpenHashMap<>();
|
||||||
|
if (json==null) return null;
|
||||||
|
try {
|
||||||
|
if (json.isJsonArray()) {
|
||||||
|
for (var entry : json.getAsJsonArray()) {
|
||||||
|
var name = entry.getAsString();
|
||||||
|
var type = "sampler2D";
|
||||||
|
if (name.matches("shadowtex")) {
|
||||||
|
type = "sampler2DShadow";
|
||||||
|
}
|
||||||
|
ret.put(name, type);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (var entry : json.getAsJsonObject().entrySet()) {
|
||||||
|
String type = "sampler2D";
|
||||||
|
if (entry.getValue().isJsonNull()) {
|
||||||
|
if (entry.getKey().matches("shadowtex")) {
|
||||||
|
type = "sampler2DShadow";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
type = entry.getValue().getAsString();
|
||||||
|
}
|
||||||
|
ret.put(entry.getKey(), type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.error(e);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record BlendState(int buffer, boolean off, int sRGB, int dRGB, int sA, int dA) {
|
||||||
|
public static BlendState ALL_OFF = new BlendState(-1, true, 0,0,0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static final class BlendStateDeserializer implements JsonDeserializer<Int2ObjectMap<BlendState>> {
|
||||||
|
private static int parseType(String type) {
|
||||||
|
type = type.toUpperCase();
|
||||||
|
if (!type.startsWith("GL_")) {
|
||||||
|
type = "GL_"+type;
|
||||||
|
}
|
||||||
|
return switch (type) {
|
||||||
|
case "GL_ZERO" -> GL_ZERO;
|
||||||
|
case "GL_ONE" -> GL_ONE;
|
||||||
|
case "GL_SRC_COLOR" -> GL_SRC_COLOR;
|
||||||
|
case "GL_ONE_MINUS_SRC_COLOR" -> GL_ONE_MINUS_SRC_COLOR;
|
||||||
|
case "GL_SRC_ALPHA" -> GL_SRC_ALPHA;
|
||||||
|
case "GL_ONE_MINUS_SRC_ALPHA" -> GL_ONE_MINUS_SRC_ALPHA;
|
||||||
|
case "GL_DST_ALPHA" -> GL_DST_ALPHA;
|
||||||
|
case "GL_ONE_MINUS_DST_ALPHA" -> GL_ONE_MINUS_DST_ALPHA;
|
||||||
|
case "GL_DST_COLOR" -> GL_DST_COLOR;
|
||||||
|
case "GL_ONE_MINUS_DST_COLOR" -> GL_ONE_MINUS_DST_COLOR;
|
||||||
|
case "GL_SRC_ALPHA_SATURATE" -> GL_SRC_ALPHA_SATURATE;
|
||||||
|
case "GL_SRC1_COLOR" -> GL_SRC1_COLOR;
|
||||||
|
case "GL_ONE_MINUS_SRC1_COLOR" -> GL_ONE_MINUS_SRC1_COLOR;
|
||||||
|
case "GL_ONE_MINUS_SRC1_ALPHA" -> GL_ONE_MINUS_SRC1_ALPHA;
|
||||||
|
default -> {
|
||||||
|
Logger.error("Unknown blend option " + type);
|
||||||
|
yield -1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Int2ObjectMap<BlendState> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||||
|
if (json==null) return null;
|
||||||
|
Int2ObjectMap<BlendState> ret = new Int2ObjectOpenHashMap<>();
|
||||||
|
try {
|
||||||
|
if (json.isJsonPrimitive()) {
|
||||||
|
if (json.getAsString().equalsIgnoreCase("off")) {
|
||||||
|
ret.put(-1, BlendState.ALL_OFF);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
} else if (json.isJsonObject()) {
|
||||||
|
for (var entry : json.getAsJsonObject().entrySet()) {
|
||||||
|
int buffer = Integer.parseInt(entry.getKey());
|
||||||
|
BlendState state = null;
|
||||||
|
var val = entry.getValue();
|
||||||
|
List<String> bs = null;
|
||||||
|
if (val.isJsonArray()) {
|
||||||
|
bs = val.getAsJsonArray().asList().stream().map(JsonElement::getAsString).toList();
|
||||||
|
} else if (val.isJsonPrimitive()) {
|
||||||
|
var str = val.getAsString();
|
||||||
|
if (str.equalsIgnoreCase("off")) {
|
||||||
|
state = new BlendState(buffer, true, 0,0,0,0);
|
||||||
|
} else {
|
||||||
|
var parts = str.split(" ");
|
||||||
|
if (parts.length < 4) {
|
||||||
|
state = new BlendState(buffer, true, -1, -1, -1, -1);
|
||||||
|
} else {
|
||||||
|
bs = List.of(parts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logger.error("Unknown blend state "+val);
|
||||||
|
state = null;
|
||||||
|
}
|
||||||
|
if (bs != null) {
|
||||||
|
int[] v = bs.stream().mapToInt(BlendStateDeserializer::parseType).toArray();
|
||||||
|
state = new BlendState(buffer, false, v[0], v[1], v[2], v[3]);
|
||||||
|
}
|
||||||
|
ret.put(buffer, state);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.error(e);
|
||||||
|
}
|
||||||
|
Logger.error("Failed to parse blend state: " + json);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PatchGson {
|
||||||
|
public int version;//TODO maybe replace with semver?
|
||||||
|
public int[] opaqueDrawBuffers;
|
||||||
|
public int[] translucentDrawBuffers;
|
||||||
|
public String[] uniforms;
|
||||||
|
@JsonAdapter(SamplerDeserializer.class)
|
||||||
|
public Object2ObjectLinkedOpenHashMap<String, String> samplers;
|
||||||
|
public String opaquePatchData;
|
||||||
|
public String translucentPatchData;
|
||||||
|
@JsonAdapter(SSBODeserializer.class)
|
||||||
|
public Int2ObjectOpenHashMap<String> ssbos;
|
||||||
|
@JsonAdapter(BlendStateDeserializer.class)
|
||||||
|
public Int2ObjectOpenHashMap<BlendState> blending;
|
||||||
|
public String taaOffset;
|
||||||
|
public boolean excludeLodsFromVanillaDepth;
|
||||||
|
public float[] renderScale;
|
||||||
|
public boolean useViewportDims;
|
||||||
|
//public boolean deferTranslucentRendering;
|
||||||
|
public String checkValid() {
|
||||||
|
if (this.blending != null) {
|
||||||
|
int i = 0;
|
||||||
|
for (BlendState state : this.blending.values()) {
|
||||||
|
if (state.buffer != -1 && (state.buffer<0||this.translucentDrawBuffers.length<=state.buffer)) {
|
||||||
|
if (state.buffer<0) {
|
||||||
|
return "Blending buffer is <0 at index: " + i;
|
||||||
|
} else {
|
||||||
|
return "Blending buffer index out of bounds at "+i+" was "+state.buffer+" maximum is " +(this.translucentDrawBuffers.length-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.opaquePatchData == null) {
|
||||||
|
return "Opaque patch data is null";
|
||||||
|
}
|
||||||
|
if (this.uniforms == null) {
|
||||||
|
return "Uniforms are null";
|
||||||
|
}
|
||||||
|
if (this.opaqueDrawBuffers == null) {
|
||||||
|
return "Opaque draw buffers are null";
|
||||||
|
}
|
||||||
|
if (this.translucentDrawBuffers == null) {
|
||||||
|
return "Translucent draw buffers are null";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private final PatchGson patchData;
|
||||||
|
private final ShaderPack pack;
|
||||||
|
private final Int2ObjectMap<String> ssbos;
|
||||||
|
private IrisShaderPatch(PatchGson patchData, ShaderPack pack) {
|
||||||
|
this.patchData = patchData;
|
||||||
|
this.pack = pack;
|
||||||
|
|
||||||
|
if (patchData.ssbos == null) {
|
||||||
|
this.ssbos = new Int2ObjectOpenHashMap<>();
|
||||||
|
} else {
|
||||||
|
this.ssbos = patchData.ssbos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean useViewportDims() {
|
||||||
|
return this.patchData.useViewportDims;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Int2ObjectMap<String> getSSBOs() {
|
||||||
|
return new Int2ObjectLinkedOpenHashMap<>(this.ssbos);
|
||||||
|
}
|
||||||
|
public String getPatchOpaqueSource() {
|
||||||
|
return this.patchData.opaquePatchData;
|
||||||
|
}
|
||||||
|
public String getPatchTranslucentSource() {
|
||||||
|
return this.patchData.translucentPatchData;
|
||||||
|
}
|
||||||
|
public String getTAAShift() {
|
||||||
|
return this.patchData.taaOffset == null?"{return vec2(0.0);}":this.patchData.taaOffset;
|
||||||
|
}
|
||||||
|
public String[] getUniformList() {
|
||||||
|
return this.patchData.uniforms;
|
||||||
|
}
|
||||||
|
public Object2ObjectLinkedOpenHashMap<String, String> getSamplerSet() {
|
||||||
|
return this.patchData.samplers;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int[] getOpqaueTargets() {
|
||||||
|
return this.patchData.opaqueDrawBuffers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] getTranslucentTargets() {
|
||||||
|
return this.patchData.translucentDrawBuffers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean emitToVanillaDepth() {
|
||||||
|
return !this.patchData.excludeLodsFromVanillaDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float[] getRenderScale() {
|
||||||
|
if (this.patchData.renderScale == null || this.patchData.renderScale.length==0) {
|
||||||
|
return new float[]{1,1};
|
||||||
|
}
|
||||||
|
if (this.patchData.renderScale.length == 1) {
|
||||||
|
return new float[]{this.patchData.renderScale[0],this.patchData.renderScale[0]};
|
||||||
|
}
|
||||||
|
return new float[]{Math.max(0.01f,this.patchData.renderScale[0]),Math.max(0.01f,this.patchData.renderScale[1])};
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean deferedTranslucentRendering() {
|
||||||
|
return false;//this.patchData.deferTranslucentRendering;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Runnable createBlendSetup() {
|
||||||
|
if (this.patchData.blending == null || this.patchData.blending.isEmpty()) {
|
||||||
|
return ()->{};//No blending change
|
||||||
|
}
|
||||||
|
return ()->{
|
||||||
|
final var BS = this.patchData.blending;
|
||||||
|
//Set inital state
|
||||||
|
var init = BS.getOrDefault(-1, null);
|
||||||
|
if (init != null) {
|
||||||
|
if (init.off) {
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
} else {
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendFuncSeparate(init.sRGB, init.dRGB, init.sA, init.dA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var entry:BS.int2ObjectEntrySet()) {
|
||||||
|
if (entry.getIntKey() == -1) continue;
|
||||||
|
final var s = entry.getValue();
|
||||||
|
if (s.off) {
|
||||||
|
glDisablei(GL_BLEND, s.buffer);
|
||||||
|
} else {
|
||||||
|
glEnablei(GL_BLEND, s.buffer);
|
||||||
|
//_sigh_ thanks nvidia
|
||||||
|
ARBDrawBuffersBlend.glBlendFuncSeparateiARB(s.buffer, s.sRGB, s.dRGB, s.sA, s.dA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Gson GSON = new GsonBuilder()
|
||||||
|
.excludeFieldsWithModifiers(Modifier.PRIVATE)
|
||||||
|
.setStrictness(Strictness.LENIENT)
|
||||||
|
.create();
|
||||||
|
|
||||||
|
public static IrisShaderPatch makePatch(ShaderPack ipack, AbsolutePackPath directory, Function<AbsolutePackPath, String> sourceProvider) {
|
||||||
|
String voxyPatchData = sourceProvider.apply(directory.resolve("voxy.json"));
|
||||||
|
if (voxyPatchData == null) {//No voxy patch data in shaderpack
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//A more graceful exit on blank string
|
||||||
|
if (voxyPatchData.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Escape things
|
||||||
|
voxyPatchData = voxyPatchData.replace("\\", "\\\\");
|
||||||
|
|
||||||
|
PatchGson patchData = null;
|
||||||
|
try {
|
||||||
|
//TODO: basicly find any "commented out" quotation marks and escape them (if the line, when stripped starts with a // or /* then escape all quotation marks in that line)
|
||||||
|
{
|
||||||
|
StringBuilder builder = new StringBuilder(voxyPatchData.length());
|
||||||
|
//Rebuild the patch, replacing commented out " with \"
|
||||||
|
for (var line : voxyPatchData.split("\n")) {
|
||||||
|
int idx = line.indexOf("//");
|
||||||
|
if (idx != -1) {
|
||||||
|
builder.append(line, 0, idx);
|
||||||
|
builder.append(line.substring(idx).replace("\"","\\\""));
|
||||||
|
} else {
|
||||||
|
builder.append(line);
|
||||||
|
}
|
||||||
|
builder.append("\n");
|
||||||
|
}
|
||||||
|
voxyPatchData = builder.toString();
|
||||||
|
}
|
||||||
|
patchData = GSON.fromJson(voxyPatchData, PatchGson.class);
|
||||||
|
if (patchData == null) {
|
||||||
|
throw new IllegalStateException("Voxy patch json returned null, this is most likely due to malformed json file");
|
||||||
|
}
|
||||||
|
|
||||||
|
{//Inject data from the auxilery files if they are present
|
||||||
|
var opaque = sourceProvider.apply(directory.resolve("voxy_opaque.glsl"));
|
||||||
|
if (opaque != null) {
|
||||||
|
Logger.info("External opaque shader patch applied");
|
||||||
|
patchData.opaquePatchData = opaque;
|
||||||
|
}
|
||||||
|
var translucent = sourceProvider.apply(directory.resolve("voxy_translucent.glsl"));
|
||||||
|
if (translucent != null) {
|
||||||
|
Logger.info("External translucent shader patch applied");
|
||||||
|
patchData.translucentPatchData = translucent;
|
||||||
|
}
|
||||||
|
//This might be ok? not.. sure if is nice or not
|
||||||
|
var taa = sourceProvider.apply(directory.resolve("voxy_taa.glsl"));
|
||||||
|
if (taa != null) {
|
||||||
|
Logger.info("External taa shader patch applied");
|
||||||
|
patchData.taaOffset = taa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var invalidPatchDataReason = patchData.checkValid();
|
||||||
|
if (invalidPatchDataReason!=null) {
|
||||||
|
throw new IllegalStateException("voxy json patch not valid: " + invalidPatchDataReason);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
patchData = null;
|
||||||
|
Logger.error("Failed to parse patch data gson",e);
|
||||||
|
throw new ShaderLoadError("Failed to parse patch data gson",e);
|
||||||
|
}
|
||||||
|
if (patchData == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (patchData.version != VERSION) {
|
||||||
|
Logger.error("Shader has voxy patch data, but patch version is incorrect. expected " + VERSION + " got "+patchData.version);
|
||||||
|
throw new IllegalStateException("Shader version mismatch expected " + VERSION + " got "+patchData.version);
|
||||||
|
}
|
||||||
|
return new IrisShaderPatch(patchData, ipack);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,549 @@
|
|||||||
|
package me.cortex.voxy.client.iris;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction;
|
||||||
|
import kroppeb.stareval.function.FunctionReturn;
|
||||||
|
import kroppeb.stareval.function.Type;
|
||||||
|
import me.cortex.voxy.client.core.IrisVoxyRenderPipeline;
|
||||||
|
import me.cortex.voxy.client.mixin.iris.CustomUniformsAccessor;
|
||||||
|
import me.cortex.voxy.client.mixin.iris.IrisRenderingPipelineAccessor;
|
||||||
|
import me.cortex.voxy.common.Logger;
|
||||||
|
import net.irisshaders.iris.gl.buffer.ShaderStorageBufferHolder;
|
||||||
|
import net.irisshaders.iris.gl.image.ImageHolder;
|
||||||
|
import net.irisshaders.iris.gl.sampler.GlSampler;
|
||||||
|
import net.irisshaders.iris.gl.sampler.SamplerHolder;
|
||||||
|
import net.irisshaders.iris.gl.state.FogMode;
|
||||||
|
import net.irisshaders.iris.gl.state.ValueUpdateNotifier;
|
||||||
|
import net.irisshaders.iris.gl.texture.InternalTextureFormat;
|
||||||
|
import net.irisshaders.iris.gl.texture.TextureType;
|
||||||
|
import net.irisshaders.iris.gl.uniform.*;
|
||||||
|
import net.irisshaders.iris.pipeline.IrisRenderingPipeline;
|
||||||
|
import net.irisshaders.iris.targets.RenderTarget;
|
||||||
|
import net.irisshaders.iris.targets.RenderTargets;
|
||||||
|
import net.irisshaders.iris.uniforms.CommonUniforms;
|
||||||
|
import net.irisshaders.iris.uniforms.custom.CustomUniforms;
|
||||||
|
import net.irisshaders.iris.uniforms.custom.cached.*;
|
||||||
|
import org.joml.*;
|
||||||
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.ARBDirectStateAccess.glBindTextureUnit;
|
||||||
|
import static org.lwjgl.opengl.ARBUniformBufferObject.glBindBufferBase;
|
||||||
|
import static org.lwjgl.opengl.GL33C.glBindSampler;
|
||||||
|
import static org.lwjgl.opengl.GL43C.GL_SHADER_STORAGE_BUFFER;
|
||||||
|
|
||||||
|
public class IrisVoxyRenderPipelineData {
|
||||||
|
public IrisVoxyRenderPipeline thePipeline;
|
||||||
|
public final int[] opaqueDrawTargets;
|
||||||
|
public final int[] translucentDrawTargets;
|
||||||
|
private final String opaquePatch;
|
||||||
|
private final String translucentPatch;
|
||||||
|
private final StructLayout uniforms;
|
||||||
|
private final Runnable blendingSetup;
|
||||||
|
private final ImageSet imageSet;
|
||||||
|
private final SSBOSet ssboSet;
|
||||||
|
public final boolean renderToVanillaDepth;
|
||||||
|
public final float[] resolutionScale;
|
||||||
|
public final String TAA;
|
||||||
|
public final boolean useViewportDims;
|
||||||
|
public final boolean deferTranslucency;
|
||||||
|
|
||||||
|
private IrisVoxyRenderPipelineData(IrisShaderPatch patch, int[] opaqueDrawTargets, int[] translucentDrawTargets, StructLayout uniformSet, Runnable blendingSetup, ImageSet imageSet, SSBOSet ssboSet) {
|
||||||
|
this.opaqueDrawTargets = opaqueDrawTargets;
|
||||||
|
this.translucentDrawTargets = translucentDrawTargets;
|
||||||
|
this.opaquePatch = patch.getPatchOpaqueSource();
|
||||||
|
this.translucentPatch = patch.getPatchTranslucentSource();
|
||||||
|
this.uniforms = uniformSet;
|
||||||
|
this.blendingSetup = blendingSetup;
|
||||||
|
this.imageSet = imageSet;
|
||||||
|
this.ssboSet = ssboSet;
|
||||||
|
this.renderToVanillaDepth = patch.emitToVanillaDepth();
|
||||||
|
this.TAA = patch.getTAAShift();
|
||||||
|
this.resolutionScale = patch.getRenderScale();
|
||||||
|
this.useViewportDims = patch.useViewportDims();
|
||||||
|
this.deferTranslucency = patch.deferedTranslucentRendering();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SSBOSet getSsboSet() {
|
||||||
|
return this.ssboSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageSet getImageSet() {
|
||||||
|
return this.imageSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StructLayout getUniforms() {
|
||||||
|
return this.uniforms;
|
||||||
|
}
|
||||||
|
public Runnable getBlender() {
|
||||||
|
return this.blendingSetup;
|
||||||
|
}
|
||||||
|
public String opaqueFragPatch() {
|
||||||
|
return this.opaquePatch;
|
||||||
|
}
|
||||||
|
public String translucentFragPatch() {
|
||||||
|
return this.translucentPatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static IrisVoxyRenderPipelineData buildPipeline(IrisRenderingPipeline ipipe, IrisShaderPatch patch, CustomUniforms cu, ShaderStorageBufferHolder ssboHolder) {
|
||||||
|
var uniforms = createUniformLayoutStructAndUpdater(createUniformSet(cu, patch));
|
||||||
|
|
||||||
|
|
||||||
|
var imageSet = createImageSet(ipipe, patch);
|
||||||
|
|
||||||
|
var ssboSet = createSSBOLayouts(patch.getSSBOs(), ssboHolder);
|
||||||
|
|
||||||
|
var opaqueDrawTargets = getDrawBuffers(patch.getOpqaueTargets(), ipipe.getFlippedAfterPrepare(), ((IrisRenderingPipelineAccessor)ipipe).getRenderTargets());
|
||||||
|
var translucentDrawTargets = getDrawBuffers(patch.getTranslucentTargets(), ipipe.getFlippedAfterPrepare(), ((IrisRenderingPipelineAccessor)ipipe).getRenderTargets());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//TODO: need to transform the string patch with the uniform decleration aswell as sampler declerations
|
||||||
|
return new IrisVoxyRenderPipelineData(patch, opaqueDrawTargets, translucentDrawTargets, uniforms, patch.createBlendSetup(), imageSet, ssboSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int[] getDrawBuffers(int[] targets, ImmutableSet<Integer> stageWritesToAlt, RenderTargets rt) {
|
||||||
|
int[] targetTextures = new int[targets.length];
|
||||||
|
for(int i = 0; i < targets.length; i++) {
|
||||||
|
RenderTarget target = rt.getOrCreate(targets[i]);
|
||||||
|
int textureId = stageWritesToAlt.contains(targets[i]) ? target.getAltTexture() : target.getMainTexture();
|
||||||
|
targetTextures[i] = textureId;
|
||||||
|
}
|
||||||
|
return targetTextures;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static String convertToGlslType(UniformType type) {
|
||||||
|
return switch (type) {
|
||||||
|
case INT -> "int";
|
||||||
|
case FLOAT -> "float";
|
||||||
|
case MAT3 -> "mat3";
|
||||||
|
case MAT4 -> "mat4";
|
||||||
|
case VEC2 -> "vec2";
|
||||||
|
case VEC2I -> "ivec2";
|
||||||
|
case VEC3 -> "vec3";
|
||||||
|
case VEC3I -> "ivec3";
|
||||||
|
case VEC4 -> "vec4";
|
||||||
|
case VEC4I -> "ivec4";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldDeferTranslucency() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record StructLayout(int size, String layout, LongConsumer updater) {}
|
||||||
|
private static StructLayout createUniformLayoutStructAndUpdater(List<UniformWritingHolder> uniforms) {
|
||||||
|
if (uniforms.size() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<UniformWritingHolder>[] ordering = new List[]{new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>()};
|
||||||
|
|
||||||
|
//Creates an optimial struct layout for the uniforms
|
||||||
|
for (var uniform : uniforms) {
|
||||||
|
int order = getUniformOrdering(uniform.type);
|
||||||
|
ordering[order].add(uniform);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Emit the ordering, note this is not optimial, but good enough, e.g. if have even number of align 2, emit that after align 4
|
||||||
|
int pos = 0;
|
||||||
|
Int2ObjectLinkedOpenHashMap<UniformWritingHolder> layout = new Int2ObjectLinkedOpenHashMap<>();
|
||||||
|
for (var uniform : ordering[0]) {//Emit exact align 4
|
||||||
|
layout.put(pos, uniform); pos += getSizeAndAlignment(uniform.type)>>5;
|
||||||
|
}
|
||||||
|
if (!ordering[1].isEmpty() && (ordering[1].size()&1)==0) {
|
||||||
|
//Emit all the align 2 as there is an even number of them
|
||||||
|
for (var uniform : ordering[1]) {
|
||||||
|
layout.put(pos, uniform); pos += getSizeAndAlignment(uniform.type)>>5;
|
||||||
|
}
|
||||||
|
ordering[1].clear();
|
||||||
|
}
|
||||||
|
//Emit align 3
|
||||||
|
for (var uniform : ordering[2]) {//Emit size odd, alignment must be 4
|
||||||
|
layout.put(pos, uniform); pos += getSizeAndAlignment(uniform.type)>>5;
|
||||||
|
//We must get a size 1 to pad to align 4
|
||||||
|
if (!ordering[3].isEmpty()) {//Size 1
|
||||||
|
uniform = ordering[3].removeFirst();
|
||||||
|
layout.put(pos, uniform); pos += getSizeAndAlignment(uniform.type)>>5;
|
||||||
|
} else {//Padding must be injected
|
||||||
|
pos += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Emit align 2
|
||||||
|
for (var uniform : ordering[1]) {
|
||||||
|
layout.put(pos, uniform); pos += getSizeAndAlignment(uniform.type)>>5;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Emit align 1
|
||||||
|
for (var uniform : ordering[3]) {
|
||||||
|
layout.put(pos, uniform); pos += getSizeAndAlignment(uniform.type)>>5;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layout.size()!=uniforms.size()) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
//We have our ordering and aligned offsets, generate an updater aswell as the layout
|
||||||
|
|
||||||
|
String structLayout;
|
||||||
|
{
|
||||||
|
StringBuilder struct = new StringBuilder("{\n");
|
||||||
|
for (var pair : layout.int2ObjectEntrySet()) {
|
||||||
|
struct.append("\t").append(convertToGlslType(pair.getValue().type)).append(" ").append(pair.getValue().name).append(";\n");
|
||||||
|
}
|
||||||
|
struct.append("}");
|
||||||
|
structLayout = struct.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
LongConsumer updater;
|
||||||
|
{
|
||||||
|
LongConsumer[] updaters = new LongConsumer[uniforms.size()];
|
||||||
|
int i = 0;
|
||||||
|
for (var pair : layout.int2ObjectEntrySet()) {
|
||||||
|
updaters[i++] = pair.getValue().writingFactory.get(pair.getIntKey()*4L);
|
||||||
|
}
|
||||||
|
|
||||||
|
updater = ptr -> {
|
||||||
|
for (var u : updaters) {
|
||||||
|
u.accept(ptr);
|
||||||
|
}
|
||||||
|
};//Writes all the uniforms to the locations
|
||||||
|
}
|
||||||
|
return new StructLayout(pos*4, structLayout, updater);//*4 since each slot is 4 bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LongConsumer createWriter(long offset, FunctionReturn ret, CachedUniform uniform) {
|
||||||
|
if (uniform instanceof BooleanCachedUniform bcu) {
|
||||||
|
return ptr->{ptr += offset;
|
||||||
|
bcu.writeTo(ret);
|
||||||
|
MemoryUtil.memPutInt(ptr, ret.booleanReturn?1:0);
|
||||||
|
};
|
||||||
|
} else if (uniform instanceof FloatCachedUniform fcu) {
|
||||||
|
return ptr->{ptr += offset;
|
||||||
|
fcu.writeTo(ret);
|
||||||
|
MemoryUtil.memPutFloat(ptr, ret.floatReturn);
|
||||||
|
};
|
||||||
|
} else if (uniform instanceof IntCachedUniform icu) {
|
||||||
|
return ptr->{ptr += offset;
|
||||||
|
icu.writeTo(ret);
|
||||||
|
MemoryUtil.memPutInt(ptr, ret.intReturn);
|
||||||
|
};
|
||||||
|
} else if (uniform instanceof Float2VectorCachedUniform v2fcu) {
|
||||||
|
return ptr->{ptr += offset;
|
||||||
|
v2fcu.writeTo(ret);
|
||||||
|
((Vector2f)ret.objectReturn).getToAddress(ptr);
|
||||||
|
};
|
||||||
|
} else if (uniform instanceof Float3VectorCachedUniform v3fcu) {
|
||||||
|
return ptr->{ptr += offset;
|
||||||
|
v3fcu.writeTo(ret);
|
||||||
|
((Vector3f)ret.objectReturn).getToAddress(ptr);
|
||||||
|
};
|
||||||
|
} else if (uniform instanceof Float4VectorCachedUniform v4fcu) {
|
||||||
|
return ptr->{ptr += offset;
|
||||||
|
v4fcu.writeTo(ret);
|
||||||
|
((Vector4f)ret.objectReturn).getToAddress(ptr);
|
||||||
|
};
|
||||||
|
} else if (uniform instanceof Int2VectorCachedUniform v2icu) {
|
||||||
|
return ptr->{ptr += offset;
|
||||||
|
v2icu.writeTo(ret);
|
||||||
|
((Vector2i)ret.objectReturn).getToAddress(ptr);
|
||||||
|
};
|
||||||
|
} else if (uniform instanceof Int3VectorCachedUniform v3icu) {
|
||||||
|
return ptr->{ptr += offset;
|
||||||
|
v3icu.writeTo(ret);
|
||||||
|
((Vector3i)ret.objectReturn).getToAddress(ptr);
|
||||||
|
};
|
||||||
|
} else if (uniform instanceof Float4MatrixCachedUniform f4mcu) {
|
||||||
|
return ptr->{ptr += offset;
|
||||||
|
f4mcu.writeTo(ret);
|
||||||
|
((Matrix4f)ret.objectReturn).getToAddress(ptr);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Unknown uniform type " + uniform.getClass().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static int P(int size, int align) {
|
||||||
|
return size<<5|align;
|
||||||
|
}
|
||||||
|
private static int getSizeAndAlignment(UniformType type) {
|
||||||
|
return switch (type) {
|
||||||
|
case INT, FLOAT -> P(1,1);//Size, Alignment
|
||||||
|
case MAT3 -> P(4+4+3,4);//is funky as each row is a vec3 padded to a vec4
|
||||||
|
case MAT4 -> P(4*4,4);
|
||||||
|
case VEC2, VEC2I -> P(2,2);
|
||||||
|
case VEC3, VEC3I -> P(3,4);
|
||||||
|
case VEC4, VEC4I -> P(4,4);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
private static int getUniformOrdering(UniformType type) {
|
||||||
|
return switch (type) {
|
||||||
|
case MAT4, VEC4, VEC4I -> 0;
|
||||||
|
case VEC2, VEC2I -> 1;
|
||||||
|
case VEC3, VEC3I, MAT3 -> 2;
|
||||||
|
case INT, FLOAT -> 3;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private record UniformWritingHolder(String name, UniformType type, Long2ObjectFunction<LongConsumer> writingFactory) {
|
||||||
|
|
||||||
|
}
|
||||||
|
private static List<UniformWritingHolder> createUniformSet(CustomUniforms cu, IrisShaderPatch patch) {
|
||||||
|
//This is a fking awful hack... but it works thinks
|
||||||
|
|
||||||
|
List<UniformWritingHolder> uniforms = new ArrayList<>();
|
||||||
|
Set<String> seenUniforms = new HashSet<>();
|
||||||
|
DynamicLocationalUniformHolder uniformBuilder = new DynamicLocationalUniformHolder() {
|
||||||
|
@Override
|
||||||
|
public DynamicLocationalUniformHolder uniform1i(UniformUpdateFrequency updateFrequency, String name, IntSupplier value) {
|
||||||
|
return this.uniform1i(name, value, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DynamicLocationalUniformHolder uniform1i(String name, IntSupplier value, ValueUpdateNotifier notifier) {
|
||||||
|
this.injectDynamicUniformType(name, UniformType.INT, offset->{
|
||||||
|
return ptr->{
|
||||||
|
MemoryUtil.memPutInt(ptr+offset, value.getAsInt());
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DynamicLocationalUniformHolder uniform1f(UniformUpdateFrequency updateFrequency, String name, FloatSupplier value) {
|
||||||
|
return this.uniform1f(name, value, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DynamicLocationalUniformHolder uniform1f(String name, FloatSupplier value, ValueUpdateNotifier notifier) {
|
||||||
|
this.injectDynamicUniformType(name, UniformType.FLOAT, offset->{
|
||||||
|
return ptr->{
|
||||||
|
MemoryUtil.memPutFloat(ptr+offset, value.getAsFloat());
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DynamicLocationalUniformHolder uniform3f(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector3f> value) {
|
||||||
|
return this.uniform3f(name, value, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DynamicLocationalUniformHolder uniform3f(String name, Supplier<Vector3f> value, ValueUpdateNotifier notifier) {
|
||||||
|
this.injectDynamicUniformType(name, UniformType.VEC3, offset->{
|
||||||
|
return ptr->{
|
||||||
|
value.get().getToAddress(ptr+offset);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void injectDynamicUniformType(String name, UniformType type, Long2ObjectFunction<LongConsumer> supplier) {
|
||||||
|
var names = patch.getUniformList();
|
||||||
|
for (int i = 0; i < names.length; i++) {
|
||||||
|
if (names[i].equals(name)) {
|
||||||
|
if (!seenUniforms.add(name)) {
|
||||||
|
throw new IllegalArgumentException("Already added uniform: " + name);
|
||||||
|
}
|
||||||
|
uniforms.add(new UniformWritingHolder(name, type, supplier));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DynamicLocationalUniformHolder addDynamicUniform(Uniform uniform, ValueUpdateNotifier valueUpdateNotifier) {
|
||||||
|
throw new IllegalStateException("Type not implemented for uniform: " + uniform);
|
||||||
|
//return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LocationalUniformHolder addUniform(UniformUpdateFrequency uniformUpdateFrequency, Uniform uniform) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OptionalInt location(String uniformName, UniformType uniformType) {
|
||||||
|
//Yes am aware how performant inefficent this is... just dont care tbh since is on setup and is small
|
||||||
|
var names = patch.getUniformList();
|
||||||
|
for (int i = 0; i < names.length; i++) {
|
||||||
|
if (names[i].equals(uniformName)) {
|
||||||
|
return OptionalInt.of(i);//Have a base uniform offset of 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return OptionalInt.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UniformHolder externallyManagedUniform(String s, UniformType uniformType) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
CommonUniforms.addDynamicUniforms(uniformBuilder, FogMode.PER_FRAGMENT);
|
||||||
|
cu.assignTo(uniformBuilder);
|
||||||
|
cu.mapholderToPass(uniformBuilder, patch);
|
||||||
|
|
||||||
|
FunctionReturn cachedReturn = new FunctionReturn();
|
||||||
|
((CustomUniformsAccessor)cu).getLocationMap().get(patch).object2IntEntrySet().forEach(entry-> {
|
||||||
|
if (!seenUniforms.add(entry.getKey().getName())) {
|
||||||
|
throw new IllegalArgumentException("Already added uniform: " + entry.getKey().getName());
|
||||||
|
}
|
||||||
|
uniforms.add(new UniformWritingHolder(entry.getKey().getName(), Type.convert(entry.getKey().getType()),offset->createWriter(offset, cachedReturn, entry.getKey())));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (uniforms.size() != patch.getUniformList().length) {
|
||||||
|
Set<String> uniformsUnseen = new HashSet<>(List.of(patch.getUniformList()));
|
||||||
|
for (var uniform : uniforms) {
|
||||||
|
uniformsUnseen.remove(uniform.name);
|
||||||
|
}
|
||||||
|
Logger.error("The following uniforms could not be found: [" + uniformsUnseen.stream().sorted(String::compareToIgnoreCase).collect(Collectors.joining(","))+"]");
|
||||||
|
}
|
||||||
|
//In _theory_ this should work?
|
||||||
|
return uniforms;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record TextureWSampler(String name, IntSupplier texture, IntSupplier sampler) { }
|
||||||
|
public record ImageSet(String layout, IntConsumer bindingFunction) {
|
||||||
|
|
||||||
|
}
|
||||||
|
private static ImageSet createImageSet(IrisRenderingPipeline ipipe, IrisShaderPatch patch) {
|
||||||
|
var samplerDataSet = patch.getSamplerSet();
|
||||||
|
if (samplerDataSet == null) return null;
|
||||||
|
Set<String> samplerNameSet = new LinkedHashSet<>(samplerDataSet.keySet());
|
||||||
|
if (samplerNameSet.isEmpty()) return null;
|
||||||
|
Set<TextureWSampler> samplerSet = new LinkedHashSet<>();
|
||||||
|
SamplerHolder samplerBuilder = new SamplerHolder() {
|
||||||
|
@Override
|
||||||
|
public boolean hasSampler(String s) {
|
||||||
|
return samplerNameSet.contains(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSampler(String... names) {
|
||||||
|
for (var name : names) {
|
||||||
|
if (samplerNameSet.contains(name)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String name(String... names) {
|
||||||
|
for (var name : names) {
|
||||||
|
if (samplerNameSet.contains(name)) return name;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addDefaultSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, Supplier<GlSampler> sampler, String... names) {
|
||||||
|
Logger.error("Unsupported default sampler");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addDynamicSampler(TextureType type, IntSupplier texture, Supplier<GlSampler> sampler, String... names) {
|
||||||
|
return this.addDynamicSampler(type, texture, null, sampler, names);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addDynamicSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, Supplier<GlSampler> sampler, String... names) {
|
||||||
|
if (!this.hasSampler(names)) return false;
|
||||||
|
samplerSet.add(new TextureWSampler(this.name(names), texture, sampler!=null?()->{
|
||||||
|
var s = sampler.get();
|
||||||
|
return s!=null?s.getId():-1;
|
||||||
|
}:()->-1));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addExternalSampler(int texture, String... names) {
|
||||||
|
if (!this.hasSampler(names)) return;
|
||||||
|
samplerSet.add(new TextureWSampler(this.name(names), ()->texture, ()->-1));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Unsupported
|
||||||
|
ImageHolder imageBuilder = new ImageHolder() {
|
||||||
|
@Override
|
||||||
|
public boolean hasImage(String s) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addTextureImage(IntSupplier intSupplier, InternalTextureFormat internalTextureFormat, String s) {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ipipe.addGbufferOrShadowSamplers(samplerBuilder, imageBuilder, ipipe::getFlippedAfterPrepare, false, true, true, false);
|
||||||
|
|
||||||
|
//samplerSet contains our samplers
|
||||||
|
if (samplerSet.size() != samplerNameSet.size()) {
|
||||||
|
Logger.error("Did not find all requested samplers. Found [" + samplerSet.stream().map(a->a.name).collect(Collectors.joining(", ")) + "] expected " + samplerNameSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: generate a layout (defines) for all the samplers with the correct types
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
TextureWSampler[] samplers = new TextureWSampler[samplerSet.size()];
|
||||||
|
int i = 0;
|
||||||
|
for (var entry : samplerSet) {
|
||||||
|
samplers[i]=entry;
|
||||||
|
|
||||||
|
String samplerType = samplerDataSet.get(entry.name);
|
||||||
|
builder.append("layout(binding=(BASE_SAMPLER_BINDING_INDEX+").append(i).append(")) uniform ").append(samplerType).append(" ").append(entry.name).append(";\n");
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
IntConsumer bindingFunction = base->{
|
||||||
|
for (int j = 0; j < samplers.length; j++) {
|
||||||
|
int unit = j+base;
|
||||||
|
var ts = samplers[j];
|
||||||
|
glBindTextureUnit(unit, ts.texture.getAsInt());
|
||||||
|
int sampler = ts.sampler.getAsInt();
|
||||||
|
if (sampler != -1) {
|
||||||
|
glBindSampler(unit, sampler);
|
||||||
|
}//TODO: might need to bind sampler 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return new ImageSet(builder.toString(), bindingFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record SSBOSet(String layout, IntConsumer bindingFunction){}
|
||||||
|
private record SSBOBinding(int irisIndex, int bindingOffset) {}
|
||||||
|
private static SSBOSet createSSBOLayouts(Int2ObjectMap<String> ssbos, ShaderStorageBufferHolder ssboStore) {
|
||||||
|
if (ssboStore == null) return null;//If there is no store, there cannot be any ssbos
|
||||||
|
if (ssbos.isEmpty()) return null;
|
||||||
|
String header = "";
|
||||||
|
if (ssbos.containsKey(-1)) header = ssbos.remove(-1);
|
||||||
|
StringBuilder builder = new StringBuilder(header);
|
||||||
|
builder.append("\n");
|
||||||
|
SSBOBinding[] bindings = new SSBOBinding[ssbos.size()];
|
||||||
|
int i = 0;
|
||||||
|
for (var entry : ssbos.int2ObjectEntrySet()) {
|
||||||
|
var val = entry.getValue();
|
||||||
|
bindings[i] = new SSBOBinding(entry.getIntKey(), i);
|
||||||
|
builder.append("layout(binding = (BUFFER_BINDING_INDEX_BASE+").append(i).append(")) restrict buffer IrisBufferBinding").append(i);
|
||||||
|
builder.append(" ").append(val).append(";\n");
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
//ssboStore.getBufferIndex()
|
||||||
|
IntConsumer bindingFunction = base->{
|
||||||
|
for (var binding : bindings) {
|
||||||
|
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, base+binding.bindingOffset, ssboStore.getBufferIndex(binding.irisIndex));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return new SSBOSet(builder.toString(), bindingFunction);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package me.cortex.voxy.client.iris;
|
||||||
|
|
||||||
|
public class ShaderLoadError extends RuntimeException {
|
||||||
|
public ShaderLoadError(String reason) {
|
||||||
|
super(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShaderLoadError(String reason, Exception cause) {
|
||||||
|
super(reason, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/main/java/me/cortex/voxy/client/iris/VoxySamplers.java
Normal file
55
src/main/java/me/cortex/voxy/client/iris/VoxySamplers.java
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package me.cortex.voxy.client.iris;
|
||||||
|
|
||||||
|
import net.irisshaders.iris.gl.sampler.GlSampler;
|
||||||
|
import net.irisshaders.iris.gl.sampler.SamplerHolder;
|
||||||
|
import net.irisshaders.iris.gl.texture.TextureType;
|
||||||
|
import net.irisshaders.iris.pipeline.IrisRenderingPipeline;
|
||||||
|
|
||||||
|
public class VoxySamplers {
|
||||||
|
public static void addSamplers(IrisRenderingPipeline pipeline, SamplerHolder samplers) {
|
||||||
|
var patchData = ((IGetVoxyPatchData)pipeline).voxy$getPatchData();
|
||||||
|
if (patchData != null) {
|
||||||
|
String[] opaqueNames = new String[]{"vxDepthTexOpaque"};
|
||||||
|
String[] translucentNames = new String[]{"vxDepthTexTrans"};
|
||||||
|
|
||||||
|
if (IrisShaderPatch.IMPERSONATE_DISTANT_HORIZONS) {
|
||||||
|
opaqueNames = new String[]{"vxDepthTexOpaque", "dhDepthTex1"};
|
||||||
|
translucentNames = new String[]{"vxDepthTexTrans", "dhDepthTex", "dhDepthTex0"};
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO replace ()->0 with the actual depth texture id
|
||||||
|
samplers.addDynamicSampler(TextureType.TEXTURE_2D, () -> {
|
||||||
|
var pipeData = ((IGetIrisVoxyPipelineData)pipeline).voxy$getPipelineData();
|
||||||
|
if (pipeData == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (pipeData.thePipeline == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//In theory the first frame could be null
|
||||||
|
var dt = pipeData.thePipeline.fb.getDepthTex();
|
||||||
|
if (dt == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return dt.id;
|
||||||
|
}, ()->GlSampler.MIPPED_NEAREST_NEAREST, opaqueNames);
|
||||||
|
|
||||||
|
samplers.addDynamicSampler(TextureType.TEXTURE_2D, () -> {
|
||||||
|
var pipeData = ((IGetIrisVoxyPipelineData)pipeline).voxy$getPipelineData();
|
||||||
|
if (pipeData == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (pipeData.thePipeline == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
//In theory the first frame could be null
|
||||||
|
var dt = pipeData.thePipeline.fbTranslucent.getDepthTex();
|
||||||
|
if (dt == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return dt.id;
|
||||||
|
}, ()->GlSampler.MIPPED_NEAREST_NEAREST, translucentNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
106
src/main/java/me/cortex/voxy/client/iris/VoxyUniforms.java
Normal file
106
src/main/java/me/cortex/voxy/client/iris/VoxyUniforms.java
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package me.cortex.voxy.client.iris;
|
||||||
|
|
||||||
|
import me.cortex.voxy.common.config.VoxyConfig;
|
||||||
|
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
||||||
|
import net.irisshaders.iris.gl.uniform.UniformHolder;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import org.joml.Matrix4f;
|
||||||
|
import org.joml.Matrix4fc;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import static net.irisshaders.iris.gl.uniform.UniformUpdateFrequency.PER_FRAME;
|
||||||
|
|
||||||
|
public class VoxyUniforms {
|
||||||
|
|
||||||
|
public static Matrix4f getViewProjection() {//This is 1 frame late ;-; cries, since the update occurs _before_ the voxy render pipeline
|
||||||
|
var getVrs = (IGetVoxyRenderSystem) Minecraft.getInstance().levelRenderer;
|
||||||
|
if (getVrs == null || getVrs.getVoxyRenderSystem() == null) {
|
||||||
|
return new Matrix4f();
|
||||||
|
}
|
||||||
|
var vrs = getVrs.getVoxyRenderSystem();
|
||||||
|
return new Matrix4f(vrs.getViewport().MVP);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Matrix4f getModelView() {//This is 1 frame late ;-; cries, since the update occurs _before_ the voxy render pipeline
|
||||||
|
var getVrs = (IGetVoxyRenderSystem) Minecraft.getInstance().levelRenderer;
|
||||||
|
if (getVrs == null || getVrs.getVoxyRenderSystem() == null) {
|
||||||
|
return new Matrix4f();
|
||||||
|
}
|
||||||
|
var vrs = getVrs.getVoxyRenderSystem();
|
||||||
|
return new Matrix4f(vrs.getViewport().modelView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Matrix4f getProjection() {//This is 1 frame late ;-; cries, since the update occurs _before_ the voxy render pipeline
|
||||||
|
var getVrs = (IGetVoxyRenderSystem) Minecraft.getInstance().levelRenderer;
|
||||||
|
if (getVrs == null || getVrs.getVoxyRenderSystem() == null) {
|
||||||
|
return new Matrix4f();
|
||||||
|
}
|
||||||
|
var vrs = getVrs.getVoxyRenderSystem();
|
||||||
|
var mat = vrs.getViewport().projection;
|
||||||
|
if (mat == null) {
|
||||||
|
return new Matrix4f();
|
||||||
|
}
|
||||||
|
return new Matrix4f(mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addUniforms(UniformHolder uniforms) {
|
||||||
|
uniforms
|
||||||
|
.uniform1i(PER_FRAME, "vxRenderDistance", ()-> VoxyConfig.CONFIG.sectionRenderDistance*32)//In chunks
|
||||||
|
.uniformMatrix(PER_FRAME, "vxViewProj", VoxyUniforms::getViewProjection)
|
||||||
|
.uniformMatrix(PER_FRAME, "vxViewProjInv", new Inverted(VoxyUniforms::getViewProjection))
|
||||||
|
.uniformMatrix(PER_FRAME, "vxViewProjPrev", new PreviousMat(VoxyUniforms::getViewProjection))
|
||||||
|
.uniformMatrix(PER_FRAME, "vxModelView", VoxyUniforms::getModelView)
|
||||||
|
.uniformMatrix(PER_FRAME, "vxModelViewInv", new Inverted(VoxyUniforms::getModelView))
|
||||||
|
.uniformMatrix(PER_FRAME, "vxModelViewPrev", new PreviousMat(VoxyUniforms::getModelView))
|
||||||
|
.uniformMatrix(PER_FRAME, "vxProj", VoxyUniforms::getProjection)
|
||||||
|
.uniformMatrix(PER_FRAME, "vxProjInv", new Inverted(VoxyUniforms::getProjection))
|
||||||
|
.uniformMatrix(PER_FRAME, "vxProjPrev", new PreviousMat(VoxyUniforms::getProjection));
|
||||||
|
|
||||||
|
if (IrisShaderPatch.IMPERSONATE_DISTANT_HORIZONS) {
|
||||||
|
uniforms
|
||||||
|
.uniform1f(PER_FRAME, "dhNearPlane", ()->16)//Presently hardcoded in voxy
|
||||||
|
.uniform1f(PER_FRAME, "dhFarPlane", ()->16*3000)//Presently hardcoded in voxy
|
||||||
|
|
||||||
|
.uniform1i(PER_FRAME, "dhRenderDistance", ()-> VoxyConfig.CONFIG.sectionRenderDistance*32*16)//In blocks
|
||||||
|
.uniformMatrix(PER_FRAME, "dhProjection", VoxyUniforms::getProjection)
|
||||||
|
.uniformMatrix(PER_FRAME, "dhProjectionInverse", new Inverted(VoxyUniforms::getProjection))
|
||||||
|
.uniformMatrix(PER_FRAME, "dhPreviousProjection", new PreviousMat(VoxyUniforms::getProjection));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private record Inverted(Supplier<Matrix4fc> parent) implements Supplier<Matrix4fc> {
|
||||||
|
private Inverted(Supplier<Matrix4fc> parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Matrix4fc get() {
|
||||||
|
Matrix4f copy = new Matrix4f(this.parent.get());
|
||||||
|
copy.invert();
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Supplier<Matrix4fc> parent() {
|
||||||
|
return this.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PreviousMat implements Supplier<Matrix4fc> {
|
||||||
|
private final Supplier<Matrix4fc> parent;
|
||||||
|
private Matrix4f previous;
|
||||||
|
|
||||||
|
PreviousMat(Supplier<Matrix4fc> parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.previous = new Matrix4f();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Matrix4fc get() {
|
||||||
|
Matrix4f previous = this.previous;
|
||||||
|
this.previous = new Matrix4f(this.parent.get());
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package me.cortex.voxy.client.mixin.flashback;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.moulberry.flashback.record.FlashbackMeta;
|
||||||
|
import me.cortex.voxy.client.compat.IFlashbackMeta;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
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.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
@Mixin(value = FlashbackMeta.class, remap = false)
|
||||||
|
public class MixinFlashbackMeta implements IFlashbackMeta {
|
||||||
|
@Unique private File voxyPath;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVoxyPath(File path) {
|
||||||
|
this.voxyPath = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getVoxyPath() {
|
||||||
|
return this.voxyPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "toJson", at = @At("RETURN"))
|
||||||
|
private void voxy$injectSaveVoxyPath(CallbackInfoReturnable<JsonObject> cir) {
|
||||||
|
var val = cir.getReturnValue();
|
||||||
|
if (val != null && this.voxyPath != null) {
|
||||||
|
val.addProperty("voxy_storage_path", this.voxyPath.getAbsoluteFile().getPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "fromJson", at = @At("RETURN"))
|
||||||
|
private static void voxy$injectGetVoxyPath(JsonObject meta, CallbackInfoReturnable<FlashbackMeta> cir) {
|
||||||
|
var val = cir.getReturnValue();
|
||||||
|
if (val != null && meta != null) {
|
||||||
|
if (meta.has("voxy_storage_path")) {
|
||||||
|
((IFlashbackMeta)val).setVoxyPath(new File(meta.get("voxy_storage_path").getAsString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package me.cortex.voxy.client.mixin.flashback;
|
||||||
|
|
||||||
|
import com.moulberry.flashback.record.FlashbackMeta;
|
||||||
|
import com.moulberry.flashback.record.Recorder;
|
||||||
|
import me.cortex.voxy.client.VoxyClientInstance;
|
||||||
|
import me.cortex.voxy.client.compat.IFlashbackMeta;
|
||||||
|
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||||
|
import net.minecraft.core.RegistryAccess;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(value = Recorder.class, remap = false)
|
||||||
|
public class MixinFlashbackRecorder {
|
||||||
|
@Shadow @Final private FlashbackMeta metadata;
|
||||||
|
|
||||||
|
@Inject(method = "<init>", at = @At("TAIL"))
|
||||||
|
private void voxy$getStoragePath(RegistryAccess registryAccess, CallbackInfo retInf) {
|
||||||
|
if (VoxyCommon.isAvailable()) {
|
||||||
|
var instance = VoxyCommon.getInstance();
|
||||||
|
if (instance instanceof VoxyClientInstance ci) {
|
||||||
|
((IFlashbackMeta)this.metadata).setVoxyPath(ci.getStorageBasePath().toFile());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package me.cortex.voxy.client.mixin.iris;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||||
|
import net.irisshaders.iris.uniforms.custom.CustomUniforms;
|
||||||
|
import net.irisshaders.iris.uniforms.custom.cached.CachedUniform;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Mixin(value = CustomUniforms.class, remap = false)
|
||||||
|
public interface CustomUniformsAccessor {
|
||||||
|
@Accessor Map<Object, Object2IntMap<CachedUniform>> getLocationMap();
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user