├── .github
└── workflows
│ ├── development.yml
│ └── stable.yml
├── .gitignore
├── LICENSE
├── README.md
├── benchmark
├── OpenCL_test
│ ├── OpenCL_test.json
│ └── OpenCL_test.octree2
└── readme.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src
└── main
├── java
└── dev
│ └── thatredox
│ └── chunkynative
│ ├── common
│ ├── export
│ │ ├── AbstractSceneLoader.java
│ │ ├── CachedResourcePalette.java
│ │ ├── Packer.java
│ │ ├── ResourcePalette.java
│ │ ├── models
│ │ │ ├── PackedAabbModel.java
│ │ │ ├── PackedBvhNode.java
│ │ │ ├── PackedQuadModel.java
│ │ │ └── PackedTriangleModel.java
│ │ ├── primitives
│ │ │ ├── PackedAabb.java
│ │ │ ├── PackedBlock.java
│ │ │ ├── PackedMaterial.java
│ │ │ ├── PackedQuad.java
│ │ │ ├── PackedSun.java
│ │ │ └── PackedTriangle.java
│ │ └── texture
│ │ │ ├── AbstractTextureLoader.java
│ │ │ └── TextureRecord.java
│ └── state
│ │ ├── SkyState.java
│ │ └── StateUtil.java
│ ├── opencl
│ ├── ChunkyCl.java
│ ├── OpenClPathTracingRenderer.java
│ ├── OpenClPreviewRenderer.java
│ ├── renderer
│ │ ├── ClSceneLoader.java
│ │ ├── KernelLoader.java
│ │ ├── RendererInstance.java
│ │ ├── export
│ │ │ ├── ClPackedResourcePalette.java
│ │ │ └── ClTextureLoader.java
│ │ └── scene
│ │ │ ├── ClCamera.java
│ │ │ └── ClSky.java
│ ├── tonemap
│ │ ├── GpuPostProcessingFilter.java
│ │ ├── ImposterCombinationGpuPostProcessingFilter.java
│ │ └── ImposterGpuPostProcessingFilter.java
│ ├── ui
│ │ ├── ChunkyClTab.java
│ │ └── GpuSelector.java
│ └── util
│ │ ├── ClIntBuffer.java
│ │ └── ClMemory.java
│ └── util
│ ├── EqualityChecker.java
│ ├── FunctionCache.java
│ ├── NativeCleaner.java
│ ├── Reflection.java
│ └── Util.java
├── opencl
├── kernel
│ ├── .clangd
│ ├── CMakeLists.txt
│ ├── include
│ │ ├── block.h
│ │ ├── bvh.h
│ │ ├── camera.h
│ │ ├── constants.h
│ │ ├── kernel.h
│ │ ├── material.h
│ │ ├── octree.h
│ │ ├── primitives.h
│ │ ├── randomness.h
│ │ ├── rayTracer.cl
│ │ ├── sky.h
│ │ ├── textureAtlas.h
│ │ ├── utils.h
│ │ └── wavefront.h
│ └── opencl.h
└── tonemap
│ ├── .clangd
│ ├── CMakeLists.txt
│ ├── include
│ ├── double.h
│ ├── post_processing_filter.cl
│ └── rgba.h
│ └── opencl.h
└── resources
└── plugin.json
/.github/workflows/development.yml:
--------------------------------------------------------------------------------
1 | name: Development
2 |
3 | on:
4 | push:
5 | branches-ignore:
6 | # Stable is not development!
7 | - stable
8 | workflow_dispatch:
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: Set up JDK 17
17 | uses: actions/setup-java@v2
18 | with:
19 | distribution: 'temurin'
20 | java-version: '17'
21 | java-package: jdk
22 | - name: Grant execute permission for gradlew
23 | run: chmod +x gradlew
24 | - name: Build with Gradle
25 | run: ./gradlew build
26 | - name: Upload a Build Artifact
27 | uses: actions/upload-artifact@v2.2.2
28 | with:
29 | name: ChunkyClPlugin
30 | path: build/libs/ChunkyClPlugin.jar
31 |
--------------------------------------------------------------------------------
/.github/workflows/stable.yml:
--------------------------------------------------------------------------------
1 | name: Stable
2 |
3 | on:
4 | push:
5 | branches:
6 | # Only run on stable branch merges
7 | - stable
8 | schedule:
9 | # Run at midnight
10 | - cron: '0 0 * * *'
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v2
18 | with:
19 | ref: stable
20 | - name: Set up JDK 1.8
21 | uses: actions/setup-java@v1
22 | with:
23 | java-version: 1.8
24 | java-package: jdk+fx
25 | - name: Grant execute permission for gradlew
26 | run: chmod +x gradlew
27 | - name: Build with Gradle
28 | run: ./gradlew build
29 | - name: Upload a Build Artifact
30 | uses: actions/upload-artifact@v2.2.2
31 | with:
32 | name: ChunkyClPlugin
33 | path: build/libs/ChunkyClPlugin.jar
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /.gradle/
3 | /.idea/
4 | /*.iml
5 | *.log
6 | /src/main/resources/kernel/.idea/
7 | /src/main/resources/kernel/cmake-build-debug/
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ⚠️ Development has been moved to [chunky-opencl](https://github.com/chunky-dev/chunky-opencl)! ⚠️
2 |
3 | # ChunkyCL
4 |
5 | ChunkyCL is a plugin to the Minecraft Path-tracer [Chunky](https://github.com/chunky-dev/chunky) which harnesses the power of the GPU with OpenCL 1.2+ to accelerate rendering.
6 | It is currently a work in progress and does not support many features such as biome tinting. The core renderer itself is still under development
7 | so render results may change drastically between versions.
8 |
9 | ## Downloads
10 | * [2.4.X Stable Build](https://nightly.link/ThatRedox/ChunkyClPlugin/workflows/stable/master/ChunkyClPlugin.zip)
11 | * [Latest development build](https://nightly.link/ThatRedox/ChunkyClPlugin/workflows/development/master/ChunkyClPlugin.zip)
12 |
13 | Note: Even if the build is failing, the link will link to the latest successful build.
14 |
15 | ## Installation
16 |
17 | ### Note: If you are using Chunky `2.4.X`, use the stable build.
18 | ### Note: The latest version development version requires the `2.5.0` snapshots.
19 | Download the latest plugin build and extract it. In the Chunky Launcher, expand `Advanced Settings` and click on `Manage plugins`. In the `Plugin Manager` window click on `Add` and select the `.jar` file in the extracted zip file. Click on `Save` and start Chunky as usual.
20 |
21 | 
22 |
23 | Select `ChunkyCL` as your renderer for the scene in the `Advanced` tab.
24 |
25 | 
26 |
27 | ## Performance
28 |
29 | Rough performance with a RTX 2070 is around 400 times that of the traditional CPU renderer as of 2022-01-27.
30 |
31 | Some settings have been added to improve render performance.
32 | * Indoor scenes should disable sunlight under `Lighting`
33 | * Draw depth may be adjusted under `Advanced`
34 | * Draw entities may be unchecked under `Advanced`
35 | * OpenCL Device selector under `Advanced`
36 |
37 | ## Compatibility
38 |
39 | * Not compatible with the Denoising Plugin.
40 |
41 | ---
42 |
43 | ## Development
44 | This project is setup to work with IntelliJ and CLion. The base directory is intended to be opened in IntelliJ and the `src/main/opencl` directory in CLion.
45 |
46 | ## Copyright & License
47 | ChunkyCL is Copyright (c) 2021 - 2022, [ThatRedox](https://github.com/ThatRedox) and [Contributors](https://github.com/ThatRedox/ChunkyClPlugin/graphs/contributors).
48 |
49 | Permission to modify and redistribute is granted under the terms of the GPLv3 license. See the file `LICENSE` for the full license.
50 |
51 | ChunkyCL uses the following 3rd party libraries:
52 | * [Chunky](https://github.com/chunky-dev/chunky/)
53 | * [JOCL](http://www.jocl.org/)
54 | * [Opencl Header from the LLVM Project](https://llvm.org)
55 |
--------------------------------------------------------------------------------
/benchmark/OpenCL_test/OpenCL_test.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThatRedox/ChunkyClPlugin/32a038413208cbc797a6739460eb652d8b03f301/benchmark/OpenCL_test/OpenCL_test.json
--------------------------------------------------------------------------------
/benchmark/OpenCL_test/OpenCL_test.octree2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThatRedox/ChunkyClPlugin/32a038413208cbc797a6739460eb652d8b03f301/benchmark/OpenCL_test/OpenCL_test.octree2
--------------------------------------------------------------------------------
/benchmark/readme.md:
--------------------------------------------------------------------------------
1 | Provide results via commenting on the following [Google Sheets doc](https://docs.google.com/spreadsheets/d/1_13XHE_5bhpVvtVAfUKMBePQI3apqkCeJOPRk-topWw/edit?usp=sharing).
2 |
3 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'org.openjfx.javafxplugin' version '0.0.10'
3 | }
4 |
5 | repositories {
6 | mavenLocal()
7 | mavenCentral()
8 | maven {
9 | url 'https://repo.lemaik.de/'
10 | }
11 | }
12 |
13 | sourceCompatibility = '1.8'
14 | targetCompatibility = '1.8'
15 |
16 | configurations {
17 | implementation.extendsFrom(provided)
18 | implementation.extendsFrom(bundled)
19 | }
20 |
21 | dependencies {
22 | provided 'se.llbit:chunky-core:2.5.0-SNAPSHOT'
23 | provided 'org.apache.commons:commons-math3:3.2'
24 | provided 'it.unimi.dsi:fastutil:8.4.4'
25 | bundled 'org.jocl:jocl:2.0.2'
26 | }
27 |
28 | javafx {
29 | version = '17'
30 | modules = ['javafx.base', 'javafx.controls', 'javafx.fxml']
31 | configuration = 'provided'
32 | }
33 |
34 | processResources {
35 | from "src/main/opencl/kernel/include", { into "kernel" }
36 | from "src/main/opencl/tonemap/include", { into "tonemap" }
37 | }
38 |
39 | jar {
40 | from {
41 | configurations.bundled.collect { it.isDirectory() ? it : zipTree(it) }
42 | }
43 | }
44 |
45 | defaultTasks 'jar'
46 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThatRedox/ChunkyClPlugin/32a038413208cbc797a6739460eb652d8b03f301/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/common/export/AbstractSceneLoader.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.common.export;
2 |
3 | import dev.thatredox.chunkynative.common.export.models.PackedAabbModel;
4 | import dev.thatredox.chunkynative.common.export.models.PackedBvhNode;
5 | import dev.thatredox.chunkynative.common.export.models.PackedQuadModel;
6 | import dev.thatredox.chunkynative.common.export.models.PackedTriangleModel;
7 | import dev.thatredox.chunkynative.common.export.primitives.PackedBlock;
8 | import dev.thatredox.chunkynative.common.export.primitives.PackedMaterial;
9 | import dev.thatredox.chunkynative.common.export.primitives.PackedSun;
10 | import dev.thatredox.chunkynative.common.export.texture.AbstractTextureLoader;
11 | import dev.thatredox.chunkynative.util.Reflection;
12 | import se.llbit.chunky.renderer.ResetReason;
13 | import se.llbit.chunky.renderer.scene.Scene;
14 | import se.llbit.chunky.renderer.scene.SceneEntities;
15 | import se.llbit.chunky.renderer.scene.Sun;
16 | import se.llbit.log.Log;
17 | import se.llbit.math.Octree;
18 | import se.llbit.math.PackedOctree;
19 | import se.llbit.math.bvh.BVH;
20 | import se.llbit.math.bvh.BinaryBVH;
21 | import se.llbit.math.primitive.TexturedTriangle;
22 |
23 | import java.lang.ref.WeakReference;
24 | import java.util.Arrays;
25 |
26 | public abstract class AbstractSceneLoader {
27 | protected int modCount = 0;
28 | protected WeakReference prevWorldBvh = new WeakReference<>(null, null);
29 | protected WeakReference prevActorBvh = new WeakReference<>(null, null);
30 | protected WeakReference prevOctree = new WeakReference<>(null, null);
31 |
32 | protected AbstractTextureLoader texturePalette = null;
33 | protected ResourcePalette blockPalette = null;
34 | protected CachedResourcePalette materialPalette = null;
35 | protected ResourcePalette aabbPalette = null;
36 | protected ResourcePalette quadPalette = null;
37 | protected ResourcePalette trigPalette = null;
38 | protected int[] worldBvh = null;
39 | protected int[] actorBvh = null;
40 | protected PackedSun packedSun = null;
41 |
42 | public boolean ensureLoad(Scene scene) {
43 | return this.ensureLoad(scene, false);
44 | }
45 |
46 | protected boolean ensureLoad(Scene scene, boolean force) {
47 | if (force ||
48 | this.texturePalette == null || this.blockPalette == null || this.materialPalette == null ||
49 | this.aabbPalette == null || this.quadPalette == null || this.trigPalette == null ||
50 | this.prevOctree.get() != scene.getWorldOctree().getImplementation()) {
51 | this.modCount = -1;
52 | return this.load(0, ResetReason.SCENE_LOADED, scene);
53 | }
54 | return true;
55 | }
56 |
57 | /**
58 | * @return True if successfully loaded. False if loading failed.
59 | */
60 | public boolean load(int modCount, ResetReason resetReason, Scene scene) {
61 | // Already up to date
62 | if (this.modCount == modCount) {
63 | return true;
64 | }
65 |
66 | if (resetReason == ResetReason.NONE || resetReason == ResetReason.MODE_CHANGE) {
67 | this.modCount = modCount;
68 | return true;
69 | }
70 |
71 | AbstractTextureLoader texturePalette = this.createTextureLoader();
72 | ResourcePalette blockPalette = this.createBlockPalette();
73 | CachedResourcePalette materialPalette = new CachedResourcePalette<>(this.createMaterialPalette());
74 | ResourcePalette aabbPalette = this.createAabbModelPalette();
75 | ResourcePalette quadPalette = this.createQuadModelPalette();
76 | ResourcePalette trigPalette = this.createTriangleModelPalette();
77 |
78 | SceneEntities entities = Reflection.getFieldValue(scene, "entities", SceneEntities.class);
79 | BVH worldBvh = Reflection.getFieldValue(entities, "bvh", BVH.class);
80 | BVH actorBvh = Reflection.getFieldValue(entities, "actorBvh", BVH.class);
81 |
82 | boolean needTextureLoad = resetReason == ResetReason.SCENE_LOADED ||
83 | resetReason == ResetReason.MATERIALS_CHANGED ||
84 | prevWorldBvh.get() != worldBvh ||
85 | prevActorBvh.get() != actorBvh;
86 |
87 | if (needTextureLoad) {
88 | if (!(worldBvh instanceof BinaryBVH || worldBvh == BVH.EMPTY)) {
89 | Log.error("BVH implementation must extend BinaryBVH");
90 | return false;
91 | }
92 | if (!(actorBvh instanceof BinaryBVH || actorBvh == BVH.EMPTY)) {
93 | Log.error("BVH implementation must extend BinaryBVH");
94 | return false;
95 | }
96 | prevWorldBvh = new WeakReference<>(worldBvh, null);
97 | prevActorBvh = new WeakReference<>(actorBvh, null);
98 | }
99 |
100 | // Preload textures
101 | if (needTextureLoad) {
102 | scene.getPalette().getPalette().forEach(b -> PackedBlock.preloadTextures(b, texturePalette));
103 | if (worldBvh != BVH.EMPTY) preloadBvh((BinaryBVH) worldBvh, texturePalette);
104 | if (actorBvh != BVH.EMPTY) preloadBvh((BinaryBVH) actorBvh, texturePalette);
105 | texturePalette.get(Sun.texture);
106 | texturePalette.build();
107 | }
108 |
109 | int[] blockMapping = null;
110 | int[] packedWorldBvh;
111 | int[] packedActorBvh;
112 |
113 | if (needTextureLoad) {
114 | blockMapping = scene.getPalette().getPalette().stream()
115 | .mapToInt(block ->
116 | blockPalette.put(new PackedBlock(block, texturePalette, materialPalette, aabbPalette, quadPalette)))
117 | .toArray();
118 | if (worldBvh != BVH.EMPTY) {
119 | packedWorldBvh = loadBvh((BinaryBVH) worldBvh, texturePalette, materialPalette, trigPalette);
120 | } else {
121 | packedWorldBvh = PackedBvhNode.EMPTY_NODE.node;
122 | }
123 | if (actorBvh != BVH.EMPTY) {
124 | packedActorBvh = loadBvh((BinaryBVH) actorBvh, texturePalette, materialPalette, trigPalette);
125 | } else {
126 | packedActorBvh = PackedBvhNode.EMPTY_NODE.node;
127 | }
128 | packedSun = new PackedSun(scene.sun(), texturePalette);
129 |
130 | if (this.texturePalette != null) this.texturePalette.release();
131 | if (this.blockPalette != null) this.blockPalette.release();
132 | if (this.materialPalette != null) this.materialPalette.release();
133 | if (this.aabbPalette != null) this.aabbPalette.release();
134 | if (this.quadPalette != null) this.quadPalette.release();
135 | if (this.trigPalette != null) this.trigPalette.release();
136 |
137 | this.texturePalette = texturePalette;
138 | this.blockPalette = blockPalette;
139 | this.materialPalette = materialPalette;
140 | this.aabbPalette = aabbPalette;
141 | this.quadPalette = quadPalette;
142 | this.trigPalette = trigPalette;
143 | this.worldBvh = packedWorldBvh;
144 | this.actorBvh = packedActorBvh;
145 | }
146 |
147 | // Need to reload octree
148 | Octree.OctreeImplementation impl = scene.getWorldOctree().getImplementation();
149 | if (resetReason == ResetReason.SCENE_LOADED || prevOctree.get() != impl) {
150 | prevOctree = new WeakReference<>(impl, null);
151 | if (impl instanceof PackedOctree) {
152 | assert blockMapping != null;
153 | if (!loadOctree(((PackedOctree) impl).treeData, impl.getDepth(), blockMapping, blockPalette))
154 | return false;
155 | } else {
156 | Log.error("Octree implementation must be PACKED");
157 | return false;
158 | }
159 | }
160 |
161 | return true;
162 | }
163 |
164 | protected static void preloadBvh(BinaryBVH bvh, AbstractTextureLoader texturePalette) {
165 | Arrays.stream(bvh.packedPrimitives).flatMap(Arrays::stream).forEach(primitive -> {
166 | if (primitive instanceof TexturedTriangle) {
167 | texturePalette.get(((TexturedTriangle) primitive).material.texture);
168 | }
169 | });
170 | }
171 |
172 | protected static int[] loadBvh(BinaryBVH bvh,
173 | AbstractTextureLoader texturePalette,
174 | ResourcePalette materialPalette,
175 | ResourcePalette trigPalette) {
176 | int[] out = new int[bvh.packed.length];
177 | for (int i = 0; i < out.length; i += 7) {
178 | PackedBvhNode node = new PackedBvhNode(bvh.packed, i, bvh.packedPrimitives, texturePalette, materialPalette, trigPalette);
179 | System.arraycopy(node.pack().elements(), 0, out, i, 7);
180 | }
181 | return out;
182 | }
183 |
184 | protected abstract boolean loadOctree(int[] octree, int depth, int[] blockMapping, ResourcePalette blockPalette);
185 |
186 | protected abstract AbstractTextureLoader createTextureLoader();
187 | protected abstract ResourcePalette createBlockPalette();
188 | protected abstract ResourcePalette createMaterialPalette();
189 | protected abstract ResourcePalette createAabbModelPalette();
190 | protected abstract ResourcePalette createQuadModelPalette();
191 | protected abstract ResourcePalette createTriangleModelPalette();
192 | }
193 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/common/export/CachedResourcePalette.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.common.export;
2 |
3 | import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
4 |
5 | /**
6 | * A cache wrapper around a resource palette.
7 | */
8 | public class CachedResourcePalette implements ResourcePalette {
9 | protected final Object2IntOpenHashMap resourceMap = new Object2IntOpenHashMap<>();
10 |
11 | /**
12 | * The wrapped resource palette.
13 | */
14 | public final ResourcePalette palette;
15 |
16 | public CachedResourcePalette(ResourcePalette palette) {
17 | this.palette = palette;
18 | }
19 |
20 | /**
21 | * Add a resource to the palette and get the reference. If this resource has already been put into the palette,
22 | * it will return the reference to the original resource.
23 | *
24 | * @throws IllegalStateException if the palette is locked. If looking for an existing resource in the palette,
25 | * use {@code get()}.
26 | */
27 | @Override
28 | public int put(T resource) {
29 | return resourceMap.computeIntIfAbsent(resource, palette::put);
30 | }
31 |
32 | @Override
33 | public void release() {
34 | this.palette.release();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/common/export/Packer.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.common.export;
2 |
3 | import it.unimi.dsi.fastutil.ints.IntArrayList;
4 |
5 | /**
6 | * A class that packs scene data into an IntArray(List).
7 | */
8 | public interface Packer {
9 | /**
10 | * Pack the data. Note: this returns an IntArrayList so the elements can be directly accessed by
11 | * calling {@code IntArrayList.elements()} and thus saving a lot of memory in a temporary array.
12 | */
13 | IntArrayList pack();
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/common/export/ResourcePalette.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.common.export;
2 |
3 | public interface ResourcePalette {
4 | /**
5 | * Add a resource to the palette and get the reference.
6 | */
7 | int put(T resource);
8 |
9 | /**
10 | * Release this resource palette. The default implementation does nothing, but palettes with native
11 | * resources can use this to free those resources.
12 | */
13 | default void release() {}
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/common/export/models/PackedAabbModel.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.common.export.models;
2 |
3 | import dev.thatredox.chunkynative.common.export.texture.AbstractTextureLoader;
4 | import dev.thatredox.chunkynative.common.export.Packer;
5 | import dev.thatredox.chunkynative.common.export.ResourcePalette;
6 | import dev.thatredox.chunkynative.common.export.primitives.PackedAabb;
7 | import dev.thatredox.chunkynative.common.export.primitives.PackedMaterial;
8 | import it.unimi.dsi.fastutil.ints.IntArrayList;
9 | import se.llbit.chunky.model.AABBModel;
10 | import se.llbit.chunky.model.Tint;
11 | import se.llbit.chunky.resources.Texture;
12 | import se.llbit.chunky.world.Material;
13 | import se.llbit.math.AABB;
14 |
15 | public class PackedAabbModel implements Packer {
16 | public final PackedAabb[] boxes;
17 |
18 | public PackedAabbModel(AABBModel model, Material material,
19 | AbstractTextureLoader texturePalette,
20 | ResourcePalette materialPalette) {
21 | boxes = new PackedAabb[model.getBoxes().length];
22 | for (int i = 0; i < boxes.length; i++) {
23 | AABB box = model.getBoxes()[i];
24 | Texture[] texs = model.getTextures()[i];
25 | Tint[] tints = null;
26 | if (model.getTints() != null)
27 | tints = model.getTints()[i];
28 | AABBModel.UVMapping[] maps = null;
29 | if (model.getUVMapping() != null)
30 | maps = model.getUVMapping()[i];
31 |
32 | boxes[i] = new PackedAabb(box, texs, tints, maps, material, texturePalette, materialPalette);
33 | }
34 | }
35 |
36 | /**
37 | * Pack this AABB model into ints. The first integer specifies the number of
38 | * AABB models and the remaining integers are the {@code PackedAbb}'s.
39 | */
40 | @Override
41 | public IntArrayList pack() {
42 | IntArrayList out = new IntArrayList();
43 | out.add(boxes.length);
44 | for (PackedAabb box : boxes)
45 | out.addAll(box.pack());
46 | return out;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/common/export/models/PackedBvhNode.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.common.export.models;
2 |
3 | import dev.thatredox.chunkynative.common.export.Packer;
4 | import dev.thatredox.chunkynative.common.export.ResourcePalette;
5 | import dev.thatredox.chunkynative.common.export.primitives.PackedMaterial;
6 | import dev.thatredox.chunkynative.common.export.texture.AbstractTextureLoader;
7 | import it.unimi.dsi.fastutil.ints.IntArrayList;
8 | import se.llbit.math.primitive.Primitive;
9 |
10 | import java.util.Arrays;
11 |
12 | public class PackedBvhNode implements Packer {
13 | /**
14 | * An empty node as a node index of 0 and an AABB of NaN.
15 | */
16 | public static final PackedBvhNode EMPTY_NODE = new PackedBvhNode(new int[] {
17 | 0, 0x7fc00000, 0x7fc00000, 0x7fc00000, 0x7fc00000, 0x7fc00000, 0x7fc00000
18 | });
19 |
20 | public final int[] node;
21 |
22 | public PackedBvhNode(int[] packed, int offset, Primitive[][] primitives,
23 | AbstractTextureLoader texturePalette,
24 | ResourcePalette materialPalette,
25 | ResourcePalette modelPalette) {
26 | this.node = Arrays.copyOfRange(packed, offset, offset+7);
27 | if (this.node[0] <= 0) {
28 | node[0] = -modelPalette.put(new PackedTriangleModel(
29 | primitives[-node[0]], texturePalette, materialPalette));
30 | }
31 | }
32 |
33 | private PackedBvhNode(int[] node) {
34 | this.node = node;
35 | }
36 |
37 | @Override
38 | public IntArrayList pack() {
39 | return new IntArrayList(node);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/common/export/models/PackedQuadModel.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.common.export.models;
2 |
3 | import dev.thatredox.chunkynative.common.export.texture.AbstractTextureLoader;
4 | import dev.thatredox.chunkynative.common.export.Packer;
5 | import dev.thatredox.chunkynative.common.export.ResourcePalette;
6 | import dev.thatredox.chunkynative.common.export.primitives.PackedMaterial;
7 | import dev.thatredox.chunkynative.common.export.primitives.PackedQuad;
8 | import it.unimi.dsi.fastutil.ints.IntArrayList;
9 | import se.llbit.chunky.model.QuadModel;
10 | import se.llbit.chunky.model.Tint;
11 | import se.llbit.chunky.resources.Texture;
12 | import se.llbit.chunky.world.Material;
13 | import se.llbit.math.Quad;
14 |
15 | public class PackedQuadModel implements Packer {
16 | public final PackedQuad[] quads;
17 |
18 | public PackedQuadModel(QuadModel model, Material material,
19 | AbstractTextureLoader texturePalette,
20 | ResourcePalette materialPalette) {
21 | quads = new PackedQuad[model.getQuads().length];
22 | for (int i = 0; i < quads.length; i++) {
23 | Quad quad = model.getQuads()[i];
24 | Texture tex = model.getTextures()[i];
25 | Tint tint = null;
26 | if (model.getTints() != null) {
27 | tint = model.getTints()[i];
28 | }
29 |
30 | quads[i] = new PackedQuad(quad, tex, tint, material, texturePalette, materialPalette);
31 | }
32 | }
33 |
34 | /**
35 | * Pack this Quad model into ints. The first integer specifies the number of
36 | * Quad models and the remaining integers are the {@code PackedQuad}'s.
37 | */
38 | @Override
39 | public IntArrayList pack() {
40 | IntArrayList out = new IntArrayList();
41 | out.add(quads.length);
42 | for (PackedQuad q: quads)
43 | out.addAll(q.pack());
44 | return out;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/common/export/models/PackedTriangleModel.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.common.export.models;
2 |
3 | import dev.thatredox.chunkynative.common.export.Packer;
4 | import dev.thatredox.chunkynative.common.export.ResourcePalette;
5 | import dev.thatredox.chunkynative.common.export.primitives.PackedMaterial;
6 | import dev.thatredox.chunkynative.common.export.primitives.PackedTriangle;
7 | import dev.thatredox.chunkynative.common.export.texture.AbstractTextureLoader;
8 | import it.unimi.dsi.fastutil.ints.IntArrayList;
9 | import se.llbit.math.primitive.Primitive;
10 | import se.llbit.math.primitive.TexturedTriangle;
11 |
12 | import java.util.Arrays;
13 |
14 | public class PackedTriangleModel implements Packer {
15 | public final PackedTriangle[] triangles;
16 |
17 | public PackedTriangleModel(Primitive[] primitives,
18 | AbstractTextureLoader texturePalette,
19 | ResourcePalette materialPalette) {
20 | triangles = Arrays.stream(primitives)
21 | .filter(p -> p instanceof TexturedTriangle)
22 | .map(p -> (TexturedTriangle) p)
23 | .map(t -> new PackedTriangle(t, texturePalette, materialPalette))
24 | .toArray(PackedTriangle[]::new);
25 | }
26 |
27 | @Override
28 | public IntArrayList pack() {
29 | IntArrayList out = new IntArrayList();
30 | out.add(triangles.length);
31 | for (PackedTriangle t : triangles)
32 | out.addAll(t.pack());
33 | return out;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/common/export/primitives/PackedAabb.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.common.export.primitives;
2 |
3 | import dev.thatredox.chunkynative.common.export.texture.AbstractTextureLoader;
4 | import dev.thatredox.chunkynative.common.export.Packer;
5 |
6 | import dev.thatredox.chunkynative.common.export.ResourcePalette;
7 | import it.unimi.dsi.fastutil.ints.IntArrayList;
8 | import it.unimi.dsi.fastutil.ints.IntList;
9 | import se.llbit.chunky.model.AABBModel;
10 | import se.llbit.chunky.model.Tint;
11 | import se.llbit.chunky.resources.Texture;
12 | import se.llbit.chunky.world.Material;
13 | import se.llbit.math.AABB;
14 |
15 | public class PackedAabb implements Packer {
16 | public final float[] bounds = new float[6];
17 | public final int[] materials = new int[6];
18 | public final int flags;
19 |
20 | public PackedAabb(AABB box, Texture[] textures, Tint[] tints, AABBModel.UVMapping[] mappings,
21 | Material material, AbstractTextureLoader texturePalette,
22 | ResourcePalette materialPalette) {
23 | this.bounds[0] = (float) box.xmin;
24 | this.bounds[1] = (float) box.xmax;
25 | this.bounds[2] = (float) box.ymin;
26 | this.bounds[3] = (float) box.ymax;
27 | this.bounds[4] = (float) box.zmin;
28 | this.bounds[5] = (float) box.zmax;
29 |
30 | int flags = 0;
31 | for (int i = 0; i < 6; i++) {
32 | Tint tint = null;
33 | if (tints != null) tint = tints[i];
34 | Texture tex = textures[i];
35 | AABBModel.UVMapping map = null;
36 | if (mappings != null) map = mappings[i];
37 |
38 | if (tex != null) {
39 | flags |= mapping2Flags(map) << (i * 4);
40 | this.materials[i] = materialPalette.put(
41 | new PackedMaterial(tex, tint, material, texturePalette));
42 | } else {
43 | flags |= (0b1000) << (i * 4);
44 | }
45 | }
46 | this.flags = flags;
47 | }
48 |
49 | private static int mapping2Flags(AABBModel.UVMapping mapping) {
50 | int flipU = 0b0100;
51 | int flipV = 0b0010;
52 | int swapUV = 0b0001;
53 |
54 | if (mapping == null) {
55 | return 0;
56 | }
57 |
58 | switch (mapping) {
59 | case ROTATE_90:
60 | return flipV | swapUV;
61 | case ROTATE_180:
62 | return flipU | flipV;
63 | case ROTATE_270:
64 | return flipU | swapUV;
65 | case FLIP_U:
66 | return flipU;
67 | case FLIP_V:
68 | return flipV;
69 | default:
70 | case NONE:
71 | return 0;
72 | }
73 | }
74 |
75 | /**
76 | * Pack this AABB. This will be compressed into 13 ints:
77 | * 0: float xmin
78 | * 1: float xmax
79 | * 2: float ymin
80 | * 3: float ymax
81 | * 4: float zmin
82 | * 5: float zmax
83 | * 6: flags - 6 4-bit flags for all 6 materials.
84 | * The corresponding flag can be accessed with {@code (flag >>> i*4) & 0b1111} where
85 | * {@code i} is the material index. Each flag is a bitfield where 0b1000 represents
86 | * no material, 0b0100 represents flip U mapping, 0b0010 represents flip V mapping,
87 | * 0b0001 represents swap U and V mapping. These should be applied in the given order.
88 | * 7: North material index
89 | * 8: East material index
90 | * 9: South material index
91 | * 10: West material index
92 | * 11: Top material index
93 | * 12: Bottom material index
94 | */
95 | @Override
96 | public IntArrayList pack() {
97 | IntArrayList out = new IntArrayList(13);
98 | for (float i : bounds) out.add(Float.floatToIntBits(i));
99 | out.add(flags);
100 | out.addAll(IntList.of(materials));
101 | return out;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/common/export/primitives/PackedBlock.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.common.export.primitives;
2 |
3 | import dev.thatredox.chunkynative.common.export.texture.AbstractTextureLoader;
4 | import dev.thatredox.chunkynative.common.export.Packer;
5 | import dev.thatredox.chunkynative.common.export.ResourcePalette;
6 | import dev.thatredox.chunkynative.common.export.models.PackedAabbModel;
7 | import dev.thatredox.chunkynative.common.export.models.PackedQuadModel;
8 | import it.unimi.dsi.fastutil.ints.IntArrayList;
9 | import se.llbit.chunky.block.AbstractModelBlock;
10 | import se.llbit.chunky.block.Block;
11 | import se.llbit.chunky.model.AABBModel;
12 | import se.llbit.chunky.model.QuadModel;
13 | import se.llbit.chunky.model.Tint;
14 | import se.llbit.chunky.resources.Texture;
15 |
16 | import java.util.Arrays;
17 | import java.util.Objects;
18 | import java.util.stream.Stream;
19 |
20 | public class PackedBlock implements Packer {
21 | public final int modelType;
22 | public final int modelPointer;
23 |
24 | public PackedBlock(Block block, AbstractTextureLoader textureLoader,
25 | ResourcePalette materialPalette,
26 | ResourcePalette aabbModels,
27 | ResourcePalette quadModels) {
28 | if (block instanceof AbstractModelBlock) {
29 | AbstractModelBlock b = (AbstractModelBlock) block;
30 | if (b.getModel() instanceof AABBModel) {
31 | modelType = 2;
32 | modelPointer = aabbModels.put(new PackedAabbModel(
33 | (AABBModel) b.getModel(), block, textureLoader, materialPalette));
34 | } else if (b.getModel() instanceof QuadModel) {
35 | modelType = 3;
36 | modelPointer = quadModels.put(new PackedQuadModel(
37 | (QuadModel) b.getModel(), block, textureLoader, materialPalette));
38 | } else {
39 | throw new RuntimeException(String.format(
40 | "Unknown model type for block %s: %s", block.name, b.getModel()));
41 | }
42 | } else if (block.invisible) {
43 | modelType = 0;
44 | modelPointer = 0;
45 | } else {
46 | modelType = 1;
47 | modelPointer = materialPalette.put(new PackedMaterial(block, Tint.NONE, textureLoader));
48 | }
49 | }
50 |
51 | public static void preloadTextures(Block block, AbstractTextureLoader textureLoader) {
52 | if (block instanceof AbstractModelBlock) {
53 | AbstractModelBlock b = (AbstractModelBlock) block;
54 | Stream textures;
55 | if (b.getModel() instanceof AABBModel) {
56 | textures = Arrays.stream(((AABBModel) b.getModel()).getTextures())
57 | .flatMap(Arrays::stream)
58 | .filter(Objects::nonNull);
59 | } else if (b.getModel() instanceof QuadModel) {
60 | textures = Arrays.stream(((QuadModel) b.getModel()).getTextures());
61 | } else {
62 | throw new RuntimeException(String.format(
63 | "Unknown model type for block %s: %s", block.name, b.getModel()));
64 | }
65 | textures.forEach(textureLoader::get);
66 | } else if (!block.invisible) {
67 | textureLoader.get(block.texture);
68 | }
69 | }
70 |
71 | /**
72 | * Pack this block into 2 ints. The first integer specifies the type of the model:
73 | * 0 - Invisible
74 | * 1 - Full size block
75 | * 2 - AABB model
76 | * 3 - Quad model
77 | * The second integer is a pointer to the model object in its respective palette.
78 | */
79 | @Override
80 | public IntArrayList pack() {
81 | IntArrayList out = new IntArrayList(2);
82 | out.add(modelType);
83 | out.add(modelPointer);
84 | return out;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/common/export/primitives/PackedMaterial.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.common.export.primitives;
2 |
3 | import dev.thatredox.chunkynative.common.export.texture.AbstractTextureLoader;
4 | import dev.thatredox.chunkynative.common.export.Packer;
5 |
6 | import it.unimi.dsi.fastutil.ints.IntArrayList;
7 | import se.llbit.chunky.PersistentSettings;
8 | import se.llbit.chunky.model.Tint;
9 | import se.llbit.chunky.resources.Texture;
10 | import se.llbit.chunky.world.Material;
11 | import se.llbit.log.Log;
12 | import se.llbit.math.ColorUtil;
13 |
14 | public class PackedMaterial implements Packer {
15 | /**
16 | * The size of a packed material in 32 bit words.
17 | */
18 | public static final int MATERIAL_DWORD_SIZE = 6;
19 |
20 | public final boolean hasColorTexture;
21 | public final boolean hasNormalEmittanceTexture;
22 | public final boolean hasSpecularMetalnessRoughnessTexture;
23 |
24 | public final int blockTint;
25 |
26 | public final long colorTexture;
27 | public final int normalEmittanceTexture;
28 | public final int specularMetalnessRoughnessTexture;
29 |
30 | public PackedMaterial(Texture texture, Tint tint, Material material, AbstractTextureLoader texturePalette) {
31 | this(texture, tint, material.emittance, material.specular, material.metalness, material.roughness, texturePalette);
32 | }
33 |
34 | public PackedMaterial(Material material, Tint tint, AbstractTextureLoader texMap) {
35 | this(material.texture, tint, material.emittance, material.specular, material.metalness, material.roughness, texMap);
36 | }
37 |
38 | public PackedMaterial(Texture texture, Tint tint, float emittance, float specular, float metalness, float roughness, AbstractTextureLoader texMap) {
39 | this.hasColorTexture = !PersistentSettings.getSingleColorTextures();
40 | this.hasNormalEmittanceTexture = false;
41 | this.hasSpecularMetalnessRoughnessTexture = false;
42 |
43 | if (tint != null) {
44 | switch (tint.type) {
45 | default:
46 | Log.warn("Unsupported tint type " + tint.type);
47 | case NONE:
48 | this.blockTint = 0;
49 | break;
50 | case CONSTANT:
51 | this.blockTint = ColorUtil.getRGB(tint.tint) | 0xFF000000;
52 | break;
53 | case BIOME_FOLIAGE:
54 | this.blockTint = 1 << 24;
55 | break;
56 | case BIOME_GRASS:
57 | this.blockTint = 2 << 24;
58 | break;
59 | case BIOME_WATER:
60 | this.blockTint = 3 << 24;
61 | break;
62 | }
63 | } else {
64 | this.blockTint = 0;
65 | }
66 |
67 | this.colorTexture = this.hasColorTexture ? texMap.get(texture).get() : texture.getAvgColor();
68 | this.normalEmittanceTexture = (int) (emittance * 255.0);
69 | this.specularMetalnessRoughnessTexture = (int) (specular * 255.0) |
70 | ((int) (metalness * 255.0) << 8) |
71 | ((int) (roughness * 255.0) << 16);
72 | }
73 |
74 | /**
75 | * Materials are packed into 6 consecutive integers:
76 | * 0: Flags - 0b100 = has color texture
77 | * 0b010 = has normal emittance texture
78 | * 0b001 = has specular metalness roughness texture
79 | * 1: Block tint - the top 8 bits control which type of tint:
80 | * 0xFF = lower 24 bits should be interpreted as RGB color
81 | * 0x01 = foliage color
82 | * 0x02 = grass color
83 | * 0x03 = water color
84 | * 2 & 3: Color texture reference
85 | * 4: Top 24 bits represent the surface normal. First 8 bits represent the emittance.
86 | * 5: First 8 bits represent the specularness. Next 8 bits represent the metalness. Next 8 bits represent the roughness.
87 | */
88 | @Override
89 | public IntArrayList pack() {
90 | IntArrayList packed = new IntArrayList(6);
91 | packed.add(((this.hasColorTexture ? 1 : 0) << 2) |
92 | ((this.hasNormalEmittanceTexture ? 1 : 0) << 1) |
93 | (this.hasSpecularMetalnessRoughnessTexture ? 1 : 0));
94 | packed.add(this.blockTint);
95 | packed.add((int) (this.colorTexture >>> 32));
96 | packed.add((int) this.colorTexture);
97 | packed.add(this.normalEmittanceTexture);
98 | packed.add(this.specularMetalnessRoughnessTexture);
99 | return packed;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/common/export/primitives/PackedQuad.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.common.export.primitives;
2 |
3 | import dev.thatredox.chunkynative.common.export.texture.AbstractTextureLoader;
4 | import dev.thatredox.chunkynative.common.export.Packer;
5 | import dev.thatredox.chunkynative.common.export.ResourcePalette;
6 | import it.unimi.dsi.fastutil.ints.IntArrayList;
7 | import se.llbit.chunky.model.Tint;
8 | import se.llbit.chunky.resources.Texture;
9 | import se.llbit.chunky.world.Material;
10 | import se.llbit.math.Quad;
11 |
12 | public class PackedQuad implements Packer {
13 | public final float[] vectors = new float[13];
14 | public final int material;
15 | public final int flags;
16 |
17 | public PackedQuad(Quad quad, Texture texture, Tint tint, Material material,
18 | AbstractTextureLoader texturePalette,
19 | ResourcePalette materialPalette) {
20 | this.vectors[0] = (float) quad.o.x;
21 | this.vectors[1] = (float) quad.o.y;
22 | this.vectors[2] = (float) quad.o.z;
23 | this.vectors[3] = (float) quad.xv.x;
24 | this.vectors[4] = (float) quad.xv.y;
25 | this.vectors[5] = (float) quad.xv.z;
26 | this.vectors[6] = (float) quad.yv.x;
27 | this.vectors[7] = (float) quad.yv.y;
28 | this.vectors[8] = (float) quad.yv.z;
29 | this.vectors[9] = (float) quad.uv.x;
30 | this.vectors[10] = (float) quad.uv.y;
31 | this.vectors[11] = (float) quad.uv.z;
32 | this.vectors[12] = (float) quad.uv.w;
33 | this.material = materialPalette.put(new PackedMaterial(texture, tint, material, texturePalette));
34 | int flags = 0;
35 | if (quad.doubleSided) {
36 | flags |= 1;
37 | }
38 | this.flags = flags;
39 | }
40 |
41 | /**
42 | * Pack this Quad. This will be compressed into 15 ints:
43 | * 0: Origin x
44 | * 1: Origin y
45 | * 2: Origin z
46 | * 3: XV x
47 | * 4: XV y
48 | * 5: XV z
49 | * 6: YV x
50 | * 7: YV y
51 | * 8: YV z
52 | * 9: UV X
53 | * 10: UV Y
54 | * 11: UV Z
55 | * 12: UV W
56 | * 13: Material reference
57 | * 14: Flags bitfield. 1 if doublesided.
58 | */
59 | @Override
60 | public IntArrayList pack() {
61 | IntArrayList out = new IntArrayList(15);
62 | for (float i : vectors) out.add(Float.floatToIntBits(i));
63 | out.add(material);
64 | out.add(flags);
65 | return out;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/common/export/primitives/PackedSun.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.common.export.primitives;
2 |
3 | import dev.thatredox.chunkynative.common.export.texture.AbstractTextureLoader;
4 | import dev.thatredox.chunkynative.common.export.Packer;
5 | import it.unimi.dsi.fastutil.ints.IntArrayList;
6 | import se.llbit.chunky.renderer.scene.Sun;
7 |
8 | public class PackedSun implements Packer {
9 | public final int flags;
10 | public final long texture;
11 | public final float intensity;
12 | public final float altitude;
13 | public final float azimuth;
14 |
15 | public PackedSun(Sun sun, AbstractTextureLoader texturePalette) {
16 | flags = sun.drawTexture() ? 1 : 0;
17 | texture = texturePalette.get(Sun.texture).get();
18 | intensity = (float) sun.getIntensity();
19 | altitude = (float) sun.getAltitude();
20 | azimuth = (float) sun.getAzimuth();
21 | }
22 |
23 | /**
24 | * Pack the sun into 6 ints.
25 | * 0: Flags. 1 if the sun should be drawn. 0 if not.
26 | * 1 & 2: Sun texture reference.
27 | * 3: float sun intensity
28 | * 4: float sun altitude
29 | * 5: float sun azimuth
30 | */
31 | @Override
32 | public IntArrayList pack() {
33 | IntArrayList out = new IntArrayList(6);
34 | out.add(flags);
35 | out.add((int) (texture >>> 32));
36 | out.add((int) texture);
37 | out.add(Float.floatToIntBits(intensity));
38 | out.add(Float.floatToIntBits(altitude));
39 | out.add(Float.floatToIntBits(azimuth));
40 | return out;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/common/export/primitives/PackedTriangle.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.common.export.primitives;
2 |
3 | import dev.thatredox.chunkynative.common.export.texture.AbstractTextureLoader;
4 | import dev.thatredox.chunkynative.common.export.Packer;
5 | import dev.thatredox.chunkynative.common.export.ResourcePalette;
6 | import it.unimi.dsi.fastutil.ints.IntArrayList;
7 | import se.llbit.chunky.model.Tint;
8 | import se.llbit.math.primitive.TexturedTriangle;
9 |
10 | public class PackedTriangle implements Packer {
11 | public final float[] vectors = new float[18];
12 | public final int material;
13 | public final int flags;
14 |
15 | public PackedTriangle(TexturedTriangle triangle, AbstractTextureLoader texturePalette,
16 | ResourcePalette materialPalette) {
17 | this.vectors[0] = (float) triangle.e1.x;
18 | this.vectors[1] = (float) triangle.e1.y;
19 | this.vectors[2] = (float) triangle.e1.z;
20 | this.vectors[3] = (float) triangle.e2.x;
21 | this.vectors[4] = (float) triangle.e2.y;
22 | this.vectors[5] = (float) triangle.e2.z;
23 | this.vectors[6] = (float) triangle.o.x;
24 | this.vectors[7] = (float) triangle.o.y;
25 | this.vectors[8] = (float) triangle.o.z;
26 | this.vectors[9] = (float) triangle.n.x;
27 | this.vectors[10] = (float) triangle.n.y;
28 | this.vectors[11] = (float) triangle.n.z;
29 | this.vectors[12] = (float) triangle.t1u;
30 | this.vectors[13] = (float) triangle.t1v;
31 | this.vectors[14] = (float) triangle.t2u;
32 | this.vectors[15] = (float) triangle.t2v;
33 | this.vectors[16] = (float) triangle.t3u;
34 | this.vectors[17] = (float) triangle.t3v;
35 |
36 | int flags = 0;
37 | flags |= 0x01; // Lower 8 bits signifies it is a triangle
38 | if (triangle.doubleSided) {
39 | flags |= 1 << 8; // Next 1 bit signifies if it is double sided
40 | }
41 | this.flags = flags;
42 |
43 | this.material = materialPalette.put(new PackedMaterial(triangle.material, Tint.NONE, texturePalette));
44 | }
45 |
46 | /**
47 | * Pack this Triangle. This will be compressed into 20 ints:
48 | * 0: Flags bitfield:
49 | * Bottom 8 bits the primitive type (1 for triangle)
50 | * 9th bit represents if this triangle is double sided
51 | * 1: e1 x
52 | * 2: e1 y
53 | * 3: e1 z
54 | * 4: e2 x
55 | * 5: e2 y
56 | * 6: e2 z
57 | * 7: origin x
58 | * 8: origin y
59 | * 9: origin z
60 | * 10: normal x
61 | * 11: normal y
62 | * 12: normal z
63 | * 13: t1 u
64 | * 14: t1 v
65 | * 15: t2 u
66 | * 16: t2 v
67 | * 17: t3 u
68 | * 18: t3 v
69 | * 19: Material reference
70 | */
71 | @Override
72 | public IntArrayList pack() {
73 | IntArrayList out = new IntArrayList(20);
74 | out.add(flags);
75 | for (float i : vectors) out.add(Float.floatToIntBits(i));
76 | out.add(this.material);
77 | return out;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/common/export/texture/AbstractTextureLoader.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.common.export.texture;
2 |
3 | import it.unimi.dsi.fastutil.Hash;
4 | import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
5 | import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap;
6 | import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
7 | import se.llbit.chunky.resources.Texture;
8 |
9 | import java.util.Arrays;
10 |
11 | public abstract class AbstractTextureLoader {
12 | private final static int MAX_IDENTITIES_PER_RECORD = 3;
13 |
14 | protected final Object2ObjectOpenCustomHashMap recordMap;
15 | protected final Reference2ObjectOpenHashMap identityRecordMap;
16 | protected boolean locked = false;
17 |
18 | public AbstractTextureLoader() {
19 | recordMap = new Object2ObjectOpenCustomHashMap<>(new Hash.Strategy() {
20 | @Override
21 | public int hashCode(Texture o) {
22 | return Arrays.hashCode(o.getData());
23 | }
24 |
25 | @Override
26 | public boolean equals(Texture a, Texture b) {
27 | if (a == b) return true;
28 | if (a == null || b == null) return false;
29 | return Arrays.equals(a.getData(), b.getData()) && a.getWidth() == b.getWidth() && a.getHeight() == b.getHeight();
30 | }
31 | });
32 | identityRecordMap = new Reference2ObjectOpenHashMap<>();
33 | }
34 |
35 | /**
36 | * Get the texture record for a texture. This will either return a cached record or computed one.
37 | */
38 | public TextureRecord get(Texture texture) {
39 | if (texture == null) {
40 | throw new NullPointerException("Cannot load null texture.");
41 | }
42 |
43 | TextureRecord record;
44 | record = this.identityRecordMap.getOrDefault(texture, null);
45 | if (record != null) return record;
46 | record = this.recordMap.getOrDefault(texture, null);
47 | if (record != null) {
48 | if (record.identities < MAX_IDENTITIES_PER_RECORD) {
49 | this.identityRecordMap.put(texture, record);
50 | record.identities++;
51 | }
52 | return record;
53 | }
54 |
55 | if (this.locked) {
56 | throw new IllegalArgumentException("Attempted to put texture in locked texture loader.");
57 | }
58 |
59 | record = new TextureRecord();
60 | this.identityRecordMap.put(texture, record);
61 | this.recordMap.put(texture, record);
62 | return record;
63 | }
64 |
65 | /**
66 | * Finalize this texture loader and lock it. After this is called, all texture records are valid and can be resolved.
67 | */
68 | public void build() {
69 | if (this.locked) {
70 | throw new IllegalStateException("Attempted to build already built texture loader.");
71 | }
72 | this.locked = true;
73 | this.buildTextures(this.recordMap);
74 | }
75 |
76 | /**
77 | * Build the textures of this texture loader and make all the texture records valid and resolvable.
78 | */
79 | protected abstract void buildTextures(Object2ObjectMap textures);
80 |
81 | /**
82 | * Release this texture loader. The default implementation does nothing but texture loaders with native
83 | * resources can use this to free them.
84 | */
85 | public void release() { }
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/common/export/texture/TextureRecord.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.common.export.texture;
2 |
3 | public class TextureRecord {
4 | // Housekeeping variable used in AbstractTextureLoader
5 | int identities = 1;
6 |
7 | private long value;
8 | private boolean resolved = false;
9 |
10 | /**
11 | * This must only be called from the texture loader.
12 | * Set the value of this texture record to a 64 bit integer. This can only be called once.
13 | */
14 | public void set(long value) {
15 | if (resolved) {
16 | throw new IllegalStateException("Texture record already set. Cannot set value again.");
17 | }
18 | this.resolved = true;
19 | this.value = value;
20 | }
21 |
22 | /**
23 | * Get the value of this texture record. This must be called after the texture loader has been built.
24 | */
25 | public long get() {
26 | if (!resolved) {
27 | throw new IllegalStateException("Texture record has not been set. Cannot get the value.");
28 | }
29 | return this.value;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/common/state/SkyState.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.common.state;
2 |
3 | import dev.thatredox.chunkynative.util.Reflection;
4 | import se.llbit.chunky.renderer.scene.SimulatedSky;
5 | import se.llbit.chunky.renderer.scene.Sky;
6 | import se.llbit.chunky.renderer.scene.Sun;
7 | import se.llbit.chunky.resources.Texture;
8 | import se.llbit.math.Matrix3;
9 | import se.llbit.math.Vector3;
10 | import se.llbit.math.Vector4;
11 |
12 | import java.util.List;
13 | import java.util.Objects;
14 | import java.util.stream.Collectors;
15 |
16 | public class SkyState {
17 | private final Texture skymap;
18 | private final Texture[] skybox;
19 | private final String skymapFileName;
20 | private final String[] skyboxFileName;
21 | private final Matrix3 rotation;
22 | private final boolean mirrored;
23 | private final double skylightModifier;
24 | private final List gradient;
25 | private final Vector3 color;
26 | private final Sky.SkyMode mode;
27 | private final SimulatedSky simulatedSkyMode;
28 | private final double horizonOffset;
29 | private final double sunIntensity;
30 | private final double sunAzimuth;
31 | private final double sunAltitude;
32 | private final boolean drawSun;
33 | private final Vector3 sunColor;
34 |
35 | public SkyState(Sky sky, Sun sun) {
36 | skymap = Reflection.getFieldValue(sky, "skymap", Texture.class);
37 | skybox = Reflection.getFieldValue(sky, "skybox", Texture[].class).clone();
38 | skymapFileName = Reflection.getFieldValue(sky, "skymapFileName", String.class);
39 | skyboxFileName = Reflection.getFieldValue(sky, "skyboxFileName", String[].class).clone();
40 | mirrored = Reflection.getFieldValue(sky, "mirrored", Boolean.class);
41 | skylightModifier = Reflection.getFieldValue(sky, "skyLightModifier", Double.class);
42 | List> gradientList = Reflection.getFieldValue(sky, "gradient", List.class);
43 | gradient = gradientList.stream().map(i -> new Vector4((Vector4) i)).collect(Collectors.toList());
44 | color = new Vector3(Reflection.getFieldValue(sky, "color", Vector3.class));
45 | mode = Reflection.getFieldValue(sky, "mode", Sky.SkyMode.class);
46 | simulatedSkyMode = Reflection.getFieldValue(sky, "simulatedSkyMode", SimulatedSky.class);
47 | horizonOffset = Reflection.getFieldValue(sky, "horizonOffset", Double.class);
48 | sunIntensity = sun.getIntensity();
49 | sunAzimuth = sun.getAzimuth();
50 | sunAltitude = sun.getAltitude();
51 | drawSun = sun.drawTexture();
52 | sunColor = new Vector3(sun.getColor());
53 |
54 | rotation = new Matrix3();
55 | Matrix3 rot = Reflection.getFieldValue(sky, "rotation", Matrix3.class);
56 | Reflection.copyPublic(rot, rotation);
57 | }
58 |
59 | @Override
60 | public boolean equals(Object o) {
61 | if (this == o) return true;
62 | if (o == null || getClass() != o.getClass()) return false;
63 |
64 | SkyState skyState = (SkyState) o;
65 |
66 | if (!Reflection.equalsPublic(skyState.rotation, rotation)) return false;
67 | if (mirrored != skyState.mirrored) return false;
68 | if (Double.compare(skyState.skylightModifier, skylightModifier) != 0) return false;
69 | if (Double.compare(skyState.horizonOffset, horizonOffset) != 0) return false;
70 | if (Double.compare(skyState.sunIntensity, sunIntensity) != 0) return false;
71 | if (Double.compare(skyState.sunAzimuth, sunAzimuth) != 0) return false;
72 | if (Double.compare(skyState.sunAltitude, sunAltitude) != 0) return false;
73 | if (drawSun != skyState.drawSun) return false;
74 | if (!Objects.equals(skymap, skyState.skymap)) return false;
75 | if (!StateUtil.equals(skybox, skyState.skybox, Objects::equals)) return false;
76 | if (!Objects.equals(skymapFileName, skyState.skymapFileName)) return false;
77 | if (!StateUtil.equals(skyboxFileName, skyState.skyboxFileName, Objects::equals)) return false;
78 | if (!StateUtil.equals(gradient, skyState.gradient, StateUtil::equals)) return false;
79 | if (!StateUtil.equals(color, skyState.color)) return false;
80 | if (mode != skyState.mode) return false;
81 | if (!Objects.equals(simulatedSkyMode, skyState.simulatedSkyMode)) return false;
82 | if (!StateUtil.equals(sunColor, skyState.sunColor)) return false;
83 |
84 | return true;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/common/state/StateUtil.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.common.state;
2 |
3 | import dev.thatredox.chunkynative.util.EqualityChecker;
4 | import se.llbit.math.Vector3;
5 | import se.llbit.math.Vector4;
6 |
7 | import java.util.Iterator;
8 | import java.util.List;
9 |
10 | public class StateUtil {
11 | private StateUtil() {}
12 |
13 | public static boolean equals(Vector3 a, Vector3 b) {
14 | if (a == b) return true;
15 | if (a == null) return false;
16 | if (a.x != b.x) return false;
17 | if (a.y != b.y) return false;
18 | return a.z == b.z;
19 | }
20 |
21 | public static boolean equals(Vector4 a, Vector4 b) {
22 | if (a == b) return true;
23 | if (a == null) return false;
24 | if (a.x != b.x) return false;
25 | if (a.y != b.y) return false;
26 | if (a.z != b.z) return false;
27 | return a.w == b.w;
28 | }
29 |
30 | public static boolean equals(T[] a, T[] b, EqualityChecker checker) {
31 | if (a.length != b.length) return false;
32 | for (int i = 0; i < a.length; i++)
33 | if (!checker.equals(a[i], b[i])) return false;
34 | return true;
35 | }
36 |
37 | public static boolean equals(List a, List b, EqualityChecker checker) {
38 | if (a.size() != b.size()) return false;
39 | Iterator iterA = a.iterator();
40 | Iterator iterB = b.iterator();
41 | while (iterA.hasNext() && iterB.hasNext())
42 | if (!checker.equals(iterA.next(), iterB.next())) return false;
43 | return true;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/opencl/ChunkyCl.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.opencl;
2 |
3 | import dev.thatredox.chunkynative.opencl.renderer.ClSceneLoader;
4 | import dev.thatredox.chunkynative.opencl.renderer.RendererInstance;
5 | import dev.thatredox.chunkynative.opencl.tonemap.ImposterCombinationGpuPostProcessingFilter;
6 | import dev.thatredox.chunkynative.opencl.ui.ChunkyClTab;
7 | import se.llbit.chunky.Plugin;
8 | import se.llbit.chunky.main.Chunky;
9 | import se.llbit.chunky.main.ChunkyOptions;
10 | import se.llbit.chunky.model.BlockModel;
11 | import se.llbit.chunky.renderer.RenderController;
12 | import se.llbit.chunky.renderer.postprocessing.PostProcessingFilters;
13 | import se.llbit.chunky.ui.ChunkyFx;
14 | import se.llbit.chunky.ui.render.RenderControlsTab;
15 | import se.llbit.chunky.ui.render.RenderControlsTabTransformer;
16 | import se.llbit.log.Log;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 |
21 | /**
22 | * This plugin changes the Chunky path tracing renderer to a gpu based path tracer.
23 | */
24 | public class ChunkyCl implements Plugin {
25 | @Override public void attach(Chunky chunky) {
26 | // Check if we have block models
27 | try {
28 | Class> test = BlockModel.class;
29 | } catch (NoClassDefFoundError e) {
30 | Log.error("ChunkyCL requires Chunky 2.5.0. Could not load block models.", e);
31 | return;
32 | }
33 |
34 | // Initialize the renderer now for easier debugging
35 | try {
36 | RendererInstance.get();
37 | } catch (UnsatisfiedLinkError e) {
38 | Log.error("Failed to load ChunkyCL. Could not load OpenCL native library.", e);
39 | return;
40 | }
41 |
42 | ClSceneLoader sceneLoader = new ClSceneLoader();
43 | Chunky.addRenderer(new OpenClPathTracingRenderer(sceneLoader));
44 | Chunky.addPreviewRenderer(new OpenClPreviewRenderer(sceneLoader));
45 |
46 | RenderControlsTabTransformer prev = chunky.getRenderControlsTabTransformer();
47 | chunky.setRenderControlsTabTransformer(tabs -> {
48 | // First, call the previous transformer (this allows other plugins to work).
49 | List transformed = new ArrayList<>(prev.apply(tabs));
50 |
51 | // Get the scene
52 | RenderController controller = chunky.getRenderController();
53 |
54 | // Add the new tab
55 | transformed.add(new ChunkyClTab());
56 |
57 | return transformed;
58 | });
59 |
60 | addImposterFilter("GAMMA", ImposterCombinationGpuPostProcessingFilter.Filter.GAMMA);
61 | addImposterFilter("TONEMAP1", ImposterCombinationGpuPostProcessingFilter.Filter.TONEMAP1);
62 | addImposterFilter("TONEMAP2", ImposterCombinationGpuPostProcessingFilter.Filter.ACES);
63 | addImposterFilter("TONEMAP3", ImposterCombinationGpuPostProcessingFilter.Filter.HABLE);
64 | }
65 |
66 | private static void addImposterFilter(String id, ImposterCombinationGpuPostProcessingFilter.Filter f) {
67 | PostProcessingFilters.getPostProcessingFilterFromId(id).ifPresent(filter ->
68 | PostProcessingFilters.addPostProcessingFilter(new ImposterCombinationGpuPostProcessingFilter(
69 | filter, "post_processing_filter.cl", "filter", f, RendererInstance.get()
70 | )));
71 | }
72 |
73 | public static void main(String[] args) throws Exception {
74 | // Start Chunky normally with this plugin attached.
75 | Chunky.loadDefaultTextures();
76 | Chunky chunky = new Chunky(ChunkyOptions.getDefaults());
77 | new ChunkyCl().attach(chunky);
78 | ChunkyFx.startChunkyUI(chunky);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/opencl/OpenClPathTracingRenderer.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.opencl;
2 |
3 | import static org.jocl.CL.*;
4 |
5 | import dev.thatredox.chunkynative.opencl.renderer.ClSceneLoader;
6 | import dev.thatredox.chunkynative.opencl.renderer.RendererInstance;
7 | import dev.thatredox.chunkynative.opencl.renderer.scene.*;
8 | import dev.thatredox.chunkynative.opencl.util.ClIntBuffer;
9 | import dev.thatredox.chunkynative.opencl.util.ClMemory;
10 | import org.jocl.*;
11 |
12 | import se.llbit.chunky.main.Chunky;
13 | import se.llbit.chunky.renderer.*;
14 | import se.llbit.chunky.renderer.scene.Scene;
15 | import se.llbit.util.TaskTracker;
16 |
17 | import java.util.Arrays;
18 | import java.util.Random;
19 | import java.util.concurrent.ForkJoinTask;
20 | import java.util.concurrent.locks.ReentrantLock;
21 | import java.util.function.BooleanSupplier;
22 |
23 | public class OpenClPathTracingRenderer implements Renderer {
24 |
25 | private BooleanSupplier postRender = () -> true;
26 |
27 | private final ClSceneLoader sceneLoader;
28 |
29 | public OpenClPathTracingRenderer(ClSceneLoader sceneLoader) {
30 | this.sceneLoader = sceneLoader;
31 | }
32 |
33 | @Override
34 | public String getId() {
35 | return "ChunkyClRenderer";
36 | }
37 |
38 | @Override
39 | public String getName() {
40 | return "ChunkyClRenderer";
41 | }
42 |
43 | @Override
44 | public String getDescription() {
45 | return "ChunkyClRenderer";
46 | }
47 |
48 | @Override
49 | public void setPostRender(BooleanSupplier callback) {
50 | postRender = callback;
51 | }
52 |
53 | @Override
54 | public void render(DefaultRenderManager manager) throws InterruptedException {
55 | RendererInstance instance = RendererInstance.get();
56 | ReentrantLock renderLock = new ReentrantLock();
57 | cl_event[] renderEvent = new cl_event[1];
58 | Scene scene = manager.bufferedScene;
59 |
60 | double[] sampleBuffer = scene.getSampleBuffer();
61 | float[] passBuffer = new float[sampleBuffer.length];
62 |
63 | // Ensure the scene is loaded
64 | sceneLoader.ensureLoad(manager.bufferedScene);
65 |
66 | // Load the kernel
67 | cl_kernel kernel = clCreateKernel(instance.program, "render", null);
68 |
69 | // Generate the camera
70 | ClCamera camera = new ClCamera(scene);
71 | ClMemory buffer = new ClMemory(clCreateBuffer(instance.context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR,
72 | (long) Sizeof.cl_float * passBuffer.length, Pointer.to(passBuffer), null));
73 | ClMemory randomSeed = new ClMemory(
74 | clCreateBuffer(instance.context, CL_MEM_READ_ONLY, Sizeof.cl_int, null, null));
75 | ClMemory bufferSpp = new ClMemory(
76 | clCreateBuffer(instance.context, CL_MEM_READ_ONLY, Sizeof.cl_int, null, null));
77 | ClIntBuffer clWidth = new ClIntBuffer(scene.width);
78 | ClIntBuffer clHeight = new ClIntBuffer(scene.height);
79 |
80 | try (ClCamera ignored1 = camera;
81 | ClMemory ignored2 = buffer;
82 | ClMemory ignored3 = randomSeed;
83 | ClMemory ignored4 = bufferSpp;
84 | ClIntBuffer ignored5 = clWidth;
85 | ClIntBuffer ignored6 = clHeight) {
86 |
87 | // Generate initial camera rays
88 | camera.generate(renderLock, true);
89 |
90 | int bufferSppReal = 0;
91 | int logicalSpp = scene.spp;
92 | final int[] sceneSpp = {scene.spp};
93 | long lastCallback = 0;
94 |
95 | Random rand = new Random(0);
96 |
97 | ForkJoinTask> cameraGenTask = Chunky.getCommonThreads().submit(() -> 0);
98 | ForkJoinTask> bufferMergeTask = Chunky.getCommonThreads().submit(() -> 0);
99 |
100 | // This is the main rendering loop. This deals with dispatching rendering tasks. The majority of time is spent
101 | // waiting for the OpenCL renderer to complete.
102 | while (logicalSpp < scene.getTargetSpp()) {
103 | renderLock.lock();
104 | renderEvent[0] = new cl_event();
105 |
106 | clEnqueueWriteBuffer(instance.commandQueue, randomSeed.get(), CL_TRUE, 0, Sizeof.cl_int,
107 | Pointer.to(new int[]{rand.nextInt()}), 0, null, null);
108 | clEnqueueWriteBuffer(instance.commandQueue, bufferSpp.get(), CL_TRUE, 0, Sizeof.cl_int,
109 | Pointer.to(new int[]{bufferSppReal}), 0, null, null);
110 |
111 | int argIndex = 0;
112 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(camera.projectorType.get()));
113 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(camera.cameraSettings.get()));
114 |
115 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getOctreeDepth().get()));
116 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getOctreeData().get()));
117 |
118 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getBlockPalette().get()));
119 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getQuadPalette().get()));
120 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getAabbPalette().get()));
121 |
122 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getWorldBvh().get()));
123 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getActorBvh().get()));
124 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getTrigPalette().get()));
125 |
126 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getTexturePalette().getAtlas()));
127 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getMaterialPalette().get()));
128 |
129 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getSky().skyTexture.get()));
130 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getSky().skyIntensity.get()));
131 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getSun().get()));
132 |
133 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(randomSeed.get()));
134 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(bufferSpp.get()));
135 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(clWidth.get()));
136 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(clHeight.get()));
137 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(buffer.get()));
138 | clEnqueueNDRangeKernel(instance.commandQueue, kernel, 1, null,
139 | new long[]{passBuffer.length / 3}, null, 0, null,
140 | renderEvent[0]);
141 | clWaitForEvents(1, renderEvent);
142 | renderLock.unlock();
143 | bufferSppReal += 1;
144 | scene.spp += 1;
145 |
146 | if (camera.needGenerate && cameraGenTask.isDone()) {
147 | cameraGenTask = Chunky.getCommonThreads().submit(() -> camera.generate(renderLock, true));
148 | }
149 |
150 | boolean saveEvent = isSaveEvent(manager.getSnapshotControl(), scene, logicalSpp + bufferSppReal);
151 | if (bufferMergeTask.isDone() || saveEvent) {
152 | if (!scene.shouldFinalizeBuffer() && !saveEvent) {
153 | long time = System.currentTimeMillis();
154 | if (time - lastCallback > 100 && !manager.shouldFinalize()) {
155 | lastCallback = time;
156 | if (postRender.getAsBoolean()) break;
157 | }
158 | if (bufferSppReal < 1024)
159 | continue;
160 | }
161 |
162 | bufferMergeTask.join();
163 | if (postRender.getAsBoolean()) break;
164 | clEnqueueReadBuffer(instance.commandQueue, buffer.get(), CL_TRUE, 0,
165 | (long) Sizeof.cl_float * passBuffer.length, Pointer.to(passBuffer),
166 | 0, null, null);
167 | int sampSpp = sceneSpp[0];
168 | int passSpp = bufferSppReal;
169 | double sinv = 1.0 / (sampSpp + passSpp);
170 | bufferSppReal = 0;
171 |
172 | bufferMergeTask = Chunky.getCommonThreads().submit(() -> {
173 | Arrays.parallelSetAll(sampleBuffer, i -> (sampleBuffer[i] * sampSpp + passBuffer[i] * passSpp) * sinv);
174 | sceneSpp[0] += passSpp;
175 | scene.postProcessFrame(TaskTracker.Task.NONE);
176 | manager.redrawScreen();
177 | });
178 | logicalSpp += passSpp;
179 | if (saveEvent) {
180 | bufferMergeTask.join();
181 | if (postRender.getAsBoolean()) break;
182 | }
183 | }
184 | }
185 |
186 | cameraGenTask.join();
187 | bufferMergeTask.join();
188 | }
189 |
190 | clReleaseKernel(kernel);
191 | }
192 |
193 | private boolean isSaveEvent(SnapshotControl control, Scene scene, int spp) {
194 | return control.saveSnapshot(scene, spp) || control.saveRenderDump(scene, spp);
195 | }
196 |
197 | @Override
198 | public boolean autoPostProcess() {
199 | return false;
200 | }
201 |
202 | @Override
203 | public void sceneReset(DefaultRenderManager manager, ResetReason reason, int resetCount) {
204 | sceneLoader.load(resetCount, reason, manager.bufferedScene);
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/opencl/OpenClPreviewRenderer.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.opencl;
2 |
3 | import dev.thatredox.chunkynative.opencl.renderer.ClSceneLoader;
4 | import dev.thatredox.chunkynative.opencl.renderer.RendererInstance;
5 | import dev.thatredox.chunkynative.opencl.renderer.scene.*;
6 | import dev.thatredox.chunkynative.opencl.util.ClIntBuffer;
7 | import dev.thatredox.chunkynative.opencl.util.ClMemory;
8 | import org.jocl.*;
9 | import se.llbit.chunky.renderer.DefaultRenderManager;
10 | import se.llbit.chunky.renderer.Renderer;
11 | import se.llbit.chunky.renderer.ResetReason;
12 | import se.llbit.chunky.renderer.scene.Scene;
13 | import java.util.function.BooleanSupplier;
14 |
15 | import static org.jocl.CL.*;
16 |
17 | public class OpenClPreviewRenderer implements Renderer {
18 | private BooleanSupplier postRender = () -> true;
19 |
20 | private final ClSceneLoader sceneLoader;
21 |
22 | public OpenClPreviewRenderer(ClSceneLoader sceneLoader) {
23 | this.sceneLoader = sceneLoader;
24 | }
25 |
26 | @Override
27 | public String getId() {
28 | return "ChunkyClPreviewRenderer";
29 | }
30 |
31 | @Override
32 | public String getName() {
33 | return "Chunky CL Preview Renderer";
34 | }
35 |
36 | @Override
37 | public String getDescription() {
38 | return "A work in progress OpenCL renderer.";
39 | }
40 |
41 | @Override
42 | public void setPostRender(BooleanSupplier callback) {
43 | postRender = callback;
44 | }
45 |
46 | @Override
47 | public void render(DefaultRenderManager manager) throws InterruptedException {
48 | cl_event[] renderEvent = new cl_event[1];
49 | Scene scene = manager.bufferedScene;
50 |
51 | RendererInstance instance = RendererInstance.get();
52 | int[] imageData = scene.getBackBuffer().data;
53 |
54 | // Ensure the scene is loaded
55 | sceneLoader.ensureLoad(manager.bufferedScene);
56 |
57 | // Load the kernel
58 | cl_kernel kernel = clCreateKernel(instance.program, "preview", null);
59 |
60 | ClCamera camera = new ClCamera(scene);
61 | ClMemory buffer = new ClMemory(clCreateBuffer(instance.context, CL_MEM_WRITE_ONLY,
62 | (long) Sizeof.cl_int * imageData.length, null, null));
63 | ClIntBuffer clWidth = new ClIntBuffer(scene.width);
64 | ClIntBuffer clHeight = new ClIntBuffer(scene.height);
65 |
66 | try (ClCamera ignored1 = camera;
67 | ClMemory ignored2 = buffer;
68 | ClIntBuffer ignored3 = clWidth;
69 | ClIntBuffer ignored4 = clHeight) {
70 |
71 | // Generate the camera rays
72 | camera.generate(null, false);
73 |
74 | renderEvent[0] = new cl_event();
75 |
76 | int argIndex = 0;
77 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(camera.projectorType.get()));
78 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(camera.cameraSettings.get()));
79 |
80 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getOctreeDepth().get()));
81 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getOctreeData().get()));
82 |
83 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getBlockPalette().get()));
84 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getQuadPalette().get()));
85 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getAabbPalette().get()));
86 |
87 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getWorldBvh().get()));
88 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getActorBvh().get()));
89 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getTrigPalette().get()));
90 |
91 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getTexturePalette().getAtlas()));
92 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getMaterialPalette().get()));
93 |
94 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getSky().skyTexture.get()));
95 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getSky().skyIntensity.get()));
96 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(sceneLoader.getSun().get()));
97 |
98 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(clWidth.get()));
99 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(clHeight.get()));
100 | clSetKernelArg(kernel, argIndex++, Sizeof.cl_mem, Pointer.to(buffer.get()));
101 | clEnqueueNDRangeKernel(instance.commandQueue, kernel, 1, null,
102 | new long[]{imageData.length}, null, 0, null,
103 | renderEvent[0]);
104 |
105 | clEnqueueReadBuffer(instance.commandQueue, buffer.get(), CL_TRUE, 0,
106 | (long) Sizeof.cl_int * imageData.length, Pointer.to(imageData),
107 | 1, renderEvent, null);
108 |
109 | manager.redrawScreen();
110 | postRender.getAsBoolean();
111 | }
112 |
113 | clReleaseKernel(kernel);
114 | clReleaseEvent(renderEvent[0]);
115 | }
116 |
117 | @Override
118 | public boolean autoPostProcess() {
119 | return false;
120 | }
121 |
122 | @Override
123 | public void sceneReset(DefaultRenderManager manager, ResetReason reason, int resetCount) {
124 | sceneLoader.load(resetCount, reason, manager.bufferedScene);
125 | }
126 | }
127 |
128 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/opencl/renderer/ClSceneLoader.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.opencl.renderer;
2 |
3 | import dev.thatredox.chunkynative.common.export.AbstractSceneLoader;
4 | import dev.thatredox.chunkynative.common.export.ResourcePalette;
5 | import dev.thatredox.chunkynative.common.export.models.PackedAabbModel;
6 | import dev.thatredox.chunkynative.common.export.models.PackedQuadModel;
7 | import dev.thatredox.chunkynative.common.export.models.PackedTriangleModel;
8 | import dev.thatredox.chunkynative.common.export.primitives.PackedBlock;
9 | import dev.thatredox.chunkynative.common.export.primitives.PackedMaterial;
10 | import dev.thatredox.chunkynative.common.export.primitives.PackedSun;
11 | import dev.thatredox.chunkynative.common.export.texture.AbstractTextureLoader;
12 | import dev.thatredox.chunkynative.common.state.SkyState;
13 | import dev.thatredox.chunkynative.opencl.renderer.export.ClPackedResourcePalette;
14 | import dev.thatredox.chunkynative.opencl.renderer.export.ClTextureLoader;
15 | import dev.thatredox.chunkynative.opencl.renderer.scene.ClSky;
16 | import dev.thatredox.chunkynative.opencl.util.ClIntBuffer;
17 | import dev.thatredox.chunkynative.util.FunctionCache;
18 | import se.llbit.chunky.renderer.ResetReason;
19 | import se.llbit.chunky.renderer.scene.Scene;
20 |
21 | import java.util.Arrays;
22 |
23 | public class ClSceneLoader extends AbstractSceneLoader {
24 | protected FunctionCache clWorldBvh = new FunctionCache<>(ClIntBuffer::new, ClIntBuffer::close, null);
25 | protected FunctionCache clActorBvh = new FunctionCache<>(ClIntBuffer::new, ClIntBuffer::close, null);
26 | protected FunctionCache clPackedSun = new FunctionCache<>(ClIntBuffer::new, ClIntBuffer::close, null);
27 | protected ClSky clSky = null;
28 | protected SkyState skyState = null;
29 |
30 | protected ClIntBuffer octreeData = null;
31 | protected ClIntBuffer octreeDepth = null;
32 |
33 | @Override
34 | public boolean ensureLoad(Scene scene) {
35 | return this.ensureLoad(scene, clSky == null);
36 | }
37 |
38 | @Override
39 | public boolean load(int modCount, ResetReason resetReason, Scene scene) {
40 | if (this.modCount != modCount) {
41 | SkyState newSky = new SkyState(scene.sky(), scene.sun());
42 | if (!newSky.equals(skyState)) {
43 | if (clSky != null) clSky.close();
44 | clSky = new ClSky(scene);
45 | skyState = newSky;
46 | }
47 | }
48 | return super.load(modCount, resetReason, scene);
49 | }
50 |
51 | @Override
52 | protected boolean loadOctree(int[] octree, int depth, int[] blockMapping, ResourcePalette blockPalette) {
53 | if (octreeData != null) octreeData.close();
54 | if (octreeDepth != null) octreeDepth.close();
55 |
56 | int[] mappedOctree = Arrays.stream(octree)
57 | .map(i -> i > 0 || -i >= blockMapping.length ? i : -blockMapping[-i])
58 | .toArray();
59 | octreeData = new ClIntBuffer(mappedOctree);
60 | octreeDepth = new ClIntBuffer(depth);
61 |
62 | return true;
63 | }
64 |
65 | @Override
66 | protected AbstractTextureLoader createTextureLoader() {
67 | return new ClTextureLoader();
68 | }
69 |
70 | @Override
71 | protected ResourcePalette createBlockPalette() {
72 | return new ClPackedResourcePalette<>();
73 | }
74 |
75 | @Override
76 | protected ResourcePalette createMaterialPalette() {
77 | return new ClPackedResourcePalette<>();
78 | }
79 |
80 | @Override
81 | protected ResourcePalette createAabbModelPalette() {
82 | return new ClPackedResourcePalette<>();
83 | }
84 |
85 | @Override
86 | protected ResourcePalette createQuadModelPalette() {
87 | return new ClPackedResourcePalette<>();
88 | }
89 |
90 | @Override
91 | protected ResourcePalette createTriangleModelPalette() {
92 | return new ClPackedResourcePalette<>();
93 | }
94 |
95 | public ClIntBuffer getOctreeData() {
96 | assert octreeData != null;
97 | return octreeData;
98 | }
99 |
100 | public ClIntBuffer getOctreeDepth() {
101 | assert octreeDepth != null;
102 | return octreeDepth;
103 | }
104 |
105 | public ClTextureLoader getTexturePalette() {
106 | assert texturePalette instanceof ClTextureLoader;
107 | return (ClTextureLoader) texturePalette;
108 | }
109 |
110 | public ClPackedResourcePalette getBlockPalette() {
111 | assert blockPalette instanceof ClPackedResourcePalette;
112 | return (ClPackedResourcePalette) blockPalette;
113 | }
114 |
115 | public ClPackedResourcePalette getMaterialPalette() {
116 | assert materialPalette.palette instanceof ClPackedResourcePalette;
117 | return (ClPackedResourcePalette) materialPalette.palette;
118 | }
119 |
120 | public ClPackedResourcePalette getAabbPalette() {
121 | assert aabbPalette instanceof ClPackedResourcePalette;
122 | return (ClPackedResourcePalette) aabbPalette;
123 | }
124 |
125 | public ClPackedResourcePalette getQuadPalette() {
126 | assert quadPalette instanceof ClPackedResourcePalette;
127 | return (ClPackedResourcePalette) quadPalette;
128 | }
129 |
130 | public ClPackedResourcePalette getTrigPalette() {
131 | assert trigPalette instanceof ClPackedResourcePalette;
132 | return (ClPackedResourcePalette) trigPalette;
133 | }
134 |
135 | public ClIntBuffer getWorldBvh() {
136 | return clWorldBvh.apply(this.worldBvh);
137 | }
138 |
139 | public ClIntBuffer getActorBvh() {
140 | return clActorBvh.apply(this.actorBvh);
141 | }
142 |
143 | public ClSky getSky() {
144 | assert clSky != null;
145 | return clSky;
146 | }
147 |
148 | public ClIntBuffer getSun() {
149 | return clPackedSun.apply(packedSun);
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/opencl/renderer/KernelLoader.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.opencl.renderer;
2 |
3 | import static org.jocl.CL.*;
4 | import org.jocl.*;
5 |
6 | import se.llbit.log.Log;
7 |
8 | import java.io.*;
9 | import java.util.*;
10 |
11 | public class KernelLoader {
12 | private KernelLoader() {}
13 |
14 | /**
15 | * Load the program in the jar resources.
16 | */
17 | public static cl_program loadProgram(String base, String kernel_name, cl_context context, cl_device_id[] devices) {
18 | // Load kernel
19 | String kernel = readResourceFile(base + "/" + kernel_name);
20 | cl_program renderKernel = clCreateProgramWithSource(context, 1, new String[] { kernel }, null, null);
21 |
22 | // Search for include headers
23 | HashMap headerFiles = new HashMap<>();
24 |
25 | // Fake `opencl.h` header
26 | headerFiles.put("../opencl.h", clCreateProgramWithSource(context, 1, new String[] {""}, null, null));
27 |
28 | // Load headers
29 | readHeaders(kernel, headerFiles);
30 |
31 | boolean newHeaders = true;
32 | while (newHeaders) {
33 | newHeaders = false;
34 | HashMap newHeaderFiles = new HashMap<>();
35 | for (Map.Entry header : headerFiles.entrySet()) {
36 | if (header.getValue() == null && header.getKey().endsWith(".h")) {
37 | String headerFile = readResourceFile(base + "/" + header.getKey());
38 | header.setValue(clCreateProgramWithSource(context, 1, new String[] {headerFile}, null, null));
39 | readHeaders(headerFile, newHeaderFiles);
40 | }
41 | }
42 |
43 | for (String header : newHeaderFiles.keySet()) {
44 | newHeaders |= headerFiles.putIfAbsent(header, null) == null;
45 | }
46 | }
47 |
48 | String[] includeNames = headerFiles.keySet().toArray(new String[0]);
49 | cl_program[] includePrograms = new cl_program[includeNames.length];
50 | Arrays.setAll(includePrograms, i -> headerFiles.get(includeNames[i]));
51 |
52 | int code = clCompileProgram(renderKernel, devices.length, devices, "-cl-std=CL1.2 -Werror",
53 | includePrograms.length, includePrograms, includeNames, null, null);
54 | if (code != CL_SUCCESS) {
55 | throw new RuntimeException("Program build failed with error code: " + code);
56 | }
57 |
58 | return clLinkProgram(context, devices.length, devices, "", 1,
59 | new cl_program[] { renderKernel }, null, null, null);
60 | }
61 |
62 | protected static void readHeaders(String kernel, HashMap headerFiles) {
63 | Arrays.stream(kernel.split("\\n")).filter(line -> line.startsWith("#include")).forEach(line -> {
64 | String stripped = line.substring("#include".length()).trim();
65 | String header = stripped.substring(1, stripped.length() - 1);
66 | headerFiles.putIfAbsent(header, null);
67 | });
68 | }
69 |
70 | protected static String readResourceFile(String file) {
71 | InputStream fileStream = KernelLoader.class.getClassLoader().getResourceAsStream(file);
72 | if (fileStream == null) {
73 | Log.error(String.format("Error loading ChunkyCl, file \"%s\" does not exist.", file));
74 | throw new IllegalStateException(String.format("File \"%s\" does not exist.", file));
75 | }
76 | Scanner s = new Scanner(fileStream).useDelimiter("\\A");
77 | return s.hasNext() ? s.next() : "";
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/opencl/renderer/RendererInstance.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.opencl.renderer;
2 |
3 | import static org.jocl.CL.*;
4 | import org.jocl.*;
5 |
6 | import se.llbit.chunky.PersistentSettings;
7 | import se.llbit.log.Log;
8 |
9 | import java.util.ArrayList;
10 | import java.util.Arrays;
11 |
12 | public class RendererInstance {
13 | public final cl_device_id[] devices;
14 | public final int[] version;
15 | public final cl_device_id device;
16 |
17 | public final cl_program program;
18 | public final cl_context context;
19 | public final cl_command_queue commandQueue;
20 |
21 | private static RendererInstance instance = null;
22 |
23 | public static RendererInstance get() {
24 | if (instance == null) {
25 | instance = new RendererInstance();
26 | }
27 | return instance;
28 | }
29 |
30 | @SuppressWarnings("deprecation")
31 | private RendererInstance() {
32 | final long deviceType = CL_DEVICE_TYPE_ALL;
33 | final int deviceIndex = PersistentSettings.settings.getInt("clDevice", 0);
34 |
35 | // Enable exceptions
36 | CL.setExceptionsEnabled(true);
37 |
38 | // Obtain the number of platforms
39 | int[] numPlatformsArray = new int[1];
40 | clGetPlatformIDs(0, null, numPlatformsArray);
41 | int numPlatforms = numPlatformsArray[0];
42 |
43 | // Obtain all platform IDs
44 | cl_platform_id[] platforms = new cl_platform_id[numPlatforms];
45 | clGetPlatformIDs(platforms.length, platforms, null);
46 |
47 | // Get list of all devices
48 | ArrayList devices = new ArrayList<>();
49 |
50 | for (cl_platform_id platform : platforms) {
51 | // Obtain the number of devices for the platform
52 | try {
53 | int[] numDevicesArray = new int[1];
54 | clGetDeviceIDs(platform, deviceType, 0, null, numDevicesArray);
55 | int numDevices = numDevicesArray[0];
56 |
57 | // Obtain a device ID
58 | cl_device_id[] platformDevices = new cl_device_id[numDevices];
59 | clGetDeviceIDs(platform, deviceType, numDevices, platformDevices, null);
60 | devices.addAll(Arrays.asList(platformDevices));
61 | } catch (CLException e) {
62 | Log.info("Error obtaining device", e);
63 | }
64 | }
65 |
66 | // Print out all connected devices
67 | this.devices = devices.toArray(new cl_device_id[0]);
68 | System.out.println("OpenCL Devices:");
69 | for (int i = 0; i < devices.size(); i++) {
70 | System.out.println(" [" + i + "] " + getString(devices.get(i), CL_DEVICE_NAME));
71 | }
72 |
73 | // Print out selected device
74 | device = devices.get(deviceIndex);
75 | System.out.println("\nUsing: " + getString(device, CL_DEVICE_NAME));
76 |
77 | // Initialize the context properties
78 | cl_context_properties contextProperties = new cl_context_properties();
79 |
80 | // Create a context for the selected device
81 | context = clCreateContext( contextProperties, 1, new cl_device_id[]{device},
82 | null, null, null);
83 |
84 | // Create a command-queue for the selected device
85 | cl_queue_properties properties = new cl_queue_properties();
86 |
87 | // Get OpenCL version
88 | this.version = new int[2];
89 | String versionString = getString(device, CL_DEVICE_VERSION);
90 | this.version[0] = Integer.parseInt(versionString.substring(7, 8));
91 | this.version[1] = Integer.parseInt(versionString.substring(9, 10));
92 | System.out.println(" " + versionString);
93 |
94 | // Create command queue with correct version
95 | if (this.version[0] >= 2) {
96 | commandQueue = clCreateCommandQueueWithProperties(
97 | context, device, properties, null);
98 | } else {
99 | commandQueue = clCreateCommandQueue(
100 | context, device, 0, null);
101 | }
102 |
103 | // Check if version is behind
104 | if (this.version[0] <= 1 && this.version[1] < 2) {
105 | Log.error("OpenCL 1.2+ required.");
106 | }
107 |
108 | // Build the program
109 | program = KernelLoader.loadProgram("kernel", "rayTracer.cl", context, new cl_device_id[] { device });
110 | }
111 |
112 | /** Get a string from OpenCL
113 | * Based on code from https://github.com/gpu/JOCLSamples/
114 | * List of available parameter names: https://www.khronos.org/registry/OpenCL/sdk/1.2/docs/man/xhtml/clGetDeviceInfo.html
115 | *
116 | * @param device Device to query
117 | * @param paramName Parameter to query
118 | */
119 | public static String getString(cl_device_id device, int paramName)
120 | {
121 | // Obtain the length of the string that will be queried
122 | long[] size = new long[1];
123 | clGetDeviceInfo(device, paramName, 0, null, size);
124 |
125 | // Create a buffer of the appropriate size and fill it with the info
126 | byte[] buffer = new byte[(int)size[0]];
127 | clGetDeviceInfo(device, paramName, buffer.length, Pointer.to(buffer), null);
128 |
129 | // Create a string from the buffer (excluding the trailing \0 byte)
130 | return new String(buffer, 0, buffer.length-1);
131 | }
132 |
133 | /** Get an integer(array) from OpenCL
134 | * Based on code from https://github.com/gpu/JOCLSamples/
135 | * List of available parameter names: https://www.khronos.org/registry/OpenCL/sdk/1.2/docs/man/xhtml/clGetDeviceInfo.html
136 | *
137 | * @param device Device to query
138 | * @param paramName Parameter to query
139 | * @param numValues Number of values to query
140 | */
141 | public static int[] getInts(cl_device_id device, int paramName, int numValues) {
142 | int[] values = new int[numValues];
143 | clGetDeviceInfo(device, paramName, (long) Sizeof.cl_int * numValues, Pointer.to(values), null);
144 | return values;
145 | }
146 |
147 | /** Get a long(array) from OpenCL
148 | * Based on code from https://github.com/gpu/JOCLSamples/
149 | * List of available parameter names: https://www.khronos.org/registry/OpenCL/sdk/1.2/docs/man/xhtml/clGetDeviceInfo.html
150 | *
151 | * @param device Device to query
152 | * @param paramName Parameter to query
153 | * @param numValues Number of values to query
154 | */
155 | public static long[] getLongs(cl_device_id device, int paramName, int numValues) {
156 | long[] values = new long[numValues];
157 | clGetDeviceInfo(device, paramName, (long) Sizeof.cl_long * numValues, Pointer.to(values), null);
158 | return values;
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/opencl/renderer/export/ClPackedResourcePalette.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.opencl.renderer.export;
2 |
3 | import dev.thatredox.chunkynative.common.export.Packer;
4 | import dev.thatredox.chunkynative.common.export.ResourcePalette;
5 | import dev.thatredox.chunkynative.opencl.util.ClIntBuffer;
6 | import it.unimi.dsi.fastutil.ints.IntArrayList;
7 | import org.jocl.cl_mem;
8 |
9 | public class ClPackedResourcePalette implements ResourcePalette, AutoCloseable {
10 | protected ClIntBuffer buffer = null;
11 | protected IntArrayList palette = new IntArrayList();
12 |
13 | @Override
14 | public int put(T resource) {
15 | if (buffer != null) throw new IllegalStateException("Attempted to modify a locked palette.");
16 | int ptr = palette.size();
17 | palette.addAll(resource.pack());
18 | return ptr;
19 | }
20 |
21 | public ClIntBuffer build() {
22 | if (buffer == null) {
23 | buffer = new ClIntBuffer(palette);
24 | }
25 | return buffer;
26 | }
27 |
28 | public cl_mem get() {
29 | return build().get();
30 | }
31 |
32 | @Override
33 | public void close() {
34 | buffer.close();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/opencl/renderer/export/ClTextureLoader.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.opencl.renderer.export;
2 |
3 | import dev.thatredox.chunkynative.opencl.renderer.RendererInstance;
4 | import dev.thatredox.chunkynative.opencl.util.ClMemory;
5 | import org.jocl.*;
6 |
7 | import dev.thatredox.chunkynative.common.export.texture.AbstractTextureLoader;
8 | import dev.thatredox.chunkynative.common.export.texture.TextureRecord;
9 | import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
10 | import se.llbit.chunky.resources.Texture;
11 |
12 | import java.util.ArrayList;
13 | import java.util.Arrays;
14 | import java.util.List;
15 | import java.util.stream.Collectors;
16 |
17 | import static org.jocl.CL.*;
18 |
19 | public class ClTextureLoader extends AbstractTextureLoader implements AutoCloseable {
20 | private ClMemory texture;
21 |
22 | public cl_mem getAtlas() {
23 | return texture.get();
24 | }
25 |
26 | @Override
27 | public void close() {
28 | texture.close();
29 | }
30 |
31 | @Override
32 | protected void buildTextures(Object2ObjectMap textures) {
33 | List texs = textures.entrySet().stream()
34 | .map(entry -> new AtlasTexture(entry.getKey(), entry.getValue()))
35 | .sorted().collect(Collectors.toList());
36 |
37 | ArrayList layers = new ArrayList<>();
38 | layers.add(new boolean[256][256]);
39 | for (AtlasTexture tex : texs) {
40 | if (!insertTex(layers, tex)) {
41 | layers.add(new boolean[256][256]);
42 | insertTex(layers, tex);
43 | }
44 | }
45 |
46 | cl_image_format fmt = new cl_image_format();
47 | fmt.image_channel_order = CL_RGBA;
48 | fmt.image_channel_data_type = CL_UNORM_INT8;
49 |
50 | cl_image_desc desc = new cl_image_desc();
51 | desc.image_width = 8192;
52 | desc.image_height = 8192;
53 | desc.image_array_size = layers.size();
54 | desc.image_type = CL_MEM_OBJECT_IMAGE2D_ARRAY;
55 |
56 | RendererInstance instance = RendererInstance.get();
57 | texture = new ClMemory(
58 | clCreateImage(instance.context, CL_MEM_READ_ONLY, fmt, desc, null, null));
59 |
60 | for (AtlasTexture tex : texs) {
61 | clEnqueueWriteImage(instance.commandQueue, texture.get(), CL_TRUE,
62 | new long[] {tex.getX()* 16L, tex.getY()* 16L, tex.getD()},
63 | new long[] {tex.getWidth(), tex.getHeight(), 1},
64 | 0, 0, Pointer.to(tex.getTexture()),
65 | 0, null, null
66 | );
67 | }
68 |
69 | texs.forEach(AtlasTexture::commit);
70 | }
71 |
72 | private static boolean insertTex(ArrayList layers, AtlasTexture tex) {
73 | int l = 0;
74 | for (boolean[][] layer : layers) {
75 | for (int x = 0; x < 256; x++) {
76 | for (int y = 0; y < 256; y++) {
77 | if (insertAt(x, y, tex.getWidth()/16, tex.getHeight()/16, layer)) {
78 | tex.setLocation(x, y, l);
79 | return true;
80 | }
81 | }
82 | }
83 | l++;
84 | }
85 | return false;
86 | }
87 |
88 | private static boolean insertAt(int x, int y, int width, int height, boolean[][] layer) {
89 | if (y + height > layer.length || x + width > layer[0].length) {
90 | return false;
91 | }
92 |
93 | if (y < 0 || x < 0) {
94 | return false;
95 | }
96 |
97 | for (int line = y; line < y + height; line++) {
98 | for (int pixel = x; pixel < x + width; pixel++) {
99 | if (layer[line][pixel]) {
100 | return false;
101 | }
102 | }
103 | }
104 |
105 | for (int line = y; line < y + height; line++) {
106 | for (int pixel = x; pixel < x + width; pixel++) {
107 | layer[line][pixel] = true;
108 | }
109 | }
110 |
111 | return true;
112 | }
113 |
114 | protected static class AtlasTexture implements Comparable {
115 | public final Texture texture;
116 | public final TextureRecord record;
117 | public final int size;
118 | public int location = 0xFFFFFFFF;
119 |
120 | protected AtlasTexture(Texture tex, TextureRecord record) {
121 | this.texture = tex;
122 | this.record = record;
123 | this.size = (tex.getWidth() << 16) | tex.getHeight();
124 | }
125 |
126 | public void commit() {
127 | this.record.set(((long) size << 32) | location);
128 | }
129 |
130 | public void setLocation(int x, int y, int d) {
131 | this.location = (x << 22) | (y << 13) | d;
132 | }
133 |
134 | public int getWidth() {
135 | return (size >>> 16) & 0xFFFF;
136 | }
137 |
138 | public int getHeight() {
139 | return size & 0xFFFF;
140 | }
141 |
142 | public int getX() {
143 | return (location >>> 22) & 0x1FF;
144 | }
145 |
146 | public int getY() {
147 | return (location >>> 13) & 0x1FF;
148 | }
149 |
150 | public int getD() {
151 | return location & 0x1FFF;
152 | }
153 |
154 | public byte[] getTexture() {
155 | byte[] out = new byte[getHeight() * getWidth() * 4];
156 | int index = 0;
157 | for (int y = 0; y < getHeight(); y++) {
158 | for (int x = 0; x < getWidth(); x++) {
159 | float[] rgba = texture.getColor(x, y);
160 | out[index] = (byte) (rgba[0] * 255.0);
161 | out[index+1] = (byte) (rgba[1] * 255.0);
162 | out[index+2] = (byte) (rgba[2] * 255.0);
163 | out[index+3] = (byte) (rgba[3] * 255.0);
164 | index += 4;
165 | }
166 | }
167 | return out;
168 | }
169 |
170 | @Override
171 | public int compareTo(AtlasTexture o) {
172 | return o.size - this.size;
173 | }
174 |
175 | @Override
176 | public int hashCode() {
177 | return Arrays.hashCode(texture.getData());
178 | }
179 |
180 | @Override
181 | public boolean equals(Object o) {
182 | if (o == null) return false;
183 | if (!(o instanceof AtlasTexture)) return false;
184 | AtlasTexture other = (AtlasTexture) o;
185 | return this.size == other.size &&
186 | Arrays.equals(this.texture.getData(), other.texture.getData());
187 | }
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/opencl/renderer/scene/ClCamera.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.opencl.renderer.scene;
2 |
3 | import dev.thatredox.chunkynative.opencl.renderer.RendererInstance;
4 | import dev.thatredox.chunkynative.opencl.util.ClMemory;
5 | import dev.thatredox.chunkynative.util.Reflection;
6 | import dev.thatredox.chunkynative.util.Util;
7 | import it.unimi.dsi.fastutil.floats.FloatArrayList;
8 | import it.unimi.dsi.fastutil.floats.FloatList;
9 | import org.jocl.Pointer;
10 | import org.jocl.Sizeof;
11 | import se.llbit.chunky.main.Chunky;
12 | import se.llbit.chunky.renderer.scene.Camera;
13 | import se.llbit.chunky.renderer.scene.Scene;
14 | import se.llbit.math.Matrix3;
15 | import se.llbit.math.Ray;
16 | import se.llbit.math.Vector3;
17 |
18 | import java.util.Random;
19 | import java.util.concurrent.ThreadLocalRandom;
20 | import java.util.concurrent.locks.Lock;
21 | import java.util.stream.IntStream;
22 |
23 | import static org.jocl.CL.*;
24 |
25 | public class ClCamera implements AutoCloseable {
26 | public ClMemory projectorType;
27 | public ClMemory cameraSettings;
28 | public final boolean needGenerate;
29 |
30 | private final Scene scene;
31 |
32 |
33 | public ClCamera(Scene scene) {
34 | RendererInstance instance = RendererInstance.get();
35 | this.scene = scene;
36 | Camera camera = scene.camera();
37 |
38 | int projType = -1;
39 | Vector3 pos = new Vector3(camera.getPosition());
40 | pos.sub(scene.getOrigin());
41 |
42 | FloatArrayList settings = new FloatArrayList();
43 | settings.addAll(FloatList.of(Util.vector3ToFloat(pos)));
44 | settings.addAll(FloatList.of(Util.matrix3ToFloat(Reflection.getFieldValue(camera, "transform", Matrix3.class))));
45 |
46 | switch (camera.getProjectionMode()) {
47 | case PINHOLE:
48 | projType = 0;
49 | settings.add(camera.infiniteDoF() ? 0 : (float) (camera.getSubjectDistance() / camera.getDof()));
50 | settings.add((float) camera.getSubjectDistance());
51 | settings.add((float) Camera.clampedFovTan(camera.getFov()));
52 | break;
53 | default:
54 | // We need to pre-generate rays
55 | break;
56 | }
57 |
58 | needGenerate = projType == -1;
59 |
60 | projectorType = new ClMemory(clCreateBuffer(instance.context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
61 | Sizeof.cl_int, Pointer.to(new int[] {projType}), null));
62 |
63 | if (needGenerate) {
64 | cameraSettings = new ClMemory(clCreateBuffer(instance.context, CL_MEM_READ_ONLY,
65 | (long) Sizeof.cl_float * scene.width * scene.height * 3 * 2, null, null));
66 | } else {
67 | cameraSettings = new ClMemory(clCreateBuffer(instance.context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
68 | (long) Sizeof.cl_float * settings.size(), Pointer.to(settings.toFloatArray()), null));
69 | }
70 | }
71 |
72 | public void generate(Lock renderLock, boolean jitter) {
73 | if (!needGenerate) return;
74 |
75 | float[] rays = new float[scene.width * scene.height * 3 * 2];
76 |
77 | double halfWidth = scene.width / (2.0 * scene.height);
78 | double invHeight = 1.0 / scene.height;
79 |
80 | Camera cam = scene.camera();
81 |
82 | Chunky.getCommonThreads().submit(() -> IntStream.range(0, scene.width).parallel().forEach(i -> {
83 | Ray ray = new Ray();
84 | Random random = jitter ? ThreadLocalRandom.current() : null;
85 | for (int j = 0; j < scene.height; j++) {
86 | int offset = (j * scene.width + i) * 3 * 2;
87 |
88 | float ox = jitter ? random.nextFloat(): 0.5f;
89 | float oy = jitter ? random.nextFloat(): 0.5f;
90 |
91 | cam.calcViewRay(ray, -halfWidth + (i + ox) * invHeight, -0.5 + (j + oy) * invHeight);
92 | ray.o.sub(scene.getOrigin());
93 |
94 | System.arraycopy(Util.vector3ToFloat(ray.o), 0, rays, offset, 3);
95 | System.arraycopy(Util.vector3ToFloat(ray.d), 0, rays, offset+3, 3);
96 | }
97 | })).join();
98 |
99 | if (renderLock != null) renderLock.lock();
100 | RendererInstance instance = RendererInstance.get();
101 | clEnqueueWriteBuffer(instance.commandQueue, this.cameraSettings.get(), CL_TRUE, 0,
102 | (long) Sizeof.cl_float * rays.length, Pointer.to(rays), 0,
103 | null, null);
104 | if (renderLock != null) renderLock.unlock();
105 | }
106 |
107 | @Override
108 | public void close() {
109 | this.projectorType.close();
110 | this.cameraSettings.close();
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/opencl/renderer/scene/ClSky.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.opencl.renderer.scene;
2 |
3 |
4 | import static org.jocl.CL.*;
5 |
6 | import dev.thatredox.chunkynative.opencl.renderer.RendererInstance;
7 | import dev.thatredox.chunkynative.opencl.util.ClMemory;
8 | import org.apache.commons.math3.util.FastMath;
9 | import org.jocl.*;
10 |
11 | import se.llbit.chunky.renderer.scene.Scene;
12 | import se.llbit.chunky.renderer.scene.Sky;
13 | import se.llbit.chunky.renderer.scene.SkyCache;
14 | import se.llbit.log.Log;
15 | import se.llbit.math.Ray;
16 |
17 | import java.lang.reflect.Field;
18 |
19 | public class ClSky implements AutoCloseable {
20 | public final ClMemory skyTexture;
21 | public final ClMemory skyIntensity;
22 |
23 | public ClSky(Scene scene) {
24 | int textureResolution = getTextureResolution(scene);
25 |
26 | RendererInstance instance = RendererInstance.get();
27 |
28 | this.skyIntensity = new ClMemory(clCreateBuffer(instance.context,
29 | CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, Sizeof.cl_float,
30 | Pointer.to(new float[] {(float) scene.sun().getIntensity()}), null));
31 |
32 | cl_image_format fmt = new cl_image_format();
33 | fmt.image_channel_data_type = CL_UNORM_INT8;
34 | fmt.image_channel_order = CL_RGBA;
35 |
36 | cl_image_desc desc = new cl_image_desc();
37 | desc.image_type = CL_MEM_OBJECT_IMAGE2D;
38 | desc.image_width = textureResolution;
39 | desc.image_height = textureResolution;
40 |
41 | byte[] texture = new byte[textureResolution * textureResolution * 4];
42 | Ray ray = new Ray();
43 | for (int i = 0; i < textureResolution; i++) {
44 | for (int j = 0; j < textureResolution; j++) {
45 | int offset = 4 * (j * textureResolution + i);
46 |
47 | double theta = ((double) i / textureResolution) * 2 * FastMath.PI;
48 | double phi = ((double) j / textureResolution) * FastMath.PI - FastMath.PI / 2;
49 | double r = FastMath.cos(phi);
50 | ray.d.set(FastMath.cos(theta) * r, FastMath.sin(phi), FastMath.sin(theta) * r);
51 |
52 | scene.sky().getSkyColor(ray, false);
53 | texture[offset + 0] = (byte) (ray.color.x * 255);
54 | texture[offset + 1] = (byte) (ray.color.y * 255);
55 | texture[offset + 2] = (byte) (ray.color.z * 255);
56 | texture[offset + 3] = (byte) 255;
57 | }
58 | }
59 |
60 | this.skyTexture = new ClMemory(clCreateImage(instance.context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
61 | fmt, desc, Pointer.to(texture), null));
62 | }
63 |
64 | private static int getTextureResolution(Scene scene) {
65 | try {
66 | Sky sky = scene.sky();
67 | Field skyCacheField = sky.getClass().getDeclaredField("skyCache");
68 | skyCacheField.setAccessible(true);
69 | SkyCache skyCache = (SkyCache) skyCacheField.get(sky);
70 |
71 | return skyCache.getSkyResolution();
72 | } catch (NoSuchFieldException | IllegalAccessException e) {
73 | Log.error(e);
74 | throw new RuntimeException();
75 | }
76 | }
77 |
78 | @Override
79 | public void close() {
80 | skyTexture.close();
81 | skyIntensity.close();
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/opencl/tonemap/GpuPostProcessingFilter.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.opencl.tonemap;
2 |
3 | import dev.thatredox.chunkynative.opencl.renderer.KernelLoader;
4 | import dev.thatredox.chunkynative.opencl.renderer.RendererInstance;
5 | import dev.thatredox.chunkynative.opencl.util.ClMemory;
6 | import org.jocl.*;
7 | import se.llbit.chunky.renderer.postprocessing.PostProcessingFilter;
8 | import se.llbit.chunky.resources.BitmapImage;
9 | import se.llbit.util.TaskTracker;
10 |
11 | import java.util.function.Consumer;
12 |
13 | import static org.jocl.CL.*;
14 | import static org.jocl.CL.clReleaseKernel;
15 |
16 | public class GpuPostProcessingFilter implements PostProcessingFilter {
17 | private final cl_program filter;
18 | private final RendererInstance instance;
19 | private final String entryPoint;
20 | private final Consumer argumentConsumer;
21 |
22 | private final String name;
23 | private final String description;
24 | private final String id;
25 |
26 | public GpuPostProcessingFilter(String name, String description, String id, String kernelName, String entryPoint,
27 | Consumer argumentConsumer, RendererInstance instance) {
28 | this.name = name;
29 | this.description = description;
30 | this.id = id;
31 |
32 | this.argumentConsumer = argumentConsumer;
33 | this.instance = instance;
34 | this.entryPoint = entryPoint;
35 | this.filter = KernelLoader.loadProgram("tonemap", kernelName, instance.context,
36 | new cl_device_id[] { instance.device });
37 | }
38 |
39 | @Override
40 | public void processFrame(int width, int height, double[] input, BitmapImage output, double exposure, TaskTracker.Task task) {
41 | cl_kernel kernel = clCreateKernel(filter, entryPoint, null);
42 | try (
43 | ClMemory inputMem = new ClMemory(clCreateBuffer(instance.context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
44 | (long) Sizeof.cl_ulong * input.length, Pointer.to(input), null));
45 | ClMemory outputMem = new ClMemory(clCreateBuffer(instance.context, CL_MEM_WRITE_ONLY,
46 | (long) Sizeof.cl_int * output.data.length, null, null));
47 | ) {
48 | clSetKernelArg(kernel, 0, Sizeof.cl_int, Pointer.to(new int[] {width}));
49 | clSetKernelArg(kernel, 1, Sizeof.cl_int, Pointer.to(new int[] {height}));
50 | clSetKernelArg(kernel, 2, Sizeof.cl_float, Pointer.to(new float[] {(float) exposure}));
51 | clSetKernelArg(kernel, 3, Sizeof.cl_mem, Pointer.to(inputMem.get()));
52 | clSetKernelArg(kernel, 4, Sizeof.cl_mem, Pointer.to(outputMem.get()));
53 | argumentConsumer.accept(kernel);
54 |
55 | cl_event event = new cl_event();
56 | clEnqueueNDRangeKernel(instance.commandQueue, kernel, 1, null,
57 | new long[] {output.data.length}, null, 0, null,
58 | event);
59 | clEnqueueReadBuffer(instance.commandQueue, outputMem.get(), CL_TRUE, 0,
60 | (long) Sizeof.cl_int * output.data.length, Pointer.to(output.data),
61 | 1, new cl_event[] {event}, null);
62 | } finally {
63 | clReleaseKernel(kernel);
64 | }
65 | }
66 |
67 | @Override
68 | public String getName() {
69 | return this.name;
70 | }
71 |
72 | @Override
73 | public String getDescription() {
74 | return this.description;
75 | }
76 |
77 | @Override
78 | public String getId() {
79 | return this.id;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/opencl/tonemap/ImposterCombinationGpuPostProcessingFilter.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.opencl.tonemap;
2 |
3 | import dev.thatredox.chunkynative.opencl.renderer.RendererInstance;
4 | import org.jocl.Pointer;
5 | import org.jocl.Sizeof;
6 | import se.llbit.chunky.renderer.postprocessing.PostProcessingFilter;
7 |
8 | import static org.jocl.CL.*;
9 |
10 | public class ImposterCombinationGpuPostProcessingFilter extends ImposterGpuPostProcessingFilter {
11 | public enum Filter {
12 | GAMMA(0),
13 | TONEMAP1(1),
14 | ACES(2),
15 | HABLE(3);
16 |
17 | public final int id;
18 | Filter(int id) {
19 | this.id = id;
20 | }
21 | }
22 |
23 | public ImposterCombinationGpuPostProcessingFilter(PostProcessingFilter imposter, String kernelName, String entryPoint, Filter filter, RendererInstance instance) {
24 | super(imposter, kernelName, entryPoint,
25 | kernel -> clSetKernelArg(kernel, 5, Sizeof.cl_int, Pointer.to(new int[] { filter.id })),
26 | instance);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/opencl/tonemap/ImposterGpuPostProcessingFilter.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.opencl.tonemap;
2 |
3 | import dev.thatredox.chunkynative.opencl.renderer.RendererInstance;
4 | import org.jocl.cl_kernel;
5 | import se.llbit.chunky.renderer.postprocessing.PostProcessingFilter;
6 |
7 | import java.util.function.Consumer;
8 |
9 | public class ImposterGpuPostProcessingFilter extends GpuPostProcessingFilter{
10 | public ImposterGpuPostProcessingFilter(PostProcessingFilter imposter, String kernelName, String entryPoint, Consumer argumentConsumer, RendererInstance instance) {
11 | super(imposter.getName(), imposter.getDescription(), imposter.getId(), kernelName, entryPoint, argumentConsumer, instance);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/opencl/ui/ChunkyClTab.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.opencl.ui;
2 |
3 | import javafx.geometry.Insets;
4 | import javafx.scene.Node;
5 | import javafx.scene.control.Button;
6 | import javafx.scene.layout.VBox;
7 | import se.llbit.chunky.renderer.scene.Scene;
8 | import se.llbit.chunky.ui.render.RenderControlsTab;
9 |
10 | public class ChunkyClTab implements RenderControlsTab {
11 | protected final VBox box;
12 |
13 | public ChunkyClTab() {
14 | box = new VBox(10.0);
15 | box.setPadding(new Insets(10.0));
16 |
17 | Button deviceSelectorButton = new Button("Select OpenCL Device");
18 | deviceSelectorButton.setOnMouseClicked(event -> {
19 | GpuSelector selector = new GpuSelector();
20 | selector.show();
21 | });
22 | box.getChildren().add(deviceSelectorButton);
23 | }
24 |
25 | @Override
26 | public void update(Scene scene) {
27 |
28 | }
29 |
30 | @Override
31 | public String getTabTitle() {
32 | return "OpenCL";
33 | }
34 |
35 | @Override
36 | public Node getTabContent() {
37 | return box;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/opencl/ui/GpuSelector.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.opencl.ui;
2 |
3 | import dev.thatredox.chunkynative.opencl.renderer.RendererInstance;
4 | import javafx.beans.property.SimpleDoubleProperty;
5 | import javafx.beans.property.SimpleStringProperty;
6 | import javafx.collections.FXCollections;
7 | import javafx.geometry.Insets;
8 | import javafx.geometry.Pos;
9 | import javafx.scene.Scene;
10 | import javafx.scene.control.*;
11 | import javafx.scene.layout.HBox;
12 | import javafx.scene.layout.Priority;
13 | import javafx.scene.layout.VBox;
14 | import javafx.stage.Stage;
15 | import org.jocl.CL;
16 | import org.jocl.cl_device_id;
17 | import se.llbit.chunky.PersistentSettings;
18 | import se.llbit.log.Log;
19 |
20 | import java.util.Arrays;
21 |
22 | public class GpuSelector extends Stage {
23 |
24 | public GpuSelector() {
25 | // Build scene
26 | RendererInstance instance = RendererInstance.get();
27 | ClDevice[] devices = new ClDevice[instance.devices.length];
28 | for (int i = 0; i < devices.length; i++) {
29 | devices[i] = new ClDevice(instance.devices[i], i);
30 | }
31 |
32 | TableView table = new TableView<>();
33 | table.setPrefWidth(500);
34 | table.setPrefHeight(200);
35 | table.setItems(FXCollections.observableList(Arrays.asList(devices)));
36 |
37 | TableColumn nameCol = new TableColumn<>("Device Name");
38 | nameCol.setCellValueFactory(dev -> new SimpleStringProperty(dev.getValue().name));
39 |
40 | TableColumn typeCol = new TableColumn<>("Type");
41 | typeCol.setCellValueFactory(dev -> new SimpleStringProperty(dev.getValue().getTypeString()));
42 |
43 | TableColumn computeCol = new TableColumn<>("Compute Capacity");
44 | computeCol.setCellValueFactory(dev -> new SimpleDoubleProperty(dev.getValue().computeCapacity).asObject());
45 |
46 | table.getColumns().clear();
47 | table.getColumns().add(nameCol);
48 | table.getColumns().add(typeCol);
49 | table.getColumns().add(computeCol);
50 |
51 | VBox box = new VBox();
52 | VBox.setVgrow(table, Priority.ALWAYS);
53 | box.setSpacing(10);
54 | box.setPadding(new Insets(10));
55 |
56 | box.getChildren().add(new Label("Select OpenCL device to use:"));
57 | box.getChildren().add(table);
58 |
59 | HBox buttons = new HBox();
60 | buttons.setAlignment(Pos.TOP_RIGHT);
61 | buttons.setSpacing(10);
62 |
63 | Button cancelButton = new Button("Cancel");
64 | cancelButton.setOnMouseClicked(event -> this.close());
65 | buttons.getChildren().add(cancelButton);
66 |
67 | Button selectButton = new Button("Select Device");
68 | selectButton.setDefaultButton(true);
69 | selectButton.setTooltip(new Tooltip("Restart Chunky for changes to take effect."));
70 | selectButton.setOnMouseClicked(event -> {
71 | if (!table.getSelectionModel().isEmpty()) {
72 | PersistentSettings.settings.setInt("clDevice", table.getSelectionModel().getSelectedItem().index);
73 | PersistentSettings.save();
74 | this.close();
75 | Log.warn("Restart Chunky to use the selected device.");
76 | }
77 | });
78 | buttons.getChildren().add(selectButton);
79 |
80 | box.getChildren().add(buttons);
81 |
82 | Scene scene = new Scene(box);
83 |
84 | // Apply scene
85 | this.setTitle("Select OpenCL Device");
86 | this.setScene(scene);
87 | }
88 |
89 | private static class ClDevice {
90 | protected final String name;
91 | protected final long type;
92 | protected final double computeCapacity;
93 | protected final int index;
94 |
95 | public ClDevice(cl_device_id device, int index) {
96 | type = RendererInstance.getLongs(device, CL.CL_DEVICE_TYPE, 1)[0];
97 |
98 | long computeScaler = 1;
99 | if ((type & CL.CL_DEVICE_TYPE_GPU) != 0) {
100 | computeScaler = 32;
101 | }
102 |
103 | long computeSpeed = (long) RendererInstance.getInts(device, CL.CL_DEVICE_MAX_CLOCK_FREQUENCY, 1)[0] *
104 | (long) RendererInstance.getInts(device, CL.CL_DEVICE_MAX_COMPUTE_UNITS, 1)[0] *
105 | computeScaler;
106 | name = RendererInstance.getString(device, CL.CL_DEVICE_NAME);
107 | computeCapacity = computeSpeed / 1000.0; // Approximate GFlops
108 | this.index = index;
109 | }
110 |
111 | public String getTypeString() {
112 | switch ((int) type) {
113 | case (int) CL.CL_DEVICE_TYPE_CPU:
114 | return "CPU";
115 | case (int) CL.CL_DEVICE_TYPE_GPU:
116 | return "GPU";
117 | case (int) CL.CL_DEVICE_TYPE_ACCELERATOR:
118 | return "Accelerator";
119 | case (int) CL.CL_DEVICE_TYPE_CUSTOM:
120 | return "Custom";
121 | default:
122 | return String.format("Unknown (%d)", type);
123 | }
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/opencl/util/ClIntBuffer.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.opencl.util;
2 |
3 | import dev.thatredox.chunkynative.common.export.Packer;
4 | import dev.thatredox.chunkynative.opencl.renderer.RendererInstance;
5 |
6 | import static org.jocl.CL.*;
7 |
8 | import it.unimi.dsi.fastutil.ints.IntArrayList;
9 | import org.jocl.*;
10 |
11 | public class ClIntBuffer implements AutoCloseable {
12 | private final ClMemory buffer;
13 |
14 | public ClIntBuffer(int[] buffer, int length) {
15 | if (length == 0) {
16 | buffer = new int[1];
17 | length = 1;
18 | }
19 | assert buffer.length >= length;
20 |
21 | RendererInstance instance = RendererInstance.get();
22 | this.buffer = new ClMemory(
23 | clCreateBuffer(instance.context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
24 | (long) Sizeof.cl_uint * length, Pointer.to(buffer), null));
25 | }
26 |
27 | public ClIntBuffer(int[] buffer) {
28 | this(buffer, buffer.length);
29 | }
30 |
31 | public ClIntBuffer(IntArrayList buffer) {
32 | this(buffer.elements(), buffer.size());
33 | }
34 |
35 | public ClIntBuffer(Packer packable) {
36 | this(packable.pack());
37 | }
38 |
39 | public ClIntBuffer(int value) {
40 | this(new int[] {value});
41 | }
42 |
43 | public cl_mem get() {
44 | return buffer.get();
45 | }
46 |
47 | public void set(int[] values, int offset) {
48 | RendererInstance instance = RendererInstance.get();
49 | clEnqueueWriteBuffer(instance.commandQueue, this.get(), CL_TRUE, (long) Sizeof.cl_uint * offset,
50 | (long) Sizeof.cl_uint * values.length, Pointer.to(values),
51 | 0, null, null);
52 | }
53 |
54 | @Override
55 | public void close() {
56 | buffer.close();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/opencl/util/ClMemory.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.opencl.util;
2 |
3 | import dev.thatredox.chunkynative.util.NativeCleaner;
4 | import org.jocl.CL;
5 | import org.jocl.cl_mem;
6 |
7 | public class ClMemory implements AutoCloseable {
8 | protected final NativeCleaner.Cleaner cleaner;
9 | protected final cl_mem memory;
10 | protected boolean valid;
11 |
12 | public ClMemory(cl_mem memory) {
13 | this.cleaner = NativeCleaner.INSTANCE.register(this, () -> CL.clReleaseMemObject(memory));
14 | this.memory = memory;
15 | this.valid = true;
16 | }
17 |
18 | public cl_mem get() {
19 | if (valid) {
20 | return memory;
21 | } else {
22 | throw new NullPointerException("Attempted to access invalid CL_MEM: " + this.memory.toString());
23 | }
24 | }
25 |
26 | @Override
27 | public void close() {
28 | this.cleaner.clean();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/util/EqualityChecker.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.util;
2 |
3 | public interface EqualityChecker {
4 | boolean equals(T a, T b);
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/util/FunctionCache.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.util;
2 |
3 | import java.lang.ref.WeakReference;
4 | import java.util.function.Consumer;
5 | import java.util.function.Function;
6 |
7 | public class FunctionCache {
8 | protected WeakReference input;
9 | protected R output;
10 |
11 | protected final Function function;
12 | protected final Consumer oldValueConsumer;
13 |
14 | public FunctionCache(Function function, Consumer oldValueConsumer, T initial) {
15 | this.function = function;
16 | this.oldValueConsumer = oldValueConsumer;
17 | this.input = new WeakReference<>(initial, null);
18 | if (initial != null) {
19 | this.output = function.apply(initial);
20 | } else {
21 | this.output = null;
22 | }
23 | }
24 |
25 | public R apply(T input) {
26 | if (this.input.get() != input) {
27 | if (this.output != null) {
28 | this.oldValueConsumer.accept(this.output);
29 | }
30 | this.output = function.apply(input);
31 | this.input = new WeakReference<>(input, null);
32 | }
33 | return this.output;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/dev/thatredox/chunkynative/util/NativeCleaner.java:
--------------------------------------------------------------------------------
1 | package dev.thatredox.chunkynative.util;
2 |
3 | import java.lang.ref.PhantomReference;
4 | import java.lang.ref.Reference;
5 | import java.lang.ref.ReferenceQueue;
6 | import java.util.ArrayList;
7 | import java.util.Collections;
8 | import java.util.List;
9 |
10 | public class NativeCleaner extends Thread {
11 | public static final NativeCleaner INSTANCE = new NativeCleaner("Chunky Native Cleaner");
12 |
13 | protected final ReferenceQueue