├── .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 | ![image](https://user-images.githubusercontent.com/42661490/116319916-28ef2580-a76c-11eb-9f93-86d444a349fd.png) 22 | 23 | Select `ChunkyCL` as your renderer for the scene in the `Advanced` tab. 24 | 25 | ![image](https://user-images.githubusercontent.com/42661490/122492084-fc040580-cf99-11eb-9b08-b166dc25db41.png) 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 cleanerQueue = new ReferenceQueue<>(); 14 | protected final List cleaners = Collections.synchronizedList(new ArrayList<>()); 15 | 16 | public static class Cleaner extends PhantomReference { 17 | protected final Runnable action; 18 | protected volatile boolean cleaned = false; 19 | 20 | protected Cleaner(Object ref, ReferenceQueue q, Runnable action) { 21 | super(ref, q); 22 | this.action = action; 23 | } 24 | 25 | public void clean() { 26 | if (!cleaned) { 27 | cleaned = true; 28 | action.run(); 29 | } 30 | } 31 | } 32 | 33 | public NativeCleaner(String name) { 34 | super(name); 35 | this.start(); 36 | } 37 | 38 | public Cleaner register(Object ref, Runnable action) { 39 | Cleaner cleaner = new Cleaner(ref, cleanerQueue, action); 40 | this.cleaners.add(cleaner); 41 | return cleaner; 42 | } 43 | 44 | @Override 45 | public void run() { 46 | while (!interrupted()) { 47 | Reference cleaner = cleanerQueue.poll(); 48 | if (cleaner instanceof Cleaner) { 49 | ((Cleaner) cleaner).clean(); 50 | this.cleaners.remove(cleaner); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/dev/thatredox/chunkynative/util/Reflection.java: -------------------------------------------------------------------------------- 1 | package dev.thatredox.chunkynative.util; 2 | 3 | import se.llbit.log.Log; 4 | 5 | import java.lang.reflect.Field; 6 | import java.util.Objects; 7 | 8 | public class Reflection { 9 | private Reflection() {} 10 | 11 | @SuppressWarnings("unchecked") 12 | public static T getFieldValue(Object obj, String name, Class cls) { 13 | try { 14 | Field field = obj.getClass().getDeclaredField(name); 15 | field.setAccessible(true); 16 | Object o = field.get(obj); 17 | if (o != null && cls.isAssignableFrom(o.getClass())) { 18 | return (T) o; 19 | } else { 20 | throw new RuntimeException(String.format( 21 | "Field %s was of type %s. Expected type %s. Do you have the wrong version of Chunky?", 22 | name, o == null ? null : o.getClass(), cls)); 23 | } 24 | } catch (NoSuchFieldException | IllegalAccessException e) { 25 | Log.error("Failed to obtain field. Do you have the wrong version of Chunky?", e); 26 | throw new RuntimeException(e); 27 | } 28 | } 29 | 30 | /** 31 | * Copy public fields between objects. 32 | * @param o1 Object to copy from 33 | * @param o2 Object to copy to 34 | */ 35 | public static void copyPublic(T o1, T o2) { 36 | try { 37 | Field[] fields = o1.getClass().getFields(); 38 | for (Field field : fields) { 39 | Field o2f = o2.getClass().getField(field.getName()); 40 | o2f.set(o2, field.get(o1)); 41 | } 42 | } catch (Exception e) { 43 | throw new RuntimeException(e); 44 | } 45 | } 46 | 47 | /** 48 | * Compare equality between two objects. Objects are compared with 49 | * {@link java.util.Objects#deepEquals(Object, Object)} 50 | */ 51 | public static boolean equalsPublic(T a, T b) { 52 | if (a == b) return true; 53 | if (!Objects.equals(a.getClass(), b.getClass())) return false; 54 | try { 55 | Field[] fields = a.getClass().getFields(); 56 | for (Field field : fields) { 57 | if (!Objects.deepEquals(field.get(a), field.get(b))) { 58 | return false; 59 | } 60 | } 61 | } catch (IllegalAccessException e) { 62 | throw new RuntimeException(e); 63 | } 64 | return true; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/dev/thatredox/chunkynative/util/Util.java: -------------------------------------------------------------------------------- 1 | package dev.thatredox.chunkynative.util; 2 | 3 | import se.llbit.math.Matrix3; 4 | import se.llbit.math.Vector3; 5 | 6 | public class Util { 7 | public static float[] vector3ToFloat(Vector3 vec) { 8 | return new float[] { (float) vec.x, (float) vec.y, (float) vec.z }; 9 | } 10 | 11 | public static float[] matrix3ToFloat(Matrix3 mat) { 12 | return new float[] { 13 | (float) mat.m11, (float) mat.m12, (float) mat.m13, 14 | (float) mat.m21, (float) mat.m22, (float) mat.m23, 15 | (float) mat.m31, (float) mat.m32, (float) mat.m33, 16 | }; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/opencl/kernel/.clangd: -------------------------------------------------------------------------------- 1 | CompileFlags: 2 | Add: [-cl-std=CL1.2, -cl-ext=all] -------------------------------------------------------------------------------- /src/main/opencl/kernel/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.23) 2 | project(ChunkyCL) 3 | 4 | set(CMAKE_C_STANDARD 99) 5 | 6 | include_directories(include) 7 | 8 | add_executable(kernel 9 | opencl.h 10 | include/block.h 11 | include/bvh.h 12 | include/camera.h 13 | include/constants.h 14 | include/kernel.h 15 | include/material.h 16 | include/octree.h 17 | include/primitives.h 18 | include/randomness.h 19 | include/rayTracer.cl 20 | include/sky.h 21 | include/textureAtlas.h 22 | include/utils.h 23 | include/wavefront.h 24 | ) 25 | set_target_properties(kernel PROPERTIES LINKER_LANGUAGE C) 26 | -------------------------------------------------------------------------------- /src/main/opencl/kernel/include/block.h: -------------------------------------------------------------------------------- 1 | // This includes stuff regarding blocks and block palettes 2 | 3 | #ifndef CHUNKYCLPLUGIN_BLOCK_H 4 | #define CHUNKYCLPLUGIN_BLOCK_H 5 | 6 | #include "../opencl.h" 7 | #include "wavefront.h" 8 | #include "utils.h" 9 | #include "constants.h" 10 | #include "textureAtlas.h" 11 | #include "material.h" 12 | #include "primitives.h" 13 | 14 | typedef struct { 15 | __global const int* blockPalette; 16 | __global const int* quadModels; 17 | __global const int* aabbModels; 18 | MaterialPalette* materialPalette; 19 | } BlockPalette; 20 | 21 | BlockPalette BlockPalette_new(__global const int* blockPalette, __global const int* quadModels, __global const int* aabbModels, MaterialPalette* materialPalette) { 22 | BlockPalette p; 23 | p.blockPalette = blockPalette; 24 | p.quadModels = quadModels; 25 | p.aabbModels = aabbModels; 26 | p.materialPalette = materialPalette; 27 | return p; 28 | } 29 | 30 | float BlockPalette_intersectBlock(BlockPalette* self, int block, int3 blockPosition, IntersectionRecord* record, float3 origin, float3 direction, float3 invRayDir, image2d_array_t atlas) { 31 | // ANY_TYPE. Should not be intersected. 32 | if (block == 0x7FFFFFFE) { 33 | return NAN; 34 | } 35 | 36 | int modelType = self->blockPalette[block + 0]; 37 | int modelPointer = self->blockPalette[block + 1]; 38 | 39 | float3 normOrigin = (origin - direction * OFFSET) - int3toFloat3(blockPosition); 40 | float3 normal; 41 | float2 uv; 42 | 43 | switch (modelType) { 44 | default: 45 | case 0: { 46 | return NAN; 47 | } 48 | case 1: { 49 | Material mat = Material_get(self->materialPalette, modelPointer); 50 | 51 | AABB box = AABB_new(0, 1, 0, 1, 0, 1); 52 | float dist = AABB_full_intersect(&box, normOrigin, origin, invRayDir, &normal, &uv); 53 | 54 | // No intersection 55 | if (isnan(dist)) { 56 | return NAN; 57 | } 58 | 59 | record->normal = normal; 60 | if (Material_sample(&mat, atlas, record, uv)) { 61 | return dist - OFFSET; 62 | } else { 63 | return NAN; 64 | } 65 | } 66 | case 2: { 67 | bool hit = false; 68 | float dist = HUGE_VALF; 69 | int material; 70 | 71 | int boxes = self->aabbModels[modelPointer]; 72 | for (int i = 0; i < boxes; i++) { 73 | int offset = modelPointer + 1 + i * TEX_AABB_SIZE; 74 | TexturedAABB box = TexturedAABB_new(self->aabbModels, offset); 75 | float t = TexturedAABB_intersect(&box, dist, normOrigin, record->ray->direction, invRayDir, &normal, &uv, &material); 76 | if (!isnan(t)) { 77 | Material mat = Material_get(self->materialPalette, material); 78 | if (Material_sample(&mat, atlas, record, uv)) { 79 | record->normal = normal; 80 | dist = t; 81 | hit = true; 82 | } 83 | } 84 | } 85 | 86 | if (hit) { 87 | return dist; 88 | } else { 89 | return NAN; 90 | } 91 | } 92 | case 3: { 93 | bool hit = false; 94 | float dist = HUGE_VALF; 95 | 96 | int quads = self->quadModels[modelPointer]; 97 | for (int i = 0; i < quads; i++) { 98 | int offset = modelPointer + 1 + i * QUAD_SIZE; 99 | Quad q = Quad_new(self->quadModels, offset); 100 | float t = Quad_intersect(&q, dist, normOrigin, record->ray->direction, &normal, &uv); 101 | if (!isnan(t)) { 102 | Material mat = Material_get(self->materialPalette, q.material); 103 | if (Material_sample(&mat, atlas, record, uv)) { 104 | record->normal = normal; 105 | dist = t; 106 | hit = true; 107 | } 108 | } 109 | } 110 | 111 | if (hit) { 112 | return dist; 113 | } else { 114 | return NAN; 115 | } 116 | } 117 | } 118 | } 119 | 120 | #endif 121 | -------------------------------------------------------------------------------- /src/main/opencl/kernel/include/bvh.h: -------------------------------------------------------------------------------- 1 | #ifndef CHUNKYCL_PLUGIN_BVH 2 | #define CHUNKYCL_PLUGIN_BVH 3 | 4 | #include "../opencl.h" 5 | #include "material.h" 6 | #include "primitives.h" 7 | 8 | typedef struct { 9 | __global const int* bvh; 10 | __global const int* trigs; 11 | MaterialPalette* materialPalette; 12 | } Bvh; 13 | 14 | Bvh Bvh_new(__global const int* bvh, __global const int* trigs, MaterialPalette* materialPalette) { 15 | Bvh b; 16 | b.bvh = bvh; 17 | b.trigs = trigs; 18 | b.materialPalette = materialPalette; 19 | return b; 20 | } 21 | 22 | bool Bvh_intersect(Bvh* self, IntersectionRecord* record, image2d_array_t atlas) { 23 | if (self->bvh[0] == 0) { 24 | if (isnan(as_float(self->bvh[1])) && 25 | isnan(as_float(self->bvh[2])) && 26 | isnan(as_float(self->bvh[3])) && 27 | isnan(as_float(self->bvh[4])) && 28 | isnan(as_float(self->bvh[5])) && 29 | isnan(as_float(self->bvh[6]))) { 30 | return false; 31 | } 32 | } 33 | 34 | bool hit = false; 35 | 36 | int toVisit = 0; 37 | int currentNode = 0; 38 | int nodesToVisit[64]; 39 | int node[7]; 40 | AABB box; 41 | float3 invDir = 1 / record->ray->direction; 42 | 43 | float3 normal; 44 | float2 uv; 45 | int material; 46 | 47 | while (true) { 48 | node[0] = self->bvh[currentNode]; 49 | 50 | if (node[0] <= 0) { 51 | // Is leaf 52 | int primIndex = -node[0]; 53 | int numPrim = self->trigs[primIndex]; 54 | 55 | for (int i = 0; i < numPrim; i++) { 56 | Triangle trig = Triangle_new(self->trigs, primIndex + 1 + TRIANGLE_SIZE * i); 57 | float dist = Triangle_intersect(&trig, record->distance, record->ray->origin, record->ray->direction, &normal, &uv, &material); 58 | 59 | if (!isnan(dist)) { 60 | Material mat = Material_get(self->materialPalette, material); 61 | if (Material_sample(&mat, atlas, record, uv)) { 62 | record->normal = normal; 63 | record->distance = dist; 64 | hit = true; 65 | } 66 | } 67 | } 68 | 69 | if (toVisit == 0) break; 70 | currentNode = nodesToVisit[--toVisit]; 71 | } else { 72 | int offset = node[0]; 73 | for (int i = 0; i < 7; i++) { 74 | node[i] = self->bvh[currentNode + 7 + i]; 75 | } 76 | box = AABB_new( 77 | as_float(node[1]), as_float(node[2]), 78 | as_float(node[3]), as_float(node[4]), 79 | as_float(node[5]), as_float(node[6]) 80 | ); 81 | float t1 = AABB_quick_intersect(&box, record->ray->origin, invDir); 82 | 83 | for (int i = 0; i < 7; i++) { 84 | node[i] = self->bvh[offset + i]; 85 | } 86 | box = AABB_new( 87 | as_float(node[1]), as_float(node[2]), 88 | as_float(node[3]), as_float(node[4]), 89 | as_float(node[5]), as_float(node[6]) 90 | ); 91 | float t2 = AABB_quick_intersect(&box, record->ray->origin, invDir); 92 | 93 | if (isnan(t1) || t1 > record->distance) { 94 | if (isnan(t2) || t2 > record->distance) { 95 | if (toVisit == 0) break; 96 | currentNode = nodesToVisit[--toVisit]; 97 | } else { 98 | currentNode = offset; 99 | } 100 | } else if (isnan(t2) || t2 > record->distance) { 101 | currentNode += 7; 102 | } else if (t1 < t2) { 103 | nodesToVisit[toVisit++] = offset; 104 | currentNode += 7; 105 | } else { 106 | nodesToVisit[toVisit++] = currentNode + 7; 107 | currentNode = offset; 108 | } 109 | } 110 | } 111 | 112 | return hit; 113 | } 114 | 115 | #endif 116 | -------------------------------------------------------------------------------- /src/main/opencl/kernel/include/camera.h: -------------------------------------------------------------------------------- 1 | #ifndef CHUNKYCLPLUGIN_CAMERA_H 2 | #define CHUNKYCLPLUGIN_CAMERA_H 3 | 4 | #include "../opencl.h" 5 | #include "wavefront.h" 6 | #include "randomness.h" 7 | 8 | void Camera_preGenerated(Ray* ray, __global const float* rays) { 9 | ray->origin = vload3(ray->pixel->index * 2, rays); 10 | ray->direction = vload3(ray->pixel->index * 2 + 1, rays); 11 | } 12 | 13 | void Camera_pinHole(float x, float y, unsigned int* state, float3* origin, float3* direction, __global const float* projectorSettings) { 14 | float aperature = projectorSettings[0]; 15 | float subjectDistance = projectorSettings[1]; 16 | float fovTan = projectorSettings[2]; 17 | 18 | *origin = (float3) (0, 0, 0); 19 | *direction = (float3) (fovTan * x, fovTan * y, 1.0); 20 | 21 | if (aperature > 0) { 22 | *direction *= subjectDistance / direction->z; 23 | 24 | float r = sqrt(Random_nextFloat(state)) * aperature; 25 | float theta = Random_nextFloat(state) * M_PI_F * 2.0; 26 | float rx = cos(theta) * r; 27 | float ry = sin(theta) * r; 28 | 29 | *direction -= (float3) (rx, ry, 0); 30 | *origin += (float3) (rx, ry, 0); 31 | } 32 | } 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /src/main/opencl/kernel/include/constants.h: -------------------------------------------------------------------------------- 1 | #ifndef CHUNKYCLPLUGIN_CONSTANTS_H 2 | #define CHUNKYCLPLUGIN_CONSTANTS_H 3 | 4 | #define EPS 0.000005f // Ray epsilon and exit offset 5 | #define OFFSET 0.0001f // TODO: refine these values? 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /src/main/opencl/kernel/include/kernel.h: -------------------------------------------------------------------------------- 1 | #ifndef CHUNKYCL_KERNEL_H 2 | #define CHUNKYCL_KERNEL_H 3 | 4 | #include "../opencl.h" 5 | #include "wavefront.h" 6 | #include "octree.h" 7 | #include "block.h" 8 | #include "constants.h" 9 | #include "randomness.h" 10 | #include "bvh.h" 11 | #include "sky.h" 12 | 13 | 14 | bool closestIntersect(IntersectionRecord* record, Octree* octree, BlockPalette* palette, image2d_array_t atlas, int drawDepth, Bvh* worldBvh, Bvh* actorBvh) { 15 | bool hit = false; 16 | hit |= Octree_octreeIntersect(octree, record, palette, atlas, drawDepth); 17 | hit |= Bvh_intersect(worldBvh, record, atlas); 18 | hit |= Bvh_intersect(actorBvh, record, atlas); 19 | 20 | if (hit) { 21 | record->point = record->ray->origin + record->ray->direction * (record->distance - OFFSET); 22 | } 23 | return hit; 24 | } 25 | 26 | void intersectSky(IntersectionRecord* record, image2d_array_t atlas, Sun* sun, image2d_t skyTexture, float skyIntensity) { 27 | Sky_intersect(record, skyTexture, skyIntensity); 28 | Sun_intersect(sun, record, atlas); 29 | 30 | record->ray->pixel->color += (float3) (record->color.x, record->color.y, record->color.z) * record->ray->pixel->throughput * record->emittance; 31 | } 32 | 33 | void applyRayColor(IntersectionRecord* record, float emitterScale) { 34 | Ray* ray = record->ray; 35 | ray->origin = record->point; 36 | 37 | // Apply ray color 38 | float3 color = (float3) (record->color.x, record->color.y, record->color.z); 39 | ray->pixel->throughput *= color; 40 | 41 | float3 emittance = color; 42 | emittance *= record->emittance * emitterScale; 43 | ray->pixel->color += emittance * ray->pixel->throughput; 44 | } 45 | 46 | bool nextPath(IntersectionRecord* record, unsigned int *state, int maxDepth) { 47 | Ray* ray = record->ray; 48 | ray->origin = record->point; 49 | 50 | { 51 | // Diffuse reflection 52 | float x1 = Random_nextFloat(state); 53 | float x2 = Random_nextFloat(state); 54 | float r = sqrt(x1); 55 | float theta = 2 * M_PI_F * x2; 56 | 57 | float tx = r * cos(theta); 58 | float ty = r * sin(theta); 59 | float tz = sqrt(1 - x1); 60 | 61 | // Transform from tangent space to world space 62 | float xx, xy, xz; 63 | float ux, uy, uz; 64 | float vx, vy, vz; 65 | 66 | if (fabs(record->normal.x) > 0.1) { 67 | xx = 0; 68 | xy = 1; 69 | } else { 70 | xx = 1; 71 | xy = 0; 72 | } 73 | xz = 0; 74 | 75 | ux = xy * record->normal.z - xz * record->normal.y; 76 | uy = xz * record->normal.x - xx * record->normal.z; 77 | uz = xx * record->normal.y - xy * record->normal.x; 78 | 79 | r = 1 / sqrt(ux*ux + uy*uy + uz*uz); 80 | 81 | ux *= r; 82 | uy *= r; 83 | uz *= r; 84 | 85 | vx = uy * record->normal.z - uz * record->normal.y; 86 | vy = uz * record->normal.x - ux * record->normal.z; 87 | vz = ux * record->normal.y - uy * record->normal.x; 88 | 89 | ray->direction.x = ux * tx + vx * ty + record->normal.x * tz; 90 | ray->direction.y = uy * tx + vy * ty + record->normal.y * tz; 91 | ray->direction.z = uz * tx + vz * ty + record->normal.z * tz; 92 | } 93 | 94 | ray->origin += ray->direction * OFFSET; 95 | ray->rayDepth += 1; 96 | record->distance = HUGE_VALF; 97 | return ray->rayDepth < maxDepth; 98 | } 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /src/main/opencl/kernel/include/material.h: -------------------------------------------------------------------------------- 1 | // This includes stuff regarding materials 2 | 3 | #ifndef CHUNKYCLPLUGIN_MATERIAL_H 4 | #define CHUNKYCLPLUGIN_MATERIAL_H 5 | 6 | #include "../opencl.h" 7 | #include "wavefront.h" 8 | #include "textureAtlas.h" 9 | #include "utils.h" 10 | #include "constants.h" 11 | 12 | typedef struct { 13 | __global const int* palette; 14 | } MaterialPalette; 15 | 16 | MaterialPalette MaterialPalette_new(__global const int* palette) { 17 | MaterialPalette p; 18 | p.palette = palette; 19 | return p; 20 | } 21 | 22 | typedef struct { 23 | unsigned int flags; 24 | unsigned int tint; 25 | unsigned int textureSize; 26 | unsigned int color; 27 | unsigned int normal_emittance; 28 | unsigned int specular_metalness_roughness; 29 | } Material; 30 | 31 | Material Material_get(MaterialPalette* self, int material) { 32 | Material m; 33 | m.flags = self->palette[material + 0]; 34 | m.tint = self->palette[material + 1]; 35 | m.textureSize = self->palette[material + 2]; 36 | m.color = self->palette[material + 3]; 37 | m.normal_emittance = self->palette[material + 4]; 38 | m.specular_metalness_roughness = self->palette[material + 5]; 39 | return m; 40 | } 41 | 42 | bool Material_sample(Material* self, image2d_array_t atlas, IntersectionRecord* record, float2 uv) { 43 | // Color 44 | float4 color; 45 | if (self->flags & 0b100) 46 | color = Atlas_read_uv(uv.x, uv.y, self->color, self->textureSize, atlas); 47 | else 48 | color = colorFromArgb(self->color); 49 | 50 | if (color.w > EPS) { 51 | record->color = color; 52 | } else { 53 | return false; 54 | } 55 | 56 | // Tint 57 | switch (self->tint >> 24) { 58 | case 0xFF: 59 | record->color *= colorFromArgb(self->tint); 60 | break; 61 | case 1: 62 | // TODO Proper foliage tint 63 | record->color *= colorFromArgb(0xFF71A74D); 64 | break; 65 | case 2: 66 | // TODO Proper grass tint 67 | record->color *= colorFromArgb(0xFF8EB971); 68 | break; 69 | case 3: 70 | // TODO Proper water tint 71 | record->color *= colorFromArgb(0xFF3F76E4); 72 | break; 73 | } 74 | 75 | // (Normal) emittance 76 | if (self->flags & 0b010) 77 | record->emittance = Atlas_read_uv(uv.x, uv.y, self->normal_emittance, self->textureSize, atlas).w; 78 | else 79 | record->emittance = (self->normal_emittance & 0xFF) / 255.0; 80 | 81 | return true; 82 | } 83 | 84 | #endif 85 | -------------------------------------------------------------------------------- /src/main/opencl/kernel/include/octree.h: -------------------------------------------------------------------------------- 1 | #ifndef CHUNKYCLPLUGIN_OCTREE_H 2 | #define CHUNKYCLPLUGIN_OCTREE_H 3 | 4 | #include "../opencl.h" 5 | #include "wavefront.h" 6 | #include "constants.h" 7 | #include "primitives.h" 8 | #include "block.h" 9 | #include "utils.h" 10 | 11 | typedef struct { 12 | __global const int* treeData; 13 | int depth; 14 | } Octree; 15 | 16 | Octree Octree_create(__global const int* treeData, int depth) { 17 | Octree octree; 18 | octree.treeData = treeData; 19 | octree.depth = depth; 20 | return octree; 21 | } 22 | 23 | int Octree_get(Octree* self, int x, int y, int z) { 24 | int3 bp = (int3) (x, y, z); 25 | 26 | // Check inbounds 27 | int3 lv = bp >> self->depth; 28 | if ((lv.x != 0) | (lv.y != 0) | (lv.z != 0)) 29 | return 0; 30 | 31 | int level = self->depth; 32 | int data = self->treeData[0]; 33 | while (data > 0) { 34 | level--; 35 | lv = 1 & (bp >> level); 36 | data = self->treeData[data + ((lv.x << 2) | (lv.y << 1) | lv.z)]; 37 | } 38 | return -data; 39 | } 40 | 41 | bool Octree_octreeIntersect(Octree* self, IntersectionRecord* record, BlockPalette* palette, image2d_array_t atlas, int drawDepth) { 42 | Ray* ray = record->ray; 43 | 44 | float3 normalMarch = (float3) (0, 1, 0); 45 | float distMarch = 0; 46 | 47 | float3 invD = 1 / ray->direction; 48 | float3 offsetD = ray->direction * OFFSET; 49 | 50 | int depth = self->depth; 51 | 52 | // Check if we are in bounds 53 | int3 lv = intFloorFloat3(ray->origin) >> depth; 54 | if ((lv.x != 0) | (lv.y != 0) | (lv.z != 0)) { 55 | // Attempt to intersect with the octree 56 | float octreeSize = 1 << depth; 57 | AABB box = AABB_new(0, octreeSize, 0, octreeSize, 0, octreeSize); 58 | float dist = AABB_quick_intersect(&box, ray->origin, invD); 59 | if (isnan(dist) || dist < 0) { 60 | return false; 61 | } else { 62 | distMarch += dist + OFFSET; 63 | } 64 | } 65 | 66 | for (int i = 0; i < drawDepth; i++) { 67 | if (distMarch > record->distance) { 68 | // Theres already been a closer intersection! 69 | return false; 70 | } 71 | 72 | float3 pos = ray->origin + (ray->direction * distMarch); 73 | int3 bp = intFloorFloat3(pos + offsetD); 74 | 75 | // Check inbounds 76 | lv = bp >> depth; 77 | if ((lv.x != 0) | (lv.y != 0) | (lv.z != 0)) 78 | return false; 79 | 80 | // Read the octree with depth 81 | int level = depth; 82 | int data = self->treeData[0]; 83 | while (data > 0) { 84 | level--; 85 | lv = 1 & (bp >> level); 86 | data = self->treeData[data + ((lv.x << 2) | (lv.y << 1) | lv.z)]; 87 | } 88 | data = -data; 89 | lv = bp >> level; 90 | 91 | // Get block data if there is an intersection 92 | if (data != ray->material) { 93 | float dist = BlockPalette_intersectBlock(palette, data, bp, record, pos, ray->direction, invD, atlas); 94 | 95 | if (!isnan(dist)) { 96 | record->distance = distMarch + dist; 97 | record->material = data; 98 | return true; 99 | } 100 | } 101 | 102 | // Exit the current leaf 103 | AABB box = AABB_new(lv.x << level, (lv.x + 1) << level, 104 | lv.y << level, (lv.y + 1) << level, 105 | lv.z << level, (lv.z + 1) << level); 106 | distMarch += AABB_exit(&box, pos + offsetD, invD) + OFFSET; 107 | } 108 | return false; 109 | } 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /src/main/opencl/kernel/include/primitives.h: -------------------------------------------------------------------------------- 1 | #ifndef CHUNKYCLPLUGIN_PRIMITIVES_H 2 | #define CHUNKYCLPLUGIN_PRIMITIVES_H 3 | 4 | #include "../opencl.h" 5 | #include "wavefront.h" 6 | #include "constants.h" 7 | 8 | typedef struct { 9 | float xmin; 10 | float xmax; 11 | float ymin; 12 | float ymax; 13 | float zmin; 14 | float zmax; 15 | } AABB; 16 | 17 | AABB AABB_new(float xmin, float xmax, float ymin, float ymax, float zmin, float zmax) { 18 | AABB box; 19 | box.xmin = xmin; 20 | box.xmax = xmax; 21 | box.ymin = ymin; 22 | box.ymax = ymax; 23 | box.zmin = zmin; 24 | box.zmax = zmax; 25 | return box; 26 | } 27 | 28 | // Test for an intersection. Returns NaN if there was no intersection. 29 | // Returns the distance if there was an intersection. 30 | float AABB_quick_intersect(AABB* self, float3 origin, float3 invDir) { 31 | float3 minVals = (float3) (self->xmin, self->ymin, self->zmin); 32 | float3 maxVals = (float3) (self->xmax, self->ymax, self->zmax); 33 | 34 | float3 t1s = (minVals - origin) * invDir; 35 | float3 t2s = (maxVals - origin) * invDir; 36 | 37 | float3 tmins = fmin(t1s, t2s); 38 | float3 tmaxs = fmax(t1s, t2s); 39 | 40 | float tmin = fmax(tmins.x, fmax(tmins.y, tmins.z)); 41 | float tmax = fmin(tmaxs.x, fmin(tmaxs.y, tmaxs.z)); 42 | 43 | if (tmax < tmin) { 44 | return NAN; 45 | } else { 46 | return tmin; 47 | } 48 | } 49 | 50 | 51 | // Exit from an AABB 52 | float AABB_exit(AABB* self, float3 origin, float3 invDir) { 53 | float3 minVals = (float3) (self->xmin, self->ymin, self->zmin); 54 | float3 maxVals = (float3) (self->xmax, self->ymax, self->zmax); 55 | 56 | float3 t1s = (minVals - origin) * invDir; 57 | float3 t2s = (maxVals - origin) * invDir; 58 | 59 | float3 tmaxs = fmax(t1s, t2s); 60 | return fmin(tmaxs.x, fmin(tmaxs.y, tmaxs.z)); 61 | } 62 | 63 | // Test for an intersection and calculate the normal and UV. 64 | // Returns NaN if there was no intersection. Returns the distance if 65 | // there was an intersection. 66 | float AABB_full_intersect(AABB* self, float3 origin, float3 dir, float3 invDir, float3* normal, float2* uv) { 67 | float3 minVals = (float3) (self->xmin, self->ymin, self->zmin); 68 | float3 maxVals = (float3) (self->xmax, self->ymax, self->zmax); 69 | 70 | float3 t1s = (minVals - origin) * invDir; 71 | float3 t2s = (maxVals - origin) * invDir; 72 | 73 | float3 tmins = fmin(t1s, t2s); 74 | float3 tmaxs = fmax(t1s, t2s); 75 | 76 | float tmin = fmax(tmins.x, fmax(tmins.y, tmins.z)); 77 | float tmax = fmin(tmaxs.x, fmin(tmaxs.y, tmaxs.z)); 78 | 79 | // No intersection 80 | if (tmax < tmin) { 81 | return NAN; 82 | } 83 | 84 | float3 o = origin + tmin * dir; 85 | float3 d = 1 / (maxVals - minVals); 86 | if (t1s.x == tmin) { 87 | *uv = (float2) (1 - (o.z - self->zmin) * d.z, (o.y - self->ymin) * d.y); 88 | *normal = (float3) (-1, 0, 0); 89 | } 90 | if (t2s.x == tmin) { 91 | *uv = (float2) ((o.z - self->zmin) * d.z, (o.y - self->ymin) * d.y); 92 | *normal = (float3) (1, 0, 0); 93 | } 94 | if (t1s.y == tmin) { 95 | *uv = (float2) ((o.x - self->xmin) * d.x, 1 - (o.z - self->zmin) * d.z); 96 | *normal = (float3) (0, -1, 0); 97 | } 98 | if (t2s.y == tmin) { 99 | *uv = (float2) ((o.x - self->xmin) * d.x, (o.z - self->zmin) * d.z); 100 | *normal = (float3) (0, 1, 0); 101 | } 102 | if (t1s.z == tmin) { 103 | *uv = (float2) ((o.x - self->xmin) * d.x, (o.y - self->ymin) * d.y); 104 | *normal = (float3) (0, 0, -1); 105 | } 106 | if (t2s.z == tmin) { 107 | *uv = (float2) (1 - (o.x - self->xmin) * d.x, (o.y - self->ymin) * d.y); 108 | *normal = (float3) (0, 0, 1); 109 | } 110 | 111 | return tmin; 112 | } 113 | 114 | // Test for an intersection and calculate the normal and UV. 115 | // Returns NaN if there was no intersection. Returns the distance if 116 | // there was an intersection. Uses full block uv mapping. 117 | float AABB_full_intersect_map_2(AABB* self, float3 origin, float3 dir, float3 invDir, float3* normal, float2* uv) { 118 | float3 minVals = (float3) (self->xmin, self->ymin, self->zmin); 119 | float3 maxVals = (float3) (self->xmax, self->ymax, self->zmax); 120 | 121 | float3 t1s = (minVals - origin) * invDir; 122 | float3 t2s = (maxVals - origin) * invDir; 123 | 124 | float3 tmins = fmin(t1s, t2s); 125 | float3 tmaxs = fmax(t1s, t2s); 126 | 127 | float tmin = fmax(tmins.x, fmax(tmins.y, tmins.z)); 128 | float tmax = fmin(tmaxs.x, fmin(tmaxs.y, tmaxs.z)); 129 | 130 | // No intersection 131 | if (tmax < tmin) { 132 | return NAN; 133 | } 134 | 135 | float3 o = origin + tmin * dir; 136 | if (t1s.x == tmin) { 137 | *uv = (float2) (o.z, o.y); 138 | *normal = (float3) (-1, 0, 0); 139 | } 140 | if (t2s.x == tmin) { 141 | *uv = (float2) (1 - o.z, o.y); 142 | *normal = (float3) (1, 0, 0); 143 | } 144 | if (t1s.y == tmin) { 145 | *uv = (float2) (o.x, o.z); 146 | *normal = (float3) (0, -1, 0); 147 | } 148 | if (t2s.y == tmin) { 149 | *uv = (float2) (o.x, 1 - o.z); 150 | *normal = (float3) (0, 1, 0); 151 | } 152 | if (t1s.z == tmin) { 153 | *uv = (float2) (1 - o.x, o.y); 154 | *normal = (float3) (0, 0, -1); 155 | } 156 | if (t2s.z == tmin) { 157 | *uv = (float2) (o.x, o.y); 158 | *normal = (float3) (0, 0, 1); 159 | } 160 | 161 | return tmin; 162 | } 163 | 164 | 165 | #define TEX_AABB_SIZE 13 166 | 167 | typedef struct { 168 | AABB box; 169 | int mn; 170 | int me; 171 | int ms; 172 | int mw; 173 | int mt; 174 | int mb; 175 | int flags; 176 | } TexturedAABB; 177 | 178 | TexturedAABB TexturedAABB_new(__global const int* aabbModels, int index) { 179 | __global const int* model = aabbModels + index; 180 | 181 | TexturedAABB b; 182 | b.box = AABB_new( 183 | as_float(model[0]), 184 | as_float(model[1]), 185 | as_float(model[2]), 186 | as_float(model[3]), 187 | as_float(model[4]), 188 | as_float(model[5]) 189 | ); 190 | b.flags = model[6]; 191 | b.mn = model[7]; 192 | b.me = model[8]; 193 | b.ms = model[9]; 194 | b.mw = model[10]; 195 | b.mt = model[11]; 196 | b.mb = model[12]; 197 | return b; 198 | } 199 | 200 | float TexturedAABB_intersect(TexturedAABB* self, float distance, float3 origin, float3 dir, float3 invDir, float3* normal, float2* uv, int* material) { 201 | float3 normal_temp; 202 | float2 uv_temp; 203 | 204 | float dist = AABB_full_intersect_map_2(&self->box, origin, dir, invDir, &normal_temp, &uv_temp); 205 | if (dist >= distance || dist < -EPS) { 206 | return NAN; 207 | } 208 | 209 | int mat; 210 | int flags = 0; 211 | if (normal_temp.z == -1) { 212 | mat = self->mn; 213 | flags = self->flags; 214 | } 215 | if (normal_temp.x == 1) { 216 | mat = self->me; 217 | flags = self->flags >> 4; 218 | } 219 | if (normal_temp.z == -1) { 220 | mat = self->ms; 221 | flags = self->flags >> 8; 222 | } 223 | if (normal_temp.x == -1) { 224 | mat = self->mw; 225 | flags = self->flags >> 12; 226 | } 227 | if (normal_temp.y == 1) { 228 | mat = self->mt; 229 | flags = self->flags >> 16; 230 | } 231 | if (normal_temp.y == -1) { 232 | mat = self->mb; 233 | flags = self->flags >> 20; 234 | } 235 | 236 | // No hit 237 | if (flags & 0b1000) { 238 | return NAN; 239 | } 240 | 241 | // Flip U 242 | if (flags & 0b0100) { 243 | uv_temp.x = 1 - uv_temp.x; 244 | } 245 | 246 | // Flip V 247 | if (flags & 0b0010) { 248 | uv_temp.y = 1 - uv_temp.y; 249 | } 250 | 251 | // Swap 252 | if (flags & 0b0001) { 253 | uv_temp = uv_temp.yx; 254 | } 255 | 256 | *material = mat; 257 | *normal = normal_temp; 258 | *uv = uv_temp; 259 | return dist; 260 | } 261 | 262 | 263 | #define QUAD_SIZE 15 264 | 265 | typedef struct { 266 | float3 origin; 267 | float3 xv; 268 | float3 yv; 269 | float4 uv; 270 | int material; 271 | int flags; 272 | } Quad; 273 | 274 | Quad Quad_new(__global const int* quadModels, int index) { 275 | Quad q; 276 | q.origin.x = as_float(quadModels[index + 0]); 277 | q.origin.y = as_float(quadModels[index + 1]); 278 | q.origin.z = as_float(quadModels[index + 2]); 279 | 280 | q.xv.x = as_float(quadModels[index + 3]); 281 | q.xv.y = as_float(quadModels[index + 4]); 282 | q.xv.z = as_float(quadModels[index + 5]); 283 | 284 | q.yv.x = as_float(quadModels[index + 6]); 285 | q.yv.y = as_float(quadModels[index + 7]); 286 | q.yv.z = as_float(quadModels[index + 8]); 287 | 288 | q.uv.x = as_float(quadModels[index + 9]); 289 | q.uv.y = as_float(quadModels[index + 10]); 290 | q.uv.z = as_float(quadModels[index + 11]); 291 | q.uv.w = as_float(quadModels[index + 12]); 292 | 293 | q.material = quadModels[index + 13]; 294 | q.flags = quadModels[index + 14]; 295 | return q; 296 | } 297 | 298 | float Quad_intersect(Quad* self, float distance, float3 origin, float3 dir, float3* normal, float2* uv) { 299 | float3 n = normalize(cross(self->xv, self->yv)); 300 | 301 | float denom = dot(dir, n); 302 | if (denom < -EPS) { 303 | float t = -(dot(origin, n) - dot(n, self->origin)) / denom; 304 | if (t > -EPS && t < distance) { 305 | float3 pt = origin + dir*t - self->origin; 306 | float u = dot(pt, self->xv) / dot(self->xv, self->xv); 307 | float v = dot(pt, self->yv) / dot(self->yv, self->yv); 308 | 309 | if (u >= 0 && u <= 1 && v >= 0 && v <= 1) { 310 | *uv = (float2) (self->uv.x + (u * self->uv.y), self->uv.z + (v * self->uv.w)); 311 | *normal = n; 312 | 313 | return t; 314 | } 315 | } 316 | } 317 | 318 | return NAN; 319 | } 320 | 321 | #define TRIANGLE_SIZE 20 322 | 323 | typedef struct { 324 | int flags; 325 | float3 e1; 326 | float3 e2; 327 | float3 o; 328 | float3 n; 329 | float2 t1; 330 | float2 t2; 331 | float2 t3; 332 | int material; 333 | } Triangle; 334 | 335 | Triangle Triangle_new(__global const int* trigModels, int index) { 336 | Triangle t; 337 | t.flags = trigModels[index + 0]; 338 | 339 | t.e1.x = as_float(trigModels[index + 1]); 340 | t.e1.y = as_float(trigModels[index + 2]); 341 | t.e1.z = as_float(trigModels[index + 3]); 342 | 343 | t.e2.x = as_float(trigModels[index + 4]); 344 | t.e2.y = as_float(trigModels[index + 5]); 345 | t.e2.z = as_float(trigModels[index + 6]); 346 | 347 | t.o.x = as_float(trigModels[index + 7]); 348 | t.o.y = as_float(trigModels[index + 8]); 349 | t.o.z = as_float(trigModels[index + 9]); 350 | 351 | t.n.x = as_float(trigModels[index + 10]); 352 | t.n.y = as_float(trigModels[index + 11]); 353 | t.n.z = as_float(trigModels[index + 12]); 354 | 355 | t.t1.x = as_float(trigModels[index + 13]); 356 | t.t1.y = as_float(trigModels[index + 14]); 357 | 358 | t.t2.x = as_float(trigModels[index + 15]); 359 | t.t2.y = as_float(trigModels[index + 16]); 360 | 361 | t.t3.x = as_float(trigModels[index + 17]); 362 | t.t3.y = as_float(trigModels[index + 18]); 363 | 364 | t.material = trigModels[index + 19]; 365 | return t; 366 | } 367 | 368 | float Triangle_intersect(Triangle* self, float distance, float3 origin, float3 dir, float3* normal, float2* uv, int* material) { 369 | float3 pvec, qvec, tvec; 370 | 371 | pvec = cross(dir, self->e2); 372 | float det = dot(self->e1, pvec); 373 | if ((self->flags >> 8) & 1) { 374 | if (det > -EPS && det < EPS) 375 | return NAN; 376 | } else if (det > -EPS) { 377 | return NAN; 378 | } 379 | float recip = 1 / det; 380 | 381 | tvec = origin - self->o; 382 | 383 | float u = dot(tvec, pvec) * recip; 384 | 385 | if (u < 0 || u > 1) 386 | return NAN; 387 | 388 | qvec = cross(tvec, self->e1); 389 | 390 | float v = dot(dir, qvec) * recip; 391 | 392 | if (v < 0 || (u+v) > 1) 393 | return NAN; 394 | 395 | float t = dot(self->e2, qvec) * recip; 396 | 397 | if (t > EPS && t < distance) { 398 | float w = 1 - u - v; 399 | *uv = (float2) ( 400 | self->t1.x * u + self->t2.x * v + self->t3.x * w, 401 | self->t1.y * u + self->t2.y * v + self->t3.y * w 402 | ); 403 | *normal = self->n; 404 | *material = self->material; 405 | return t; 406 | } 407 | 408 | return NAN; 409 | } 410 | 411 | #endif -------------------------------------------------------------------------------- /src/main/opencl/kernel/include/randomness.h: -------------------------------------------------------------------------------- 1 | #ifndef CHUNKYCLPLUGIN_RANDOMNESS_H 2 | #define CHUNKYCLPLUGIN_RANDOMNESS_H 3 | 4 | // PCG hash from 5 | // https://www.reedbeta.com/blog/hash-functions-for-gpu-rendering/ 6 | unsigned int Random_nextState(unsigned int *state) { 7 | *state = *state * 47796405u + 2891336453u; 8 | *state = ((*state >> ((*state >> 28u) + 4u)) ^ *state) * 277803737u; 9 | *state = (*state >> 22u) ^ *state; 10 | return *state; 11 | } 12 | 13 | // Calculate the next float based on the formula on 14 | // https://docs.oracle.com/javase/8/docs/api/java/util/Random.html#nextFloat-- 15 | float Random_nextFloat(unsigned int *state) { 16 | return (Random_nextState(state) >> 8) / ((float) (1 << 24)); 17 | } 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/main/opencl/kernel/include/rayTracer.cl: -------------------------------------------------------------------------------- 1 | #include "../opencl.h" 2 | #include "octree.h" 3 | #include "wavefront.h" 4 | #include "block.h" 5 | #include "material.h" 6 | #include "kernel.h" 7 | #include "camera.h" 8 | #include "bvh.h" 9 | #include "sky.h" 10 | 11 | __kernel void render( 12 | __global const int* projectorType, 13 | __global const float* cameraSettings, 14 | 15 | __global const int* octreeDepth, 16 | __global const int* octreeData, 17 | 18 | __global const int* bPalette, 19 | __global const int* quadModels, 20 | __global const int* aabbModels, 21 | 22 | __global const int* worldBvhData, 23 | __global const int* actorBvhData, 24 | __global const int* bvhTrigs, 25 | 26 | image2d_array_t textureAtlas, 27 | __global const int* matPalette, 28 | 29 | image2d_t skyTexture, 30 | __global const float* skyIntensity, 31 | __global const int* sunData, 32 | 33 | __global const int* randomSeed, 34 | __global const int* bufferSpp, 35 | __global const int* width, 36 | __global const int* height, 37 | __global float* res 38 | 39 | ) { 40 | int gid = get_global_id(0); 41 | 42 | Pixel pixel = Pixel_new(gid); 43 | Ray ray = Ray_new(&pixel); 44 | IntersectionRecord record = IntersectionRecord_new(&ray); 45 | MaterialPalette materialPalette = MaterialPalette_new(matPalette); 46 | 47 | Octree octree = Octree_create(octreeData, *octreeDepth); 48 | Bvh worldBvh = Bvh_new(worldBvhData, bvhTrigs, &materialPalette); 49 | Bvh actorBvh = Bvh_new(actorBvhData, bvhTrigs, &materialPalette); 50 | 51 | BlockPalette blockPalette = BlockPalette_new(bPalette, quadModels, aabbModels, &materialPalette); 52 | 53 | Sun sun = Sun_new(sunData); 54 | 55 | unsigned int randomState = *randomSeed + gid; 56 | unsigned int* state = &randomState; 57 | Random_nextState(state); 58 | 59 | // Set camera 60 | if (*projectorType != -1) { 61 | float3 cameraPos = vload3(0, cameraSettings); 62 | float3 m1s = vload3(1, cameraSettings); 63 | float3 m2s = vload3(2, cameraSettings); 64 | float3 m3s = vload3(3, cameraSettings); 65 | 66 | float halfWidth = (*width) / (2.0 * (*height)); 67 | float invHeight = 1.0 / (*height); 68 | float x = -halfWidth + ((pixel.index % (*width)) + Random_nextFloat(state)) * invHeight; 69 | float y = -0.5 + ((pixel.index / (*width)) + Random_nextFloat(state)) * invHeight; 70 | 71 | switch (*projectorType) { 72 | case 0: 73 | Camera_pinHole(x, y, state, &ray.origin, &ray.direction, cameraSettings+12); 74 | break; 75 | } 76 | 77 | ray.direction = (float3) ( 78 | dot(m1s, ray.direction), 79 | dot(m2s, ray.direction), 80 | dot(m3s, ray.direction) 81 | ); 82 | ray.origin = (float3) ( 83 | dot(m1s, ray.origin), 84 | dot(m2s, ray.origin), 85 | dot(m3s, ray.origin) 86 | ); 87 | 88 | ray.origin += cameraPos; 89 | } else { 90 | Camera_preGenerated(&ray, cameraSettings); 91 | } 92 | 93 | do { 94 | if (!closestIntersect(&record, &octree, &blockPalette, textureAtlas, 256, &worldBvh, &actorBvh)) { 95 | record.emittance = 1; 96 | intersectSky(&record, textureAtlas, &sun, skyTexture, *skyIntensity); 97 | break; 98 | } 99 | applyRayColor(&record, 13.0f); 100 | 101 | if (Sun_sampleDirection(&sun, &record, state)) { 102 | IntersectionRecord sampleRecord = IntersectionRecord_copy(&record); 103 | if (!closestIntersect(&sampleRecord, &octree, &blockPalette, textureAtlas, 256, &worldBvh, &actorBvh)) { 104 | intersectSky(&sampleRecord, textureAtlas, &sun, skyTexture, * skyIntensity); 105 | } 106 | } 107 | } while (nextPath(&record, state, 5)); 108 | 109 | int spp = *bufferSpp; 110 | float3 bufferColor = vload3(gid, res); 111 | bufferColor = (bufferColor * spp + pixel.color) / (spp + 1); 112 | vstore3(bufferColor, gid, res); 113 | } 114 | 115 | __kernel void preview( 116 | __global const int* projectorType, 117 | __global const float* cameraSettings, 118 | 119 | __global const int* octreeDepth, 120 | __global const int* octreeData, 121 | 122 | __global const int* bPalette, 123 | __global const int* quadModels, 124 | __global const int* aabbModels, 125 | 126 | __global const int* worldBvhData, 127 | __global const int* actorBvhData, 128 | __global const int* bvhTrigs, 129 | 130 | image2d_array_t textureAtlas, 131 | __global const int* matPalette, 132 | 133 | image2d_t skyTexture, 134 | __global const float* skyIntensity, 135 | __global const int* sunData, 136 | 137 | __global const int* width, 138 | __global const int* height, 139 | __global int* res 140 | ) { 141 | int gid = get_global_id(0); 142 | int px = gid % *width; 143 | int py = gid / *width; 144 | 145 | // Crosshairs? 146 | if ((px == *width / 2 && (py >= *height / 2 - 5 && py <= *height / 2 + 5)) || 147 | (py == *height / 2 && (px >= *width / 2 - 5 && px <= *width / 2 + 5))) { 148 | res[gid] = 0xFFFFFFFF; 149 | return; 150 | } 151 | 152 | Pixel pixel = Pixel_new(gid); 153 | Ray ray = Ray_new(&pixel); 154 | IntersectionRecord record = IntersectionRecord_new(&ray); 155 | MaterialPalette materialPalette = MaterialPalette_new(matPalette); 156 | 157 | Octree octree = Octree_create(octreeData, *octreeDepth); 158 | Bvh worldBvh = Bvh_new(worldBvhData, bvhTrigs, &materialPalette); 159 | Bvh actorBvh = Bvh_new(actorBvhData, bvhTrigs, &materialPalette); 160 | 161 | BlockPalette blockPalette = BlockPalette_new(bPalette, quadModels, aabbModels, &materialPalette); 162 | 163 | Sun sun = Sun_new(sunData); 164 | 165 | unsigned int randomState = 0; 166 | unsigned int* state = &randomState; 167 | Random_nextState(state); 168 | 169 | // Set camera 170 | if (*projectorType != -1) { 171 | float3 cameraPos = vload3(0, cameraSettings); 172 | float3 m1s = vload3(1, cameraSettings); 173 | float3 m2s = vload3(2, cameraSettings); 174 | float3 m3s = vload3(3, cameraSettings); 175 | 176 | float halfWidth = (*width) / (2.0 * (*height)); 177 | float invHeight = 1.0 / (*height); 178 | float x = -halfWidth + ((pixel.index % (*width)) + Random_nextFloat(state)) * invHeight; 179 | float y = -0.5 + ((pixel.index / (*width)) + Random_nextFloat(state)) * invHeight; 180 | 181 | switch (*projectorType) { 182 | case 0: 183 | Camera_pinHole(x, y, state, &ray.origin, &ray.direction, cameraSettings+12); 184 | break; 185 | } 186 | ray.direction = normalize(ray.direction); 187 | 188 | ray.direction = (float3) ( 189 | dot(m1s, ray.direction), 190 | dot(m2s, ray.direction), 191 | dot(m3s, ray.direction) 192 | ); 193 | ray.origin = (float3) ( 194 | dot(m1s, ray.origin), 195 | dot(m2s, ray.origin), 196 | dot(m3s, ray.origin) 197 | ); 198 | 199 | ray.origin += cameraPos; 200 | } else { 201 | Camera_preGenerated(&ray, cameraSettings); 202 | } 203 | 204 | if (closestIntersect(&record, &octree, &blockPalette, textureAtlas, 256, &worldBvh, &actorBvh)) { 205 | float shading = dot(record.normal, (float3) (0.25, 0.866, 0.433)); 206 | shading = fmax(0.3f, shading); 207 | record.color *= shading; 208 | } else { 209 | record.emittance = 1; 210 | intersectSky(&record, textureAtlas, &sun, skyTexture, *skyIntensity); 211 | } 212 | 213 | record.color = sqrt(record.color); 214 | 215 | int3 rgb = intFloorFloat3(clamp(float4toFloat3(record.color) * 255.0f, 0.0f, 255.0f)); 216 | res[gid] = 0xFF000000 | (rgb.x << 16) | (rgb.y << 8) | rgb.z; 217 | } 218 | -------------------------------------------------------------------------------- /src/main/opencl/kernel/include/sky.h: -------------------------------------------------------------------------------- 1 | #ifndef CHUNKYCLPLUGIN_SKY_H 2 | #define CHUNKYCLPLUGIN_SKY_H 3 | 4 | #include "../opencl.h" 5 | #include "wavefront.h" 6 | #include "textureAtlas.h" 7 | #include "randomness.h" 8 | 9 | typedef struct { 10 | int flags; 11 | int textureSize; 12 | int texture; 13 | float intensity; 14 | float3 su; 15 | float3 sv; 16 | float3 sw; 17 | } Sun; 18 | 19 | Sun Sun_new(__global const int* data) { 20 | Sun sun; 21 | sun.flags = data[0]; 22 | sun.textureSize = data[1]; 23 | sun.texture = data[2]; 24 | sun.intensity = as_float(data[3]); 25 | 26 | float phi = as_float(data[4]); 27 | float theta = as_float(data[5]); 28 | float r = fabs(cos(phi)); 29 | 30 | sun.sw = (float3) (cos(theta) * r, sin(phi), sin(theta) * r); 31 | if (fabs(sun.sw.x) > 0.1f) { 32 | sun.su = (float3) (0, 1, 0); 33 | } else { 34 | sun.su = (float3) (1, 0, 0); 35 | } 36 | sun.sv = normalize(cross(sun.sw, sun.su)); 37 | sun.su = cross(sun.sv, sun.sw); 38 | 39 | return sun; 40 | } 41 | 42 | bool Sun_intersect(Sun* self, IntersectionRecord* record, image2d_array_t atlas) { 43 | float3 direction = record->ray->direction; 44 | 45 | if (!(self->flags & 1) || dot(direction, self->sw) < 0.5f) { 46 | return false; 47 | } 48 | 49 | float radius = 0.03; 50 | 51 | float width = radius * 4; 52 | float width2 = width * 2; 53 | float a = M_PI_2_F - acos(dot(direction, self->su)) + width; 54 | if (a >= 0 && a < width2) { 55 | float b = M_PI_2_F - acos(dot(direction, self->sv)) + width; 56 | if (b >= 0 && b < width2) { 57 | float4 color = Atlas_read_uv(a / width2, b / width2, 58 | self->texture, self->textureSize, atlas); 59 | color *= self->intensity; 60 | record->color += color; 61 | return true; 62 | } 63 | } 64 | 65 | return false; 66 | } 67 | 68 | bool Sun_sampleDirection(Sun* self, IntersectionRecord* record, unsigned int* state) { 69 | if (!(self->flags & 1)) { 70 | return false; 71 | } 72 | 73 | float radius_cos = cos(0.03f); 74 | 75 | float x1 = Random_nextFloat(state); 76 | float x2 = Random_nextFloat(state); 77 | 78 | float cos_a = 1 - x1 + x1 * radius_cos; 79 | float sin_a = sqrt(1 - cos_a * cos_a); 80 | float phi = 2 * M_PI_F * x2; 81 | 82 | float3 u = self->su * (cos(phi) * sin_a); 83 | float3 v = self->sv * (sin(phi) * sin_a); 84 | float3 w = self->sw * cos_a; 85 | 86 | record->ray->direction = u * v; 87 | record->ray->direction += w; 88 | record->ray->direction = normalize(record->ray->direction); 89 | 90 | record->emittance = fabs(dot(record->ray->direction, record->normal)); 91 | 92 | return true; 93 | } 94 | 95 | const sampler_t skySampler = CLK_NORMALIZED_COORDS_TRUE | CLK_ADDRESS_MIRRORED_REPEAT | CLK_FILTER_LINEAR; 96 | 97 | void Sky_intersect(IntersectionRecord* record, image2d_t skyTexture, float skyIntensity) { 98 | float3 direction = record->ray->direction; 99 | 100 | float theta = atan2(direction.z, direction.x); 101 | theta /= M_PI_F * 2; 102 | theta = fmod(fmod(theta, 1) + 1, 1); 103 | float phi = (asin(clamp(direction.y, -1.0f, 1.0f)) + M_PI_2_F) * M_1_PI_F; 104 | 105 | record->color = read_imagef(skyTexture, skySampler, (float2) (theta, phi)) * skyIntensity; 106 | } 107 | 108 | #endif 109 | -------------------------------------------------------------------------------- /src/main/opencl/kernel/include/textureAtlas.h: -------------------------------------------------------------------------------- 1 | #ifndef CHUNKYCL_TEXTURE_ATLAS_H 2 | #define CHUNKYCL_TEXTURE_ATLAS_H 3 | 4 | #include "../opencl.h" 5 | #include "constants.h" 6 | 7 | // Image sampler for texture atlases. 8 | const sampler_t Atlas_sampler = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP_TO_EDGE | CLK_FILTER_NEAREST; 9 | 10 | float4 Atlas_read_xy(int x, int y, int location, image2d_array_t atlas) { 11 | x += ((location >> 22) & 0x1FF) * 16; 12 | y += ((location >> 13) & 0x1FF) * 16; 13 | int d = location & 0x7FFFF; 14 | 15 | return read_imagef(atlas, Atlas_sampler, (int4) (x, y, d, 0)); 16 | } 17 | 18 | float4 Atlas_read_uv(float u, float v, int location, int size, image2d_array_t atlas) { 19 | int width = (size >> 16) & 0xFFFF; 20 | int height = size & 0xFFFF; 21 | 22 | v = (1 - v); 23 | 24 | int x = clamp((int) ((u - EPS) * width), 0, width-1); 25 | int y = clamp((int) ((v - EPS) * height), 0, height-1); 26 | 27 | return Atlas_read_xy(x, y, location, atlas); 28 | } 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/main/opencl/kernel/include/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef CHUNKYCL_PLUGIN_UTILS_H 2 | #define CHUNKYCL_PLUGIN_UTILS_H 3 | 4 | #include "../opencl.h" 5 | 6 | float4 colorFromArgb(unsigned int argb) { 7 | float4 color; 8 | color.w = (argb >> 24) & 0xFF; 9 | color.x = (argb >> 16) & 0xFF; 10 | color.y = (argb >> 8) & 0xFF; 11 | color.z = argb & 0xFF; 12 | color /= 256.0f; 13 | return color; 14 | } 15 | 16 | int3 intFloorFloat3(float3 value) { 17 | value = floor(value); 18 | return (int3) (value.x, value.y, value.z); 19 | } 20 | 21 | float3 int3toFloat3(int3 value) { 22 | return (float3) (value.x, value.y, value.z); 23 | } 24 | 25 | float3 float4toFloat3(float4 value) { 26 | return (float3) (value.x, value.y, value.z); 27 | } 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/main/opencl/kernel/include/wavefront.h: -------------------------------------------------------------------------------- 1 | #ifndef CHUNKYCLPLUGIN_WAVEFRONT_H 2 | #define CHUNKYCLPLUGIN_WAVEFRONT_H 3 | 4 | #include "../opencl.h" 5 | 6 | typedef struct { 7 | int index; 8 | 9 | float3 color; 10 | float3 throughput; 11 | } Pixel; 12 | 13 | Pixel Pixel_new(int index) { 14 | Pixel pixel; 15 | pixel.index = index; 16 | pixel.color = (float3) (0, 0, 0); 17 | pixel.throughput = (float3) (1, 1, 1); 18 | return pixel; 19 | } 20 | 21 | typedef struct { 22 | Pixel* pixel; 23 | 24 | float3 origin; 25 | float3 direction; 26 | int material; 27 | 28 | int rayDepth; 29 | } Ray; 30 | 31 | Ray Ray_new(Pixel* pixel) { 32 | Ray ray; 33 | ray.pixel = pixel; 34 | ray.material = 0; 35 | ray.rayDepth = 0; 36 | return ray; 37 | } 38 | 39 | typedef struct { 40 | Pixel* pixel; 41 | Ray* ray; 42 | 43 | float distance; 44 | int material; 45 | 46 | float3 normal; 47 | float3 point; 48 | 49 | float4 color; 50 | float emittance; 51 | } IntersectionRecord; 52 | 53 | IntersectionRecord IntersectionRecord_new(Ray* ray) { 54 | IntersectionRecord record; 55 | record.pixel = ray->pixel; 56 | record.ray = ray; 57 | 58 | record.distance = HUGE_VALF; 59 | record.material = 0; 60 | 61 | return record; 62 | } 63 | 64 | IntersectionRecord IntersectionRecord_copy(IntersectionRecord* other) { 65 | IntersectionRecord record; 66 | record.pixel = other->pixel; 67 | record.ray = other->ray; 68 | 69 | record.distance = other->distance; 70 | record.material = other->material; 71 | 72 | record.normal = other->normal; 73 | record.point = other->normal; 74 | 75 | record.color = other->color; 76 | record.emittance = other->emittance; 77 | return record; 78 | } 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /src/main/opencl/tonemap/.clangd: -------------------------------------------------------------------------------- 1 | CompileFlags: 2 | Add: [-cl-std=CL1.2, -cl-ext=all] -------------------------------------------------------------------------------- /src/main/opencl/tonemap/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.23) 2 | project(ChunkyCL) 3 | 4 | set(CMAKE_C_STANDARD 99) 5 | 6 | include_directories(include) 7 | 8 | add_executable(tonemap 9 | opencl.h 10 | include/double.h 11 | include/post_processing_filter.cl 12 | include/rgba.h) 13 | set_target_properties(tonemap PROPERTIES LINKER_LANGUAGE C) 14 | -------------------------------------------------------------------------------- /src/main/opencl/tonemap/include/double.h: -------------------------------------------------------------------------------- 1 | #ifndef KERNEL_DOUBLE_H 2 | #define KERNEL_DOUBLE_H 3 | 4 | #include "../opencl.h" 5 | 6 | #if defined(cl_khr_fp64) 7 | #pragma OPENCL EXTENSION cl_khr_fp64: enable 8 | #define CL_DOUBLE_SUPPORT 9 | #elif defined(cl_amd_fp64) 10 | #pragma OPENCL EXTENSION cl_amd_fp64: enable 11 | #define CL_DOUBLE_SUPPORT 12 | #endif 13 | 14 | typedef ulong imposter_double; 15 | 16 | /// Convert an (imposter) double to a float. 17 | float idouble_to_float(imposter_double value) { 18 | #ifdef CL_DOUBLE_SUPPORT 19 | return (float) as_double(value); 20 | #else 21 | float sign = ((value >> 63) == 0) ? 1 : -1; 22 | uint exponent = (value >> 52) & 0x7FF; 23 | ulong mantissa = value & ((1ull << 52) - 1); 24 | 25 | if (exponent == 0) { 26 | // Subnormal 27 | return sign * 0.0f; 28 | } else if (exponent == 0x7FF) { 29 | // NAN or INFINITY 30 | if (mantissa == 0) { 31 | return sign * INFINITY; 32 | } else { 33 | return NAN; 34 | } 35 | } else { 36 | return sign * ldexp(1 + ((float) mantissa / (1ull << 52)), exponent - 1023); 37 | } 38 | #endif 39 | } 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/main/opencl/tonemap/include/post_processing_filter.cl: -------------------------------------------------------------------------------- 1 | #include "../opencl.h" 2 | #include "double.h" 3 | #include "rgba.h" 4 | 5 | __kernel void filter( 6 | const int width, 7 | const int height, 8 | const float exposure, 9 | __global const imposter_double* input, 10 | __global unsigned int* res, 11 | const int type 12 | ) { 13 | int gid = get_global_id(0); 14 | int offset = gid * 3; 15 | 16 | float color_float[3]; 17 | for (int i = 0; i < 3; i++) { 18 | color_float[i] = idouble_to_float(input[offset + i]); 19 | } 20 | float3 color = vload3(0, color_float); 21 | color *= exposure; 22 | 23 | switch (type) { 24 | case 0: 25 | // GAMMA 26 | color = pow(color, 1.0 / 2.2); 27 | break; 28 | case 1: 29 | // TONEMAP1 30 | color = fmax((float3)(0), color - 0.004f); 31 | color = (color * (6.2f * color + 0.5f)) / (color * (6.2f * color + 1.7f) + 0.06f); 32 | break; 33 | case 2: 34 | // ACES 35 | color = (color * (2.51f * color + 0.03f)) / (color * (2.43f * color + 0.59f) + 0.14f); 36 | color = clamp(color, (float3)(0), (float3)(1)); 37 | color = pow(color, 1.0 / 2.2); 38 | break; 39 | case 3: 40 | // HABLE 41 | color *= 16; 42 | color = ((color * (0.15f * color + 0.10f * 0.50f) + 0.20f * 0.02f) / (color * (0.15f * color + 0.50f) + 0.20f * 0.30f)) - 0.02f / 0.30f; 43 | color /= (((11.2f * (0.15f * 11.2f + 0.10f * 0.50f) + 0.20f * 0.02f) / (11.2f * (0.15f * 11.2f + 0.50f) + 0.20f * 0.30f)) - 0.02f / 0.30f); 44 | break; 45 | } 46 | 47 | float4 pixel; 48 | pixel.xyz = color; 49 | pixel.w = 1; 50 | res[gid] = color_to_argb(pixel); 51 | } 52 | -------------------------------------------------------------------------------- /src/main/opencl/tonemap/include/rgba.h: -------------------------------------------------------------------------------- 1 | #ifndef TONEMAP_RGBA_H 2 | #define TONEMAP_RGBA_H 3 | 4 | #include "../opencl.h" 5 | 6 | unsigned int color_to_argb(float4 color) { 7 | color *= 255.0f; 8 | color += 0.5f; 9 | uint4 argb = (uint4) ( 10 | color.x, 11 | color.y, 12 | color.z, 13 | color.w 14 | ); 15 | argb = clamp(argb, (uint4) (0, 0, 0, 0), (uint4) (255, 255, 255, 255)); 16 | return (argb.w << 24) | (argb.x << 16) | (argb.y << 8) | argb.z << 0; 17 | } 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/main/resources/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ChunkyCl", 3 | "author": "ThatRedox", 4 | "main": "dev.thatredox.chunkynative.opencl.ChunkyCl", 5 | "version": "0.2", 6 | "targetVersion": "2.5.0", 7 | "description": "OpenCL rendering for Chunky." 8 | } 9 | --------------------------------------------------------------------------------