├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src ├── main ├── kotlin │ └── me │ │ └── hydos │ │ └── rosella │ │ ├── audio │ │ └── SoundManager.kt │ │ ├── render │ │ ├── Vk.kt │ │ ├── material │ │ │ └── Material.kt │ │ ├── model │ │ │ ├── AssimpHelper.kt │ │ │ ├── GuiRenderObject.kt │ │ │ └── ModelLoader.kt │ │ ├── resource │ │ │ ├── CascadingResourceLoader.kt │ │ │ ├── ClassLoaderResourceLoader.kt │ │ │ ├── Global.kt │ │ │ ├── Identifier.kt │ │ │ ├── Resource.kt │ │ │ └── ResourceLoader.kt │ │ ├── shader │ │ │ ├── RawShaderProgram.kt │ │ │ ├── Shader.kt │ │ │ ├── ShaderManager.kt │ │ │ ├── ShaderProgram.kt │ │ │ ├── pushconstant │ │ │ │ └── ModelPushConstant.kt │ │ │ └── ubo │ │ │ │ └── Ubo.kt │ │ ├── swapchain │ │ │ ├── DepthBuffer.kt │ │ │ └── Frame.kt │ │ ├── texture │ │ │ ├── StbiImage.kt │ │ │ ├── Texture.kt │ │ │ ├── TextureImage.kt │ │ │ └── UploadableImage.kt │ │ └── util │ │ │ ├── SprirVUtils.kt │ │ │ └── VkUtils.kt │ │ └── scene │ │ ├── Scene.kt │ │ └── SceneModel.kt └── resources │ └── rosella │ ├── editor │ └── gui │ │ ├── files │ │ ├── class.png │ │ ├── file.png │ │ └── interface.png │ │ └── folder.png │ ├── fonts │ └── DIN Bold.otf │ └── shaders │ ├── base.f.glsl │ ├── base.v.glsl │ ├── fonts.f.glsl │ ├── fonts.v.glsl │ ├── gui.colour.f.glsl │ ├── gui.f.glsl │ └── gui.v.glsl └── test └── java └── me └── hydos └── rosella └── example └── PortalJava.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | run/ 3 | out/ 4 | target/ 5 | .gradle/ 6 | build/ 7 | *.log 8 | *.iml 9 | *.class 10 | *.kotlin_module 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rosella 2 | an work in progress, vulkan, modern jvm render engine 3 | 4 | ## Goals 5 | 1. give people an opportunity to learn vulkan. 6 | 2. be efficient 7 | 3. be "just enough" abstraction 8 | 9 | ## Progress 10 | Currently im doing: 11 | #### Fonts :tm: 12 | 13 | ## Long Term goals 14 | 1. use "modern rendering techniques" such as gpu driven rendering (https://vkguide.dev/docs/gpudriven) 15 | 2. easy to use both in java and kotlin 16 | 3. document everything 17 | 18 | P.S: im also working on some portal 2 related stuff with the engine 19 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.gradle.internal.os.OperatingSystem 2 | 3 | plugins { 4 | java 5 | kotlin("jvm") version "1.5.10" 6 | id("com.github.johnrengelman.shadow") version "7.0.0" 7 | } 8 | 9 | group = "me.hydos" 10 | version = "1.0-SNAPSHOT" 11 | 12 | val lwjglVersion = "3.3.0-SNAPSHOT" 13 | val lwjglNatives = when (org.gradle.internal.os.OperatingSystem.current()) { 14 | org.gradle.internal.os.OperatingSystem.LINUX -> System.getProperty("os.arch").let { 15 | if (it.startsWith("arm") || it.startsWith("aarch64")) { 16 | val arch = if (it.contains("64") || it.startsWith("armv8")) { 17 | "arm64" 18 | } else { 19 | "arm32" 20 | } 21 | 22 | "natives-linux-$arch" 23 | } else { 24 | "natives-linux" 25 | } 26 | } 27 | org.gradle.internal.os.OperatingSystem.MAC_OS -> if (System.getProperty("os.arch") 28 | .startsWith("aarch64") 29 | ) "natives-macos-arm64" else "natives-macos" 30 | org.gradle.internal.os.OperatingSystem.WINDOWS -> "natives-windows" 31 | else -> error("Unrecognized or unsupported Operating system. Please set \"lwjglNatives\" manually") 32 | } 33 | 34 | repositories { 35 | mavenCentral() 36 | 37 | maven { 38 | name = "Sonatype Snapshots" 39 | url = uri("https://oss.sonatype.org/content/repositories/snapshots/") 40 | } 41 | } 42 | 43 | dependencies { 44 | api(platform("org.lwjgl:lwjgl-bom:$lwjglVersion")) 45 | 46 | api("org.lwjgl", "lwjgl") 47 | api("org.lwjgl", "lwjgl-assimp") 48 | api("org.lwjgl", "lwjgl-glfw") 49 | api("org.lwjgl", "lwjgl-openal") 50 | api("org.lwjgl", "lwjgl-shaderc") 51 | api("org.lwjgl", "lwjgl-stb") 52 | api("org.lwjgl", "lwjgl-vma") 53 | api("org.lwjgl", "lwjgl-vulkan") 54 | 55 | api("org.joml", "joml", "1.10.1") 56 | api("it.unimi.dsi", "fastutil", "8.5.4") 57 | api("com.google.code.gson", "gson", "2.8.7") 58 | api("org.apache.logging.log4j", "log4j-core", "2.14.1") 59 | 60 | runtimeOnly("org.lwjgl", "lwjgl", classifier = lwjglNatives) 61 | runtimeOnly("org.lwjgl", "lwjgl-assimp", classifier = lwjglNatives) 62 | runtimeOnly("org.lwjgl", "lwjgl-glfw", classifier = lwjglNatives) 63 | runtimeOnly("org.lwjgl", "lwjgl-openal", classifier = lwjglNatives) 64 | runtimeOnly("org.lwjgl", "lwjgl-shaderc", classifier = lwjglNatives) 65 | runtimeOnly("org.lwjgl", "lwjgl-stb", classifier = lwjglNatives) 66 | runtimeOnly("org.lwjgl", "lwjgl-vma", classifier = lwjglNatives) 67 | 68 | if (lwjglNatives == "natives-macos" || lwjglNatives == "natives-macos-arm64") { 69 | runtimeOnly("org.lwjgl", "lwjgl-vulkan", classifier = lwjglNatives) 70 | } 71 | 72 | testImplementation("org.junit.jupiter:junit-jupiter:5.7.1") 73 | } 74 | 75 | tasks.test { 76 | useJUnitPlatform { 77 | } 78 | } 79 | 80 | tasks.register("fastCITest") { 81 | useJUnitPlatform { 82 | excludeTags("exclude_frequent_ci") 83 | } 84 | } 85 | 86 | tasks.register("slowCITest") { 87 | useJUnitPlatform { 88 | } // In the future we can add tags to exclude tests that require certain vulkan features which arent available on github 89 | } 90 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hYdos/Rosella/50a0ee2c5bdfe8255ceb1120f9de4ea7679da9a4/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.0.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 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "Rosella" 2 | 3 | pluginManagement { 4 | repositories { 5 | gradlePluginPortal() 6 | jcenter() 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/audio/SoundManager.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.audio 2 | 3 | import me.hydos.rosella.render.resource.Resource 4 | import org.apache.logging.log4j.LogManager 5 | import org.lwjgl.BufferUtils 6 | import org.lwjgl.openal.AL 7 | import org.lwjgl.openal.AL10.* 8 | import org.lwjgl.openal.ALC 9 | import org.lwjgl.openal.ALC10.alcMakeContextCurrent 10 | import org.lwjgl.openal.ALC11.alcCreateContext 11 | import org.lwjgl.openal.ALC11.alcOpenDevice 12 | import org.lwjgl.openal.EXTThreadLocalContext.alcSetThreadContext 13 | import org.lwjgl.stb.STBVorbis.* 14 | import org.lwjgl.stb.STBVorbisInfo 15 | import org.lwjgl.system.MemoryUtil 16 | import org.lwjgl.system.MemoryUtil.NULL 17 | import java.nio.IntBuffer 18 | import java.nio.ShortBuffer 19 | 20 | object SoundManager { 21 | 22 | @JvmStatic 23 | fun initialize() { 24 | try { 25 | val device = alcOpenDevice(BufferUtils.createByteBuffer(1)) 26 | check(device != NULL) { "Failed to open an OpenAL device." } 27 | 28 | val deviceCaps = ALC.createCapabilities(device) 29 | check(deviceCaps.OpenALC10) 30 | 31 | val context = alcCreateContext(device, null as IntBuffer?) 32 | val useTLC = deviceCaps.ALC_EXT_thread_local_context && alcSetThreadContext(context) 33 | 34 | if (!useTLC) { 35 | check(alcMakeContextCurrent(context)) 36 | } 37 | 38 | AL.createCapabilities(deviceCaps, MemoryUtil::memCallocPointer) 39 | } catch (e: Exception) { 40 | LogManager.getFormatterLogger("Rosella").error("Unable to initialize sound manager: " + e.message) 41 | } 42 | } 43 | 44 | @JvmStatic 45 | fun playback(file: Resource) { 46 | val buffer: Int = alGenBuffers() 47 | val source: Int = alGenSources() 48 | STBVorbisInfo.malloc().use { info -> 49 | val pcm = readVorbis(file, info) 50 | 51 | alBufferData( 52 | buffer, 53 | if (info.channels() == 1) AL_FORMAT_MONO16 else AL_FORMAT_STEREO16, 54 | pcm, 55 | info.sample_rate() 56 | ) 57 | } 58 | 59 | //set up source input 60 | alSourcei(source, AL_BUFFER, buffer) 61 | 62 | //play source 63 | alSourcePlay(source) 64 | 65 | Thread { 66 | while (true) { 67 | try { 68 | Thread.sleep(1000) 69 | } catch (ignored: InterruptedException) { 70 | break 71 | } 72 | 73 | if (alGetSourcei(source, AL_SOURCE_STATE) == AL_STOPPED) { 74 | break 75 | } 76 | } 77 | 78 | alSourceStop(source) 79 | alDeleteSources(source) 80 | alDeleteBuffers(buffer) 81 | }.apply { 82 | isDaemon = true 83 | start() 84 | } 85 | } 86 | 87 | private fun readVorbis(resource: Resource, info: STBVorbisInfo): ShortBuffer { 88 | val vorbis = resource.readAllBytes(true) 89 | val error = BufferUtils.createIntBuffer(1) 90 | val decoder = stb_vorbis_open_memory(vorbis, error, null) 91 | 92 | if (decoder == NULL) { 93 | throw RuntimeException("Failed to open Ogg Vorbis file. Error: " + error[0]) 94 | } 95 | 96 | stb_vorbis_get_info(decoder, info) 97 | 98 | val channels = info.channels() 99 | val pcm = BufferUtils.createShortBuffer(stb_vorbis_stream_length_in_samples(decoder) * channels) 100 | 101 | stb_vorbis_get_samples_short_interleaved(decoder, channels, pcm) 102 | stb_vorbis_close(decoder) 103 | 104 | return pcm 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/Vk.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is for accessing vulkan indirectly. it manages structs so engine code can look better. 3 | */ 4 | @file:JvmName("VkKt") 5 | 6 | package me.hydos.rosella.render 7 | 8 | import me.hydos.rosella.device.QueueFamilyIndices 9 | import me.hydos.rosella.device.VulkanDevice 10 | import me.hydos.rosella.fbo.RenderPass 11 | import me.hydos.rosella.memory.Memory 12 | import me.hydos.rosella.render.renderer.Renderer 13 | import me.hydos.rosella.render.swapchain.DepthBuffer 14 | import me.hydos.rosella.render.swapchain.Swapchain 15 | import me.hydos.rosella.render.texture.ImageRegion 16 | import me.hydos.rosella.render.texture.Texture 17 | import me.hydos.rosella.render.texture.TextureImage 18 | import me.hydos.rosella.render.texture.UploadableImage 19 | import me.hydos.rosella.render.util.ok 20 | import org.lwjgl.PointerBuffer 21 | import org.lwjgl.system.MemoryStack 22 | import org.lwjgl.vulkan.* 23 | import org.lwjgl.vulkan.VK10.* 24 | import java.nio.LongBuffer 25 | 26 | fun allocateCmdBuffers( 27 | stack: MemoryStack, 28 | device: VulkanDevice, 29 | commandPool: Long, 30 | commandBuffersCount: Int, 31 | level: Int = VK_COMMAND_BUFFER_LEVEL_PRIMARY 32 | ): PointerBuffer { 33 | val allocInfo = VkCommandBufferAllocateInfo.callocStack(stack) 34 | .sType(VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO) 35 | .commandPool(commandPool) 36 | .level(level) 37 | .commandBufferCount(commandBuffersCount) 38 | val pCommandBuffers = stack.callocPointer(commandBuffersCount) 39 | vkAllocateCommandBuffers(device.rawDevice, allocInfo, pCommandBuffers).ok() 40 | return pCommandBuffers 41 | } 42 | 43 | fun createBeginInfo(stack: MemoryStack): VkCommandBufferBeginInfo { 44 | return VkCommandBufferBeginInfo.callocStack(stack) 45 | .sType(VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO) 46 | } 47 | 48 | fun createRenderPassInfo(stack: MemoryStack, renderPass: RenderPass): VkRenderPassBeginInfo { 49 | return VkRenderPassBeginInfo.callocStack(stack) 50 | .sType(VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO) 51 | .renderPass(renderPass.renderPass) 52 | } 53 | 54 | fun createRenderArea(stack: MemoryStack, x: Int = 0, y: Int = 0, swapchain: Swapchain): VkRect2D { 55 | return VkRect2D.callocStack(stack) 56 | .offset(VkOffset2D.callocStack(stack).set(x, y)) 57 | .extent(swapchain.swapChainExtent) 58 | } 59 | 60 | fun createImageView(image: Long, format: Int, aspectFlags: Int, device: VulkanDevice): Long { 61 | MemoryStack.stackPush().use { stack -> 62 | val viewInfo = VkImageViewCreateInfo.callocStack(stack) 63 | .sType(VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO) 64 | .image(image) 65 | .viewType(VK_IMAGE_VIEW_TYPE_2D) 66 | .format(format) 67 | viewInfo.subresourceRange().aspectMask(aspectFlags) 68 | .baseMipLevel(0) 69 | .levelCount(1) 70 | .baseArrayLayer(0) 71 | .layerCount(1) 72 | 73 | val pImageView = stack.mallocLong(1) 74 | vkCreateImageView(device.rawDevice, viewInfo, null, pImageView).ok("Failed to create texture image view") 75 | return pImageView[0] 76 | } 77 | } 78 | 79 | fun createImgViews(swapchain: Swapchain, device: VulkanDevice) { 80 | swapchain.swapChainImageViews = ArrayList(swapchain.swapChainImages.size) 81 | for (swapChainImage in swapchain.swapChainImages) { 82 | swapchain.swapChainImageViews.add( 83 | createImageView( 84 | swapChainImage, 85 | swapchain.swapChainImageFormat, 86 | VK_IMAGE_ASPECT_COLOR_BIT, 87 | device 88 | ) 89 | ) 90 | } 91 | } 92 | 93 | fun createCmdPool(device: VulkanDevice, renderer: Renderer, surface: Long) { 94 | MemoryStack.stackPush().use { stack -> 95 | val queueFamilyIndices = findQueueFamilies(device, surface) 96 | val poolInfo = VkCommandPoolCreateInfo.callocStack(stack) 97 | .sType(VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO) 98 | .queueFamilyIndex(queueFamilyIndices.graphicsFamily) 99 | val pCommandPool = stack.mallocLong(1) 100 | vkCreateCommandPool(device.rawDevice, poolInfo, null, pCommandPool).ok() 101 | renderer.commandPool = pCommandPool[0] 102 | } 103 | } 104 | 105 | fun createClearValues( 106 | stack: MemoryStack, 107 | r: Float = 0f, 108 | g: Float = 0f, 109 | b: Float = 0f, 110 | depth: Float = 1.0f, 111 | stencil: Int = 0 112 | ): VkClearValue.Buffer { 113 | val clearValues = VkClearValue.callocStack(2, stack) 114 | clearValues[0].color().float32(stack.floats(r, g, b, 1.0f)) 115 | clearValues[1].depthStencil().set(depth, stencil) 116 | return clearValues 117 | } 118 | 119 | fun beginSingleTimeCommands(renderer: Renderer, device: VulkanDevice): VkCommandBuffer { 120 | MemoryStack.stackPush().use { stack -> 121 | val pCommandBuffer = stack.mallocPointer(1) 122 | return renderer.beginCmdBuffer(stack, pCommandBuffer, device) 123 | } 124 | } 125 | 126 | fun endSingleTimeCommands(commandBuffer: VkCommandBuffer, device: VulkanDevice, renderer: Renderer) { 127 | MemoryStack.stackPush().use { stack -> 128 | vkEndCommandBuffer(commandBuffer) 129 | val submitInfo = VkSubmitInfo.callocStack(1, stack) 130 | .sType(VK_STRUCTURE_TYPE_SUBMIT_INFO) 131 | .pCommandBuffers(stack.pointers(commandBuffer)) 132 | renderer.queues.graphicsQueue.vkQueueSubmit(submitInfo, VK_NULL_HANDLE) 133 | renderer.queues.graphicsQueue.vkQueueWaitIdle() 134 | vkFreeCommandBuffers(device.rawDevice, renderer.commandPool, commandBuffer) 135 | } 136 | } 137 | 138 | fun findQueueFamilies(device: VkDevice, surface: Long): QueueFamilyIndices { 139 | return findQueueFamilies(device.physicalDevice, surface) 140 | } 141 | 142 | fun findQueueFamilies(device: VulkanDevice, surface: Long): QueueFamilyIndices { 143 | return findQueueFamilies(device.physicalDevice, surface) 144 | } 145 | 146 | fun findQueueFamilies(device: VkPhysicalDevice, surface: Long): QueueFamilyIndices { 147 | MemoryStack.stackPush().use { stack -> 148 | val indices = QueueFamilyIndices() 149 | 150 | val queueFamilyCount = stack.ints(0) 151 | vkGetPhysicalDeviceQueueFamilyProperties(device, queueFamilyCount, null) 152 | 153 | val queueFamilies = VkQueueFamilyProperties.mallocStack(queueFamilyCount[0], stack) 154 | vkGetPhysicalDeviceQueueFamilyProperties(device, queueFamilyCount, queueFamilies) 155 | 156 | val presentSupport = stack.ints(VK_FALSE) 157 | 158 | var i = 0 159 | while (i < queueFamilies.capacity() || !indices.isComplete) { 160 | if (queueFamilies[i].queueFlags() and VK_QUEUE_GRAPHICS_BIT != 0) { 161 | indices.graphicsFamily = i 162 | } 163 | KHRSurface.vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, presentSupport) 164 | if (presentSupport.get(0) == VK_TRUE) { 165 | indices.presentFamily = i 166 | } 167 | i++ 168 | } 169 | return indices 170 | } 171 | } 172 | 173 | fun findMemoryType(typeFilter: Int, properties: Int, device: VulkanDevice): Int { 174 | val memProperties = VkPhysicalDeviceMemoryProperties.mallocStack() 175 | vkGetPhysicalDeviceMemoryProperties(device.physicalDevice, memProperties) 176 | for (i in 0 until memProperties.memoryTypeCount()) { 177 | if (typeFilter and (1 shl i) != 0 && memProperties.memoryTypes(i) 178 | .propertyFlags() and properties == properties 179 | ) { 180 | return i 181 | } 182 | } 183 | error("Failed to find suitable memory type") 184 | } 185 | 186 | fun createTextureImageView(device: VulkanDevice, imgFormat: Int, textureImage: Long): Long { 187 | return createImageView( 188 | textureImage, 189 | imgFormat, 190 | VK_IMAGE_ASPECT_COLOR_BIT, 191 | device 192 | ) 193 | } 194 | 195 | fun createImage( 196 | width: Int, height: Int, format: Int, tiling: Int, usage: Int, memProperties: Int, 197 | pTextureImage: LongBuffer, pTextureImageMemory: LongBuffer, device: VulkanDevice 198 | ) { 199 | MemoryStack.stackPush().use { stack -> 200 | val imageInfo = VkImageCreateInfo.callocStack(stack) 201 | .sType(VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO) 202 | .imageType(VK_IMAGE_TYPE_2D) 203 | imageInfo.extent() 204 | .width(width) 205 | .height(height) 206 | .depth(1) 207 | imageInfo 208 | .mipLevels(1) 209 | .arrayLayers(1) 210 | .format(format) 211 | .tiling(tiling) 212 | .initialLayout(VK_IMAGE_LAYOUT_UNDEFINED) 213 | .usage(usage) 214 | .samples(VK_SAMPLE_COUNT_1_BIT) 215 | .sharingMode(VK_SHARING_MODE_EXCLUSIVE) 216 | vkCreateImage(device.rawDevice, imageInfo, null, pTextureImage).ok("Failed to allocate image memory") 217 | val memRequirements = VkMemoryRequirements.mallocStack(stack) 218 | vkGetImageMemoryRequirements(device.rawDevice, pTextureImage[0], memRequirements) 219 | val allocInfo = VkMemoryAllocateInfo.callocStack(stack) 220 | .sType(VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO) 221 | .allocationSize(memRequirements.size()) 222 | .memoryTypeIndex(findMemoryType(memRequirements.memoryTypeBits(), memProperties, device)) 223 | vkAllocateMemory(device.rawDevice, allocInfo, null, pTextureImageMemory).ok("Failed to allocate image memory") 224 | vkBindImageMemory(device.rawDevice, pTextureImage[0], pTextureImageMemory[0], 0) 225 | } 226 | } 227 | 228 | fun transitionImageLayout( 229 | renderer: Renderer, 230 | device: VulkanDevice, 231 | depthBuffer: DepthBuffer, 232 | image: Long, 233 | format: Int, 234 | oldLayout: Int, 235 | newLayout: Int 236 | ) { 237 | MemoryStack.stackPush().use { stack -> 238 | val barrier = VkImageMemoryBarrier.callocStack(1, stack) 239 | .sType(VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER) 240 | .oldLayout(oldLayout) 241 | .newLayout(newLayout) 242 | .srcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) 243 | .dstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) 244 | .image(image) 245 | barrier.subresourceRange().baseMipLevel(0) 246 | barrier.subresourceRange().levelCount(1) 247 | barrier.subresourceRange().baseArrayLayer(0) 248 | barrier.subresourceRange().layerCount(1) 249 | 250 | 251 | if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { 252 | barrier.subresourceRange().aspectMask(VK_IMAGE_ASPECT_DEPTH_BIT) 253 | if (depthBuffer.hasStencilComponent(format)) { 254 | barrier.subresourceRange().aspectMask( 255 | barrier.subresourceRange().aspectMask() or VK_IMAGE_ASPECT_STENCIL_BIT 256 | ) 257 | } 258 | } else { 259 | barrier.subresourceRange().aspectMask(VK_IMAGE_ASPECT_COLOR_BIT) 260 | } 261 | 262 | val sourceStage: Int 263 | val destinationStage: Int 264 | if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { 265 | 266 | barrier.srcAccessMask(0) 267 | .dstAccessMask(VK_ACCESS_TRANSFER_WRITE_BIT) 268 | 269 | sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT 270 | destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT 271 | 272 | } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { 273 | 274 | barrier.srcAccessMask(VK_ACCESS_TRANSFER_WRITE_BIT) 275 | .dstAccessMask(VK_ACCESS_SHADER_READ_BIT) 276 | 277 | sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT 278 | destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT 279 | 280 | } else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { 281 | 282 | barrier.srcAccessMask(0) 283 | .dstAccessMask(VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT or VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT) 284 | 285 | sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT 286 | destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT 287 | 288 | } else if (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { 289 | 290 | barrier.srcAccessMask(VK_ACCESS_SHADER_READ_BIT) 291 | .dstAccessMask(VK_ACCESS_TRANSFER_WRITE_BIT) 292 | 293 | sourceStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT 294 | destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT 295 | 296 | } else { 297 | throw IllegalArgumentException("Unsupported layout transition") 298 | } 299 | val commandBuffer: VkCommandBuffer = beginSingleTimeCommands(renderer, device) 300 | vkCmdPipelineBarrier( 301 | commandBuffer, 302 | sourceStage, destinationStage, 303 | 0, 304 | null, 305 | null, 306 | barrier 307 | ) 308 | endSingleTimeCommands(commandBuffer, device, renderer) 309 | } 310 | } 311 | 312 | fun createTextureImage( 313 | renderer: Renderer, 314 | device: VulkanDevice, 315 | width: Int, 316 | height: Int, 317 | imgFormat: Int, 318 | textureImage: TextureImage 319 | ) { 320 | MemoryStack.stackPush().use { stack -> 321 | 322 | val pTextureImage = stack.mallocLong(1) 323 | val pTextureImageMemory = stack.mallocLong(1) 324 | createImage( 325 | width, height, 326 | imgFormat, VK_IMAGE_TILING_OPTIMAL, 327 | VK_IMAGE_USAGE_TRANSFER_DST_BIT or VK_IMAGE_USAGE_SAMPLED_BIT, 328 | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 329 | pTextureImage, 330 | pTextureImageMemory, 331 | device 332 | ) 333 | textureImage.textureImage = pTextureImage[0] 334 | textureImage.textureImageMemory = pTextureImageMemory[0] 335 | 336 | 337 | transitionImageLayout( 338 | renderer, 339 | device, 340 | renderer.depthBuffer, 341 | textureImage.textureImage, 342 | imgFormat, 343 | VK_IMAGE_LAYOUT_UNDEFINED, 344 | VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL 345 | ) 346 | } 347 | } 348 | 349 | fun copyToTexture( 350 | renderer: Renderer, 351 | device: VulkanDevice, 352 | memory: Memory, 353 | image: UploadableImage, 354 | srcRegion: ImageRegion, 355 | dstRegion: ImageRegion, 356 | texture: Texture 357 | ) { 358 | MemoryStack.stackPush().use { stack -> 359 | val pBuffer = stack.mallocLong(1) 360 | val stagingBuf = memory.createStagingBuf( 361 | image.getSize(), 362 | pBuffer, 363 | stack 364 | ) { data -> 365 | val pixels = image.getPixels()!! 366 | val newData = data.getByteBuffer(0, pixels.limit()) 367 | newData.put(0, pixels, 0, pixels.limit()) 368 | } 369 | 370 | copyBufferToImage( 371 | renderer, 372 | device, 373 | stagingBuf.buffer(), 374 | texture.textureImage.textureImage, 375 | image.getWidth(), 376 | image.getHeight(), 377 | srcRegion.xOffset, 378 | srcRegion.yOffset, 379 | image.getFormat().pixelSize, 380 | dstRegion.width, 381 | dstRegion.height, 382 | dstRegion.xOffset, 383 | dstRegion.yOffset 384 | ) 385 | 386 | stagingBuf.free(device, memory) 387 | } 388 | } 389 | 390 | fun copyBufferToImage( 391 | renderer: Renderer, 392 | device: VulkanDevice, 393 | buffer: Long, 394 | image: Long, 395 | srcImageWidth: Int, 396 | srcImageHeight: Int, 397 | srcXOffset: Int, 398 | srcYOffset: Int, 399 | srcPixelSize: Int, 400 | dstRegionWidth: Int, 401 | dstRegionHeight: Int, 402 | dstXOffset: Int, 403 | dstYOffset: Int 404 | ) { 405 | MemoryStack.stackPush().use { stack -> 406 | val region = VkBufferImageCopy.callocStack(1, stack) 407 | .bufferOffset((((srcYOffset * srcImageWidth) + srcXOffset) * srcPixelSize).toLong()) 408 | .bufferRowLength(srcImageWidth) 409 | .bufferImageHeight(srcImageHeight) 410 | region.imageOffset().set(dstXOffset, dstYOffset, 0) 411 | region.imageSubresource() 412 | .aspectMask(VK_IMAGE_ASPECT_COLOR_BIT) 413 | .mipLevel(0) 414 | .baseArrayLayer(0) 415 | .layerCount(1) 416 | region.imageExtent().set(dstRegionWidth, dstRegionHeight, 1) 417 | 418 | val commandBuffer: VkCommandBuffer = beginSingleTimeCommands(renderer, device) 419 | vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, region) 420 | endSingleTimeCommands(commandBuffer, device, renderer) 421 | } 422 | 423 | 424 | } 425 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/material/Material.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.material 2 | 3 | import me.hydos.rosella.Rosella 4 | import me.hydos.rosella.render.Topology 5 | import me.hydos.rosella.render.material.state.StateInfo 6 | import me.hydos.rosella.render.resource.Resource 7 | import me.hydos.rosella.render.shader.ShaderProgram 8 | import me.hydos.rosella.render.texture.* 9 | import me.hydos.rosella.render.vertex.VertexFormat 10 | 11 | /** 12 | * A Material is like texture information, normal information, and all of those things which give an object character wrapped into one class. 13 | * similar to how unity material's works 14 | * guaranteed to change in the future 15 | */ 16 | open class Material( 17 | val resource: Resource, 18 | var shader: ShaderProgram, 19 | private val imgFormat: Int, 20 | val topology: Topology, 21 | val vertexFormat: VertexFormat, 22 | private val samplerCreateInfo: SamplerCreateInfo, 23 | val stateInfo: StateInfo 24 | ) { 25 | lateinit var pipeline: PipelineInfo 26 | 27 | lateinit var textures: Array 28 | 29 | open fun loadTextures(rosella: Rosella) { //FIXME this is also temporary 30 | if (resource != Resource.Empty) { 31 | val textureManager = rosella.common.textureManager 32 | val textureId = textureManager.generateTextureId() // FIXME this texture can't be removed 33 | val image: UploadableImage = StbiImage(resource, ImageFormat.fromVkFormat(imgFormat)) 34 | textureManager.createTexture( 35 | rosella.renderer, 36 | textureId, 37 | image.getWidth(), 38 | image.getHeight(), 39 | imgFormat 40 | ) 41 | textureManager.setTextureSampler( 42 | textureId, 43 | 0, 44 | samplerCreateInfo 45 | ) // 0 is the default texture no, but it's still gross 46 | textureManager.drawToExistingTexture(rosella.renderer, rosella.common.memory, textureId, image) 47 | val texture = textureManager.getTexture(textureId)!! 48 | textures = arrayOf(texture) //FIXME THIS SUCKS 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/model/AssimpHelper.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.model 2 | 3 | import me.hydos.rosella.render.resource.Identifier 4 | import me.hydos.rosella.render.resource.Resource 5 | import org.lwjgl.assimp.AIFile 6 | import org.lwjgl.assimp.AIFileIO 7 | import org.lwjgl.assimp.AIScene 8 | import org.lwjgl.assimp.Assimp.* 9 | import org.lwjgl.system.MemoryUtil 10 | 11 | fun loadScene(resource: Resource, flags: Int): AIScene? { 12 | val identifier = resource.identifier 13 | 14 | val context = identifier.path.run { substring(0, lastIndexOf('/')) } 15 | val name = identifier.path.run { substring(lastIndexOf('/') + 1) } 16 | 17 | val io = AIFileIO.create().apply { 18 | OpenProc { _, nFileName, _ -> 19 | val fileName = MemoryUtil.memASCII(nFileName) 20 | val id = Identifier(identifier.namespace, context + fileName) 21 | val data = resource.loader.ensureResource(id).readAllBytes(true) 22 | 23 | AIFile.create().apply { 24 | ReadProc { _, pBuffer, size, count -> 25 | val max = (data.remaining().toLong() / size).coerceAtMost(count) 26 | MemoryUtil.memCopy(MemoryUtil.memAddress(data), pBuffer, max * size) 27 | data.position(data.position() + (max * size).toInt()) 28 | max 29 | } 30 | 31 | SeekProc { _, offset, origin -> 32 | when (origin) { 33 | aiOrigin_CUR -> { 34 | data.position(data.position() + offset.toInt()) 35 | } 36 | aiOrigin_SET -> { 37 | data.position(offset.toInt()) 38 | } 39 | aiOrigin_END -> { 40 | data.position(data.limit() + offset.toInt()) 41 | } 42 | } 43 | 44 | 0 45 | } 46 | 47 | TellProc { data.position().toLong() } 48 | FileSizeProc { data.limit().toLong() } 49 | 50 | FlushProc { 51 | error("Cannot flush") 52 | } 53 | 54 | WriteProc { _, _, _, _ -> 55 | error("Cannot write") 56 | } 57 | }.address() 58 | } 59 | CloseProc { _, pFile -> 60 | val file = AIFile.create(pFile) 61 | 62 | file.FlushProc().free() 63 | file.SeekProc().free() 64 | file.FileSizeProc().free() 65 | file.TellProc().free() 66 | file.WriteProc().free() 67 | file.ReadProc().free() 68 | } 69 | } 70 | 71 | val scene = aiImportFileEx("/$name", flags, io) 72 | 73 | io.OpenProc().free() 74 | io.CloseProc().free() 75 | 76 | return scene 77 | } 78 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/model/GuiRenderObject.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.model 2 | 3 | import it.unimi.dsi.fastutil.ints.IntArrayList 4 | import me.hydos.rosella.Rosella 5 | import me.hydos.rosella.render.material.Material 6 | import me.hydos.rosella.render.resource.Resource 7 | import me.hydos.rosella.render.vertex.BufferVertexConsumer 8 | import me.hydos.rosella.scene.`object`.RenderObject 9 | import org.joml.Matrix4f 10 | import org.joml.Vector3f 11 | 12 | open class GuiRenderObject( 13 | material: Material, 14 | private var z: Float = -1f, 15 | private var colour: Vector3f = Vector3f(0f, 0f, 0f), 16 | viewMatrix: Matrix4f, 17 | projectionMatrix: Matrix4f 18 | ) : RenderObject(Resource.Empty, material, viewMatrix, projectionMatrix) { 19 | 20 | constructor( 21 | material: Material, 22 | z: Float, 23 | colour: Vector3f, 24 | scaleX: Float, 25 | scaleZ: Float, 26 | viewMatrix: Matrix4f, 27 | projectionMatrix: Matrix4f 28 | ) : this(material, z, colour, viewMatrix, projectionMatrix) { 29 | scale(scaleX, scaleZ) 30 | } 31 | 32 | constructor( 33 | material: Material, 34 | z: Float, 35 | colour: Vector3f, 36 | scaleX: Float, 37 | scaleZ: Float, 38 | translateX: Float, 39 | translateZ: Float, 40 | viewMatrix: Matrix4f, 41 | projectionMatrix: Matrix4f 42 | ) : this(material, z, colour, scaleX, scaleZ, viewMatrix, projectionMatrix) { 43 | translate(translateX, translateZ) 44 | } 45 | 46 | override fun loadModelInfo() { 47 | // FIXME FIXME FIXME FIXME this is bad, don't force cast here 48 | val vertexConsumer: BufferVertexConsumer = renderInfo.bufferProvider as BufferVertexConsumer 49 | 50 | vertexConsumer.clear() 51 | renderInfo.indices = IntArrayList() 52 | 53 | colour = Vector3f(0f, 0f, 0f) 54 | 55 | // TODO: is this conversion doing what it should be? should convert int representing unsigned byte to signed byte through wrapping 56 | vertexConsumer 57 | .pos(-0.5f, -0.5f, 0f) 58 | .color(colour.x().toInt().toByte(), colour.y().toInt().toByte(), colour.z().toInt().toByte()) 59 | .uv(0f, 0f) 60 | .nextVertex() 61 | 62 | vertexConsumer 63 | .pos(0.5f, -0.5f, 0f) 64 | .color(colour.x().toInt().toByte(), colour.y().toInt().toByte(), colour.z().toInt().toByte()) 65 | .uv(1f, 0f) 66 | .nextVertex() 67 | 68 | vertexConsumer 69 | .pos(0.5f, 0.5f, 0f) 70 | .color(colour.x().toInt().toByte(), colour.y().toInt().toByte(), colour.z().toInt().toByte()) 71 | .uv(1f, 1f) 72 | .nextVertex() 73 | 74 | vertexConsumer 75 | .pos(-0.5f, 0.5f, 0f) 76 | .color(colour.x().toInt().toByte(), colour.y().toInt().toByte(), colour.z().toInt().toByte()) 77 | .uv(0f, 1f) 78 | .nextVertex() 79 | 80 | renderInfo.indices.add(0) 81 | renderInfo.indices.add(1) 82 | renderInfo.indices.add(2) 83 | renderInfo.indices.add(2) 84 | renderInfo.indices.add(3) 85 | renderInfo.indices.add(0) 86 | } 87 | 88 | override fun onAddedToScene(rosella: Rosella) { 89 | super.onAddedToScene(rosella) 90 | modelMatrix.translate(0f, 0f, z) 91 | } 92 | 93 | private fun scale(x: Float, y: Float) { 94 | modelMatrix.scale(x, y, 1f) 95 | } 96 | 97 | private fun translate(x: Float, y: Float) { 98 | modelMatrix.translate(x, -y, 0f) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/model/ModelLoader.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.model 2 | 3 | import me.hydos.rosella.render.resource.Resource 4 | import org.joml.Vector2f 5 | import org.joml.Vector2fc 6 | import org.joml.Vector3f 7 | import org.joml.Vector3fc 8 | import org.lwjgl.assimp.AIMesh 9 | import org.lwjgl.assimp.AINode 10 | import org.lwjgl.assimp.AIScene 11 | import org.lwjgl.assimp.AIVector3D 12 | import org.lwjgl.assimp.Assimp.aiGetErrorString 13 | import java.util.Objects.requireNonNull 14 | import java.util.logging.Logger 15 | 16 | object ModelLoader { 17 | @JvmStatic 18 | fun loadModel(resource: Resource, flags: Int): SimpleModel { 19 | loadScene(resource, flags).use { scene -> 20 | Logger.getLogger(ModelLoader::class.java.simpleName) 21 | println("Loading model " + resource.identifier) 22 | 23 | if (scene?.mRootNode() == null) { 24 | throw RuntimeException("Could not load model " + aiGetErrorString()) 25 | } 26 | 27 | val model = SimpleModel() 28 | val startTime = System.nanoTime() 29 | processNode(scene.mRootNode()!!, scene, model) 30 | println("mdl loaded in " + (System.nanoTime() - startTime) / 1e6 + "ms") 31 | return model 32 | } 33 | } 34 | 35 | private fun processNode(node: AINode, scene: AIScene, model: SimpleModel) { 36 | if (node.mMeshes() != null) { 37 | processNodeMeshes(scene, node, model) 38 | } 39 | if (node.mChildren() != null) { 40 | val children = node.mChildren() 41 | for (i in 0 until node.mNumChildren()) { 42 | processNode(AINode.create(children!![i]), scene, model) 43 | } 44 | } 45 | } 46 | 47 | private fun processNodeMeshes(scene: AIScene, node: AINode, model: SimpleModel) { 48 | val pMeshes = scene.mMeshes() 49 | val meshIndices = node.mMeshes() 50 | for (i in 0 until meshIndices!!.capacity()) { 51 | val mesh = AIMesh.create(pMeshes!![meshIndices[i]]) 52 | processMesh(scene, mesh, model) 53 | } 54 | } 55 | 56 | private fun processMesh(scene: AIScene, mesh: AIMesh, model: SimpleModel) { 57 | processPositions(mesh, model.positions) 58 | processTexCoords(mesh, model.texCoords) 59 | processIndices(mesh, model.indices) 60 | } 61 | 62 | private fun processPositions(mesh: AIMesh, positions: MutableList) { 63 | val vertices: AIVector3D.Buffer = requireNonNull(mesh.mVertices()) 64 | for (i in 0 until vertices.capacity()) { 65 | val position = vertices[i] 66 | positions.add(Vector3f(position.x(), position.y(), position.z())) 67 | } 68 | } 69 | 70 | private fun processTexCoords(mesh: AIMesh, texCoords: MutableList) { 71 | val aiTexCoords: AIVector3D.Buffer = mesh.mTextureCoords(0)!! 72 | for (i in 0 until aiTexCoords.capacity()) { 73 | val coords = aiTexCoords[i] 74 | texCoords.add(Vector2f(coords.x(), coords.y())) 75 | } 76 | } 77 | 78 | private fun processIndices(mesh: AIMesh, indices: MutableList) { 79 | val aiFaces = mesh.mFaces() 80 | for (i in 0 until mesh.mNumFaces()) { 81 | val face = aiFaces[i] 82 | val pIndices = face.mIndices() 83 | for (j in 0 until face.mNumIndices()) { 84 | indices.add(pIndices[j]) 85 | } 86 | } 87 | } 88 | 89 | class SimpleModel { 90 | val positions: MutableList = ArrayList() 91 | val texCoords: MutableList = ArrayList() 92 | val indices: MutableList = ArrayList() 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/resource/CascadingResourceLoader.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.resource 2 | 3 | class CascadingResourceLoader(private val loaders: Collection) : ResourceLoader { 4 | 5 | override fun loadResource(id: Identifier): Resource? { 6 | for (loader in loaders) { 7 | val resource = loader.loadResource(id) 8 | 9 | if (resource != null) { 10 | return object : Resource by resource { 11 | override val loader: ResourceLoader 12 | get() = this@CascadingResourceLoader 13 | } 14 | } 15 | } 16 | 17 | return null 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/resource/ClassLoaderResourceLoader.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.resource 2 | 3 | import java.io.InputStream 4 | 5 | class ClassLoaderResourceLoader(private val loader: ClassLoader) : ResourceLoader { 6 | 7 | override fun loadResource(id: Identifier): Resource? { 8 | val url = loader.getResource(id.file) 9 | 10 | return if (url == null) { 11 | null 12 | } else object : Resource { 13 | override val identifier: Identifier 14 | get() = id 15 | 16 | override val loader: ResourceLoader 17 | get() = this@ClassLoaderResourceLoader 18 | 19 | override fun openStream(): InputStream = url.openStream() 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/resource/Global.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.resource 2 | 3 | import me.hydos.rosella.Rosella 4 | import java.awt.image.BufferedImage 5 | import java.io.ByteArrayInputStream 6 | import java.io.ByteArrayOutputStream 7 | import java.io.InputStream 8 | import java.nio.ByteBuffer 9 | import javax.imageio.ImageIO 10 | 11 | /** 12 | * Don't use this once [Rosella] get its own ResourceLoader field 13 | */ 14 | object Global : ResourceLoader by ClassLoaderResourceLoader(ClassLoader.getSystemClassLoader()) { 15 | 16 | fun fromBufferedImage(image: BufferedImage, id: Identifier): Resource { 17 | return object : Resource { 18 | override val identifier: Identifier 19 | get() = id 20 | 21 | override val loader: ResourceLoader 22 | get() = this@Global 23 | 24 | override fun openStream(): InputStream { 25 | val out = ByteArrayOutputStream() 26 | ImageIO.write( 27 | image, 28 | "png", 29 | out 30 | ) 31 | return ByteArrayInputStream(out.toByteArray()) 32 | } 33 | } 34 | } 35 | 36 | fun fromByteBuffer(bb: ByteBuffer, id: Identifier): Resource { 37 | return object : Resource { 38 | override val identifier: Identifier 39 | get() = id 40 | 41 | override val loader: ResourceLoader 42 | get() = this@Global 43 | 44 | override fun openStream(): InputStream { 45 | val byteArray = ByteArray(bb.remaining()) 46 | bb.get(byteArray) 47 | return ByteArrayInputStream(byteArray) 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/resource/Identifier.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.resource 2 | 3 | data class Identifier(val namespace: String, val path: String) { 4 | 5 | val file: String = "$namespace/$path" 6 | 7 | override fun toString(): String { 8 | return "$namespace:$path" 9 | } 10 | 11 | companion object { 12 | @JvmStatic 13 | val EMPTY = Identifier("rosella", "empty") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/resource/Resource.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.resource 2 | 3 | import java.io.InputStream 4 | import java.nio.ByteBuffer 5 | 6 | interface Resource { 7 | 8 | val identifier: Identifier 9 | 10 | val loader: ResourceLoader 11 | 12 | fun openStream(): InputStream 13 | 14 | fun readAllBytes(native: Boolean = false): ByteBuffer { 15 | val bytes = openStream().readBytes() 16 | 17 | if (native) { 18 | val buffer = ByteBuffer.allocateDirect(bytes.size) 19 | buffer.put(bytes) 20 | buffer.rewind() 21 | return buffer 22 | } 23 | 24 | return ByteBuffer.wrap(bytes) 25 | } 26 | 27 | object Empty : Resource { 28 | override val identifier: Identifier 29 | get() = TODO("Not yet implemented") 30 | override val loader: ResourceLoader 31 | get() = TODO("Not yet implemented") 32 | 33 | override fun openStream(): InputStream { 34 | TODO("Not yet implemented") 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/resource/ResourceLoader.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.resource 2 | 3 | interface ResourceLoader { 4 | 5 | fun loadResource(id: Identifier): Resource? 6 | 7 | fun ensureResource(id: Identifier): Resource { 8 | return loadResource(id) ?: error("Could not open $id") 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/shader/RawShaderProgram.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.shader 2 | 3 | import it.unimi.dsi.fastutil.Hash.VERY_FAST_LOAD_FACTOR 4 | import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet 5 | import me.hydos.rosella.device.VulkanDevice 6 | import me.hydos.rosella.memory.Memory 7 | import me.hydos.rosella.render.descriptorsets.DescriptorSets 8 | import me.hydos.rosella.render.renderer.Renderer 9 | import me.hydos.rosella.render.resource.Resource 10 | import me.hydos.rosella.render.shader.ubo.Ubo 11 | import me.hydos.rosella.render.swapchain.Swapchain 12 | import me.hydos.rosella.render.texture.Texture 13 | import me.hydos.rosella.render.texture.TextureManager 14 | import me.hydos.rosella.render.util.ok 15 | import me.hydos.rosella.scene.`object`.impl.SimpleFramebufferObjectManager 16 | import org.lwjgl.system.MemoryStack 17 | import org.lwjgl.vulkan.* 18 | import org.lwjgl.vulkan.VK10.* 19 | 20 | open class RawShaderProgram( 21 | var vertexShader: Resource?, 22 | var fragmentShader: Resource?, 23 | val device: VulkanDevice, 24 | val memory: Memory, 25 | var maxObjCount: Int, 26 | private vararg var poolObjects: PoolObjectInfo 27 | ) { 28 | var descriptorPool: Long = 0 29 | var descriptorSetLayout: Long = 0 30 | 31 | private val preparableTextures = ReferenceOpenHashSet(3, VERY_FAST_LOAD_FACTOR) 32 | 33 | fun updateUbos(currentImage: Int, swapchain: Swapchain, objectManager: SimpleFramebufferObjectManager) { 34 | for (instances in objectManager.renderObjects.values) { 35 | for (instance in instances) { 36 | instance.ubo.update( 37 | currentImage, 38 | swapchain 39 | ) 40 | } 41 | } 42 | } 43 | 44 | fun prepareTexturesForRender( 45 | renderer: Renderer, 46 | textureManager: TextureManager 47 | ) { // TODO: should we move this? 48 | preparableTextures.forEach { 49 | if (it != null) { 50 | textureManager.prepareTexture(renderer, it) 51 | } 52 | } 53 | preparableTextures.clear() 54 | } 55 | 56 | private fun createPool(swapchain: Swapchain) { 57 | if (descriptorPool != 0L) { 58 | vkDestroyDescriptorPool(device.rawDevice, descriptorPool, null) 59 | } 60 | MemoryStack.stackPush().use { stack -> 61 | val poolSizes = VkDescriptorPoolSize.callocStack(poolObjects.size, stack) 62 | 63 | poolObjects.forEachIndexed { i, poolObj -> 64 | poolSizes[i] 65 | .type(poolObj.getVkType()) 66 | .descriptorCount(swapchain.swapChainImages.size * maxObjCount) 67 | } 68 | 69 | val poolInfo = VkDescriptorPoolCreateInfo.callocStack(stack) 70 | .sType(VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO) 71 | .pPoolSizes(poolSizes) 72 | .maxSets(swapchain.swapChainImages.size * maxObjCount) 73 | .flags(VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT) 74 | 75 | val pDescriptorPool = stack.mallocLong(1) 76 | vkCreateDescriptorPool( 77 | device.rawDevice, 78 | poolInfo, 79 | null, 80 | pDescriptorPool 81 | ).ok("Failed to create descriptor pool") 82 | 83 | descriptorPool = pDescriptorPool[0] 84 | } 85 | } 86 | 87 | fun createDescriptorSetLayout() { 88 | MemoryStack.stackPush().use { 89 | val bindings = VkDescriptorSetLayoutBinding.callocStack(poolObjects.size, it) 90 | 91 | poolObjects.forEachIndexed { i, poolObj -> 92 | bindings[i] 93 | .binding(if (poolObj.getBindingLocation() == -1) i else poolObj.getBindingLocation()) 94 | .descriptorCount(1) 95 | .descriptorType(poolObj.getVkType()) 96 | .pImmutableSamplers(null) 97 | .stageFlags(poolObj.getShaderStage()) 98 | } 99 | 100 | val layoutInfo = VkDescriptorSetLayoutCreateInfo.callocStack(it) 101 | layoutInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO) 102 | layoutInfo.pBindings(bindings) 103 | val pDescriptorSetLayout = it.mallocLong(1) 104 | vkCreateDescriptorSetLayout( 105 | device.rawDevice, 106 | layoutInfo, 107 | null, 108 | pDescriptorSetLayout 109 | ).ok("Failed to create descriptor set layout") 110 | descriptorSetLayout = pDescriptorSetLayout[0] 111 | } 112 | } 113 | 114 | fun createDescriptorSets( 115 | swapchain: Swapchain, 116 | logger: org.apache.logging.log4j.Logger, 117 | currentTextures: Array, 118 | ubo: Ubo 119 | ) { 120 | this.preparableTextures.addAll(currentTextures) 121 | 122 | if (descriptorPool == 0L) { 123 | createPool(swapchain) 124 | } 125 | if (descriptorSetLayout == 0L) { 126 | logger.warn("Descriptor Set Layouts are invalid! rebuilding... (THIS IS NOT FAST)") 127 | createDescriptorSetLayout() 128 | } 129 | MemoryStack.stackPush().use { stack -> 130 | val layouts = stack.mallocLong(swapchain.swapChainImages.size) 131 | for (i in 0 until layouts.capacity()) { 132 | layouts.put(i, descriptorSetLayout) 133 | } 134 | val allocInfo = VkDescriptorSetAllocateInfo.callocStack(stack) 135 | .sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO) 136 | .descriptorPool(descriptorPool) 137 | .pSetLayouts(layouts) 138 | val pDescriptorSets = stack.mallocLong(swapchain.swapChainImages.size) 139 | 140 | vkAllocateDescriptorSets(device.rawDevice, allocInfo, pDescriptorSets) 141 | .ok("Failed to allocate descriptor sets") 142 | 143 | val descriptorSets = DescriptorSets(descriptorPool, pDescriptorSets.capacity()) 144 | val bufferInfo = VkDescriptorBufferInfo.callocStack(1, stack) 145 | .offset(0) 146 | .range(ubo.getSize().toLong()) 147 | 148 | val descriptorWrites = VkWriteDescriptorSet.callocStack(poolObjects.size, stack) 149 | 150 | for (i in 0 until pDescriptorSets.capacity()) { 151 | val descriptorSet = pDescriptorSets[i] 152 | bufferInfo.buffer(ubo.getUniformBuffers()[i].buffer()) 153 | poolObjects.forEachIndexed { index, poolObj -> 154 | // TODO OPT: maybe group descriptors up by type if that's faster than defining each one by itself 155 | val descriptorWrite = descriptorWrites[index] 156 | .sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET) 157 | .dstBinding(if (poolObj.getBindingLocation() == -1) index else poolObj.getBindingLocation()) 158 | .dstArrayElement(0) 159 | .descriptorType(poolObj.getVkType()) 160 | .descriptorCount(1) 161 | 162 | when (poolObj.getVkType()) { 163 | VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER -> { 164 | descriptorWrite.pBufferInfo(bufferInfo) 165 | } 166 | 167 | VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER -> { 168 | if (poolObj is PoolSamplerInfo) { 169 | val texture = if (poolObj.samplerIndex == -1) { 170 | TextureManager.BLANK_TEXTURE 171 | } else { 172 | currentTextures[poolObj.samplerIndex] ?: TextureManager.BLANK_TEXTURE 173 | } 174 | 175 | val imageInfo = VkDescriptorImageInfo.callocStack(1, stack) 176 | .imageLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) 177 | .imageView(texture.textureImage.view) 178 | .sampler(texture.textureSampler!!) 179 | 180 | descriptorWrite.pImageInfo(imageInfo) 181 | } 182 | } 183 | } 184 | descriptorWrite.dstSet(descriptorSet) 185 | } 186 | vkUpdateDescriptorSets(device.rawDevice, descriptorWrites, null) 187 | descriptorSets.setDescriptorPool(descriptorPool) 188 | descriptorSets.add(descriptorSet) 189 | } 190 | 191 | ubo.setDescriptors(descriptorSets) 192 | } 193 | } 194 | 195 | fun free() { 196 | vkDestroyDescriptorSetLayout(device.rawDevice, descriptorSetLayout, null) 197 | vkDestroyDescriptorPool(device.rawDevice, descriptorPool, null) 198 | } 199 | 200 | interface PoolObjectInfo { 201 | /** 202 | * If -1, the object will use the current index in the list when iterating 203 | * TODO: when converting this to java, make a static variable for -1 and use that 204 | */ 205 | fun getBindingLocation(): Int 206 | fun getVkType(): Int 207 | fun getShaderStage(): Int 208 | } 209 | 210 | enum class PoolUboInfo : PoolObjectInfo { 211 | INSTANCE; 212 | 213 | override fun getBindingLocation(): Int { 214 | return -1 215 | } 216 | 217 | override fun getVkType(): Int { 218 | return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER 219 | } 220 | 221 | override fun getShaderStage(): Int { 222 | return VK_SHADER_STAGE_ALL 223 | } 224 | } 225 | 226 | data class PoolSamplerInfo(private val bindingLocation: Int, val samplerIndex: Int) : PoolObjectInfo { 227 | 228 | override fun getBindingLocation(): Int { 229 | return bindingLocation 230 | } 231 | 232 | override fun getVkType(): Int { 233 | return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER 234 | } 235 | 236 | override fun getShaderStage(): Int { 237 | return VK_SHADER_STAGE_ALL 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/shader/Shader.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.shader 2 | 3 | import me.hydos.rosella.render.resource.Resource 4 | 5 | class Shader(val shaderLocation: Resource) -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/shader/ShaderManager.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.shader 2 | 3 | import me.hydos.rosella.Rosella 4 | 5 | class ShaderManager(val rosella: Rosella) { 6 | 7 | var cachedShaders = HashMap() 8 | 9 | fun getOrCreateShader(rawShader: RawShaderProgram): ShaderProgram? { 10 | if (!cachedShaders.containsKey(rawShader)) { 11 | cachedShaders[rawShader] = ShaderProgram(rawShader, rosella, rawShader.maxObjCount) 12 | } 13 | 14 | return cachedShaders[rawShader] 15 | } 16 | 17 | fun free() { 18 | for (program in cachedShaders.values) { 19 | program.free() 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/shader/ShaderProgram.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.shader 2 | 3 | import me.hydos.rosella.Rosella 4 | import me.hydos.rosella.device.VulkanDevice 5 | import me.hydos.rosella.render.util.compileShaderFile 6 | import me.hydos.rosella.render.util.ok 7 | import me.hydos.rosella.ubo.DescriptorManager 8 | import org.lwjgl.system.MemoryStack 9 | import org.lwjgl.vulkan.VK10 10 | import org.lwjgl.vulkan.VkShaderModuleCreateInfo 11 | import java.nio.ByteBuffer 12 | 13 | class ShaderProgram(val raw: RawShaderProgram, val rosella: Rosella, maxObjects: Int) { 14 | 15 | private val fragmentShader by lazy { 16 | compileShaderFile(raw.fragmentShader!!, ShaderType.FRAGMENT_SHADER).also { 17 | fragmentShaderCompiled = true 18 | } 19 | } 20 | private val vertexShader by lazy { 21 | compileShaderFile(raw.vertexShader!!, ShaderType.VERTEX_SHADER).also { 22 | vertexShaderCompiled = true 23 | } 24 | } 25 | val descriptorManager = DescriptorManager(maxObjects, this, rosella.renderer.swapchain, rosella.common.device, rosella.common.memory) 26 | 27 | private var fragmentShaderCompiled: Boolean = false 28 | private var vertexShaderCompiled: Boolean = false 29 | 30 | /** 31 | * Create a Vulkan shader module. used during pipeline creation. 32 | */ 33 | private fun createShader(spirvCode: ByteBuffer, device: VulkanDevice): Long { 34 | MemoryStack.stackPush().use { stack -> 35 | val createInfo = VkShaderModuleCreateInfo.callocStack(stack) 36 | .sType(VK10.VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO) 37 | .pCode(spirvCode) 38 | val pShaderModule = stack.mallocLong(1) 39 | VK10.vkCreateShaderModule(device.rawDevice, createInfo, null, pShaderModule).ok() 40 | return pShaderModule[0] 41 | } 42 | } 43 | 44 | fun getVertShaderModule(): Long { 45 | return createShader(vertexShader.bytecode(), rosella.common.device) 46 | } 47 | 48 | fun getFragShaderModule(): Long { 49 | return createShader(fragmentShader.bytecode(), rosella.common.device) 50 | } 51 | 52 | /** 53 | * Free Shaders 54 | */ 55 | fun free() { 56 | if (vertexShaderCompiled) vertexShader.free() 57 | if (fragmentShaderCompiled) fragmentShader.free() 58 | raw.free() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/shader/pushconstant/ModelPushConstant.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.shader.pushconstant 2 | 3 | import org.joml.Vector3f 4 | 5 | class ModelPushConstant { 6 | val position: Vector3f = Vector3f() 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/shader/ubo/Ubo.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.shader.ubo 2 | 3 | import me.hydos.rosella.device.VulkanDevice 4 | import me.hydos.rosella.memory.BufferInfo 5 | import me.hydos.rosella.memory.Memory 6 | import me.hydos.rosella.memory.MemoryCloseable 7 | import me.hydos.rosella.render.descriptorsets.DescriptorSets 8 | import me.hydos.rosella.render.swapchain.Swapchain 9 | 10 | /** 11 | * A Uniform Buffer Object (ubo) is an object used to do things such as sending transformation matrices to the shader, sending lighting values to the shader, etc 12 | */ 13 | abstract class Ubo : MemoryCloseable { 14 | 15 | /** 16 | * Called when the uniform buffers should be created 17 | */ 18 | abstract fun create(swapchain: Swapchain) 19 | 20 | /** 21 | * Called before each frame to update the ubo 22 | */ 23 | abstract fun update(currentImg: Int, swapchain: Swapchain) 24 | 25 | /** 26 | * Called when the program is closing and free's memory 27 | */ 28 | abstract fun free() 29 | 30 | /** 31 | * Gets the size of the ubo 32 | */ 33 | abstract fun getSize(): Int 34 | 35 | /** 36 | * Gets an list of pointers to the ubo frames 37 | */ 38 | abstract fun getUniformBuffers(): List 39 | 40 | /** 41 | * Gets the descriptor sets used with this ubo 42 | */ 43 | abstract fun getDescriptors(): DescriptorSets 44 | 45 | /** 46 | * Called when the program is closing and free's memory 47 | */ 48 | override fun free(device: VulkanDevice?, memory: Memory?) { 49 | free() 50 | } 51 | 52 | abstract fun setDescriptors(descriptorSets: DescriptorSets) 53 | } 54 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/swapchain/DepthBuffer.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.swapchain 2 | 3 | import me.hydos.rosella.device.VulkanDevice 4 | import me.hydos.rosella.render.createImage 5 | import me.hydos.rosella.render.createImageView 6 | import me.hydos.rosella.render.renderer.Renderer 7 | import me.hydos.rosella.render.transitionImageLayout 8 | import org.lwjgl.system.MemoryStack 9 | import org.lwjgl.vulkan.VK10.* 10 | import org.lwjgl.vulkan.VkFormatProperties 11 | import java.nio.IntBuffer 12 | 13 | /** 14 | * Since vulkan gives us so much control, we must make our own depth buffer instead of relying on the driver to create one for us. 15 | */ 16 | class DepthBuffer { 17 | 18 | private var depthImage: Long = 0 19 | private var depthImageMemory: Long = 0 20 | var depthImageView: Long = 0 21 | 22 | fun createDepthResources(device: VulkanDevice, swapchain: Swapchain, renderer: Renderer) { 23 | MemoryStack.stackPush().use { stack -> 24 | val depthFormat: Int = findDepthFormat(device) 25 | val pDepthImage = stack.mallocLong(1) 26 | val pDepthImageMemory = stack.mallocLong(1) 27 | createImage( 28 | swapchain.swapChainExtent.width(), 29 | swapchain.swapChainExtent.height(), 30 | depthFormat, 31 | VK_IMAGE_TILING_OPTIMAL, 32 | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, 33 | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 34 | pDepthImage, 35 | pDepthImageMemory, 36 | device 37 | ) 38 | depthImage = pDepthImage[0] 39 | depthImageMemory = pDepthImageMemory[0] 40 | depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT, device) 41 | 42 | // Explicitly transitioning the depth image 43 | transitionImageLayout( 44 | renderer, 45 | device, 46 | renderer.depthBuffer, 47 | depthImage, 48 | depthFormat, 49 | VK_IMAGE_LAYOUT_UNDEFINED, 50 | VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL 51 | ) 52 | } 53 | } 54 | 55 | fun findDepthFormat(device: VulkanDevice): Int { 56 | return findSupportedFormat( 57 | MemoryStack.stackGet() 58 | .ints(VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT), 59 | VK_IMAGE_TILING_OPTIMAL, 60 | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT, 61 | device 62 | ) 63 | } 64 | 65 | fun hasStencilComponent(format: Int): Boolean { 66 | return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT 67 | } 68 | 69 | private fun findSupportedFormat( 70 | formatCandidates: IntBuffer, 71 | tiling: Int, 72 | features: Int, 73 | device: VulkanDevice 74 | ): Int { 75 | MemoryStack.stackPush().use { stack -> 76 | val props = VkFormatProperties.callocStack(stack) 77 | for (i in 0 until formatCandidates.capacity()) { 78 | val format = formatCandidates[i] 79 | vkGetPhysicalDeviceFormatProperties(device.physicalDevice, format, props) 80 | if (tiling == VK_IMAGE_TILING_LINEAR && props.linearTilingFeatures() and features == features) { 81 | return format 82 | } else if (tiling == VK_IMAGE_TILING_OPTIMAL && props.optimalTilingFeatures() and features == features) { 83 | return format 84 | } 85 | } 86 | } 87 | error("Failed to find supported format") 88 | } 89 | 90 | fun free(device: VulkanDevice) { 91 | vkDestroyImageView(device.rawDevice, depthImageView, null) 92 | vkDestroyImage(device.rawDevice, depthImage, null) 93 | vkFreeMemory(device.rawDevice, depthImageMemory, null) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/swapchain/Frame.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.swapchain 2 | 3 | import org.lwjgl.system.MemoryStack.stackGet 4 | import java.nio.LongBuffer 5 | 6 | /** 7 | * Represents a frame that will be rendered to the window 8 | */ 9 | class Frame( 10 | private val imageAvailableSemaphore: Long, 11 | private val renderFinishedSemaphore: Long, 12 | private val fence: Long 13 | ) { 14 | fun imageAvailableSemaphore(): Long { 15 | return imageAvailableSemaphore 16 | } 17 | 18 | fun pImageAvailableSemaphore(): LongBuffer { 19 | return stackGet().longs(imageAvailableSemaphore) 20 | } 21 | 22 | fun renderFinishedSemaphore(): Long { 23 | return renderFinishedSemaphore 24 | } 25 | 26 | fun pRenderFinishedSemaphore(): LongBuffer { 27 | return stackGet().longs(renderFinishedSemaphore) 28 | } 29 | 30 | fun fence(): Long { 31 | return fence 32 | } 33 | 34 | fun pFence(): LongBuffer { 35 | return stackGet().longs(fence) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/texture/StbiImage.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.texture 2 | 3 | import me.hydos.rosella.render.resource.Resource 4 | import org.lwjgl.stb.STBImage 5 | import org.lwjgl.system.MemoryStack 6 | import java.nio.ByteBuffer 7 | 8 | class StbiImage(resource: Resource, private val format: ImageFormat) : UploadableImage { 9 | 10 | private var width: Int 11 | private var height: Int 12 | private var size: Int 13 | private var pixels: ByteBuffer 14 | 15 | init { 16 | MemoryStack.stackPush().use { stack -> 17 | val file = resource.readAllBytes(true) 18 | val pWidth = stack.mallocInt(1) 19 | val pHeight = stack.mallocInt(1) 20 | val pChannels = stack.mallocInt(1) 21 | var pixels: ByteBuffer? = STBImage.stbi_load_from_memory(file, pWidth, pHeight, pChannels, format.channels) 22 | if (pixels != null) { 23 | if (pChannels[0] != format.channels) { 24 | throw RuntimeException("Failed to load texture image ${resource.identifier}: Expected channel count (${format.channels}) did not match returned channel count (${pChannels[0]})") 25 | } 26 | } else { 27 | pixels = ByteBuffer.wrap(resource.openStream().readAllBytes()) 28 | if (pixels == null) { 29 | throw RuntimeException("Failed to load texture image ${resource.identifier}") 30 | } 31 | } 32 | 33 | this.width = pWidth[0] 34 | this.height = pHeight[0] 35 | this.size = width * height * format.pixelSize 36 | this.pixels = pixels 37 | } 38 | } 39 | 40 | 41 | override fun getWidth(): Int { 42 | return width 43 | } 44 | 45 | override fun getHeight(): Int { 46 | return height 47 | } 48 | 49 | override fun getFormat(): ImageFormat { 50 | return format 51 | } 52 | 53 | override fun getSize(): Int { 54 | return size 55 | } 56 | 57 | override fun getPixels(): ByteBuffer { 58 | return pixels 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/texture/Texture.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.texture 2 | 3 | data class Texture( 4 | val imgFormat: Int, 5 | val width: Int, 6 | val height: Int, 7 | val textureImage: TextureImage, 8 | var textureSampler: Long? 9 | ) 10 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/texture/TextureImage.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.texture 2 | 3 | data class TextureImage( 4 | var textureImage: Long, 5 | var textureImageMemory: Long, 6 | var view: Long 7 | ) 8 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/texture/UploadableImage.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.texture 2 | 3 | import java.nio.ByteBuffer 4 | 5 | /** 6 | * Allows the ability for the software to load the image their own way. especially handy when you generate images 7 | */ 8 | interface UploadableImage { 9 | 10 | fun getWidth(): Int 11 | fun getHeight(): Int 12 | fun getFormat(): ImageFormat 13 | fun getSize(): Int 14 | fun getPixels(): ByteBuffer? 15 | } 16 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/util/SprirVUtils.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.util 2 | 3 | import me.hydos.rosella.render.resource.Resource 4 | import me.hydos.rosella.render.shader.ShaderType 5 | import org.lwjgl.system.MemoryUtil.NULL 6 | import org.lwjgl.system.NativeResource 7 | import org.lwjgl.util.shaderc.Shaderc.* 8 | import java.nio.ByteBuffer 9 | 10 | fun compileShaderFile(shader: Resource, shaderType: ShaderType): SpirV { 11 | val source = shader.openStream().readBytes().decodeToString() 12 | return compileShader(shader.identifier.file, source, shaderType) 13 | } 14 | 15 | fun compileShader(filename: String, source: String, shaderType: ShaderType): SpirV { 16 | val compiler = shaderc_compiler_initialize() 17 | if (compiler == NULL) { 18 | throw RuntimeException("Failed to create shader compiler") 19 | } 20 | 21 | val result: Long = shaderc_compile_into_spv(compiler, source, shaderType.shaderCType, filename, "main", NULL) 22 | if (result == NULL) { 23 | throw RuntimeException("Failed to compile shader $filename into SPIR-V") 24 | } 25 | 26 | if (shaderc_result_get_compilation_status(result) != shaderc_compilation_status_success) { 27 | error("Failed to compile shader $filename into SPIR-V: ${shaderc_result_get_error_message(result)}") 28 | } 29 | shaderc_compiler_release(compiler) 30 | 31 | return SpirV(result, shaderc_result_get_bytes(result)) 32 | } 33 | 34 | class SpirV(private val handle: Long, private var bytecode: ByteBuffer?) : NativeResource { 35 | fun bytecode(): ByteBuffer { 36 | return bytecode!! 37 | } 38 | 39 | override fun free() { 40 | shaderc_result_release(handle) 41 | bytecode = null 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/render/util/VkUtils.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.render.util 2 | 3 | import org.joml.Matrix4f 4 | import org.joml.Vector2f 5 | import org.joml.Vector3f 6 | import org.joml.Vector4f 7 | import org.lwjgl.vulkan.EXTDebugReport.VK_ERROR_VALIDATION_FAILED_EXT 8 | import org.lwjgl.vulkan.KHRSurface 9 | import org.lwjgl.vulkan.VK10 10 | import org.lwjgl.vulkan.VK11 11 | import kotlin.reflect.KClass 12 | 13 | private val errorMap = mutableMapOf().apply { 14 | this[VK10.VK_NOT_READY] = "VK_NOT_READY" 15 | this[VK10.VK_TIMEOUT] = "VK_TIMEOUT" 16 | this[VK10.VK_EVENT_SET] = "VK_EVENT_SET" 17 | this[VK10.VK_EVENT_RESET] = "VK_EVENT_RESET" 18 | this[VK10.VK_INCOMPLETE] = "VK_INCOMPLETE" 19 | this[VK10.VK_ERROR_OUT_OF_HOST_MEMORY] = "VK_ERROR_OUT_OF_HOST_MEMORY" 20 | this[VK11.VK_ERROR_OUT_OF_POOL_MEMORY] = "VK_ERROR_OUT_OF_POOL_MEMORY" 21 | this[VK10.VK_ERROR_OUT_OF_DEVICE_MEMORY] = "VK_ERROR_OUT_OF_DEVICE_MEMORY" 22 | this[VK10.VK_ERROR_INITIALIZATION_FAILED] = "VK_ERROR_INITIALIZATION_FAILED" 23 | this[VK10.VK_ERROR_DEVICE_LOST] = "VK_ERROR_DEVICE_LOST" 24 | this[VK10.VK_ERROR_MEMORY_MAP_FAILED] = "VK_ERROR_MEMORY_MAP_FAILED" 25 | this[VK10.VK_ERROR_LAYER_NOT_PRESENT] = "VK_ERROR_LAYER_NOT_PRESENT" 26 | this[VK10.VK_ERROR_EXTENSION_NOT_PRESENT] = "VK_ERROR_EXTENSION_NOT_PRESENT" 27 | this[VK10.VK_ERROR_FEATURE_NOT_PRESENT] = "VK_ERROR_FEATURE_NOT_PRESENT" 28 | this[VK10.VK_ERROR_INCOMPATIBLE_DRIVER] = "VK_ERROR_INCOMPATIBLE_DRIVER" 29 | this[VK10.VK_ERROR_TOO_MANY_OBJECTS] = "VK_ERROR_TOO_MANY_OBJECTS" 30 | this[VK10.VK_ERROR_FORMAT_NOT_SUPPORTED] = "VK_ERROR_FORMAT_NOT_SUPPORTED" 31 | this[VK10.VK_ERROR_FRAGMENTED_POOL] = "VK_ERROR_FRAGMENTED_POOL" 32 | this[VK10.VK_ERROR_UNKNOWN] = "VK_ERROR_UNKNOWN" 33 | this[KHRSurface.VK_ERROR_NATIVE_WINDOW_IN_USE_KHR] = "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR" 34 | this[VK_ERROR_VALIDATION_FAILED_EXT] = "VK_ERROR_VALIDATION_FAILED_EXT" 35 | } 36 | 37 | private val SIZEOF_CACHE = mutableMapOf, Int>().apply { 38 | this[Byte::class.java] = Byte.SIZE_BYTES 39 | this[Character::class.java] = Character.BYTES 40 | this[Short::class.java] = Short.SIZE_BYTES 41 | this[Integer::class.java] = Integer.BYTES 42 | this[Float::class.java] = Float.SIZE_BYTES 43 | this[Long::class.java] = Long.SIZE_BYTES 44 | this[Double::class.java] = Double.SIZE_BYTES 45 | 46 | this[Vector2f::class.java] = 2 * Float.SIZE_BYTES 47 | this[Vector3f::class.java] = 3 * Float.SIZE_BYTES 48 | this[Vector4f::class.java] = 4 * Float.SIZE_BYTES 49 | 50 | this[Matrix4f::class.java] = 16 * java.lang.Float.BYTES 51 | } 52 | 53 | fun sizeof(kClass: KClass<*>): Int { 54 | return SIZEOF_CACHE[kClass.java] ?: 0 55 | } 56 | 57 | fun alignof(obj: Any?): Int { 58 | return if (obj == null) 0 else SIZEOF_CACHE[obj.javaClass] ?: Integer.BYTES 59 | } 60 | 61 | fun alignas(offset: Int, alignment: Int): Int { 62 | return if (offset % alignment == 0) offset else (offset - 1 or alignment - 1) + 1 63 | } 64 | 65 | fun Int.ok(): Int { 66 | if (this != VK10.VK_SUCCESS) { 67 | throw RuntimeException(errorMap[this] ?: toString()) 68 | } 69 | return this 70 | } 71 | 72 | fun Int.ok(message: String): Int { 73 | if (this != VK10.VK_SUCCESS) { 74 | throw RuntimeException(message + " Caused by: " + errorMap[this] + " (" + this + ")") 75 | } 76 | return this 77 | } 78 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/scene/Scene.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.scene 2 | 3 | import me.hydos.rosella.render.resource.Identifier 4 | 5 | class Scene { 6 | var id: Identifier = Identifier("rosella", "empty") 7 | var models: MutableList = ArrayList() 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/me/hydos/rosella/scene/SceneModel.kt: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.scene 2 | 3 | import me.hydos.rosella.render.resource.Identifier 4 | 5 | class SceneModel { 6 | var id: Identifier? = null 7 | var location: Identifier? = null 8 | } 9 | -------------------------------------------------------------------------------- /src/main/resources/rosella/editor/gui/files/class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hYdos/Rosella/50a0ee2c5bdfe8255ceb1120f9de4ea7679da9a4/src/main/resources/rosella/editor/gui/files/class.png -------------------------------------------------------------------------------- /src/main/resources/rosella/editor/gui/files/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hYdos/Rosella/50a0ee2c5bdfe8255ceb1120f9de4ea7679da9a4/src/main/resources/rosella/editor/gui/files/file.png -------------------------------------------------------------------------------- /src/main/resources/rosella/editor/gui/files/interface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hYdos/Rosella/50a0ee2c5bdfe8255ceb1120f9de4ea7679da9a4/src/main/resources/rosella/editor/gui/files/interface.png -------------------------------------------------------------------------------- /src/main/resources/rosella/editor/gui/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hYdos/Rosella/50a0ee2c5bdfe8255ceb1120f9de4ea7679da9a4/src/main/resources/rosella/editor/gui/folder.png -------------------------------------------------------------------------------- /src/main/resources/rosella/fonts/DIN Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hYdos/Rosella/50a0ee2c5bdfe8255ceb1120f9de4ea7679da9a4/src/main/resources/rosella/fonts/DIN Bold.otf -------------------------------------------------------------------------------- /src/main/resources/rosella/shaders/base.f.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(binding = 1) uniform sampler2D texSampler; 5 | 6 | layout(location = 0) in vec3 fragColor; 7 | layout(location = 1) in vec2 fragTexCoord; 8 | 9 | layout(location = 0) out vec4 outColor; 10 | 11 | void main() { 12 | outColor = texture(texSampler, fragTexCoord); 13 | } -------------------------------------------------------------------------------- /src/main/resources/rosella/shaders/base.v.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(binding = 0) uniform UniformBufferObject { 5 | mat4 model; 6 | mat4 view; 7 | mat4 proj; 8 | } ubo; 9 | 10 | layout(location = 0) in vec3 inPosition; 11 | layout(location = 1) in vec3 inColor; 12 | layout(location = 2) in vec2 inTexCoord; 13 | 14 | layout(location = 0) out vec3 fragColor; 15 | layout(location = 1) out vec2 fragTexCoord; 16 | 17 | void main() { 18 | vec4 worldPosition = ubo.model * vec4(inPosition, 1.0); 19 | 20 | gl_Position = ubo.proj * ubo.view * worldPosition; 21 | fragColor = inColor; 22 | fragTexCoord = inTexCoord; 23 | } -------------------------------------------------------------------------------- /src/main/resources/rosella/shaders/fonts.f.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(binding = 1) uniform sampler2D texSampler; 5 | 6 | layout(location = 0) in vec3 fragColor; 7 | layout(location = 1) in vec2 fragTexCoord; 8 | 9 | layout(location = 0) out vec4 outColor; 10 | 11 | void main() { 12 | bool wireframe = false; 13 | 14 | outColor = texture(texSampler, fragTexCoord); 15 | 16 | if (outColor.a < 0.05 && !wireframe) { 17 | discard; 18 | } else { 19 | outColor.r = fragColor.r; 20 | outColor.g = fragColor.g; 21 | outColor.b = fragColor.b; 22 | } 23 | 24 | if (wireframe) { 25 | outColor = vec4(1, 1, 1, 1); 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/resources/rosella/shaders/fonts.v.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(binding = 0) uniform UniformBufferObject { 5 | mat4 model; 6 | mat4 view; 7 | mat4 proj; 8 | } ubo; 9 | 10 | layout(location = 0) in vec3 inPosition; 11 | layout(location = 1) in vec3 inColor; 12 | layout(location = 2) in vec2 inTexCoord; 13 | 14 | layout(location = 0) out vec3 fragColor; 15 | layout(location = 1) out vec2 fragTexCoord; 16 | 17 | void main() { 18 | gl_Position = ubo.proj * ubo.model * vec4(inPosition.xy, 0.0, 1.0); 19 | fragColor = inColor; 20 | fragTexCoord = vec2(inTexCoord.x, 1.0 - inTexCoord.y); 21 | } -------------------------------------------------------------------------------- /src/main/resources/rosella/shaders/gui.colour.f.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(binding = 1) uniform sampler2D texSampler; 5 | 6 | layout(location = 0) in vec3 fragColor; 7 | layout(location = 1) in vec2 fragTexCoord; 8 | 9 | layout(location = 0) out vec4 outColor; 10 | 11 | void main() { 12 | outColor = vec4(fragColor, 1.0); 13 | } -------------------------------------------------------------------------------- /src/main/resources/rosella/shaders/gui.f.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(binding = 1) uniform sampler2D texSampler; 5 | 6 | layout(location = 0) in vec3 fragColor; 7 | layout(location = 1) in vec2 fragTexCoord; 8 | 9 | layout(location = 0) out vec4 outColor; 10 | 11 | void main() { 12 | bool wireframe = false; 13 | 14 | outColor = texture(texSampler, fragTexCoord); 15 | 16 | if(outColor.a < 0.05 && !wireframe) { 17 | discard; 18 | } 19 | 20 | if(wireframe) { 21 | outColor = vec4(1, 1, 1, 1); 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/resources/rosella/shaders/gui.v.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(binding = 0) uniform UniformBufferObject { 5 | mat4 model; 6 | mat4 view; 7 | mat4 proj; 8 | } ubo; 9 | 10 | layout(location = 0) in vec3 inPosition; 11 | layout(location = 1) in vec3 inColor; 12 | layout(location = 2) in vec2 inTexCoord; 13 | 14 | layout(location = 0) out vec3 fragColor; 15 | layout(location = 1) out vec2 fragTexCoord; 16 | 17 | void main() { 18 | gl_Position = ubo.proj * ubo.model * vec4(inPosition.xy, 0.0, 1.0); 19 | fragColor = inColor; 20 | fragTexCoord = vec2(inTexCoord.x, 1.0 - inTexCoord.y); 21 | } -------------------------------------------------------------------------------- /src/test/java/me/hydos/rosella/example/PortalJava.java: -------------------------------------------------------------------------------- 1 | package me.hydos.rosella.example; 2 | 3 | import me.hydos.rosella.Rosella; 4 | import me.hydos.rosella.display.GlfwWindow; 5 | import me.hydos.rosella.render.Topology; 6 | import me.hydos.rosella.render.material.Material; 7 | import me.hydos.rosella.render.material.state.StateInfo; 8 | import me.hydos.rosella.render.model.GuiRenderObject; 9 | import me.hydos.rosella.render.resource.Global; 10 | import me.hydos.rosella.render.resource.Identifier; 11 | import me.hydos.rosella.render.shader.RawShaderProgram; 12 | import me.hydos.rosella.render.shader.ShaderProgram; 13 | import me.hydos.rosella.render.texture.SamplerCreateInfo; 14 | import me.hydos.rosella.render.texture.TextureFilter; 15 | import me.hydos.rosella.render.texture.WrapMode; 16 | import me.hydos.rosella.render.vertex.VertexFormats; 17 | import me.hydos.rosella.scene.object.impl.SimpleFramebufferObjectManager; 18 | import org.joml.Matrix4f; 19 | import org.joml.Vector3f; 20 | import org.lwjgl.vulkan.VK10; 21 | 22 | public class PortalJava { 23 | 24 | public static final GlfwWindow window = new GlfwWindow(1280, 720, "Portal 3: Java Edition", true); 25 | public static final Rosella rosella = new Rosella(window, "portal 3", true); 26 | 27 | public static final Matrix4f viewMatrix = new Matrix4f().lookAt(2.0f, -40.0f, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f); 28 | public static final Matrix4f projectionMatrix = new Matrix4f().perspective( 29 | (float) Math.toRadians(45.0), 30 | 1280 / 720f, 31 | 0.1f, 32 | 1000.0f 33 | ); 34 | 35 | public static Material menuBackground; 36 | public static Material portalLogo; 37 | 38 | public static ShaderProgram basicShader; 39 | public static ShaderProgram guiShader; 40 | 41 | public static StateInfo defaultStateInfo = new StateInfo( 42 | VK10.VK_COLOR_COMPONENT_R_BIT | VK10.VK_COLOR_COMPONENT_G_BIT | VK10.VK_COLOR_COMPONENT_B_BIT | VK10.VK_COLOR_COMPONENT_A_BIT, 43 | true, 44 | false, 45 | 0, 0, 0, 0, 46 | false, 47 | true, 48 | VK10.VK_BLEND_FACTOR_ONE, VK10.VK_BLEND_FACTOR_ZERO, VK10.VK_BLEND_FACTOR_ONE, VK10.VK_BLEND_FACTOR_ZERO, 49 | VK10.VK_BLEND_OP_ADD, 50 | true, 51 | false, 52 | VK10.VK_COMPARE_OP_LESS, 53 | false, 54 | VK10.VK_LOGIC_OP_COPY, 55 | 1.0f 56 | ); 57 | 58 | public static void main(String[] args) { 59 | System.loadLibrary("renderdoc"); 60 | loadShaders(); 61 | loadMaterials(); 62 | setupMainMenuScene(); 63 | rosella.renderer.rebuildCommandBuffers(rosella.renderer.renderPass, (SimpleFramebufferObjectManager) rosella.objectManager); 64 | window.startAutomaticLoop(rosella); 65 | } 66 | 67 | private static void setupMainMenuScene() { 68 | rosella.getMainFboObjManager().addObject( 69 | new GuiRenderObject(menuBackground, -1f, new Vector3f(0, 0, 0), 1.5f, 1f, viewMatrix, projectionMatrix) 70 | ); 71 | 72 | rosella.getMainFboObjManager().addObject( 73 | new GuiRenderObject(portalLogo, -0.9f, new Vector3f(0, 0, 0), 0.4f, 0.1f, -1f, -2.6f, viewMatrix, projectionMatrix) 74 | ); 75 | } 76 | 77 | private static void loadMaterials() { 78 | menuBackground = rosella.objectManager.registerMaterial( 79 | new Material( 80 | Global.INSTANCE.ensureResource(new Identifier("example", "textures/background/background01.png")), 81 | guiShader, 82 | VK10.VK_FORMAT_R8G8B8A8_UNORM, 83 | Topology.TRIANGLES, 84 | VertexFormats.POSITION_COLOR3_UV0, 85 | new SamplerCreateInfo(TextureFilter.NEAREST, WrapMode.REPEAT), 86 | defaultStateInfo 87 | ) 88 | ); 89 | 90 | portalLogo = rosella.objectManager.registerMaterial( 91 | new Material( 92 | Global.INSTANCE.ensureResource(new Identifier("example", "textures/gui/portal2logo.png")), 93 | guiShader, 94 | VK10.VK_FORMAT_R8G8B8A8_SRGB, 95 | Topology.TRIANGLES, 96 | VertexFormats.POSITION_COLOR3_UV0, 97 | new SamplerCreateInfo(TextureFilter.NEAREST, WrapMode.REPEAT), 98 | defaultStateInfo 99 | ) 100 | ); 101 | 102 | rosella.objectManager.submitMaterials(); 103 | } 104 | 105 | private static void loadShaders() { 106 | basicShader = rosella.objectManager.addShader( 107 | new RawShaderProgram( 108 | Global.INSTANCE.ensureResource(new Identifier("rosella", "shaders/base.v.glsl")), 109 | Global.INSTANCE.ensureResource(new Identifier("rosella", "shaders/base.f.glsl")), 110 | rosella.common.device, 111 | rosella.common.memory, 112 | 10, 113 | RawShaderProgram.PoolUboInfo.INSTANCE, 114 | new RawShaderProgram.PoolSamplerInfo(-1, 0) 115 | ) 116 | ); 117 | 118 | guiShader = rosella.objectManager.addShader( 119 | new RawShaderProgram( 120 | Global.INSTANCE.ensureResource(new Identifier("rosella", "shaders/gui.v.glsl")), 121 | Global.INSTANCE.ensureResource(new Identifier("rosella", "shaders/gui.f.glsl")), 122 | rosella.common.device, 123 | rosella.common.memory, 124 | 10, 125 | RawShaderProgram.PoolUboInfo.INSTANCE, 126 | new RawShaderProgram.PoolSamplerInfo(-1, 0) 127 | ) 128 | ); 129 | } 130 | } 131 | --------------------------------------------------------------------------------