├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── extern ├── CMakeLists.txt └── glad │ ├── CMakeLists.txt │ ├── include │ ├── KHR │ │ └── khrplatform.h │ └── glad │ │ └── glad.h │ └── src │ └── glad.c ├── shaders ├── renderBoundingBox.vert ├── renderCurvature.frag ├── renderGeometry.frag ├── renderGeometry.vert ├── renderShading.frag ├── simStep1.comp ├── simStep2.comp ├── simStep3.comp ├── simStep4.comp ├── simStep5.comp └── simStep6.comp └── src ├── CMakeLists.txt ├── flut ├── CMakeLists.txt ├── Camera.cpp ├── Camera.hpp ├── GlHelper.cpp ├── GlHelper.hpp ├── GlQueryRetriever.cpp ├── GlQueryRetriever.hpp ├── Simulation.cpp ├── Simulation.hpp ├── Window.cpp ├── Window.hpp └── main.cpp └── imgui ├── CMakeLists.txt ├── include ├── imconfig.h ├── imgui.h └── imgui_impl_sdl_glad.h └── src ├── imgui.cpp ├── imgui_demo.cpp ├── imgui_draw.cpp ├── imgui_impl_sdl_glad.cpp ├── imgui_internal.h ├── stb_rect_pack.h ├── stb_textedit.h └── stb_truetype.h /.gitignore: -------------------------------------------------------------------------------- 1 | ### Project specific ### 2 | 3 | 3rdparty/INSTALL 4 | 3rdparty/temp 5 | BUILD 6 | 7 | ### C++ ### 8 | # Prerequisites 9 | *.d 10 | 11 | # Compiled Object files 12 | *.slo 13 | *.lo 14 | *.o 15 | *.obj 16 | 17 | # Precompiled Headers 18 | *.gch 19 | *.pch 20 | 21 | # Compiled Dynamic libraries 22 | *.so 23 | *.dylib 24 | *.dll 25 | 26 | # Fortran module files 27 | *.mod 28 | *.smod 29 | 30 | # Compiled Static libraries 31 | *.lai 32 | *.la 33 | *.a 34 | *.lib 35 | 36 | # Executables 37 | *.exe 38 | *.out 39 | *.app 40 | 41 | ### CLion ### 42 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 43 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 44 | 45 | # User-specific stuff: 46 | .idea/**/workspace.xml 47 | .idea/**/tasks.xml 48 | .idea/dictionaries 49 | 50 | # Sensitive or high-churn files: 51 | .idea/**/dataSources/ 52 | .idea/**/dataSources.ids 53 | .idea/**/dataSources.xml 54 | .idea/**/dataSources.local.xml 55 | .idea/**/sqlDataSources.xml 56 | .idea/**/dynamic.xml 57 | .idea/**/uiDesigner.xml 58 | 59 | # Gradle: 60 | .idea/**/gradle.xml 61 | .idea/**/libraries 62 | 63 | # CMake 64 | cmake-build-debug/ 65 | 66 | # Mongo Explorer plugin: 67 | .idea/**/mongoSettings.xml 68 | 69 | ## File-based project format: 70 | *.iws 71 | 72 | ## Plugin-specific files: 73 | 74 | # IntelliJ 75 | /out/ 76 | 77 | # mpeltonen/sbt-idea plugin 78 | .idea_modules/ 79 | 80 | # JIRA plugin 81 | atlassian-ide-plugin.xml 82 | 83 | # Cursive Clojure plugin 84 | .idea/replstate.xml 85 | 86 | # Ruby plugin and RubyMine 87 | /.rakeTasks 88 | 89 | # Crashlytics plugin (for Android Studio and IntelliJ) 90 | com_crashlytics_export_strings.xml 91 | crashlytics.properties 92 | crashlytics-build.properties 93 | fabric.properties 94 | 95 | ### CLion Patch ### 96 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 97 | 98 | # *.iml 99 | # modules.xml 100 | # .idea/misc.xml 101 | # *.ipr 102 | 103 | # Sonarlint plugin 104 | .idea/sonarlint 105 | 106 | ### CMake ### 107 | CMakeCache.txt 108 | CMakeFiles 109 | CMakeScripts 110 | Testing 111 | Makefile 112 | cmake_install.cmake 113 | install_manifest.txt 114 | compile_commands.json 115 | CTestTestfile.cmake 116 | build 117 | 118 | ### VisualStudio ### 119 | ## Ignore Visual Studio temporary files, build results, and 120 | ## files generated by popular Visual Studio add-ons. 121 | ## 122 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 123 | 124 | # User-specific files 125 | *.suo 126 | *.user 127 | *.userosscache 128 | *.sln.docstates 129 | 130 | # User-specific files (MonoDevelop/Xamarin Studio) 131 | *.userprefs 132 | 133 | # Build results 134 | [Dd]ebug/ 135 | [Dd]ebugPublic/ 136 | [Rr]elease/ 137 | [Rr]eleases/ 138 | x64/ 139 | x86/ 140 | bld/ 141 | [Bb]in/ 142 | [Oo]bj/ 143 | [Ll]og/ 144 | 145 | # Visual Studio 2015 cache/options directory 146 | .vs/ 147 | # Uncomment if you have tasks that create the project's static files in wwwroot 148 | #wwwroot/ 149 | 150 | # MSTest test Results 151 | [Tt]est[Rr]esult*/ 152 | [Bb]uild[Ll]og.* 153 | 154 | # NUNIT 155 | *.VisualState.xml 156 | TestResult.xml 157 | 158 | # Build Results of an ATL Project 159 | [Dd]ebugPS/ 160 | [Rr]eleasePS/ 161 | dlldata.c 162 | 163 | # .NET Core 164 | project.lock.json 165 | project.fragment.lock.json 166 | artifacts/ 167 | **/Properties/launchSettings.json 168 | 169 | *_i.c 170 | *_p.c 171 | *_i.h 172 | *.ilk 173 | *.meta 174 | *.pdb 175 | *.pgc 176 | *.pgd 177 | *.rsp 178 | *.sbr 179 | *.tlb 180 | *.tli 181 | *.tlh 182 | *.tmp 183 | *.tmp_proj 184 | *.log 185 | *.vspscc 186 | *.vssscc 187 | .builds 188 | *.pidb 189 | *.svclog 190 | *.scc 191 | 192 | # Chutzpah Test files 193 | _Chutzpah* 194 | 195 | # Visual C++ cache files 196 | ipch/ 197 | *.aps 198 | *.ncb 199 | *.opendb 200 | *.opensdf 201 | *.sdf 202 | *.cachefile 203 | *.VC.db 204 | *.VC.VC.opendb 205 | 206 | # Visual Studio profiler 207 | *.psess 208 | *.vsp 209 | *.vspx 210 | *.sap 211 | 212 | # TFS 2012 Local Workspace 213 | $tf/ 214 | 215 | # Guidance Automation Toolkit 216 | *.gpState 217 | 218 | # ReSharper is a .NET coding add-in 219 | _ReSharper*/ 220 | *.[Rr]e[Ss]harper 221 | *.DotSettings.user 222 | 223 | # JustCode is a .NET coding add-in 224 | .JustCode 225 | 226 | # TeamCity is a build add-in 227 | _TeamCity* 228 | 229 | # DotCover is a Code Coverage Tool 230 | *.dotCover 231 | 232 | # Visual Studio code coverage results 233 | *.coverage 234 | *.coveragexml 235 | 236 | # NCrunch 237 | _NCrunch_* 238 | .*crunch*.local.xml 239 | nCrunchTemp_* 240 | 241 | # MightyMoose 242 | *.mm.* 243 | AutoTest.Net/ 244 | 245 | # Web workbench (sass) 246 | .sass-cache/ 247 | 248 | # Installshield output folder 249 | [Ee]xpress/ 250 | 251 | # DocProject is a documentation generator add-in 252 | DocProject/buildhelp/ 253 | DocProject/Help/*.HxT 254 | DocProject/Help/*.HxC 255 | DocProject/Help/*.hhc 256 | DocProject/Help/*.hhk 257 | DocProject/Help/*.hhp 258 | DocProject/Help/Html2 259 | DocProject/Help/html 260 | 261 | # Click-Once directory 262 | publish/ 263 | 264 | # Publish Web Output 265 | *.[Pp]ublish.xml 266 | *.azurePubxml 267 | # TODO: Uncomment the next line to ignore your web deploy settings. 268 | # By default, sensitive information, such as encrypted password 269 | # should be stored in the .pubxml.user file. 270 | #*.pubxml 271 | *.pubxml.user 272 | *.publishproj 273 | 274 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 275 | # checkin your Azure Web App publish settings, but sensitive information contained 276 | # in these scripts will be unencrypted 277 | PublishScripts/ 278 | 279 | # NuGet Packages 280 | *.nupkg 281 | # The packages folder can be ignored because of Package Restore 282 | **/packages/* 283 | # except build/, which is used as an MSBuild target. 284 | !**/packages/build/ 285 | # Uncomment if necessary however generally it will be regenerated when needed 286 | #!**/packages/repositories.config 287 | # NuGet v3's project.json files produces more ignorable files 288 | *.nuget.props 289 | *.nuget.targets 290 | 291 | # Microsoft Azure Build Output 292 | csx/ 293 | *.build.csdef 294 | 295 | # Microsoft Azure Emulator 296 | ecf/ 297 | rcf/ 298 | 299 | # Windows Store app package directories and files 300 | AppPackages/ 301 | BundleArtifacts/ 302 | Package.StoreAssociation.xml 303 | _pkginfo.txt 304 | 305 | # Visual Studio cache files 306 | # files ending in .cache can be ignored 307 | *.[Cc]ache 308 | # but keep track of directories ending in .cache 309 | !*.[Cc]ache/ 310 | 311 | # Others 312 | ClientBin/ 313 | ~$* 314 | *~ 315 | *.dbmdl 316 | *.dbproj.schemaview 317 | *.jfm 318 | *.pfx 319 | *.publishsettings 320 | orleans.codegen.cs 321 | 322 | # Since there are multiple workflows, uncomment next line to ignore bower_components 323 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 324 | #bower_components/ 325 | 326 | # RIA/Silverlight projects 327 | Generated_Code/ 328 | 329 | # Backup & report files from converting an old project file 330 | # to a newer Visual Studio version. Backup files are not needed, 331 | # because we have git ;-) 332 | _UpgradeReport_Files/ 333 | Backup*/ 334 | UpgradeLog*.XML 335 | UpgradeLog*.htm 336 | 337 | # SQL Server files 338 | *.mdf 339 | *.ldf 340 | *.ndf 341 | 342 | # Business Intelligence projects 343 | *.rdl.data 344 | *.bim.layout 345 | *.bim_*.settings 346 | 347 | # Microsoft Fakes 348 | FakesAssemblies/ 349 | 350 | # GhostDoc plugin setting file 351 | *.GhostDoc.xml 352 | 353 | # Node.js Tools for Visual Studio 354 | .ntvs_analysis.dat 355 | node_modules/ 356 | 357 | # Typescript v1 declaration files 358 | typings/ 359 | 360 | # Visual Studio 6 build log 361 | *.plg 362 | 363 | # Visual Studio 6 workspace options file 364 | *.opt 365 | 366 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 367 | *.vbw 368 | 369 | # Visual Studio LightSwitch build output 370 | **/*.HTMLClient/GeneratedArtifacts 371 | **/*.DesktopClient/GeneratedArtifacts 372 | **/*.DesktopClient/ModelManifest.xml 373 | **/*.Server/GeneratedArtifacts 374 | **/*.Server/ModelManifest.xml 375 | _Pvt_Extensions 376 | 377 | # Paket dependency manager 378 | .paket/paket.exe 379 | paket-files/ 380 | 381 | # FAKE - F# Make 382 | .fake/ 383 | 384 | # JetBrains Rider 385 | .idea/ 386 | *.sln.iml 387 | 388 | # CodeRush 389 | .cr/ 390 | 391 | # Python Tools for Visual Studio (PTVS) 392 | __pycache__/ 393 | *.pyc 394 | 395 | # Cake - Uncomment if you are using it 396 | # tools/** 397 | # !tools/packages.config 398 | 399 | # Telerik's JustMock configuration file 400 | *.jmconfig 401 | 402 | # BizTalk build output 403 | *.btp.cs 404 | *.btm.cs 405 | *.odx.cs 406 | *.xsd.cs 407 | 408 | ### VisualStudio Patch ### 409 | # By default, sensitive information, such as encrypted password 410 | # should be stored in the .pubxml.user file. 411 | 412 | ### MacOS ### 413 | 414 | .DS_Store 415 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "extern/glm"] 2 | path = extern/glm 3 | url = https://github.com/g-truc/glm 4 | [submodule "extern/SDL"] 5 | path = extern/SDL 6 | url = https://github.com/libsdl-org/SDL.git 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | 3 | project(flut VERSION 0.1) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | set(FLUT_OUTPUT_DIR "${CMAKE_BINARY_DIR}/bin" CACHE PATH "Location of build process output.") 9 | set(FLUT_SHADERS_DIR "${CMAKE_SOURCE_DIR}/shaders" CACHE PATH "Location of the shaders folder.") 10 | set(FLUT_SOURCE_DIR "${PROJECT_SOURCE_DIR}/src" CACHE PATH "Location of the source root folder.") 11 | 12 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${FLUT_OUTPUT_DIR}") 13 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${FLUT_OUTPUT_DIR}") 14 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${FLUT_OUTPUT_DIR}") 15 | 16 | foreach(CONFIG_TYPE ${CMAKE_CONFIGURATION_TYPES}) 17 | string(TOUPPER ${CONFIG_TYPE} CONFIG_TYPE) 18 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${CONFIG_TYPE} ${FLUT_OUTPUT_DIR}) 19 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${CONFIG_TYPE} ${FLUT_OUTPUT_DIR}) 20 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${CONFIG_TYPE} ${FLUT_OUTPUT_DIR}) 21 | endforeach(CONFIG_TYPE CMAKE_CONFIGURATION_TYPES) 22 | 23 | include(CheckIPOSupported) 24 | check_ipo_supported(RESULT ENABLE_IPO) 25 | if (ENABLE_IPO) 26 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON) 27 | endif() 28 | 29 | find_package(OpenGL REQUIRED) 30 | 31 | add_subdirectory(extern) 32 | add_subdirectory(src) 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU 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 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flut 2 | 3 | GPU-based fluid simulation and rendering using OpenGL 4.6 compute shaders, DSA and bindless textures. Fluid behaviour is simulated using _smoothed-particle hydrodynamics_ (SPH) as described by Müller et al. [1]. The GPU simulation pipeline roughly follows the work of Harada et al. [2]. After particle simulation, a screen-space rendering technique is performed to suggest a fluid-like continuous surface [3]. 4 | 5 | [1] Particle-Based Fluid Simulation for Interactive Applications, Müller et al. 2003 6 | [2] Smoothed Particle Hydrodynamics on GPUs, Harada et al. 2007 7 | [3] Screen Space Fluid Rendering with Curvature Flow, van der Laan et al. 2009 8 | 9 | https://github.com/user-attachments/assets/56878c47-0011-4204-90dd-7f5a3ff81442 10 | 11 | ## Simulation pipeline 12 | 13 | flut first builds a uniform grid to speed up SPH neighbor search. 14 | In an earlier version, bitonic mergesort was used for this purpose. 15 | It got later replaced by counting sort, as outlined below. 16 | After grid construction, per-particle densities are computed. 17 | From these values, forces are derived which contribute to velocity and position changes. 18 | Lastly, the particles are splatted as screen space spheres, smoothed to evoke the look of a surface and shaded using Phong. 19 | 20 | 21 | 22 | 23 | ### Fast uniform grid construction 24 | 25 | To speed up uniform grid construction, counting sort ([Hoetzlein 2014](https://ramakarl.com/pdfs/2014_Hoetzlein_FastFixedRadius_Neighbors.pdf)) is used. 26 | The implementation follows closely what Sebastian Aaltonen described to me about Claybook's implementation. 27 | Particles are first splatted to a 3D grid to obtain the cell particle count. 28 | Next, a reduction is performed to obtain per-cell particle offsets. 29 | Finally, the particles are copied to a new buffer in cell-local order for optimal memory locality. 30 | 31 | ### Minor optimizations 32 | 33 | * The neighborhood search uses an unrolled single loop with interleaved particle fetching as described [here](https://x.com/SebAaltonen/status/1270613495768330241) 34 | * Curvature flow fragment shader is only executed on oriented bounding box 35 | * Very small but nice: negative grid bounds checks are avoided using `uvec3` cast (from [this talk](https://www.graphicsprogrammingconference.nl/realtime-fluid-simulations/)) 36 | 37 | ## Build 38 | 39 | Make sure you `git clone` with the `--recursive` flag or execute `git submodule update --init --recursive` after a non-recursive clone. 40 | 41 | Then, invoke CMake for your buildsystem of choice and build the `flut` target. 42 | 43 | Example: 44 | ```sh 45 | mkdir -p BUILD && cd BUILD 46 | cmake .. -G "Visual Studio 16 2019 Win64" -DCMAKE_BUILD_TYPE=Release 47 | cmake --build . -j 8 --target flut --config Release 48 | ``` 49 | 50 | ## Future improvements 51 | 52 | - Improved rendering 53 | - Boundary density contribution 54 | - Surface tension forces 55 | - BDF-2 integration 56 | 57 | ## Acknowledgements 58 | 59 | Special thanks to Sebastian Aaltonen for his detailed description of Claybook's SPH implementation. 60 | 61 | ## License 62 | 63 | ``` 64 | 65 | Copyright (C) 2020 Pablo Delgado Krämer 66 | 67 | This program is free software: you can redistribute it and/or modify 68 | it under the terms of the GNU General Public License as published by 69 | the Free Software Foundation, either version 3 of the License, or 70 | (at your option) any later version. 71 | 72 | This program is distributed in the hope that it will be useful, 73 | but WITHOUT ANY WARRANTY; without even the implied warranty of 74 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 75 | GNU General Public License for more details. 76 | 77 | You should have received a copy of the GNU General Public License 78 | along with this program. If not, see . 79 | 80 | ``` 81 | -------------------------------------------------------------------------------- /extern/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(glm) 2 | add_subdirectory(glad) 3 | add_subdirectory(SDL) 4 | -------------------------------------------------------------------------------- /extern/glad/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(GLAD_INCLUDES 2 | include/glad/glad.h 3 | include/KHR/khrplatform.h 4 | ) 5 | 6 | set(GLAD_SRCS 7 | src/glad.c 8 | ) 9 | 10 | add_library( 11 | glad 12 | STATIC 13 | ${GLAD_INCLUDES} 14 | ${GLAD_SRCS} 15 | ) 16 | 17 | target_include_directories( 18 | glad 19 | PUBLIC 20 | include 21 | PRIVATE 22 | src 23 | ) 24 | -------------------------------------------------------------------------------- /extern/glad/include/KHR/khrplatform.h: -------------------------------------------------------------------------------- 1 | #ifndef __khrplatform_h_ 2 | #define __khrplatform_h_ 3 | 4 | /* 5 | ** Copyright (c) 2008-2018 The Khronos Group Inc. 6 | ** 7 | ** Permission is hereby granted, free of charge, to any person obtaining a 8 | ** copy of this software and/or associated documentation files (the 9 | ** "Materials"), to deal in the Materials without restriction, including 10 | ** without limitation the rights to use, copy, modify, merge, publish, 11 | ** distribute, sublicense, and/or sell copies of the Materials, and to 12 | ** permit persons to whom the Materials are furnished to do so, subject to 13 | ** the following conditions: 14 | ** 15 | ** The above copyright notice and this permission notice shall be included 16 | ** in all copies or substantial portions of the Materials. 17 | ** 18 | ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. 25 | */ 26 | 27 | /* Khronos platform-specific types and definitions. 28 | * 29 | * The master copy of khrplatform.h is maintained in the Khronos EGL 30 | * Registry repository at https://github.com/KhronosGroup/EGL-Registry 31 | * The last semantic modification to khrplatform.h was at commit ID: 32 | * 67a3e0864c2d75ea5287b9f3d2eb74a745936692 33 | * 34 | * Adopters may modify this file to suit their platform. Adopters are 35 | * encouraged to submit platform specific modifications to the Khronos 36 | * group so that they can be included in future versions of this file. 37 | * Please submit changes by filing pull requests or issues on 38 | * the EGL Registry repository linked above. 39 | * 40 | * 41 | * See the Implementer's Guidelines for information about where this file 42 | * should be located on your system and for more details of its use: 43 | * http://www.khronos.org/registry/implementers_guide.pdf 44 | * 45 | * This file should be included as 46 | * #include 47 | * by Khronos client API header files that use its types and defines. 48 | * 49 | * The types in khrplatform.h should only be used to define API-specific types. 50 | * 51 | * Types defined in khrplatform.h: 52 | * khronos_int8_t signed 8 bit 53 | * khronos_uint8_t unsigned 8 bit 54 | * khronos_int16_t signed 16 bit 55 | * khronos_uint16_t unsigned 16 bit 56 | * khronos_int32_t signed 32 bit 57 | * khronos_uint32_t unsigned 32 bit 58 | * khronos_int64_t signed 64 bit 59 | * khronos_uint64_t unsigned 64 bit 60 | * khronos_intptr_t signed same number of bits as a pointer 61 | * khronos_uintptr_t unsigned same number of bits as a pointer 62 | * khronos_ssize_t signed size 63 | * khronos_usize_t unsigned size 64 | * khronos_float_t signed 32 bit floating point 65 | * khronos_time_ns_t unsigned 64 bit time in nanoseconds 66 | * khronos_utime_nanoseconds_t unsigned time interval or absolute time in 67 | * nanoseconds 68 | * khronos_stime_nanoseconds_t signed time interval in nanoseconds 69 | * khronos_boolean_enum_t enumerated boolean type. This should 70 | * only be used as a base type when a client API's boolean type is 71 | * an enum. Client APIs which use an integer or other type for 72 | * booleans cannot use this as the base type for their boolean. 73 | * 74 | * Tokens defined in khrplatform.h: 75 | * 76 | * KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values. 77 | * 78 | * KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0. 79 | * KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0. 80 | * 81 | * Calling convention macros defined in this file: 82 | * KHRONOS_APICALL 83 | * KHRONOS_APIENTRY 84 | * KHRONOS_APIATTRIBUTES 85 | * 86 | * These may be used in function prototypes as: 87 | * 88 | * KHRONOS_APICALL void KHRONOS_APIENTRY funcname( 89 | * int arg1, 90 | * int arg2) KHRONOS_APIATTRIBUTES; 91 | */ 92 | 93 | #if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC) 94 | # define KHRONOS_STATIC 1 95 | #endif 96 | 97 | /*------------------------------------------------------------------------- 98 | * Definition of KHRONOS_APICALL 99 | *------------------------------------------------------------------------- 100 | * This precedes the return type of the function in the function prototype. 101 | */ 102 | #if defined(KHRONOS_STATIC) 103 | /* If the preprocessor constant KHRONOS_STATIC is defined, make the 104 | * header compatible with static linking. */ 105 | # define KHRONOS_APICALL 106 | #elif defined(_WIN32) 107 | # define KHRONOS_APICALL __declspec(dllimport) 108 | #elif defined (__SYMBIAN32__) 109 | # define KHRONOS_APICALL IMPORT_C 110 | #elif defined(__ANDROID__) 111 | # define KHRONOS_APICALL __attribute__((visibility("default"))) 112 | #else 113 | # define KHRONOS_APICALL 114 | #endif 115 | 116 | /*------------------------------------------------------------------------- 117 | * Definition of KHRONOS_APIENTRY 118 | *------------------------------------------------------------------------- 119 | * This follows the return type of the function and precedes the function 120 | * name in the function prototype. 121 | */ 122 | #if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__) 123 | /* Win32 but not WinCE */ 124 | # define KHRONOS_APIENTRY __stdcall 125 | #else 126 | # define KHRONOS_APIENTRY 127 | #endif 128 | 129 | /*------------------------------------------------------------------------- 130 | * Definition of KHRONOS_APIATTRIBUTES 131 | *------------------------------------------------------------------------- 132 | * This follows the closing parenthesis of the function prototype arguments. 133 | */ 134 | #if defined (__ARMCC_2__) 135 | #define KHRONOS_APIATTRIBUTES __softfp 136 | #else 137 | #define KHRONOS_APIATTRIBUTES 138 | #endif 139 | 140 | /*------------------------------------------------------------------------- 141 | * basic type definitions 142 | *-----------------------------------------------------------------------*/ 143 | #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__) 144 | 145 | 146 | /* 147 | * Using 148 | */ 149 | #include 150 | typedef int32_t khronos_int32_t; 151 | typedef uint32_t khronos_uint32_t; 152 | typedef int64_t khronos_int64_t; 153 | typedef uint64_t khronos_uint64_t; 154 | #define KHRONOS_SUPPORT_INT64 1 155 | #define KHRONOS_SUPPORT_FLOAT 1 156 | 157 | #elif defined(__VMS ) || defined(__sgi) 158 | 159 | /* 160 | * Using 161 | */ 162 | #include 163 | typedef int32_t khronos_int32_t; 164 | typedef uint32_t khronos_uint32_t; 165 | typedef int64_t khronos_int64_t; 166 | typedef uint64_t khronos_uint64_t; 167 | #define KHRONOS_SUPPORT_INT64 1 168 | #define KHRONOS_SUPPORT_FLOAT 1 169 | 170 | #elif defined(_WIN32) && !defined(__SCITECH_SNAP__) 171 | 172 | /* 173 | * Win32 174 | */ 175 | typedef __int32 khronos_int32_t; 176 | typedef unsigned __int32 khronos_uint32_t; 177 | typedef __int64 khronos_int64_t; 178 | typedef unsigned __int64 khronos_uint64_t; 179 | #define KHRONOS_SUPPORT_INT64 1 180 | #define KHRONOS_SUPPORT_FLOAT 1 181 | 182 | #elif defined(__sun__) || defined(__digital__) 183 | 184 | /* 185 | * Sun or Digital 186 | */ 187 | typedef int khronos_int32_t; 188 | typedef unsigned int khronos_uint32_t; 189 | #if defined(__arch64__) || defined(_LP64) 190 | typedef long int khronos_int64_t; 191 | typedef unsigned long int khronos_uint64_t; 192 | #else 193 | typedef long long int khronos_int64_t; 194 | typedef unsigned long long int khronos_uint64_t; 195 | #endif /* __arch64__ */ 196 | #define KHRONOS_SUPPORT_INT64 1 197 | #define KHRONOS_SUPPORT_FLOAT 1 198 | 199 | #elif 0 200 | 201 | /* 202 | * Hypothetical platform with no float or int64 support 203 | */ 204 | typedef int khronos_int32_t; 205 | typedef unsigned int khronos_uint32_t; 206 | #define KHRONOS_SUPPORT_INT64 0 207 | #define KHRONOS_SUPPORT_FLOAT 0 208 | 209 | #else 210 | 211 | /* 212 | * Generic fallback 213 | */ 214 | #include 215 | typedef int32_t khronos_int32_t; 216 | typedef uint32_t khronos_uint32_t; 217 | typedef int64_t khronos_int64_t; 218 | typedef uint64_t khronos_uint64_t; 219 | #define KHRONOS_SUPPORT_INT64 1 220 | #define KHRONOS_SUPPORT_FLOAT 1 221 | 222 | #endif 223 | 224 | 225 | /* 226 | * Types that are (so far) the same on all platforms 227 | */ 228 | typedef signed char khronos_int8_t; 229 | typedef unsigned char khronos_uint8_t; 230 | typedef signed short int khronos_int16_t; 231 | typedef unsigned short int khronos_uint16_t; 232 | 233 | /* 234 | * Types that differ between LLP64 and LP64 architectures - in LLP64, 235 | * pointers are 64 bits, but 'long' is still 32 bits. Win64 appears 236 | * to be the only LLP64 architecture in current use. 237 | */ 238 | #ifdef _WIN64 239 | typedef signed long long int khronos_intptr_t; 240 | typedef unsigned long long int khronos_uintptr_t; 241 | typedef signed long long int khronos_ssize_t; 242 | typedef unsigned long long int khronos_usize_t; 243 | #else 244 | typedef signed long int khronos_intptr_t; 245 | typedef unsigned long int khronos_uintptr_t; 246 | typedef signed long int khronos_ssize_t; 247 | typedef unsigned long int khronos_usize_t; 248 | #endif 249 | 250 | #if KHRONOS_SUPPORT_FLOAT 251 | /* 252 | * Float type 253 | */ 254 | typedef float khronos_float_t; 255 | #endif 256 | 257 | #if KHRONOS_SUPPORT_INT64 258 | /* Time types 259 | * 260 | * These types can be used to represent a time interval in nanoseconds or 261 | * an absolute Unadjusted System Time. Unadjusted System Time is the number 262 | * of nanoseconds since some arbitrary system event (e.g. since the last 263 | * time the system booted). The Unadjusted System Time is an unsigned 264 | * 64 bit value that wraps back to 0 every 584 years. Time intervals 265 | * may be either signed or unsigned. 266 | */ 267 | typedef khronos_uint64_t khronos_utime_nanoseconds_t; 268 | typedef khronos_int64_t khronos_stime_nanoseconds_t; 269 | #endif 270 | 271 | /* 272 | * Dummy value used to pad enum types to 32 bits. 273 | */ 274 | #ifndef KHRONOS_MAX_ENUM 275 | #define KHRONOS_MAX_ENUM 0x7FFFFFFF 276 | #endif 277 | 278 | /* 279 | * Enumerated boolean type 280 | * 281 | * Values other than zero should be considered to be true. Therefore 282 | * comparisons should not be made against KHRONOS_TRUE. 283 | */ 284 | typedef enum { 285 | KHRONOS_FALSE = 0, 286 | KHRONOS_TRUE = 1, 287 | KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM 288 | } khronos_boolean_enum_t; 289 | 290 | #endif /* __khrplatform_h_ */ 291 | -------------------------------------------------------------------------------- /shaders/renderBoundingBox.vert: -------------------------------------------------------------------------------- 1 | layout (location = 0) uniform mat4 MVP; 2 | 3 | in vec3 vertPos; 4 | 5 | void main() 6 | { 7 | gl_Position = MVP * vec4(vertPos, 1.0); 8 | } 9 | -------------------------------------------------------------------------------- /shaders/renderCurvature.frag: -------------------------------------------------------------------------------- 1 | #extension GL_ARB_bindless_texture: require 2 | 3 | const float Z_THRESHOLD = 0.0005; 4 | const float SMOOTH_DT = 0.0005; 5 | 6 | layout (location = 0) uniform mat4 MVP; 7 | layout (location = 1, bindless_sampler) uniform sampler2D depthTex; 8 | layout (location = 2) uniform mat4 projection; 9 | layout (location = 3) uniform ivec2 res; 10 | 11 | out float finalDepth; 12 | 13 | void main(void) 14 | { 15 | vec2 coords = gl_FragCoord.xy / res; 16 | vec2 dx = vec2(1.0 / res.x, 0.0); 17 | vec2 dy = vec2(0.0, 1.0 / res.y); 18 | 19 | float z = texture(depthTex, coords).x; 20 | float zRight = texture(depthTex, coords + dx).x; 21 | float zLeft = texture(depthTex, coords - dx).x; 22 | float zTop = texture(depthTex, coords + dy).x; 23 | float zBottom = texture(depthTex, coords - dy).x; 24 | float zTopRight = texture(depthTex, coords + dx + dy).x; 25 | float zBottomLeft = texture(depthTex, coords - dx - dy).x; 26 | float zBottomRight = texture(depthTex, coords + dx - dy).x; 27 | float zTopLeft = texture(depthTex, coords - dx + dy).x; 28 | 29 | // Gradients (first derivative) 30 | float dzdx = 0.5 * (zRight - zLeft); 31 | float dzdy = 0.5 * (zTop - zBottom); 32 | 33 | // (central difference for better results) 34 | float dzdxy = (zTopRight + zBottomLeft - zBottomRight - zTopLeft) * 0.25; 35 | 36 | // Equation (3) 37 | float Fx = -projection[0][0]; // 2n / (r-l) 38 | float Fy = -projection[1][1]; // 2n / (t-b) 39 | float Cx = 2.0 / (res.x * Fx); 40 | float Cy = 2.0 / (res.y * Fy); 41 | float Cy2 = Cy * Cy; 42 | float Cx2 = Cx * Cx; 43 | 44 | // Equation (5) 45 | float D = Cy2 * (dzdx * dzdx) + Cx2 * (dzdy * dzdy) + Cx2 * Cy2 * (z * z); 46 | 47 | float dzdx2 = zRight + zLeft - z * 2.0; 48 | float dzdy2 = zTop + zBottom - z * 2.0; 49 | float dDdx = 2.0 * Cy2 * dzdx * dzdx2 + 2.0 * Cx2 * dzdy * dzdxy + 2.0 * Cx2 * Cy2 * z * dzdx; 50 | float dDdy = 2.0 * Cy2 * dzdx * dzdxy + 2.0 * Cx2 * dzdy * dzdy2 + 2.0 * Cx2 * Cy2 * z * dzdy; 51 | 52 | // Mean Curvature (7)(8)(6) 53 | float Ex = 0.5 * dzdx * dDdx - dzdx2 * D; 54 | float Ey = 0.5 * dzdy * dDdy - dzdy2 * D; 55 | float H2 = (Cy * Ex + Cx * Ey) / pow(D, 1.5); 56 | 57 | // Discontinuity handling 58 | bool screenEdge = (gl_FragCoord.xy == ivec2(0.0)) || (gl_FragCoord.xy == (res - ivec2(1))); 59 | bool bgPixel = (zRight == 1.0 || zLeft == 1.0 || zTop == 1.0 || zBottom == 1.0); 60 | bool depthDifferenceTooLarge = abs(zRight - z) > Z_THRESHOLD || abs(zLeft - z) > Z_THRESHOLD || 61 | abs(zTop - z) > Z_THRESHOLD || abs(zBottom - z) > Z_THRESHOLD; 62 | bool zeroOutCurvature = bgPixel || screenEdge || depthDifferenceTooLarge; 63 | 64 | finalDepth = z + float(!zeroOutCurvature) * (0.5 * H2) * SMOOTH_DT; 65 | } 66 | -------------------------------------------------------------------------------- /shaders/renderGeometry.frag: -------------------------------------------------------------------------------- 1 | in vec3 color; 2 | in vec3 centerPos; 3 | in vec2 uv; 4 | 5 | out vec3 finalColor; 6 | 7 | layout (location = 0) uniform mat4 VP; 8 | layout (location = 1) uniform mat4 V; 9 | layout (location = 2) uniform mat4 P; 10 | layout (location = 3) uniform vec3 gridSize; 11 | layout (location = 4) uniform vec3 gridOrigin; 12 | layout (location = 5) uniform ivec3 gridRes; 13 | layout (location = 6) uniform uint particleCount; 14 | layout (location = 7) uniform float pointRadius; 15 | layout (location = 8) uniform int colorMode; 16 | 17 | #define SPHERE 18 | #define DEPTH_REPLACEMENT 19 | 20 | void main(void) 21 | { 22 | finalColor = color; 23 | 24 | #ifdef SPHERE 25 | vec3 N; 26 | 27 | N.xy = uv * 2.0 - 1.0; 28 | 29 | float r2 = dot(N.xy, N.xy); 30 | 31 | if (r2 > 1.0) 32 | { 33 | discard; 34 | } 35 | 36 | #ifdef DEPTH_REPLACEMENT 37 | N.z = sqrt(1.0 - r2); 38 | 39 | vec4 pos_eye_space = vec4(centerPos + N * pointRadius, 1.0); 40 | vec4 pos_clip_space = P * pos_eye_space; 41 | float depth_ndc = pos_clip_space.z / pos_clip_space.w; 42 | float depth_winspace = depth_ndc * 0.5 + 0.5; 43 | gl_FragDepth = depth_winspace; 44 | #endif 45 | #endif 46 | } 47 | -------------------------------------------------------------------------------- /shaders/renderGeometry.vert: -------------------------------------------------------------------------------- 1 | const float FLOAT_MIN = 1.175494351e-38; 2 | const float GRID_EPS = 0.000001; 3 | const float MAX_DENSITY = 30.0; 4 | const vec3 PARTICLE_COLOR = vec3(0.0, 0.0, 0.6); 5 | 6 | struct Particle 7 | { 8 | vec3 position; 9 | float density; 10 | vec3 velocity; 11 | float pressure; 12 | }; 13 | 14 | layout(binding = 0, std430) restrict readonly buffer particleBuf 15 | { 16 | Particle particles[]; 17 | }; 18 | 19 | layout (location = 0) uniform mat4 VP; 20 | layout (location = 1) uniform mat4 V; 21 | layout (location = 2) uniform mat4 P; 22 | layout (location = 3) uniform vec3 gridSize; 23 | layout (location = 4) uniform vec3 gridOrigin; 24 | layout (location = 5) uniform ivec3 gridRes; 25 | layout (location = 6) uniform uint particleCount; 26 | layout (location = 7) uniform float pointRadius; 27 | layout (location = 8) uniform int colorMode; 28 | 29 | out vec3 color; 30 | out vec3 centerPos; 31 | out vec2 uv; 32 | 33 | vec2 UVS[4] = { vec2(0,0), vec2(1,0), vec2(0,1), vec2(1,1) }; 34 | vec2 OFFSETS[4] = { vec2(-1,-1), vec2(-1,+1), vec2(+1,-1), vec2(+1,+1) }; 35 | 36 | void main() 37 | { 38 | uint gid = gl_VertexID / 4; 39 | uint lid = gl_VertexID % 4; 40 | 41 | vec3 particlePos = particles[gid].position; 42 | 43 | if (colorMode == 0) 44 | { 45 | color = PARTICLE_COLOR; 46 | } 47 | else if (colorMode == 1) 48 | { 49 | vec3 velocity = abs(particles[gid].velocity); 50 | float w = max(max(FLOAT_MIN, velocity.x), max(velocity.y, velocity.z)); 51 | bool invalid = any(isnan(velocity)) || any(isinf(velocity)); 52 | color = mix(velocity / w, vec3(1.0, 0.0, 0.0), float(invalid)); 53 | } 54 | else if (colorMode == 2) 55 | { 56 | vec3 velocity = particles[gid].velocity; 57 | float speed = length(velocity); 58 | color = vec3(speed, speed, 0.0); 59 | } 60 | else if (colorMode == 3) 61 | { 62 | float density = particles[gid].density; 63 | float norm = density / MAX_DENSITY; 64 | bool invalid = (density <= 0.0) || any(isnan(density)) || any(isinf(density)); 65 | color = mix(vec3(0.0, norm, 0.0), vec3(1.0, 0.0, 0.0), float(invalid)); 66 | } 67 | else if (colorMode == 4) 68 | { 69 | vec3 normPos = (particlePos - gridOrigin) / gridSize; 70 | ivec3 voxelCoord = ivec3(normPos * (1.0f - GRID_EPS) * gridRes); 71 | color = vec3(voxelCoord) / gridRes; 72 | } 73 | 74 | centerPos = (V * vec4(particlePos, 1.0)).xyz; 75 | uv = UVS[lid]; 76 | 77 | vec3 wsCameraRight = vec3(V[0][0], V[1][0], V[2][0]); 78 | vec3 wsCameraUp = vec3(V[0][1], V[1][1], V[2][1]); 79 | vec3 wsCameraForward = vec3(V[0][2], V[1][2], V[2][2]); 80 | 81 | vec3 wsVertPos = particlePos + (wsCameraRight * OFFSETS[lid].x + wsCameraUp * OFFSETS[lid].y) * pointRadius; 82 | 83 | gl_Position = VP * vec4(wsVertPos, 1.0); 84 | } 85 | -------------------------------------------------------------------------------- /shaders/renderShading.frag: -------------------------------------------------------------------------------- 1 | #extension GL_ARB_bindless_texture: require 2 | 3 | const vec3 LIGHT_POS = vec3(0.0, 10.0, 0.0); 4 | const float AMBIENT_COEFF = 0.3; 5 | const float SHININESS = 25.0; 6 | 7 | layout (location = 0) uniform mat4 MVP; 8 | layout (location = 1, bindless_sampler) uniform sampler2D depthTex; 9 | layout (location = 2, bindless_sampler) uniform sampler2D colorTex; 10 | layout (location = 3) uniform uint width; 11 | layout (location = 4) uniform uint height; 12 | layout (location = 5) uniform mat4 invProjection; 13 | layout (location = 6) uniform mat4 view; 14 | 15 | out vec4 finalColor; 16 | 17 | vec3 getEyePos(vec2 coord) 18 | { 19 | float viewportDepth = texture(depthTex, coord).x; 20 | 21 | float ndcDepth = viewportDepth * 2.0 - 1.0; 22 | vec4 clipSpacePos = vec4(coord * 2.0 - vec2(1.0), ndcDepth, 1.0); 23 | vec4 eyeSpacePos = invProjection * clipSpacePos; 24 | 25 | return eyeSpacePos.xyz / eyeSpacePos.w; 26 | } 27 | 28 | void main() 29 | { 30 | // Retrieve values from GBuffer 31 | vec2 texelSize = 1.0 / vec2(width, height); 32 | vec2 coord = gl_FragCoord.xy * texelSize; 33 | 34 | float viewportDepth = texture(depthTex, coord).x; 35 | 36 | if (viewportDepth == 1.0) 37 | { 38 | discard; 39 | } 40 | 41 | // Reconstruct position from depth 42 | vec3 eyeSpacePos = getEyePos(coord); 43 | 44 | // Reconstruct normal from depth 45 | vec3 ddx = getEyePos(coord + vec2(texelSize.x, 0.0)) - eyeSpacePos; 46 | vec3 ddx2 = eyeSpacePos - getEyePos(coord + vec2(-texelSize.x, 0.0)); 47 | 48 | if (abs(ddx.z) > abs(ddx2.z)) 49 | { 50 | ddx = ddx2; 51 | } 52 | 53 | vec3 ddy = getEyePos(coord + vec2(0.0, texelSize.y)) - eyeSpacePos; 54 | vec3 ddy2 = eyeSpacePos - getEyePos(coord + vec2(0.0, -texelSize.y)); 55 | 56 | if (abs(ddy.z) > abs(ddy2.z)) 57 | { 58 | ddy = ddy2; 59 | } 60 | 61 | vec3 normal = normalize(cross(ddx, ddy)); 62 | 63 | // Diffuse 64 | vec3 color = texture(colorTex, coord).xyz; 65 | vec3 lightPosEye = (view * vec4(LIGHT_POS, 1.0)).xyz; 66 | vec3 lightDir = normalize(lightPosEye - eyeSpacePos.xyz); 67 | float dcoeff = max(0.0, dot(normal, lightDir)); 68 | vec3 diffColor = color * dcoeff; 69 | 70 | // Specular (Blinn-Phong) 71 | vec3 incidence = normalize(lightPosEye - eyeSpacePos.xyz); 72 | vec3 viewDir = normalize(-eyeSpacePos.xyz); 73 | vec3 halfDir = normalize(incidence + viewDir); 74 | float cosAngle = max(dot(halfDir, normal), 0.0); 75 | float scoeff = pow(cosAngle, SHININESS); 76 | vec3 specColor = vec3(1.0) * scoeff; 77 | 78 | // Final 79 | vec3 ambientColor = AMBIENT_COEFF * color; 80 | finalColor = vec4(ambientColor + diffColor + specColor, 1.0); 81 | } 82 | -------------------------------------------------------------------------------- /shaders/simStep1.comp: -------------------------------------------------------------------------------- 1 | #extension GL_ARB_bindless_texture: require 2 | 3 | layout(local_size_x = 32) in; 4 | 5 | struct Particle 6 | { 7 | vec3 position; 8 | float density; 9 | vec3 velocity; 10 | float pressure; 11 | }; 12 | 13 | layout(binding = 0, std430) restrict buffer particleBuf1 14 | { 15 | Particle particles[]; 16 | }; 17 | 18 | layout(location = 0, r32ui, bindless_image) uniform restrict uimage3D grid; 19 | layout(location = 1) uniform float dt; 20 | 21 | const float SAFE_BOUNDS = 0.5; 22 | 23 | void main() 24 | { 25 | uint particleId = gl_GlobalInvocationID.x; 26 | 27 | Particle particle = particles[particleId]; 28 | 29 | vec3 newVelo = particle.velocity; 30 | vec3 newPos = particle.position + newVelo * dt; 31 | 32 | float wallDamping = 0.5; 33 | vec3 boundsL = GRID_ORIGIN + SAFE_BOUNDS; 34 | vec3 boundsH = GRID_ORIGIN + GRID_SIZE - SAFE_BOUNDS; 35 | 36 | if (newPos.x < boundsL.x) { newVelo.x *= -wallDamping; newPos.x = boundsL.x; } 37 | if (newPos.x > boundsH.x) { newVelo.x *= -wallDamping; newPos.x = boundsH.x; } 38 | if (newPos.y < boundsL.y) { newVelo.y *= -wallDamping; newPos.y = boundsL.y; } 39 | if (newPos.y > boundsH.y) { newVelo.y *= -wallDamping; newPos.y = boundsH.y; } 40 | if (newPos.z < boundsL.z) { newVelo.z *= -wallDamping; newPos.z = boundsL.z; } 41 | if (newPos.z > boundsH.z) { newVelo.z *= -wallDamping; newPos.z = boundsH.z; } 42 | 43 | particle.velocity = newVelo; 44 | particle.position = newPos; 45 | 46 | particles[particleId] = particle; 47 | 48 | ivec3 voxelCoord = ivec3(INV_CELL_SIZE * (newPos - GRID_ORIGIN)); 49 | 50 | imageAtomicAdd(grid, voxelCoord, 1); 51 | } 52 | -------------------------------------------------------------------------------- /shaders/simStep2.comp: -------------------------------------------------------------------------------- 1 | #extension GL_ARB_bindless_texture: require 2 | 3 | layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in; 4 | 5 | layout(binding = 0) restrict buffer counters 6 | { 7 | uint globalParticleCount; 8 | }; 9 | 10 | layout(location = 0, r32ui, bindless_image) uniform restrict uimage3D grid; 11 | 12 | shared uint localParticleCount; 13 | shared uint globalParticleBaseOffset; 14 | 15 | void main() 16 | { 17 | ivec3 voxelId = ivec3(gl_GlobalInvocationID); 18 | 19 | if (gl_LocalInvocationIndex == 0) 20 | { 21 | localParticleCount = 0; 22 | } 23 | 24 | barrier(); 25 | 26 | uint voxelParticleCount = imageLoad(grid, voxelId).x; 27 | 28 | uint localParticleOffset = atomicAdd(localParticleCount, voxelParticleCount); 29 | 30 | barrier(); 31 | 32 | if (gl_LocalInvocationIndex == 0) 33 | { 34 | globalParticleBaseOffset = atomicAdd(globalParticleCount, localParticleCount); 35 | } 36 | 37 | barrier(); 38 | 39 | uint globalParticleOffset = globalParticleBaseOffset + localParticleOffset; 40 | 41 | imageStore(grid, voxelId, uvec4(globalParticleOffset << 8)); 42 | } 43 | -------------------------------------------------------------------------------- /shaders/simStep3.comp: -------------------------------------------------------------------------------- 1 | #extension GL_ARB_bindless_texture: require 2 | 3 | layout(local_size_x = 32) in; 4 | 5 | struct Particle 6 | { 7 | vec3 position; 8 | float density; 9 | vec3 velocity; 10 | float pressure; 11 | }; 12 | 13 | layout(binding = 0, std430) restrict readonly buffer particleBuf1 14 | { 15 | Particle inParticles[]; 16 | }; 17 | 18 | layout(binding = 1, std430) restrict writeonly buffer particleBuf2 19 | { 20 | Particle outParticles[]; 21 | }; 22 | 23 | layout(location = 0, r32ui, bindless_image) uniform restrict uimage3D grid; 24 | 25 | void main() 26 | { 27 | uint inParticleId = gl_GlobalInvocationID.x; 28 | 29 | Particle particle = inParticles[inParticleId]; 30 | 31 | ivec3 voxelCoord = ivec3(INV_CELL_SIZE * (particle.position - GRID_ORIGIN)); 32 | 33 | uint voxelValue = imageAtomicAdd(grid, voxelCoord, 1); 34 | 35 | uint outParticleId = (voxelValue >> 8) + (voxelValue & 0xFF); 36 | 37 | outParticles[outParticleId] = particle; 38 | } 39 | -------------------------------------------------------------------------------- /shaders/simStep4.comp: -------------------------------------------------------------------------------- 1 | #extension GL_ARB_bindless_texture: require 2 | 3 | layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in; 4 | 5 | struct Particle 6 | { 7 | vec3 position; 8 | float density; 9 | vec3 velocity; 10 | float pressure; 11 | }; 12 | 13 | layout(binding = 0, std430) restrict readonly buffer particleBuf 14 | { 15 | Particle particles[]; 16 | }; 17 | 18 | layout(location = 0, r32ui, bindless_image) uniform restrict readonly uimage3D grid; 19 | layout(location = 1, rgba32f, bindless_image) uniform restrict writeonly image3D velocity; 20 | 21 | void main() 22 | { 23 | ivec3 voxelCoord = ivec3(gl_GlobalInvocationID); 24 | 25 | if (any(greaterThanEqual(voxelCoord, GRID_RES))) 26 | { 27 | return; 28 | } 29 | 30 | uint voxelValue = imageLoad(grid, voxelCoord).x; 31 | uint particleCount = (voxelValue & 0xFF); 32 | uint particleOffset = (voxelValue >> 8); 33 | 34 | vec3 voxelVelocity = vec3(0.0); 35 | 36 | for (uint i = 0; i < particleCount; i++) 37 | { 38 | voxelVelocity += particles[particleOffset + i].velocity; 39 | } 40 | 41 | if (particleCount > 0) 42 | { 43 | voxelVelocity /= float(particleCount); 44 | } 45 | 46 | imageStore(velocity, voxelCoord, vec4(voxelVelocity, 1.0)); 47 | } 48 | -------------------------------------------------------------------------------- /shaders/simStep5.comp: -------------------------------------------------------------------------------- 1 | #extension GL_ARB_bindless_texture: require 2 | 3 | layout(local_size_x = 64) in; 4 | 5 | layout(location = 0, r32ui, bindless_image) uniform restrict readonly uimage3D grid; 6 | 7 | struct Particle 8 | { 9 | vec3 position; 10 | float density; 11 | vec3 velocity; 12 | float pressure; 13 | }; 14 | 15 | layout(binding = 0, std430) restrict buffer particleBuf 16 | { 17 | Particle particles[]; 18 | }; 19 | 20 | const ivec3 NEIGHBORHOOD_LUT[27] = { 21 | ivec3(-1, -1, -1), ivec3(0, -1, -1), ivec3(1, -1, -1), 22 | ivec3(-1, -1, 0), ivec3(0, -1, 0), ivec3(1, -1, 0), 23 | ivec3(-1, -1, 1), ivec3(0, -1, 1), ivec3(1, -1, 1), 24 | ivec3(-1, 0, -1), ivec3(0, 0, -1), ivec3(1, 0, -1), 25 | ivec3(-1, 0, 0), ivec3(0, 0, 0), ivec3(1, 0, 0), 26 | ivec3(-1, 0, 1), ivec3(0, 0, 1), ivec3(1, 0, 1), 27 | ivec3(-1, 1, -1), ivec3(0, 1, -1), ivec3(1, 1, -1), 28 | ivec3(-1, 1, 0), ivec3(0, 1, 0), ivec3(1, 1, 0), 29 | ivec3(-1, 1, 1), ivec3(0, 1, 1), ivec3(1, 1, 1) 30 | }; 31 | 32 | void main() 33 | { 34 | uint particleId = gl_GlobalInvocationID.x; 35 | 36 | Particle particle = particles[particleId]; 37 | 38 | ivec3 voxelId = ivec3(INV_CELL_SIZE * (particle.position - GRID_ORIGIN)); 39 | 40 | float density = 0.0; 41 | 42 | uint voxelCount = 0; 43 | uint voxelParticleCount = 0; 44 | uint voxelParticleOffset = 0; 45 | 46 | #pragma unroll 1 47 | while (voxelCount < 27 || voxelParticleCount > 0) 48 | { 49 | if (voxelParticleCount == 0) 50 | { 51 | ivec3 newVoxelId = voxelId + NEIGHBORHOOD_LUT[voxelCount]; 52 | voxelCount++; 53 | 54 | if (any(greaterThanEqual(uvec3(newVoxelId), GRID_RES))) 55 | { 56 | continue; 57 | } 58 | 59 | uint voxelValue = imageLoad(grid, newVoxelId).r; 60 | voxelParticleOffset = (voxelValue >> 8); 61 | voxelParticleCount = (voxelValue & 0xFF); 62 | 63 | if (voxelParticleCount == 0) 64 | { 65 | continue; 66 | } 67 | } 68 | 69 | voxelParticleCount--; 70 | 71 | uint otherParticleId = voxelParticleOffset + voxelParticleCount; 72 | vec3 otherParticlePos = particles[otherParticleId].position; 73 | 74 | vec3 r = particle.position - otherParticlePos; 75 | 76 | float rLen = length(r); 77 | 78 | if (rLen >= KERNEL_RADIUS) 79 | { 80 | continue; 81 | } 82 | 83 | float weight = pow(KERNEL_RADIUS * KERNEL_RADIUS - rLen * rLen, 3) * POLY6_KERNEL_WEIGHT_CONST; 84 | 85 | density += MASS * weight; 86 | } 87 | 88 | float pressure = REST_PRESSURE + STIFFNESS_K * (density - REST_DENSITY); 89 | 90 | particle.density = density; 91 | particle.pressure = pressure; 92 | 93 | particles[particleId] = particle; 94 | } 95 | -------------------------------------------------------------------------------- /shaders/simStep6.comp: -------------------------------------------------------------------------------- 1 | #extension GL_ARB_bindless_texture: require 2 | 3 | layout (local_size_x = 64) in; 4 | 5 | layout(location = 0, r32ui, bindless_image) uniform restrict readonly uimage3D grid; 6 | layout(location = 1, bindless_sampler) uniform sampler3D velocity; 7 | layout(location = 2) uniform float DT; 8 | layout(location = 3) uniform vec3 GRAVITY; 9 | 10 | struct Particle 11 | { 12 | vec3 position; 13 | float density; 14 | vec3 velocity; 15 | float pressure; 16 | }; 17 | 18 | layout(binding = 0, std430) restrict buffer particleBuf 19 | { 20 | Particle particles[]; 21 | }; 22 | 23 | const ivec3 NEIGHBORHOOD_LUT[27] = { 24 | ivec3(-1, -1, -1), ivec3(0, -1, -1), ivec3(1, -1, -1), 25 | ivec3(-1, -1, 0), ivec3(0, -1, 0), ivec3(1, -1, 0), 26 | ivec3(-1, -1, 1), ivec3(0, -1, 1), ivec3(1, -1, 1), 27 | ivec3(-1, 0, -1), ivec3(0, 0, -1), ivec3(1, 0, -1), 28 | ivec3(-1, 0, 0), ivec3(0, 0, 0), ivec3(1, 0, 0), 29 | ivec3(-1, 0, 1), ivec3(0, 0, 1), ivec3(1, 0, 1), 30 | ivec3(-1, 1, -1), ivec3(0, 1, -1), ivec3(1, 1, -1), 31 | ivec3(-1, 1, 0), ivec3(0, 1, 0), ivec3(1, 1, 0), 32 | ivec3(-1, 1, 1), ivec3(0, 1, 1), ivec3(1, 1, 1) 33 | }; 34 | 35 | void main() 36 | { 37 | uint particleId = gl_GlobalInvocationID.x; 38 | 39 | Particle particle = particles[particleId]; 40 | 41 | ivec3 voxelId = ivec3(INV_CELL_SIZE * (particle.position - GRID_ORIGIN)); 42 | 43 | vec3 forcePressure = vec3(0.0); 44 | vec3 forceViscosity = vec3(0.0); 45 | 46 | uint voxelCount = 0; 47 | uint voxelParticleCount = 0; 48 | uint voxelParticleOffset = 0; 49 | 50 | #pragma unroll 1 51 | while (voxelCount < 27 || voxelParticleCount > 0) 52 | { 53 | if (voxelParticleCount == 0) 54 | { 55 | ivec3 newVoxelId = voxelId + NEIGHBORHOOD_LUT[voxelCount]; 56 | voxelCount++; 57 | 58 | if (any(greaterThanEqual(uvec3(newVoxelId), GRID_RES))) 59 | { 60 | continue; 61 | } 62 | 63 | uint voxelValue = imageLoad(grid, newVoxelId).r; 64 | voxelParticleOffset = (voxelValue >> 8); 65 | voxelParticleCount = (voxelValue & 0xFF); 66 | 67 | if (voxelParticleCount == 0) 68 | { 69 | continue; 70 | } 71 | } 72 | 73 | voxelParticleCount--; 74 | 75 | uint otherParticleId = voxelParticleOffset + voxelParticleCount; 76 | 77 | Particle otherParticle = particles[otherParticleId]; 78 | 79 | vec3 r = particle.position - otherParticle.position; 80 | 81 | float rLen = length(r); 82 | 83 | if (rLen >= KERNEL_RADIUS) 84 | { 85 | continue; 86 | } 87 | 88 | vec3 weightPressure = vec3(0.0); 89 | 90 | if (rLen > 0.0) 91 | { 92 | weightPressure = SPIKY_KERNEL_WEIGHT_CONST * pow(KERNEL_RADIUS - rLen, 3) * (r / rLen); 93 | } 94 | 95 | float pressure = particle.pressure + otherParticle.pressure; 96 | 97 | forcePressure += (MASS * pressure * weightPressure) / (2.0 * otherParticle.density); 98 | 99 | float weightVis = VIS_KERNEL_WEIGHT_CONST * (KERNEL_RADIUS - rLen); 100 | 101 | vec3 filteredVelocity = texture(velocity, (otherParticle.position - GRID_ORIGIN) / GRID_SIZE).xyz; 102 | 103 | vec3 velocityDiff = filteredVelocity - particle.velocity; 104 | 105 | forceViscosity += (MASS * velocityDiff * weightVis) / otherParticle.density; 106 | } 107 | 108 | vec3 forceGravity = GRAVITY * particle.density; 109 | 110 | vec3 force = (forceViscosity * VIS_COEFF) - forcePressure + forceGravity; 111 | 112 | vec3 acceleration = force / particle.density; 113 | 114 | particles[particleId].velocity += acceleration * DT; 115 | } 116 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(imgui) 2 | add_subdirectory(flut) 3 | -------------------------------------------------------------------------------- /src/flut/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable( 2 | flut WIN32 3 | main.cpp 4 | Camera.cpp 5 | Camera.hpp 6 | GlHelper.cpp 7 | GlHelper.hpp 8 | GlQueryRetriever.hpp 9 | GlQueryRetriever.cpp 10 | Simulation.cpp 11 | Simulation.hpp 12 | Window.cpp 13 | Window.hpp 14 | ) 15 | 16 | if(MSVC) 17 | target_compile_options(flut PRIVATE /MP) 18 | target_compile_options(flut PRIVATE /Wall) 19 | target_compile_options(flut PRIVATE /D_USE_MATH_DEFINES) 20 | target_compile_options(flut PRIVATE /DNOMINMAX) 21 | else() 22 | target_compile_options(flut PRIVATE -Wall) 23 | target_compile_options(flut PRIVATE -Wextra) 24 | target_compile_options(flut PRIVATE -Wno-unused-parameter) 25 | target_compile_options(flut PRIVATE -Wno-reorder) 26 | target_compile_options(flut PRIVATE -Wno-error=int-in-bool-context) 27 | endif() 28 | 29 | target_compile_definitions( 30 | flut PRIVATE 31 | SHADERS_DIR="${FLUT_SHADERS_DIR}" 32 | ) 33 | 34 | target_link_libraries( 35 | flut PRIVATE 36 | imgui 37 | SDL2 38 | SDL2main 39 | glm 40 | glad 41 | OpenGL::GL 42 | ) 43 | -------------------------------------------------------------------------------- /src/flut/Camera.cpp: -------------------------------------------------------------------------------- 1 | #include "Camera.hpp" 2 | #include "Window.hpp" 3 | 4 | #include 5 | #include 6 | 7 | using namespace flut; 8 | 9 | Camera::Camera(const Window& window) 10 | : m_window(window) 11 | { 12 | m_width = window.width(); 13 | m_height = window.height(); 14 | m_radius = INITIAL_RADIUS; 15 | m_theta = static_cast(M_PI) / 2.0f; 16 | m_phi = 0.0f; 17 | m_up = {0.0f, 1.0f, 0.0f}; 18 | m_center = {0.0f, 0.0f, 0.0f}; 19 | m_position = {0.0f, 0.0f, m_radius}; 20 | m_oldMouseX = m_window.mouseX(); 21 | m_oldMouseY = m_window.mouseY(); 22 | m_projection = glm::mat4(); 23 | m_invProjection = glm::mat4(); 24 | m_view = glm::mat4(1); 25 | recalcView(); 26 | recalcProjection(); 27 | } 28 | 29 | Camera::~Camera() {} 30 | 31 | void Camera::update(float dt) 32 | { 33 | const auto& mouseX = m_window.mouseX(); 34 | const auto& mouseY = m_window.mouseY(); 35 | bool recalcPos = false; 36 | 37 | if (m_window.mouseDown()) 38 | { 39 | const float deltaX = (mouseX - m_oldMouseX) * SENSITIVITY; 40 | const float deltaY = (mouseY - m_oldMouseY) * SENSITIVITY; 41 | 42 | m_theta -= deltaY; 43 | if (m_theta < 0.01f) { 44 | m_theta = 0.01f; 45 | } 46 | else if (m_theta > M_PI - 0.01f) { 47 | m_theta = static_cast(M_PI - 0.01f); 48 | } 49 | 50 | m_phi -= deltaX; 51 | if (m_phi < 0.0f) { 52 | m_phi += 2 * M_PI; 53 | } 54 | else if (m_phi > 2 * M_PI) { 55 | m_phi -= 2 * M_PI; 56 | } 57 | 58 | recalcPos = true; 59 | } 60 | 61 | if (m_window.keyUp()) 62 | { 63 | m_radius = std::max(0.001f, m_radius - 5.0f * dt); 64 | recalcPos = true; 65 | } 66 | else if (m_window.keyDown()) 67 | { 68 | m_radius += 5.0f * dt; 69 | recalcPos = true; 70 | } 71 | 72 | if (recalcPos) 73 | { 74 | const float x = m_center[0] + m_radius * sin(m_theta) * sin(m_phi); 75 | const float y = m_center[1] + m_radius * cos(m_theta); 76 | const float z = m_center[2] + m_radius * sin(m_theta) * cos(m_phi); 77 | m_position = {x, y, z}; 78 | recalcView(); 79 | } 80 | 81 | m_oldMouseX = mouseX; 82 | m_oldMouseY = mouseY; 83 | 84 | if (m_width != m_window.width() || m_height != m_window.height()) 85 | { 86 | m_width = m_window.width(); 87 | m_height = m_window.height(); 88 | recalcProjection(); 89 | } 90 | } 91 | 92 | void Camera::recalcView() 93 | { 94 | const glm::vec3 f = glm::normalize(m_center - m_position); 95 | const glm::vec3 s = glm::normalize(glm::cross(f, m_up)); 96 | const glm::vec3 u = glm::normalize(glm::cross(s, f)); 97 | m_view[0][0] = s.x; 98 | m_view[1][0] = s.y; 99 | m_view[2][0] = s.z; 100 | m_view[3][0] = -glm::dot(s, m_position); 101 | m_view[0][1] = u.x; 102 | m_view[1][1] = u.y; 103 | m_view[2][1] = u.z; 104 | m_view[3][1] = -glm::dot(u, m_position); 105 | m_view[0][2] = -f.x; 106 | m_view[1][2] = -f.y; 107 | m_view[2][2] = -f.z; 108 | m_view[3][2] = glm::dot(f, m_position); 109 | } 110 | 111 | void Camera::recalcProjection() 112 | { 113 | const float aspect = static_cast(m_width) / m_height; 114 | const float theta = static_cast(FOV * 0.5); 115 | const float range = FAR_PLANE - NEAR_PLANE; 116 | const float invtan = 1.0f / std::tan(theta); 117 | m_projection[0][0] = invtan / aspect; 118 | m_projection[1][1] = invtan; 119 | m_projection[2][2] = -(NEAR_PLANE + FAR_PLANE) / range; 120 | m_projection[2][3] = -1.0f; 121 | m_projection[3][2] = -(2.0f * NEAR_PLANE * FAR_PLANE) / range; 122 | m_invProjection = glm::inverse(m_projection); 123 | } 124 | 125 | glm::mat4 Camera::view() const 126 | { 127 | return m_view; 128 | } 129 | 130 | glm::mat4 Camera::projection() const 131 | { 132 | return m_projection; 133 | } 134 | 135 | glm::mat4 Camera::invProjection() const 136 | { 137 | return m_invProjection; 138 | } 139 | -------------------------------------------------------------------------------- /src/flut/Camera.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Window.hpp" 6 | 7 | namespace flut 8 | { 9 | class Camera 10 | { 11 | public: 12 | constexpr static float NEAR_PLANE = 0.01f; 13 | constexpr static float FAR_PLANE = 50.0f; 14 | 15 | private: 16 | constexpr static float FOV = static_cast(60.0f * M_PI / 180.0f); 17 | constexpr static float SENSITIVITY = 0.005f; 18 | constexpr static float INITIAL_RADIUS = 18.0f; 19 | 20 | public: 21 | Camera(const Window& window); 22 | 23 | ~Camera(); 24 | 25 | void update(float dt); 26 | 27 | glm::mat4 view() const; 28 | 29 | glm::mat4 projection() const; 30 | 31 | glm::mat4 invProjection() const; 32 | 33 | private: 34 | void recalcView(); 35 | 36 | void recalcProjection(); 37 | 38 | private: 39 | const Window& m_window; 40 | uint32_t m_width; 41 | uint32_t m_height; 42 | glm::mat4 m_view; 43 | glm::mat4 m_projection; 44 | glm::mat4 m_invProjection; 45 | glm::vec3 m_position; 46 | glm::vec3 m_center; 47 | glm::vec3 m_up; 48 | int32_t m_oldMouseX; 49 | int32_t m_oldMouseY; 50 | float m_radius; 51 | float m_theta; 52 | float m_phi; 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /src/flut/GlHelper.cpp: -------------------------------------------------------------------------------- 1 | #include "GlHelper.hpp" 2 | 3 | #include 4 | #include 5 | 6 | using namespace flut; 7 | 8 | void _gladPostCallback(char* name, void* funcptr, int len_args, ...) 9 | { 10 | if (!strcmp(name, "glGetError")) { 11 | return; 12 | } 13 | GLenum errorCode = glGetError(); 14 | if (errorCode != GL_NO_ERROR) { 15 | do { 16 | fprintf(stderr, "GL/ERROR: 0x%04x in %s\n", errorCode, name); 17 | } while ((errorCode = glGetError()) != GL_NO_ERROR); 18 | } 19 | } 20 | 21 | void GlHelper::enableDebugHooks() 22 | { 23 | GLint flags; 24 | glGetIntegerv(GL_CONTEXT_FLAGS, &flags); 25 | 26 | if ((flags & GL_CONTEXT_FLAG_DEBUG_BIT) != GL_NONE) 27 | { 28 | glEnable(GL_DEBUG_OUTPUT); 29 | glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); 30 | glDebugMessageCallback((GLDEBUGPROC) &glDebugOutput, nullptr); 31 | glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); 32 | printf("Debug output enabled.\n"); 33 | } 34 | else 35 | { 36 | printf("Debug output not available (context flag not set).\n"); 37 | } 38 | fflush(stdout); 39 | 40 | glad_set_post_callback((GLADcallback) _gladPostCallback); 41 | } 42 | 43 | std::string GlHelper::loadFileText(const std::string& filePath) 44 | { 45 | std::ifstream file{ filePath, std::ios_base::in | std::ios_base::binary }; 46 | if (!file.is_open()) { 47 | fprintf(stderr, "Unable to open file %s", filePath.c_str()); 48 | abort(); 49 | } 50 | 51 | std::string text; 52 | file.seekg(0, std::ios_base::end); 53 | text.resize(file.tellg()); 54 | file.seekg(0, std::ios_base::beg); 55 | text.assign((std::istreambuf_iterator(file)), std::istreambuf_iterator()); 56 | return text; 57 | } 58 | 59 | std::string GlHelper::preprocessShaderSource(const std::string& text, std::vector defines) 60 | { 61 | std::stringstream ss; 62 | 63 | ss << "#version 460 core\n"; 64 | ss << "#extension GL_ARB_bindless_texture : require\n"; 65 | 66 | for (auto& define : defines) 67 | { 68 | ss << "#define " << define.name << " " << define.valueStr << "\n"; 69 | } 70 | 71 | ss << text; 72 | 73 | return ss.str(); 74 | } 75 | 76 | GLuint GlHelper::createVertFragShader(const char* vertPath, const char* fragPath, std::vector defines) 77 | { 78 | GLuint handle = glCreateProgram(); 79 | 80 | if (!handle) { 81 | fprintf(stderr, "Unable to create shader program."); 82 | abort(); 83 | } 84 | 85 | std::string vertSource = loadFileText(vertPath); 86 | vertSource = preprocessShaderSource(vertSource, defines); 87 | 88 | const GLuint vertHandle = glCreateShader(GL_VERTEX_SHADER); 89 | const GLint vertSize = vertSource.size(); 90 | const char* vertShaderPtr = vertSource.data(); 91 | glShaderSource(vertHandle, 1, &vertShaderPtr, &vertSize); 92 | glCompileShader(vertHandle); 93 | 94 | GLint logLength; 95 | GLint result = GL_FALSE; 96 | glGetShaderiv(vertHandle, GL_COMPILE_STATUS, &result); 97 | 98 | if (result == GL_FALSE) 99 | { 100 | glGetShaderiv(vertHandle, GL_INFO_LOG_LENGTH, &logLength); 101 | if (logLength == 0) { 102 | fprintf(stderr, "Unable to compile shader"); 103 | abort(); 104 | } 105 | std::vector errorMessage(logLength + 1); 106 | glGetShaderInfoLog(vertHandle, logLength, nullptr, &errorMessage.front()); 107 | std::string message(errorMessage.begin(), errorMessage.end()); 108 | fprintf(stderr, "Unable to compile shader: %s", message.c_str()); 109 | abort(); 110 | } 111 | 112 | std::string fragSource = loadFileText(fragPath); 113 | fragSource = preprocessShaderSource(fragSource, defines); 114 | 115 | const GLuint fragHandle = glCreateShader(GL_FRAGMENT_SHADER); 116 | const GLint fragSize = fragSource.size(); 117 | const char* fragShaderPtr = fragSource.data(); 118 | glShaderSource(fragHandle, 1, &fragShaderPtr, &fragSize); 119 | glCompileShader(fragHandle); 120 | 121 | glGetShaderiv(fragHandle, GL_COMPILE_STATUS, &result); 122 | 123 | if (result == GL_FALSE) 124 | { 125 | glGetShaderiv(fragHandle, GL_INFO_LOG_LENGTH, &logLength); 126 | if (logLength == 0) { 127 | fprintf(stderr, "Unable to compile shader"); 128 | abort(); 129 | } 130 | std::vector errorMessage(logLength + 1); 131 | glGetShaderInfoLog(fragHandle, logLength, nullptr, &errorMessage.front()); 132 | std::string message(errorMessage.begin(), errorMessage.end()); 133 | fprintf(stderr, "Unable to compile shader: %s", message.c_str()); 134 | abort(); 135 | } 136 | 137 | glAttachShader(handle, vertHandle); 138 | glAttachShader(handle, fragHandle); 139 | glLinkProgram(handle); 140 | 141 | glGetProgramiv(handle, GL_LINK_STATUS, &result); 142 | 143 | if (result == GL_FALSE) 144 | { 145 | glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &logLength); 146 | if (logLength == 0) { 147 | fprintf(stderr, "Unable to link program"); 148 | abort(); 149 | } 150 | std::vector errorMessage(logLength + 1); 151 | glGetProgramInfoLog(handle, logLength, nullptr, &errorMessage.front()); 152 | std::string message(errorMessage.begin(), errorMessage.end()); 153 | fprintf(stderr, "Unable to link program: %s", message.c_str()); 154 | abort(); 155 | } 156 | 157 | glDetachShader(handle, vertHandle); 158 | glDetachShader(handle, fragHandle); 159 | glDeleteShader(vertHandle); 160 | glDeleteShader(fragHandle); 161 | return handle; 162 | } 163 | 164 | GLuint GlHelper::createComputeShader(const char* path, std::vector defines) 165 | { 166 | const GLuint handle = glCreateProgram(); 167 | 168 | if (!handle) { 169 | throw std::runtime_error("Unable to create shader program."); 170 | } 171 | 172 | std::string source = loadFileText(path); 173 | source = preprocessShaderSource(source, defines); 174 | 175 | const GLuint sourceHandle = glCreateShader(GL_COMPUTE_SHADER); 176 | const GLint sourceSize = source.size(); 177 | const char* sourceShaderPtr = source.data(); 178 | glShaderSource(sourceHandle, 1, &sourceShaderPtr, &sourceSize); 179 | glCompileShader(sourceHandle); 180 | 181 | GLint logLength; 182 | GLint result = GL_FALSE; 183 | glGetShaderiv(sourceHandle, GL_COMPILE_STATUS, &result); 184 | 185 | if (result == GL_FALSE) 186 | { 187 | glGetShaderiv(sourceHandle, GL_INFO_LOG_LENGTH, &logLength); 188 | if (logLength == 0) { 189 | fprintf(stderr, "Unable to compile shader"); 190 | abort(); 191 | } 192 | std::vector errorMessage(logLength + 1); 193 | glGetShaderInfoLog(sourceHandle, logLength, nullptr, &errorMessage.front()); 194 | std::string message(errorMessage.begin(), errorMessage.end()); 195 | fprintf(stderr, "Unable to compile shader: %s", message.c_str()); 196 | abort(); 197 | } 198 | 199 | glAttachShader(handle, sourceHandle); 200 | glLinkProgram(handle); 201 | 202 | glGetProgramiv(handle, GL_LINK_STATUS, &result); 203 | 204 | if (result == GL_FALSE) 205 | { 206 | glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &logLength); 207 | if (logLength == 0) { 208 | fprintf(stderr, "Unable to link program"); 209 | abort(); 210 | } 211 | std::vector errorMessage(logLength + 1); 212 | glGetProgramInfoLog(handle, logLength, nullptr, &errorMessage.front()); 213 | std::string message(errorMessage.begin(), errorMessage.end()); 214 | fprintf(stderr, "Unable to link program: %s", message.c_str()); 215 | abort(); 216 | } 217 | 218 | glDetachShader(handle, sourceHandle); 219 | glDeleteShader(sourceHandle); 220 | return handle; 221 | } 222 | 223 | void GlHelper::glDebugOutput( 224 | GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) 225 | { 226 | const char* sourceStr = "Unknown"; 227 | if (source == GL_DEBUG_SOURCE_API) { sourceStr = "API"; } 228 | else if (source == GL_DEBUG_SOURCE_WINDOW_SYSTEM) { sourceStr = "Window System"; } 229 | else if (source == GL_DEBUG_SOURCE_SHADER_COMPILER) { sourceStr = "Shader Compiler"; } 230 | else if (source == GL_DEBUG_SOURCE_THIRD_PARTY) { sourceStr = "Third Party"; } 231 | else if (source == GL_DEBUG_SOURCE_APPLICATION) { sourceStr = "Application"; } 232 | else if (source == GL_DEBUG_SOURCE_OTHER) { sourceStr = "Other"; } 233 | 234 | const char* typeStr = "Unknown"; 235 | if (type == GL_DEBUG_TYPE_ERROR) { typeStr = "Error"; } 236 | else if (type == GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR) { typeStr = "Deprecated Behaviour"; } 237 | else if (type == GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR) { typeStr = "Undefined Behaviour"; } 238 | else if (type == GL_DEBUG_TYPE_PORTABILITY) { typeStr = "Portability"; } 239 | else if (type == GL_DEBUG_TYPE_PERFORMANCE) { typeStr = "Performance"; } 240 | else if (type == GL_DEBUG_TYPE_MARKER) { typeStr = "Marker"; } 241 | else if (type == GL_DEBUG_TYPE_PUSH_GROUP) { typeStr = "Push Group"; } 242 | else if (type == GL_DEBUG_TYPE_POP_GROUP) { typeStr = "Pop Group"; } 243 | else if (type == GL_DEBUG_TYPE_OTHER) { typeStr = "Other"; } 244 | 245 | if (severity == GL_DEBUG_SEVERITY_HIGH) { 246 | fprintf(stderr, "GL/ERROR: \"%s\" (%s/%s)\n", message, sourceStr, typeStr); 247 | } 248 | else if (severity == GL_DEBUG_SEVERITY_MEDIUM) { 249 | fprintf(stderr, "GL/WARNING: \"%s\" (%s/%s)\n", message, sourceStr, typeStr); 250 | } 251 | else if (severity == GL_DEBUG_SEVERITY_LOW) { 252 | printf("GL/INFO: \"%s\" (%s/%s)\n", message, sourceStr, typeStr); 253 | fflush(stdout); 254 | } 255 | else { 256 | printf("GL/DEBUG: \"%s\" (%s/%s)\n", message, sourceStr, typeStr); 257 | fflush(stdout); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/flut/GlHelper.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace flut 11 | { 12 | class GlHelper 13 | { 14 | public: 15 | struct ShaderDefine 16 | { 17 | ShaderDefine(std::string_view name, uint32_t value) 18 | : name(name) 19 | { 20 | valueStr = std::to_string(value); 21 | } 22 | ShaderDefine(std::string_view name, float value) 23 | : name(name) 24 | { 25 | valueStr = std::to_string(value); 26 | } 27 | ShaderDefine(std::string_view name, glm::ivec3 value) 28 | : name(name) 29 | { 30 | std::stringstream ss; 31 | ss << "ivec3(" << value.x << ", " << value.y << ", " << value.z << ")"; 32 | valueStr = ss.str(); 33 | } 34 | ShaderDefine(std::string_view name, glm::vec3 value) 35 | : name(name) 36 | { 37 | std::stringstream ss; 38 | ss << "vec3(" << value.x << ", " << value.y << ", " << value.z << ")"; 39 | valueStr = ss.str(); 40 | } 41 | std::string_view name; 42 | std::string valueStr; 43 | }; 44 | 45 | public: 46 | static void enableDebugHooks(); 47 | 48 | static GLuint createVertFragShader(const char* vertPath, const char* fragPath, std::vector defines = {}); 49 | 50 | static GLuint createComputeShader(const char* path, std::vector defines = {}); 51 | 52 | private: 53 | static std::string loadFileText(const std::string& filePath); 54 | 55 | static std::string preprocessShaderSource(const std::string& text, std::vector defines); 56 | 57 | static void glDebugOutput(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, 58 | const GLchar* message, const void* userParam); 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /src/flut/GlQueryRetriever.cpp: -------------------------------------------------------------------------------- 1 | #include "GlQueryRetriever.hpp" 2 | 3 | #include 4 | 5 | using namespace flut; 6 | 7 | GlQueryRetriever::GlQueryRetriever() 8 | { 9 | glCreateQueries(GL_TIME_ELAPSED, MAX_FRAME_DELAY, m_renderQueries); 10 | 11 | for (uint32_t i = 0; i < MAX_FRAME_DELAY; i++) 12 | { 13 | for (uint32_t j = 0; j < MAX_SIM_ITERS_PER_FRAME; j++) 14 | { 15 | glCreateQueries(GL_TIME_ELAPSED, SIM_STEP_COUNT, m_simQueries[i][j]); 16 | } 17 | } 18 | 19 | for (uint32_t i = 0; i < MAX_FRAME_DELAY; i++) 20 | { 21 | m_simIterCounts[i] = 0; 22 | } 23 | } 24 | 25 | GlQueryRetriever::~GlQueryRetriever() 26 | { 27 | glDeleteQueries(MAX_FRAME_DELAY, m_renderQueries); 28 | 29 | for (uint32_t i = 0; i < MAX_FRAME_DELAY; i++) 30 | { 31 | for (uint32_t j = 0; j < MAX_SIM_ITERS_PER_FRAME; j++) 32 | { 33 | glDeleteQueries(SIM_STEP_COUNT, m_simQueries[i][j]); 34 | } 35 | } 36 | } 37 | 38 | void GlQueryRetriever::incFrame() 39 | { 40 | m_head = (m_head + 1) % MAX_FRAME_DELAY; 41 | assert(m_head != m_tail); 42 | m_simIterCounts[m_head] = 0; 43 | m_currSimIter = 0; 44 | } 45 | 46 | void GlQueryRetriever::incSimIter() 47 | { 48 | m_currSimIter++; 49 | m_simIterCounts[m_head]++; 50 | } 51 | 52 | void GlQueryRetriever::beginSimQuery(uint32_t stepIdx) 53 | { 54 | assert(m_currSimIter < MAX_SIM_ITERS_PER_FRAME); 55 | assert(stepIdx < SIM_STEP_COUNT); 56 | glBeginQuery(GL_TIME_ELAPSED, m_simQueries[m_head][m_currSimIter][stepIdx]); 57 | } 58 | 59 | void GlQueryRetriever::beginRenderQuery() 60 | { 61 | glBeginQuery(GL_TIME_ELAPSED, m_renderQueries[m_head]); 62 | } 63 | 64 | void GlQueryRetriever::endQuery() 65 | { 66 | glEndQuery(GL_TIME_ELAPSED); 67 | } 68 | 69 | void GlQueryRetriever::readFinishedQueries(QueryTimings& timings) 70 | { 71 | // If the last query in the frame is available, this means that all previous 72 | // queries are available. 73 | GLuint64 state; 74 | glGetQueryObjectui64v(m_renderQueries[m_tail], GL_QUERY_RESULT_AVAILABLE, &state); 75 | if (state == GL_FALSE) 76 | { 77 | return; 78 | } 79 | 80 | glGetQueryObjectui64v(m_renderQueries[m_tail], GL_QUERY_RESULT_NO_WAIT, &state); 81 | timings.renderMs = state / 1000000.0f; 82 | 83 | // Retrieve all sim step queries and calculate frame average. 84 | for (uint32_t j = 0; j < SIM_STEP_COUNT; j++) 85 | { 86 | timings.simStempMs[j] = 0.0f; 87 | 88 | uint32_t simIterCount = m_simIterCounts[m_tail]; 89 | if (simIterCount == 0) 90 | { 91 | continue; 92 | } 93 | 94 | for (uint32_t i = 0; i < simIterCount; i++) 95 | { 96 | glGetQueryObjectui64v(m_simQueries[m_tail][i][j], GL_QUERY_RESULT_NO_WAIT, &state); 97 | float elapsedMs = state / 1000000.0f; 98 | timings.simStempMs[j] += elapsedMs; 99 | } 100 | 101 | timings.simStempMs[j] /= simIterCount; 102 | } 103 | 104 | m_tail = (m_tail + 1) % MAX_FRAME_DELAY; 105 | } 106 | -------------------------------------------------------------------------------- /src/flut/GlQueryRetriever.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace flut 7 | { 8 | class GlQueryRetriever 9 | { 10 | public: 11 | constexpr static uint32_t MAX_SIM_ITERS_PER_FRAME = 40; 12 | constexpr static uint32_t SIM_STEP_COUNT = 6; 13 | 14 | struct QueryTimings 15 | { 16 | float simStempMs[SIM_STEP_COUNT]; 17 | float renderMs = 0.0f; 18 | }; 19 | 20 | private: 21 | constexpr static uint32_t MAX_FRAME_DELAY = 8; 22 | 23 | public: 24 | GlQueryRetriever(); 25 | ~GlQueryRetriever(); 26 | 27 | void incFrame(); 28 | void incSimIter(); 29 | 30 | void beginSimQuery(uint32_t stepIdx); 31 | void beginRenderQuery(); 32 | void endQuery(); 33 | 34 | void readFinishedQueries(QueryTimings& timings); 35 | 36 | private: 37 | uint32_t m_head = 1; 38 | uint32_t m_tail = 0; 39 | uint32_t m_currSimIter = 0; 40 | uint32_t m_simIterCounts[MAX_FRAME_DELAY]; 41 | GLuint m_simQueries[MAX_FRAME_DELAY][MAX_SIM_ITERS_PER_FRAME][SIM_STEP_COUNT]; 42 | GLuint m_renderQueries[MAX_FRAME_DELAY]; 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /src/flut/Simulation.cpp: -------------------------------------------------------------------------------- 1 | #include "Simulation.hpp" 2 | #include "GlHelper.hpp" 3 | #include "GlQueryRetriever.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace flut; 12 | 13 | struct Particle 14 | { 15 | float position_x; 16 | float position_y; 17 | float position_z; 18 | float density; 19 | float velocity_x; 20 | float velocity_y; 21 | float velocity_z; 22 | float pressure; 23 | }; 24 | 25 | Simulation::Simulation(uint32_t width, uint32_t height) 26 | : m_width(width) 27 | , m_height(height) 28 | , m_newWidth(width) 29 | , m_newHeight(height) 30 | , m_swapFrame{false} 31 | , m_frame{0} 32 | , m_integrationsPerFrame{1} 33 | { 34 | #ifndef NDEBUG 35 | GlHelper::enableDebugHooks(); 36 | #endif 37 | 38 | // Pad particle count so that we can get rid of bounds checks in shaders. 39 | m_particleCount = (MIN_PARTICLE_COUNT + MAX_GROUP_SIZE - 1) / MAX_GROUP_SIZE * MAX_GROUP_SIZE; 40 | 41 | // Shaders 42 | { 43 | glm::vec3 invCellSize = glm::vec3(GRID_RES) * (1.0f - 0.001f) / GRID_SIZE; 44 | 45 | float viscosityKernelWeightConst = static_cast(45.0f / (M_PI * std::pow(KERNEL_RADIUS, 6))); 46 | float spikyKernelWeightConst = static_cast(15.0f / (M_PI * std::pow(KERNEL_RADIUS, 6))); 47 | float poly6KernelWeightConst = static_cast(315.0f / (64.0f * M_PI * std::pow(KERNEL_RADIUS, 9))); 48 | 49 | m_programSimStep1 = GlHelper::createComputeShader(SHADERS_DIR "/simStep1.comp", { 50 | { "INV_CELL_SIZE", invCellSize }, 51 | { "GRID_ORIGIN", GRID_ORIGIN }, 52 | { "GRID_SIZE", GRID_SIZE } 53 | }); 54 | 55 | m_programSimStep2 = GlHelper::createComputeShader(SHADERS_DIR "/simStep2.comp", { 56 | { "GRID_RES", GRID_RES } 57 | }); 58 | 59 | m_programSimStep3 = GlHelper::createComputeShader(SHADERS_DIR "/simStep3.comp", { 60 | { "INV_CELL_SIZE", invCellSize }, 61 | { "GRID_ORIGIN", GRID_ORIGIN } 62 | }); 63 | 64 | m_programSimStep4 = GlHelper::createComputeShader(SHADERS_DIR "/simStep4.comp", { 65 | { "GRID_RES", GRID_RES } 66 | }); 67 | 68 | m_programSimStep5 = GlHelper::createComputeShader(SHADERS_DIR "/simStep5.comp", { 69 | { "INV_CELL_SIZE", invCellSize }, 70 | { "GRID_ORIGIN", GRID_ORIGIN }, 71 | { "GRID_RES", GRID_RES }, 72 | { "MASS", MASS }, 73 | { "KERNEL_RADIUS", KERNEL_RADIUS }, 74 | { "POLY6_KERNEL_WEIGHT_CONST", poly6KernelWeightConst }, 75 | { "STIFFNESS_K", STIFFNESS }, 76 | { "REST_DENSITY", REST_DENSITY }, 77 | { "REST_PRESSURE", REST_PRESSURE } 78 | }); 79 | 80 | m_programSimStep6 = GlHelper::createComputeShader(SHADERS_DIR "/simStep6.comp", { 81 | { "INV_CELL_SIZE", invCellSize }, 82 | { "GRID_SIZE", GRID_SIZE }, 83 | { "GRID_ORIGIN", GRID_ORIGIN }, 84 | { "GRID_RES", GRID_RES }, 85 | { "MASS", MASS }, 86 | { "KERNEL_RADIUS", KERNEL_RADIUS }, 87 | { "VIS_COEFF", VIS_COEFF }, 88 | { "VIS_KERNEL_WEIGHT_CONST", viscosityKernelWeightConst }, 89 | { "SPIKY_KERNEL_WEIGHT_CONST", spikyKernelWeightConst } 90 | }); 91 | 92 | m_programRenderGeometry = GlHelper::createVertFragShader(SHADERS_DIR "/renderGeometry.vert", SHADERS_DIR "/renderGeometry.frag"); 93 | m_programRenderCurvature = GlHelper::createVertFragShader(SHADERS_DIR "/renderBoundingBox.vert", SHADERS_DIR "/renderCurvature.frag", { 94 | { "NEAR", Camera::NEAR_PLANE }, 95 | { "FAR", Camera::FAR_PLANE } 96 | }); 97 | m_programRenderShading = GlHelper::createVertFragShader(SHADERS_DIR "/renderBoundingBox.vert", SHADERS_DIR "/renderShading.frag"); 98 | } 99 | 100 | // Bounding box 101 | const std::vector bboxVertices{ 102 | GRID_ORIGIN + glm::vec3{ 0.0f, 0.0f, GRID_SIZE.z}, 103 | GRID_ORIGIN + glm::vec3{GRID_SIZE.x, 0.0f, GRID_SIZE.z}, 104 | GRID_ORIGIN + glm::vec3{GRID_SIZE.x, GRID_SIZE.y, GRID_SIZE.z}, 105 | GRID_ORIGIN + glm::vec3{ 0.0f, GRID_SIZE.y, GRID_SIZE.z}, 106 | GRID_ORIGIN + glm::vec3{ 0.0f, 0.0f, 0.0f}, 107 | GRID_ORIGIN + glm::vec3{GRID_SIZE.x, 0.0f, 0.0f}, 108 | GRID_ORIGIN + glm::vec3{GRID_SIZE.x, GRID_SIZE.y, 0.0f}, 109 | GRID_ORIGIN + glm::vec3{ 0.0f, GRID_SIZE.y, 0.0f}, 110 | }; 111 | glCreateBuffers(1, &m_bufBBoxVertices); 112 | glNamedBufferStorage(m_bufBBoxVertices, bboxVertices.size() * sizeof(float) * 3, glm::value_ptr(bboxVertices.data()[0]), 0); 113 | 114 | const std::vector bboxIndices { 115 | 0, 1, 2, 2, 3, 0, 116 | 1, 5, 6, 6, 2, 1, 117 | 7, 6, 5, 5, 4, 7, 118 | 4, 0, 3, 3, 7, 4, 119 | 4, 5, 1, 1, 0, 4, 120 | 3, 2, 6, 6, 7, 3 121 | }; 122 | glCreateBuffers(1, &m_bufBBoxIndices); 123 | glNamedBufferStorage(m_bufBBoxIndices, bboxIndices.size() * sizeof(uint32_t), bboxIndices.data(), 0); 124 | 125 | glCreateVertexArrays(1, &m_vao3); 126 | glEnableVertexArrayAttrib(m_vao3, 0); 127 | glVertexArrayVertexBuffer(m_vao3, 0, m_bufBBoxVertices, 0, 3 * sizeof(float)); 128 | glVertexArrayAttribBinding(m_vao3, 0, 0); 129 | glVertexArrayAttribFormat(m_vao3, 0, 3, GL_FLOAT, GL_FALSE, 0); 130 | glEnableVertexArrayAttrib(m_vao3, 1); 131 | glVertexArrayElementBuffer(m_vao3, m_bufBBoxIndices); 132 | glVertexArrayAttribBinding(m_vao3, 1, 0); 133 | glVertexArrayAttribFormat(m_vao3, 1, 1, GL_FLOAT, GL_FALSE, 3 * sizeof(float)); 134 | 135 | // Uniform grid 136 | glCreateTextures(GL_TEXTURE_3D, 1, &m_texGrid); 137 | glTextureStorage3D(m_texGrid, 1, GL_R32UI, GRID_RES.x, GRID_RES.y, GRID_RES.z); 138 | m_texGridImgHandle = glGetImageHandleARB(m_texGrid, 0, GL_FALSE, 0, GL_R32UI); 139 | glMakeImageHandleResidentARB(m_texGridImgHandle, GL_READ_WRITE); 140 | 141 | glCreateBuffers(1, &m_bufCounters); 142 | glNamedBufferStorage(m_bufCounters, 4, nullptr, GL_DYNAMIC_STORAGE_BIT); 143 | 144 | // Velocity texture 145 | glCreateTextures(GL_TEXTURE_3D, 1, &m_texVelocity); 146 | glTextureParameteri(m_texVelocity, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 147 | glTextureParameteri(m_texVelocity, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 148 | glTextureStorage3D(m_texVelocity, 1, GL_RGBA32F, GRID_RES.x, GRID_RES.y, GRID_RES.z); 149 | m_texVelocityHandle = glGetTextureHandleARB(m_texVelocity); 150 | glMakeTextureHandleResidentARB(m_texVelocityHandle); 151 | m_texVelocityImgHandle = glGetImageHandleARB(m_texVelocity, 0, GL_FALSE, 0, GL_RGBA32F); 152 | glMakeImageHandleResidentARB(m_texVelocityImgHandle, GL_READ_WRITE); 153 | 154 | // Billboards index buffer 155 | { 156 | uint32_t billboardIndexCount = 6; 157 | uint32_t billboardVertexCount = 4; 158 | uint32_t billboardIndices[] = { 0, 1, 2, 2, 1, 3 }; 159 | 160 | std::vector indices(billboardIndexCount * m_particleCount); 161 | 162 | for (uint32_t i = 0; i < indices.size(); i++) 163 | { 164 | uint32_t particleOffset = i / billboardIndexCount; 165 | uint32_t particleIndexOffset = i % billboardIndexCount; 166 | indices[i] = billboardIndices[particleIndexOffset] + particleOffset * billboardVertexCount; 167 | } 168 | 169 | glCreateBuffers(1, &m_bufBillboards); 170 | glNamedBufferData(m_bufBillboards, indices.size() * sizeof(uint32_t), indices.data(), GL_STATIC_DRAW); 171 | } 172 | 173 | // Initial particles 174 | std::vector particles; 175 | particles.resize(m_particleCount); 176 | for (uint32_t i = 0; i < m_particleCount; ++i) 177 | { 178 | Particle& p = particles[i]; 179 | const float x = ((std::rand() % 10000) / 10000.0f) * (GRID_SIZE.x * 0.5); 180 | const float y = ((std::rand() % 10000) / 10000.0f) * (GRID_SIZE.y * 0.5); 181 | const float z = ((std::rand() % 10000) / 10000.0f) * (GRID_SIZE.z * 0.5); 182 | p.position_x = GRID_ORIGIN.x + GRID_SIZE.x * 0.25f + x; 183 | p.position_y = GRID_ORIGIN.y + GRID_SIZE.y * 0.25f + y; 184 | p.position_z = GRID_ORIGIN.z + GRID_SIZE.z * 0.25f + z; 185 | p.density = 0.0f; 186 | p.velocity_x = 0.0f; 187 | p.velocity_y = 0.0f; 188 | p.velocity_z = 0.0f; 189 | p.pressure = 0.0f; 190 | } 191 | 192 | const auto size = m_particleCount * sizeof(Particle); 193 | glCreateBuffers(1, &m_bufParticles1); 194 | glCreateBuffers(1, &m_bufParticles2); 195 | glNamedBufferStorage(m_bufParticles1, size, particles.data(), 0); 196 | glNamedBufferStorage(m_bufParticles2, size, particles.data(), 0); 197 | 198 | glCreateVertexArrays(1, &m_vao1); 199 | glVertexArrayElementBuffer(m_vao1, m_bufBillboards); 200 | glCreateVertexArrays(1, &m_vao2); 201 | glVertexArrayElementBuffer(m_vao2, m_bufBillboards); 202 | 203 | // Default state 204 | glClearColor(1.0f, 1.0f, 1.0f, 0.0f); 205 | glEnable(GL_DEPTH_TEST); 206 | glDepthMask(GL_TRUE); 207 | 208 | // Textures and buffers 209 | createFrameObjects(); 210 | 211 | // Timer queries 212 | m_queries = std::make_unique(); 213 | } 214 | 215 | void flut::Simulation::createFrameObjects() 216 | { 217 | glCreateTextures(GL_TEXTURE_2D, 1, &m_texDepth); 218 | glTextureStorage2D(m_texDepth, 1, GL_DEPTH_COMPONENT24, m_width, m_height); 219 | glTextureParameteri(m_texDepth, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 220 | glTextureParameteri(m_texDepth, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 221 | m_texDepthHandle = glGetTextureHandleARB(m_texDepth); 222 | glMakeTextureHandleResidentARB(m_texDepthHandle); 223 | 224 | glCreateTextures(GL_TEXTURE_2D, 1, &m_texColor); 225 | glTextureStorage2D(m_texColor, 1, GL_RGB32F, m_width, m_height); 226 | glTextureParameteri(m_texColor, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 227 | glTextureParameteri(m_texColor, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 228 | m_texColorHandle = glGetTextureHandleARB(m_texColor); 229 | glMakeTextureHandleResidentARB(m_texColorHandle); 230 | 231 | glCreateTextures(GL_TEXTURE_2D, 1, &m_texTemp1); 232 | glTextureStorage2D(m_texTemp1, 1, GL_R32F, m_width, m_height); 233 | glTextureParameteri(m_texTemp1, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 234 | glTextureParameteri(m_texTemp1, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 235 | m_texTemp1Handle = glGetTextureHandleARB(m_texTemp1); 236 | glMakeTextureHandleResidentARB(m_texTemp1Handle); 237 | 238 | glCreateTextures(GL_TEXTURE_2D, 1, &m_texTemp2); 239 | glTextureStorage2D(m_texTemp2, 1, GL_R32F, m_width, m_height); 240 | glTextureParameteri(m_texTemp2, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 241 | glTextureParameteri(m_texTemp2, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 242 | m_texTemp2Handle = glGetTextureHandleARB(m_texTemp2); 243 | glMakeTextureHandleResidentARB(m_texTemp2Handle); 244 | 245 | glCreateFramebuffers(1, &m_fbo1); 246 | glNamedFramebufferTexture(m_fbo1, GL_DEPTH_ATTACHMENT, m_texDepth, 0); 247 | glNamedFramebufferTexture(m_fbo1, GL_COLOR_ATTACHMENT0, m_texColor, 0); 248 | 249 | glCreateFramebuffers(1, &m_fbo2); 250 | glNamedFramebufferTexture(m_fbo2, GL_COLOR_ATTACHMENT0, m_texTemp1, 0); 251 | 252 | glCreateFramebuffers(1, &m_fbo3); 253 | glNamedFramebufferTexture(m_fbo3, GL_COLOR_ATTACHMENT0, m_texTemp2, 0); 254 | } 255 | 256 | void flut::Simulation::deleteFrameObjects() 257 | { 258 | glDeleteFramebuffers(1, &m_fbo1); 259 | glDeleteFramebuffers(1, &m_fbo2); 260 | glDeleteFramebuffers(1, &m_fbo3); 261 | 262 | glMakeTextureHandleNonResidentARB(m_texDepthHandle); 263 | glDeleteTextures(1, &m_texDepth); 264 | 265 | glMakeTextureHandleNonResidentARB(m_texColorHandle); 266 | glDeleteTextures(1, &m_texColor); 267 | 268 | glMakeTextureHandleNonResidentARB(m_texTemp1Handle); 269 | glDeleteTextures(1, &m_texTemp1); 270 | 271 | glMakeTextureHandleNonResidentARB(m_texTemp2Handle); 272 | glDeleteTextures(1, &m_texTemp2); 273 | } 274 | 275 | Simulation::~Simulation() 276 | { 277 | deleteFrameObjects(); 278 | glDeleteProgram(m_programSimStep1); 279 | glDeleteProgram(m_programSimStep2); 280 | glDeleteProgram(m_programSimStep3); 281 | glDeleteProgram(m_programSimStep5); 282 | glDeleteProgram(m_programSimStep6); 283 | glDeleteProgram(m_programRenderGeometry); 284 | glDeleteProgram(m_programRenderCurvature); 285 | glDeleteProgram(m_programRenderShading); 286 | glDeleteBuffers(1, &m_bufBBoxVertices); 287 | glDeleteBuffers(1, &m_bufBBoxIndices); 288 | glDeleteBuffers(1, &m_bufParticles1); 289 | glDeleteBuffers(1, &m_bufParticles2); 290 | glMakeImageHandleNonResidentARB(m_texGridImgHandle); 291 | glDeleteTextures(1, &m_texGrid); 292 | glMakeImageHandleNonResidentARB(m_texVelocityImgHandle); 293 | glMakeTextureHandleNonResidentARB(m_texVelocityHandle); 294 | glDeleteTextures(1, &m_texVelocity); 295 | glDeleteBuffers(1, &m_bufCounters); 296 | glDeleteVertexArrays(1, &m_vao1); 297 | glDeleteVertexArrays(1, &m_vao2); 298 | glDeleteVertexArrays(1, &m_vao3); 299 | } 300 | 301 | void Simulation::render(const Camera& camera, float dt) 302 | { 303 | ++m_frame; 304 | 305 | // Resize window if needed. 306 | if (m_width != m_newWidth || m_height != m_newHeight) 307 | { 308 | m_width = m_newWidth; 309 | m_height = m_newHeight; 310 | deleteFrameObjects(); 311 | createFrameObjects(); 312 | } 313 | 314 | for (uint32_t f = 0; f < m_integrationsPerFrame; f++) 315 | { 316 | float dt = DT * m_options.deltaTimeMod; 317 | 318 | auto singleDimGroupCountForParticles = [this](uint32_t groupSize) { 319 | assert(groupSize <= MAX_GROUP_SIZE && (m_particleCount % groupSize) == 0); 320 | return m_particleCount / groupSize; 321 | }; 322 | 323 | // Step 1: Integrate position, do boundary handling. 324 | // Write particle count to voxel grid. 325 | m_queries->beginSimQuery(0); 326 | const uint32_t fClearValue = 0; 327 | glClearTexImage(m_texGrid, 0, GL_RED_INTEGER, GL_UNSIGNED_INT, &fClearValue); 328 | glMemoryBarrier(GL_TEXTURE_UPDATE_BARRIER_BIT); 329 | 330 | glUseProgram(m_programSimStep1); 331 | glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_swapFrame ? m_bufParticles1 : m_bufParticles2); 332 | glProgramUniformHandleui64ARB(m_programSimStep1, 0, m_texGridImgHandle); 333 | glProgramUniform1f(m_programSimStep1, 1, dt); 334 | glDispatchCompute(singleDimGroupCountForParticles(32), 1, 1); 335 | glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT); 336 | m_queries->endQuery(); 337 | 338 | // Step 2: Write global particle array offsets into voxel grid. 339 | m_queries->beginSimQuery(1); 340 | const uint32_t uiClearValue = 0; 341 | glClearNamedBufferData(m_bufCounters, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, &uiClearValue); 342 | glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); 343 | 344 | glUseProgram(m_programSimStep2); 345 | glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_bufCounters); 346 | glProgramUniformHandleui64ARB(m_programSimStep2, 0, m_texGridImgHandle); 347 | glDispatchCompute( 348 | (GRID_RES.x + 4 - 1) / 4, 349 | (GRID_RES.y + 4 - 1) / 4, 350 | (GRID_RES.z + 4 - 1) / 4 351 | ); 352 | glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); 353 | m_queries->endQuery(); 354 | 355 | // Step 3: Write particles to new location in second particle buffer. 356 | // Write particle count to voxel grid (again). 357 | m_queries->beginSimQuery(2); 358 | glUseProgram(m_programSimStep3); 359 | glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_swapFrame ? m_bufParticles1 : m_bufParticles2); 360 | glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_swapFrame ? m_bufParticles2 : m_bufParticles1); 361 | glProgramUniformHandleui64ARB(m_programSimStep3, 0, m_texGridImgHandle); 362 | glDispatchCompute(singleDimGroupCountForParticles(32), 1, 1); 363 | glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT); 364 | m_queries->endQuery(); 365 | 366 | // Step 4: Write average voxel velocities into second 3D-texture. 367 | m_queries->beginSimQuery(3); 368 | glUseProgram(m_programSimStep4); 369 | glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_swapFrame ? m_bufParticles2 : m_bufParticles1); 370 | glProgramUniformHandleui64ARB(m_programSimStep4, 0, m_texGridImgHandle); 371 | glProgramUniformHandleui64ARB(m_programSimStep4, 1, m_texVelocityImgHandle); 372 | glDispatchCompute( 373 | (GRID_RES.x + 4 - 1) / 4, 374 | (GRID_RES.y + 4 - 1) / 4, 375 | (GRID_RES.z + 4 - 1) / 4 376 | ); 377 | glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_TEXTURE_FETCH_BARRIER_BIT); 378 | m_queries->endQuery(); 379 | 380 | // Step 5: Compute density and pressure for each particle. 381 | m_queries->beginSimQuery(4); 382 | glUseProgram(m_programSimStep5); 383 | glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_swapFrame ? m_bufParticles2 : m_bufParticles1); 384 | glProgramUniformHandleui64ARB(m_programSimStep5, 0, m_texGridImgHandle); 385 | glDispatchCompute(singleDimGroupCountForParticles(64), 1, 1); 386 | glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); 387 | m_queries->endQuery(); 388 | 389 | // Step 6: Compute pressure and viscosity forces, use them to write new velocity. 390 | // For the old velocity, we use the coarse 3d-texture and do trilinear HW filtering. 391 | m_queries->beginSimQuery(5); 392 | glUseProgram(m_programSimStep6); 393 | glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_swapFrame ? m_bufParticles2 : m_bufParticles1); 394 | glProgramUniformHandleui64ARB(m_programSimStep6, 0, m_texGridImgHandle); 395 | glProgramUniformHandleui64ARB(m_programSimStep6, 1, m_texVelocityHandle); 396 | glProgramUniform1f(m_programSimStep6, 2, dt); 397 | glProgramUniform3fv(m_programSimStep6, 3, 1, &m_options.gravity[0]); 398 | glDispatchCompute(singleDimGroupCountForParticles(64), 1, 1); 399 | glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); 400 | m_queries->endQuery(); 401 | 402 | m_swapFrame = !m_swapFrame; 403 | m_queries->incSimIter(); 404 | } 405 | 406 | // Step 7: Render the geometry as screen-space spheres. 407 | m_queries->beginRenderQuery(); 408 | glBindFramebuffer(GL_FRAMEBUFFER, m_fbo1); 409 | glUseProgram(m_programRenderGeometry); 410 | glClearColor(1.0f, 1.0f, 1.0f, 1.0f); 411 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 412 | const float pointRadius = KERNEL_RADIUS * m_options.pointScale; 413 | const auto& view = camera.view(); 414 | const auto& projection = camera.projection(); 415 | const auto& invProjection = camera.invProjection(); 416 | const glm::mat4 vp = projection * view; 417 | glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_swapFrame ? m_bufParticles2 : m_bufParticles1); 418 | glProgramUniformMatrix4fv(m_programRenderGeometry, 0, 1, GL_FALSE, glm::value_ptr(vp)); 419 | glProgramUniformMatrix4fv(m_programRenderGeometry, 1, 1, GL_FALSE, glm::value_ptr(view)); 420 | glProgramUniformMatrix4fv(m_programRenderGeometry, 2, 1, GL_FALSE, glm::value_ptr(projection)); 421 | glProgramUniform3fv(m_programRenderGeometry, 3, 1, glm::value_ptr(GRID_SIZE)); 422 | glProgramUniform3fv(m_programRenderGeometry, 4, 1, glm::value_ptr(GRID_ORIGIN)); 423 | glProgramUniform3iv(m_programRenderGeometry, 5, 1, glm::value_ptr(GRID_RES)); 424 | glProgramUniform1ui(m_programRenderGeometry, 6, m_particleCount); 425 | glProgramUniform1f(m_programRenderGeometry, 7, pointRadius); 426 | glProgramUniform1i(m_programRenderGeometry, 8, m_options.colorMode); 427 | glBindVertexArray(m_swapFrame ? m_vao2 : m_vao1); 428 | const uint32_t index_count = 6 * m_particleCount; 429 | glDrawElements(GL_TRIANGLES, index_count, GL_UNSIGNED_INT, nullptr); 430 | 431 | // Step 7.1: Perform curvature flow (multiple iterations). 432 | const uint32_t bboxTriVertexCount = 36; 433 | glDisable(GL_DEPTH_TEST); 434 | glBindVertexArray(m_vao3); 435 | glUseProgram(m_programRenderCurvature); 436 | glProgramUniformMatrix4fv(m_programRenderCurvature, 0, 1, GL_FALSE, glm::value_ptr(vp)); 437 | glProgramUniformMatrix4fv(m_programRenderCurvature, 2, 1, GL_FALSE, glm::value_ptr(projection)); 438 | glProgramUniform2i(m_programRenderCurvature, 3, m_width, m_height); 439 | 440 | GLuint64 inputDepthTexHandle = m_texDepthHandle; 441 | bool swap = false; 442 | for (uint32_t i = 0; i < SMOOTH_ITERATIONS; ++i) 443 | { 444 | glBindFramebuffer(GL_FRAMEBUFFER, swap ? m_fbo3 : m_fbo2); 445 | glClear(GL_COLOR_BUFFER_BIT); 446 | glProgramUniformHandleui64ARB(m_programRenderCurvature, 1, inputDepthTexHandle); 447 | glDrawElements(GL_TRIANGLES, bboxTriVertexCount, GL_UNSIGNED_INT, nullptr); 448 | inputDepthTexHandle = swap ? m_texTemp2Handle : m_texTemp1Handle; 449 | swap = !swap; 450 | } 451 | 452 | // Step 7.2: Do blinn-phong shading. 453 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 454 | glViewport(0, 0, m_width, m_height); 455 | glClearColor(1.0f, 1.0f, 1.0f, 1.0f); 456 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 457 | 458 | glUseProgram(m_programRenderShading); 459 | glProgramUniformMatrix4fv(m_programRenderShading, 0, 1, GL_FALSE, glm::value_ptr(vp)); 460 | glProgramUniformHandleui64ARB(m_programRenderShading, 1, inputDepthTexHandle); 461 | glProgramUniformHandleui64ARB(m_programRenderShading, 2, m_texColorHandle); 462 | glProgramUniform1ui(m_programRenderShading, 3, m_width); 463 | glProgramUniform1ui(m_programRenderShading, 4, m_height); 464 | glProgramUniformMatrix4fv(m_programRenderShading, 5, 1, GL_FALSE, glm::value_ptr(invProjection)); 465 | glProgramUniformMatrix4fv(m_programRenderShading, 6, 1, GL_FALSE, glm::value_ptr(view)); 466 | glDrawElements(GL_TRIANGLES, bboxTriVertexCount, GL_UNSIGNED_INT, nullptr); 467 | glEnable(GL_DEPTH_TEST); 468 | 469 | m_queries->endQuery(); 470 | 471 | m_queries->readFinishedQueries(m_time); 472 | 473 | m_queries->incFrame(); 474 | } 475 | 476 | void Simulation::resize(uint32_t width, uint32_t height) 477 | { 478 | m_newWidth = width; 479 | m_newHeight = height; 480 | } 481 | 482 | Simulation::SimulationOptions& Simulation::options() 483 | { 484 | return m_options; 485 | } 486 | 487 | const Simulation::SimulationTimes& Simulation::times() const 488 | { 489 | return m_time; 490 | } 491 | 492 | void flut::Simulation::setIntegrationsPerFrame(uint32_t ipF) 493 | { 494 | m_integrationsPerFrame = ipF; 495 | } 496 | 497 | uint32_t flut::Simulation::particleCount() const 498 | { 499 | return m_particleCount; 500 | } 501 | -------------------------------------------------------------------------------- /src/flut/Simulation.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "Camera.hpp" 9 | #include "GlQueryRetriever.hpp" 10 | 11 | namespace flut 12 | { 13 | class Simulation 14 | { 15 | public: 16 | struct SimulationOptions 17 | { 18 | float gravity[3] = {0.0f, -9.81f, 0.0f}; 19 | float deltaTimeMod = 1.0f; 20 | int32_t colorMode = 0; 21 | float pointScale = 0.75f; 22 | }; 23 | 24 | using SimulationTimes = GlQueryRetriever::QueryTimings; 25 | 26 | public: 27 | constexpr static float DT = 0.0012f; 28 | constexpr static float STIFFNESS = 250.0; 29 | constexpr static float MASS = 0.02f; 30 | constexpr static float PARTICLE_RADIUS = 0.0457f; 31 | constexpr static float KERNEL_RADIUS = PARTICLE_RADIUS * 4.0f; 32 | constexpr static float CELL_SIZE = PARTICLE_RADIUS * 4.0f; 33 | constexpr static float VIS_COEFF = 0.035f; 34 | constexpr static float REST_DENSITY = 998.27f; 35 | constexpr static float REST_PRESSURE = 0.0f; 36 | constexpr static uint32_t MIN_PARTICLE_COUNT = 100000; 37 | 38 | const glm::vec3 GRID_SIZE = glm::vec3{ 11.0f, 8.0f, 2.5f } * glm::vec3{ 2.0f }; 39 | const glm::vec3 GRID_ORIGIN = GRID_SIZE * -0.5f; 40 | const glm::ivec3 GRID_RES = glm::ivec3((GRID_SIZE / CELL_SIZE) + 1.0f); 41 | const uint32_t GRID_VOXEL_COUNT = GRID_RES.x * GRID_RES.y * GRID_RES.z; 42 | 43 | private: 44 | constexpr static uint32_t SMOOTH_ITERATIONS = 50; 45 | constexpr static uint32_t MAX_GROUP_SIZE = 512; 46 | 47 | public: 48 | Simulation(uint32_t width, uint32_t height); 49 | 50 | ~Simulation(); 51 | 52 | public: 53 | void render(const Camera& camera, float dt); 54 | 55 | void resize(uint32_t width, uint32_t height); 56 | 57 | SimulationOptions& options(); 58 | 59 | const SimulationTimes& times() const; 60 | 61 | void setIntegrationsPerFrame(uint32_t ipF); 62 | 63 | uint32_t particleCount() const; 64 | 65 | private: 66 | void createFrameObjects(); 67 | 68 | void deleteFrameObjects(); 69 | 70 | private: 71 | uint32_t m_width; 72 | uint32_t m_height; 73 | uint32_t m_newWidth; 74 | uint32_t m_newHeight; 75 | uint64_t m_frame; 76 | SimulationTimes m_time; 77 | SimulationOptions m_options; 78 | std::unique_ptr m_queries; 79 | uint32_t m_integrationsPerFrame; 80 | uint32_t m_particleCount; 81 | GLuint m_programSimStep1; 82 | GLuint m_programSimStep2; 83 | GLuint m_programSimStep3; 84 | GLuint m_programSimStep4; 85 | GLuint m_programSimStep5; 86 | GLuint m_programSimStep6; 87 | GLuint m_programRenderGeometry; 88 | GLuint m_programRenderCurvature; 89 | GLuint m_programRenderShading; 90 | GLuint m_bufBBoxVertices; 91 | GLuint m_bufBBoxIndices; 92 | GLuint m_bufParticles1; 93 | GLuint m_bufParticles2; 94 | GLuint m_bufCounters; 95 | GLuint m_bufBillboards; 96 | GLuint m_texGrid; 97 | GLuint64 m_texGridImgHandle; 98 | GLuint m_texVelocity; 99 | GLuint64 m_texVelocityHandle; 100 | GLuint64 m_texVelocityImgHandle; 101 | GLuint m_vao1; 102 | GLuint m_vao2; 103 | GLuint m_vao3; 104 | GLuint m_fbo1; 105 | GLuint m_fbo2; 106 | GLuint m_fbo3; 107 | GLuint m_texDepth; 108 | GLuint64 m_texDepthHandle; 109 | GLuint m_texColor; 110 | GLuint64 m_texColorHandle; 111 | GLuint m_texTemp1; 112 | GLuint64 m_texTemp1Handle; 113 | GLuint m_texTemp2; 114 | GLuint64 m_texTemp2Handle; 115 | bool m_swapFrame; 116 | }; 117 | } 118 | -------------------------------------------------------------------------------- /src/flut/Window.cpp: -------------------------------------------------------------------------------- 1 | #include "Window.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace flut; 10 | 11 | Window::Window(const char* title, uint32_t width, uint32_t height) 12 | : m_shouldClose{false} 13 | { 14 | if (SDL_InitSubSystem(SDL_INIT_VIDEO)) { 15 | fprintf(stderr, "%s", SDL_GetError()); 16 | abort(); 17 | } 18 | 19 | m_window = SDL_CreateWindow( 20 | title, 21 | SDL_WINDOWPOS_CENTERED, 22 | SDL_WINDOWPOS_CENTERED, 23 | width, 24 | height, 25 | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_OPENGL 26 | ); 27 | 28 | if (!m_window) { 29 | fprintf(stderr, "%s", SDL_GetError()); 30 | abort(); 31 | } 32 | 33 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); 34 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6); 35 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); 36 | #ifdef NDEBUG 37 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_NO_ERROR, 1); 38 | #else 39 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); 40 | #endif 41 | 42 | m_context = SDL_GL_CreateContext(m_window); 43 | 44 | if (!m_context) { 45 | fprintf(stderr, "%s", SDL_GetError()); 46 | abort(); 47 | } 48 | 49 | if (!gladLoadGLLoader(SDL_GL_GetProcAddress)) { 50 | fprintf(stderr, "Unable to initialize Glad"); 51 | abort(); 52 | } 53 | 54 | if (GLVersion.major < 4 || (GLVersion.major == 4 && GLVersion.minor < 6)) { 55 | fprintf(stderr, "OpenGL 4.6 required"); 56 | abort(); 57 | } 58 | 59 | if (!GLAD_GL_ARB_bindless_texture) { 60 | fprintf(stderr, "GL_ARB_bindless_texture extension is required"); 61 | abort(); 62 | } 63 | 64 | printf("OpenGL Version %d.%d loaded.\n", GLVersion.major, GLVersion.minor); 65 | fflush(stdout); 66 | 67 | ImGui_ImplSdlGlad_Init(m_window); 68 | ImGui_ImplSdlGlad_NewFrame(m_window); 69 | } 70 | 71 | Window::~Window() 72 | { 73 | ImGui_ImplSdlGlad_Shutdown(); 74 | SDL_GL_DeleteContext(m_context); 75 | SDL_DestroyWindow(m_window); 76 | SDL_QuitSubSystem(SDL_INIT_VIDEO); 77 | } 78 | 79 | void Window::pollEvents() 80 | { 81 | SDL_Event event; 82 | 83 | while (SDL_PollEvent(&event)) 84 | { 85 | if (ImGui_ImplSdlGlad_ProcessEvent(&event)) 86 | { 87 | continue; 88 | } 89 | 90 | if (event.type == SDL_QUIT) 91 | { 92 | m_shouldClose = true; 93 | continue; 94 | } 95 | if (event.type != SDL_WINDOWEVENT) 96 | { 97 | continue; 98 | } 99 | 100 | switch (event.window.event) 101 | { 102 | case SDL_WINDOWEVENT_RESIZED: 103 | case SDL_WINDOWEVENT_SIZE_CHANGED: 104 | const auto width = static_cast(event.window.data1); 105 | const auto height = static_cast(event.window.data2); 106 | if (m_resizeCallback) { 107 | m_resizeCallback(width, height); 108 | } 109 | break; 110 | } 111 | } 112 | } 113 | 114 | bool Window::shouldClose() 115 | { 116 | return m_shouldClose; 117 | } 118 | 119 | void Window::swap() 120 | { 121 | ImGui::Render(); 122 | SDL_GL_SwapWindow(m_window); 123 | ImGui_ImplSdlGlad_NewFrame(m_window); 124 | } 125 | 126 | uint32_t Window::width() const 127 | { 128 | int width; 129 | SDL_GL_GetDrawableSize(m_window, &width, nullptr); 130 | return static_cast(width); 131 | } 132 | 133 | uint32_t Window::height() const 134 | { 135 | int height; 136 | SDL_GL_GetDrawableSize(m_window, nullptr, &height); 137 | return static_cast(height); 138 | } 139 | 140 | int32_t Window::mouseX() const 141 | { 142 | int x; 143 | SDL_GetMouseState(&x, nullptr); 144 | return x; 145 | } 146 | 147 | int32_t Window::mouseY() const 148 | { 149 | int y; 150 | SDL_GetMouseState(nullptr, &y); 151 | return y; 152 | } 153 | 154 | bool Window::mouseDown() const 155 | { 156 | return (SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0; 157 | } 158 | 159 | bool Window::keyUp() const 160 | { 161 | const auto& states = SDL_GetKeyboardState(nullptr); 162 | return states[SDL_SCANCODE_W] != 0; 163 | } 164 | 165 | bool Window::keyDown() const 166 | { 167 | const auto& states = SDL_GetKeyboardState(nullptr); 168 | return states[SDL_SCANCODE_S] != 0; 169 | } 170 | 171 | void Window::resize(std::function callback) 172 | { 173 | m_resizeCallback = callback; 174 | } 175 | -------------------------------------------------------------------------------- /src/flut/Window.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace flut 8 | { 9 | class Window 10 | { 11 | public: 12 | Window(const char* title, uint32_t width, uint32_t height); 13 | 14 | ~Window(); 15 | 16 | public: 17 | void pollEvents(); 18 | 19 | bool shouldClose(); 20 | 21 | void swap(); 22 | 23 | uint32_t width() const; 24 | 25 | uint32_t height() const; 26 | 27 | int32_t mouseX() const; 28 | 29 | int32_t mouseY() const; 30 | 31 | bool mouseDown() const; 32 | 33 | bool keyUp() const; 34 | 35 | bool keyDown() const; 36 | 37 | void resize(std::function callback); 38 | 39 | private: 40 | bool m_shouldClose; 41 | SDL_Window* m_window; 42 | SDL_GLContext m_context; 43 | std::function m_resizeCallback; 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /src/flut/main.cpp: -------------------------------------------------------------------------------- 1 | #include "Simulation.hpp" 2 | #include "Camera.hpp" 3 | #include "Window.hpp" 4 | #include "GlQueryRetriever.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace flut; 13 | 14 | int main(int argc, char* argv[]) 15 | { 16 | constexpr uint32_t WIDTH = 1200; 17 | constexpr uint32_t HEIGHT = 800; 18 | 19 | Window window{"flut", WIDTH, HEIGHT}; 20 | Camera camera{window}; 21 | Simulation simulation{WIDTH, HEIGHT}; 22 | 23 | window.resize([&](uint32_t width, uint32_t height) { 24 | simulation.resize(width, height); 25 | }); 26 | 27 | auto& options = simulation.options(); 28 | auto& times = simulation.times(); 29 | using clock = std::chrono::high_resolution_clock; 30 | auto lastTime = clock::now(); 31 | 32 | int ipF = 8; 33 | 34 | while (!window.shouldClose()) 35 | { 36 | const std::chrono::duration timeSpan{clock::now() - lastTime}; 37 | const float deltaTime = timeSpan.count(); 38 | lastTime = clock::now(); 39 | 40 | // IO 41 | window.pollEvents(); 42 | const auto& imguiIO = ImGui::GetIO(); 43 | 44 | if (!imguiIO.WantCaptureMouse && !imguiIO.WantCaptureKeyboard) 45 | { 46 | camera.update(deltaTime); 47 | } 48 | 49 | // Simulation / Render 50 | simulation.setIntegrationsPerFrame(ipF); 51 | 52 | simulation.render(camera, deltaTime); 53 | 54 | // UI 55 | ImGui::SetNextWindowPos({50, 50}); 56 | ImGui::Begin("SPH GPU Fluid Simulation", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove); 57 | 58 | ImGui::Text("Particles: %d", simulation.particleCount()); 59 | ImGui::Text("Delta-time: %f", simulation.DT * options.deltaTimeMod); 60 | ImGui::Text("Grid: %dx%dx%d", simulation.GRID_RES.x, simulation.GRID_RES.y, simulation.GRID_RES.z); 61 | ImGui::Text("Frame: %.2fms", deltaTime * 1000.0f); 62 | 63 | ImGui::Text("Step 1 Step 2 Step 3 Step 4 Step 5 Step 6 Render"); 64 | ImGui::Text("%.2fms %.2fms %.2fms %.2fms %.2fms %.2fms %.2fms", 65 | times.simStempMs[0], times.simStempMs[1], times.simStempMs[2], 66 | times.simStempMs[3], times.simStempMs[4], times.simStempMs[5], times.renderMs); 67 | 68 | ImGui::SliderFloat("Delta-Time mod", &options.deltaTimeMod, 0.0f, 2.0f, nullptr, 1.0f); 69 | 70 | ImGui::DragInt("Integrations per Frame", &ipF, 1.0f, 0, GlQueryRetriever::MAX_SIM_ITERS_PER_FRAME); 71 | 72 | ImGui::DragFloat3("Gravity", &options.gravity[0], 0.075f, -10.0f, 10.0f, nullptr, 1.0f); 73 | 74 | ImGui::Text("Particle Color:"); 75 | ImGui::RadioButton("Initial", &options.colorMode, 0); 76 | ImGui::SameLine(); 77 | ImGui::RadioButton("Velocity", &options.colorMode, 1); 78 | ImGui::SameLine(); 79 | ImGui::RadioButton("Speed", &options.colorMode, 2); 80 | ImGui::RadioButton("Density", &options.colorMode, 3); 81 | ImGui::SameLine(); 82 | ImGui::RadioButton("Uniform Grid", &options.colorMode, 4); 83 | 84 | ImGui::DragFloat("Point scale", &options.pointScale, 0.01f, 0.1f, 2.5f); 85 | 86 | ImGui::End(); 87 | 88 | window.swap(); 89 | } 90 | 91 | return EXIT_SUCCESS; 92 | } 93 | -------------------------------------------------------------------------------- /src/imgui/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library( 2 | imgui STATIC 3 | include/imgui.h 4 | include/imconfig.h 5 | include/imgui_impl_sdl_glad.h 6 | src/imgui.cpp 7 | src/imgui_demo.cpp 8 | src/imgui_draw.cpp 9 | src/imgui_impl_sdl_glad.cpp 10 | src/imgui_internal.h 11 | src/stb_rect_pack.h 12 | src/stb_textedit.h 13 | src/stb_truetype.h 14 | ) 15 | 16 | target_link_libraries( 17 | imgui PRIVATE 18 | SDL2 19 | OpenGL::GL 20 | glad 21 | ) 22 | 23 | target_include_directories( 24 | imgui 25 | PUBLIC 26 | include 27 | PRIVATE 28 | src 29 | ) 30 | -------------------------------------------------------------------------------- /src/imgui/include/imconfig.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // USER IMPLEMENTATION 3 | // This file contains compile-time options for ImGui. 4 | // Other options (memory allocation overrides, callbacks, etc.) can be set at runtime via the ImGuiIO structure - ImGui::GetIO(). 5 | //----------------------------------------------------------------------------- 6 | 7 | #pragma once 8 | 9 | //---- Define assertion handler. Defaults to calling assert(). 10 | //#define IM_ASSERT(_EXPR) MyAssert(_EXPR) 11 | 12 | //---- Define attributes of all API symbols declarations, e.g. for DLL under Windows. 13 | //#define IMGUI_API __declspec( dllexport ) 14 | //#define IMGUI_API __declspec( dllimport ) 15 | 16 | //---- Don't define obsolete functions names. Consider enabling from time to time or when updating to reduce like hood of using already obsolete function/names 17 | //#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS 18 | 19 | //---- Include imgui_user.h at the end of imgui.h 20 | //#define IMGUI_INCLUDE_IMGUI_USER_H 21 | 22 | //---- Don't implement default handlers for Windows (so as not to link with OpenClipboard() and others Win32 functions) 23 | //#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS 24 | //#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS 25 | 26 | //---- Don't implement test window functionality (ShowTestWindow()/ShowStyleEditor()/ShowUserGuide() methods will be empty) 27 | //---- It is very strongly recommended to NOT disable the test windows. Please read the comment at the top of imgui_demo.cpp to learn why. 28 | //#define IMGUI_DISABLE_TEST_WINDOWS 29 | 30 | //---- Don't implement ImFormatString(), ImFormatStringV() so you can reimplement them yourself. 31 | //#define IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS 32 | 33 | //---- Pack colors to BGRA instead of RGBA (remove need to post process vertex buffer in back ends) 34 | //#define IMGUI_USE_BGRA_PACKED_COLOR 35 | 36 | //---- Implement STB libraries in a namespace to avoid linkage conflicts 37 | //#define IMGUI_STB_NAMESPACE ImGuiStb 38 | 39 | //---- Define constructor and implicit cast operators to convert back<>forth from your math types and ImVec2/ImVec4. 40 | /* 41 | #define IM_VEC2_CLASS_EXTRA \ 42 | ImVec2(const MyVec2& f) { x = f.x; y = f.y; } \ 43 | operator MyVec2() const { return MyVec2(x,y); } 44 | 45 | #define IM_VEC4_CLASS_EXTRA \ 46 | ImVec4(const MyVec4& f) { x = f.x; y = f.y; z = f.z; w = f.w; } \ 47 | operator MyVec4() const { return MyVec4(x,y,z,w); } 48 | */ 49 | 50 | //---- Use 32-bit vertex indices (instead of default: 16-bit) to allow meshes with more than 64K vertices 51 | //#define ImDrawIdx unsigned int 52 | 53 | //---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files. 54 | //---- e.g. create variants of the ImGui::Value() helper for your low-level math types, or your own widgets/helpers. 55 | /* 56 | namespace ImGui 57 | { 58 | void Value(const char* prefix, const MyMatrix44& v, const char* float_format = NULL); 59 | } 60 | */ 61 | 62 | -------------------------------------------------------------------------------- /src/imgui/include/imgui_impl_sdl_glad.h: -------------------------------------------------------------------------------- 1 | // 2 | // ImGui SDL2 binding with glbinding. 3 | // Based on OpenGL3 features and C++11. 4 | // 5 | // In this binding, ImTextureID is used to store an OpenGL 'GLuint' texture identifier. Read the FAQ 6 | // about ImTextureID in imgui.cpp. 7 | // SDL is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan 8 | // graphics context creation, etc. glbinding is a helper library to access OpenGL functions since 9 | // there is no standard header to access modern OpenGL functions easily. Alternatives are GL3W, 10 | // GLEW, Glad, etc. 11 | // 12 | // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example 13 | // of using this. If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), 14 | // ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown(). If you are new to 15 | // ImGui, see examples/README.txt and documentation at the top of imgui.cpp. 16 | // https://github.com/ocornut/imgui 17 | // 18 | 19 | struct SDL_Window; 20 | typedef union SDL_Event SDL_Event; 21 | 22 | IMGUI_API bool ImGui_ImplSdlGlad_Init(SDL_Window *window); 23 | IMGUI_API void ImGui_ImplSdlGlad_Shutdown(); 24 | IMGUI_API void ImGui_ImplSdlGlad_NewFrame(SDL_Window *window); 25 | IMGUI_API bool ImGui_ImplSdlGlad_ProcessEvent(SDL_Event *event); 26 | 27 | IMGUI_API void ImGui_ImplSdlGlad_InvalidateDeviceObjects(); 28 | IMGUI_API bool ImGui_ImplSdlGlad_CreateDeviceObjects(); 29 | -------------------------------------------------------------------------------- /src/imgui/src/imgui_impl_sdl_glad.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // ImGui SDL2 binding with glbinding. 3 | // Based on OpenGL3 features and C++11. 4 | // 5 | // In this binding, ImTextureID is used to store an OpenGL 'GLuint' texture identifier. Read the FAQ 6 | // about ImTextureID in imgui.cpp. 7 | // SDL is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan 8 | // graphics context creation, etc. glbinding is a helper library to access OpenGL functions since 9 | // there is no standard header to access modern OpenGL functions easily. Alternatives are GL3W, 10 | // GLEW, Glad, etc. 11 | // 12 | // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example 13 | // of using this. If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), 14 | // ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown(). If you are new to 15 | // ImGui, see examples/README.txt and documentation at the top of imgui.cpp. 16 | // https://github.com/ocornut/imgui 17 | // 18 | 19 | #include "imgui.h" 20 | #include "imgui_impl_sdl_glad.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | static double time_ = 0.0f; 28 | static bool mousePressed_[3] = {false, false, false}; 29 | static float mouseWheel_ = 0.0f; 30 | static GLuint fontTexture_ = 0; 31 | static GLuint shaderHandle_ = 0, vertHandle_ = 0, fragHandle_ = 0; 32 | static GLint attribLocationTex_ = 0, attribLocationProj_ = 0, attribLocationPos_ = 0, 33 | attribLocationUV_ = 0, attribLocationCol_ = 0; 34 | static GLuint vboHandle_ = 0, vaoHandle_ = 0, elementsHandle_ = 0; 35 | 36 | void ImGui_ImplSdlGlad_RenderDrawLists(ImDrawData *drawData) { 37 | // Avoid rendering when minimized, scale coordinates for retina displays 38 | ImGuiIO &io = ImGui::GetIO(); 39 | int framebufWidth = static_cast(io.DisplaySize.x * io.DisplayFramebufferScale.x); 40 | int framebufHeight = static_cast(io.DisplaySize.y * io.DisplayFramebufferScale.y); 41 | if (framebufWidth == 0 || framebufHeight == 0) 42 | return; 43 | drawData->ScaleClipRects(io.DisplayFramebufferScale); 44 | 45 | // Back up GL state 46 | GLenum lastActiveTexture; 47 | glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint *)&lastActiveTexture); 48 | glActiveTexture(GL_TEXTURE0); 49 | GLint lastProgram; 50 | glGetIntegerv(GL_CURRENT_PROGRAM, &lastProgram); 51 | GLint lastTexture; 52 | glGetIntegerv(GL_TEXTURE_BINDING_2D, &lastTexture); 53 | GLint lastSampler; 54 | glGetIntegerv(GL_SAMPLER_BINDING, &lastSampler); 55 | GLint lastArrayBuffer; 56 | glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &lastArrayBuffer); 57 | GLint lastElementArrayBuffer; 58 | glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &lastElementArrayBuffer); 59 | GLint lastVertexArray; 60 | glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &lastVertexArray); 61 | GLint lastPolygonMode[2]; 62 | glGetIntegerv(GL_POLYGON_MODE, lastPolygonMode); 63 | GLint lastViewport[4]; 64 | glGetIntegerv(GL_VIEWPORT, lastViewport); 65 | GLint lastScissorBox[4]; 66 | glGetIntegerv(GL_SCISSOR_BOX, lastScissorBox); 67 | GLenum lastBlendSrcRgb; 68 | glGetIntegerv(GL_BLEND_SRC_RGB, (GLint *)&lastBlendSrcRgb); 69 | GLenum lastBlendDstRgb; 70 | glGetIntegerv(GL_BLEND_DST_RGB, (GLint *)&lastBlendDstRgb); 71 | GLenum lastBlendSrcAlpha; 72 | glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint *)&lastBlendSrcAlpha); 73 | GLenum lastBlendDstAlpha; 74 | glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint *)&lastBlendDstAlpha); 75 | GLenum lastBlendEquationRgb; 76 | glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint *)&lastBlendEquationRgb); 77 | GLenum lastBlendEquationAlpha; 78 | glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (GLint *)&lastBlendEquationAlpha); 79 | GLboolean lastEnableBlend = glIsEnabled(GL_BLEND); 80 | GLboolean lastEnableCullface = glIsEnabled(GL_CULL_FACE); 81 | GLboolean lastEnableDepthTest = glIsEnabled(GL_DEPTH_TEST); 82 | GLboolean lastEnableScissorTest = glIsEnabled(GL_SCISSOR_TEST); 83 | 84 | // Setup render state 85 | glEnable(GL_BLEND); 86 | glBlendEquation(GL_FUNC_ADD); 87 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 88 | glDisable(GL_CULL_FACE); 89 | glDisable(GL_DEPTH_TEST); 90 | glEnable(GL_SCISSOR_TEST); 91 | glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); 92 | 93 | // Setup viewport, orthographic projection matrix 94 | glViewport(0, 0, (GLsizei)framebufWidth, (GLsizei)framebufHeight); 95 | const float orthoProj[4][4] = { 96 | {2.0f / io.DisplaySize.x, 0.0f, 0.0f, 0.0f}, 97 | {0.0f, 2.0f / -io.DisplaySize.y, 0.0f, 0.0f}, 98 | {0.0f, 0.0f, -1.0f, 0.0f}, 99 | {-1.0f, 1.0f, 0.0f, 1.0f}, 100 | }; 101 | glUseProgram(shaderHandle_); 102 | glUniform1i(attribLocationTex_, 0); 103 | glUniformMatrix4fv(attribLocationProj_, 1, GL_FALSE, &orthoProj[0][0]); 104 | glBindVertexArray(vaoHandle_); 105 | // Rely on combined texture/sampler state. 106 | glBindSampler(0, 0); 107 | 108 | for (int n = 0; n < drawData->CmdListsCount; n++) { 109 | const ImDrawList *cmdList = drawData->CmdLists[n]; 110 | const ImDrawIdx *bufferOffset = 0; 111 | 112 | glBindBuffer(GL_ARRAY_BUFFER, vboHandle_); 113 | glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)cmdList->VtxBuffer.Size * sizeof(ImDrawVert), 114 | (const GLvoid *)cmdList->VtxBuffer.Data, GL_STREAM_DRAW); 115 | 116 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementsHandle_); 117 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)cmdList->IdxBuffer.Size * sizeof(ImDrawIdx), 118 | (const GLvoid *)cmdList->IdxBuffer.Data, GL_STREAM_DRAW); 119 | 120 | for (int i = 0; i < cmdList->CmdBuffer.Size; i++) { 121 | const ImDrawCmd *pcmd = &cmdList->CmdBuffer[i]; 122 | if (pcmd->UserCallback) { 123 | pcmd->UserCallback(cmdList, pcmd); 124 | } else { 125 | glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); 126 | glScissor((int)pcmd->ClipRect.x, (int)(framebufHeight - pcmd->ClipRect.w), 127 | (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y)); 128 | glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, 129 | sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, bufferOffset); 130 | } 131 | bufferOffset += pcmd->ElemCount; 132 | } 133 | } 134 | 135 | // Restore modified GL state 136 | glUseProgram(static_cast(lastProgram)); 137 | glBindTexture(GL_TEXTURE_2D, static_cast(lastTexture)); 138 | glBindSampler(0, static_cast(lastSampler)); 139 | glActiveTexture(lastActiveTexture); 140 | glBindVertexArray(static_cast(lastVertexArray)); 141 | glBindBuffer(GL_ARRAY_BUFFER, static_cast(lastArrayBuffer)); 142 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, static_cast(lastElementArrayBuffer)); 143 | glBlendEquationSeparate(lastBlendEquationRgb, lastBlendEquationAlpha); 144 | glBlendFuncSeparate(lastBlendSrcRgb, lastBlendDstRgb, lastBlendSrcAlpha, lastBlendDstAlpha); 145 | if (lastEnableBlend) 146 | glEnable(GL_BLEND); 147 | else 148 | glDisable(GL_BLEND); 149 | if (lastEnableCullface) 150 | glEnable(GL_CULL_FACE); 151 | else 152 | glDisable(GL_CULL_FACE); 153 | if (lastEnableDepthTest) 154 | glEnable(GL_DEPTH_TEST); 155 | else 156 | glDisable(GL_DEPTH_TEST); 157 | if (lastEnableScissorTest) 158 | glEnable(GL_SCISSOR_TEST); 159 | else 160 | glDisable(GL_SCISSOR_TEST); 161 | glPolygonMode(GL_FRONT_AND_BACK, (GLenum)(lastPolygonMode[0])); 162 | glViewport(lastViewport[0], lastViewport[1], (GLsizei)lastViewport[2], (GLsizei)lastViewport[3]); 163 | glScissor( 164 | lastScissorBox[0], lastScissorBox[1], (GLsizei)lastScissorBox[2], (GLsizei)lastScissorBox[3]); 165 | } 166 | 167 | static const char *ImGui_ImplSdlGlad_GetClipboardText(void *) { 168 | return SDL_GetClipboardText(); 169 | } 170 | 171 | static void ImGui_ImplSdlGlad_SetClipboardText(void *, const char *text) { 172 | SDL_SetClipboardText(text); 173 | } 174 | 175 | // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to 176 | // use your inputs. 177 | // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application. 178 | // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main 179 | // application. Generally you may always pass all inputs to dear imgui, and hide them from your 180 | // application based on those two flags. 181 | bool ImGui_ImplSdlGlad_ProcessEvent(SDL_Event *event) { 182 | ImGuiIO &io = ImGui::GetIO(); 183 | switch (event->type) { 184 | case SDL_MOUSEWHEEL: { 185 | if (event->wheel.y > 0) 186 | mouseWheel_ = 1; 187 | if (event->wheel.y < 0) 188 | mouseWheel_ = -1; 189 | return true; 190 | } 191 | case SDL_MOUSEBUTTONDOWN: { 192 | if (event->button.button == SDL_BUTTON_LEFT) 193 | mousePressed_[0] = true; 194 | if (event->button.button == SDL_BUTTON_RIGHT) 195 | mousePressed_[1] = true; 196 | if (event->button.button == SDL_BUTTON_MIDDLE) 197 | mousePressed_[2] = true; 198 | return true; 199 | } 200 | case SDL_TEXTINPUT: { 201 | io.AddInputCharactersUTF8(event->text.text); 202 | return true; 203 | } 204 | case SDL_KEYDOWN: 205 | case SDL_KEYUP: { 206 | int key = event->key.keysym.sym & ~SDLK_SCANCODE_MASK; 207 | io.KeysDown[key] = (event->type == SDL_KEYDOWN); 208 | io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0); 209 | io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0); 210 | io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0); 211 | io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0); 212 | return true; 213 | } 214 | default: { return false; } 215 | } 216 | } 217 | 218 | void ImGui_ImplSdlBinding_CreateFontsTexture() { 219 | // Build texture atlas 220 | ImGuiIO &io = ImGui::GetIO(); 221 | unsigned char *pixels; 222 | int width, height; 223 | // Load as RGBA 32-bits because it is more likely to be compatible with user's existing shader 224 | io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); 225 | // Upload texture 226 | GLint lastTexture; 227 | glGetIntegerv(GL_TEXTURE_BINDING_2D, &lastTexture); 228 | glGenTextures(1, &fontTexture_); 229 | glBindTexture(GL_TEXTURE_2D, fontTexture_); 230 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 231 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 232 | glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 233 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); 234 | // Store our identifier 235 | io.Fonts->TexID = (void *)(intptr_t)fontTexture_; 236 | // Restore state 237 | glBindTexture(GL_TEXTURE_2D, static_cast(lastTexture)); 238 | } 239 | 240 | bool ImGui_ImplSdlGlad_CreateDeviceObjects() { 241 | // Backup GL state 242 | GLint lastTexture, lastArrayBuffer, lastVertexArray; 243 | glGetIntegerv(GL_TEXTURE_BINDING_2D, &lastTexture); 244 | glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &lastArrayBuffer); 245 | glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &lastVertexArray); 246 | 247 | constexpr const GLchar *vertexShader = 248 | "#version 330\n" 249 | "uniform mat4 Projection;\n" 250 | "in vec2 Position;\n" 251 | "in vec2 UV;\n" 252 | "in vec4 Color;\n" 253 | "out vec2 Frag_UV;\n" 254 | "out vec4 Frag_Color;\n" 255 | "void main()\n" 256 | "{\n" 257 | " Frag_UV = UV;\n" 258 | " Frag_Color = Color;\n" 259 | " gl_Position = Projection * vec4(Position.xy,0,1);\n" 260 | "}\n"; 261 | 262 | constexpr const GLchar *fragmentShader = 263 | "#version 330\n" 264 | "uniform sampler2D Texture;\n" 265 | "in vec2 Frag_UV;\n" 266 | "in vec4 Frag_Color;\n" 267 | "out vec4 Out_Color;\n" 268 | "void main()\n" 269 | "{\n" 270 | " Out_Color = Frag_Color * texture( Texture, Frag_UV.st);\n" 271 | "}\n"; 272 | 273 | shaderHandle_ = glCreateProgram(); 274 | vertHandle_ = glCreateShader(GL_VERTEX_SHADER); 275 | fragHandle_ = glCreateShader(GL_FRAGMENT_SHADER); 276 | glShaderSource(vertHandle_, 1, &vertexShader, 0); 277 | glShaderSource(fragHandle_, 1, &fragmentShader, 0); 278 | glCompileShader(vertHandle_); 279 | glCompileShader(fragHandle_); 280 | glAttachShader(shaderHandle_, vertHandle_); 281 | glAttachShader(shaderHandle_, fragHandle_); 282 | glLinkProgram(shaderHandle_); 283 | 284 | attribLocationTex_ = glGetUniformLocation(shaderHandle_, "Texture"); 285 | attribLocationProj_ = glGetUniformLocation(shaderHandle_, "Projection"); 286 | attribLocationPos_ = glGetAttribLocation(shaderHandle_, "Position"); 287 | attribLocationUV_ = glGetAttribLocation(shaderHandle_, "UV"); 288 | attribLocationCol_ = glGetAttribLocation(shaderHandle_, "Color"); 289 | 290 | glGenBuffers(1, &vboHandle_); 291 | glGenBuffers(1, &elementsHandle_); 292 | 293 | glGenVertexArrays(1, &vaoHandle_); 294 | glBindVertexArray(vaoHandle_); 295 | glBindBuffer(GL_ARRAY_BUFFER, vboHandle_); 296 | glEnableVertexAttribArray(static_cast(attribLocationPos_)); 297 | glEnableVertexAttribArray(static_cast(attribLocationUV_)); 298 | glEnableVertexAttribArray(static_cast(attribLocationCol_)); 299 | 300 | #define OFFSETOF(TYPE, ELEMENT) ((size_t) & (((TYPE *)0)->ELEMENT)) 301 | glVertexAttribPointer(static_cast(attribLocationPos_), 2, GL_FLOAT, GL_FALSE, 302 | sizeof(ImDrawVert), (GLvoid *)OFFSETOF(ImDrawVert, pos)); 303 | glVertexAttribPointer(static_cast(attribLocationUV_), 2, GL_FLOAT, GL_FALSE, 304 | sizeof(ImDrawVert), (GLvoid *)OFFSETOF(ImDrawVert, uv)); 305 | glVertexAttribPointer(static_cast(attribLocationCol_), 4, GL_UNSIGNED_BYTE, GL_TRUE, 306 | sizeof(ImDrawVert), (GLvoid *)OFFSETOF(ImDrawVert, col)); 307 | #undef OFFSETOF 308 | 309 | ImGui_ImplSdlBinding_CreateFontsTexture(); 310 | 311 | // Restore modified GL state 312 | glBindTexture(GL_TEXTURE_2D, static_cast(lastTexture)); 313 | glBindBuffer(GL_ARRAY_BUFFER, static_cast(lastArrayBuffer)); 314 | glBindVertexArray(static_cast(lastVertexArray)); 315 | return true; 316 | } 317 | 318 | void ImGui_ImplSdlGlad_InvalidateDeviceObjects() { 319 | if (vaoHandle_) 320 | glDeleteVertexArrays(1, &vaoHandle_); 321 | if (vboHandle_) 322 | glDeleteBuffers(1, &vboHandle_); 323 | if (elementsHandle_) 324 | glDeleteBuffers(1, &elementsHandle_); 325 | vaoHandle_ = vboHandle_ = elementsHandle_ = 0; 326 | 327 | if (shaderHandle_ && vertHandle_) 328 | glDetachShader(shaderHandle_, vertHandle_); 329 | if (vertHandle_) 330 | glDeleteShader(vertHandle_); 331 | vertHandle_ = 0; 332 | 333 | if (shaderHandle_ && fragHandle_) 334 | glDetachShader(shaderHandle_, fragHandle_); 335 | if (fragHandle_) 336 | glDeleteShader(fragHandle_); 337 | fragHandle_ = 0; 338 | 339 | if (shaderHandle_) 340 | glDeleteProgram(shaderHandle_); 341 | shaderHandle_ = 0; 342 | 343 | if (fontTexture_) { 344 | glDeleteTextures(1, &fontTexture_); 345 | ImGui::GetIO().Fonts->TexID = 0; 346 | fontTexture_ = 0; 347 | } 348 | } 349 | 350 | bool ImGui_ImplSdlGlad_Init(SDL_Window *window) { 351 | 352 | ImGuiIO &io = ImGui::GetIO(); 353 | io.KeyMap[ImGuiKey_Tab] = SDLK_TAB; 354 | io.KeyMap[ImGuiKey_LeftArrow] = SDL_SCANCODE_LEFT; 355 | io.KeyMap[ImGuiKey_RightArrow] = SDL_SCANCODE_RIGHT; 356 | io.KeyMap[ImGuiKey_UpArrow] = SDL_SCANCODE_UP; 357 | io.KeyMap[ImGuiKey_DownArrow] = SDL_SCANCODE_DOWN; 358 | io.KeyMap[ImGuiKey_PageUp] = SDL_SCANCODE_PAGEUP; 359 | io.KeyMap[ImGuiKey_PageDown] = SDL_SCANCODE_PAGEDOWN; 360 | io.KeyMap[ImGuiKey_Home] = SDL_SCANCODE_HOME; 361 | io.KeyMap[ImGuiKey_End] = SDL_SCANCODE_END; 362 | io.KeyMap[ImGuiKey_Delete] = SDLK_DELETE; 363 | io.KeyMap[ImGuiKey_Backspace] = SDLK_BACKSPACE; 364 | io.KeyMap[ImGuiKey_Enter] = SDLK_RETURN; 365 | io.KeyMap[ImGuiKey_Escape] = SDLK_ESCAPE; 366 | io.KeyMap[ImGuiKey_A] = SDLK_a; 367 | io.KeyMap[ImGuiKey_C] = SDLK_c; 368 | io.KeyMap[ImGuiKey_V] = SDLK_v; 369 | io.KeyMap[ImGuiKey_X] = SDLK_x; 370 | io.KeyMap[ImGuiKey_Y] = SDLK_y; 371 | io.KeyMap[ImGuiKey_Z] = SDLK_z; 372 | 373 | io.RenderDrawListsFn = ImGui_ImplSdlGlad_RenderDrawLists; 374 | io.SetClipboardTextFn = ImGui_ImplSdlGlad_SetClipboardText; 375 | io.GetClipboardTextFn = ImGui_ImplSdlGlad_GetClipboardText; 376 | io.ClipboardUserData = nullptr; 377 | 378 | #ifdef _WIN32 379 | SDL_SysWMinfo wmInfo; 380 | SDL_VERSION(&wmInfo.version); 381 | SDL_GetWindowWMInfo(window, &wmInfo); 382 | io.ImeWindowHandle = wmInfo.info.win.window; 383 | #else 384 | (void)window; 385 | #endif 386 | return true; 387 | } 388 | 389 | void ImGui_ImplSdlGlad_Shutdown() { 390 | ImGui_ImplSdlGlad_InvalidateDeviceObjects(); 391 | ImGui::Shutdown(); 392 | } 393 | 394 | void ImGui_ImplSdlGlad_NewFrame(SDL_Window *window) { 395 | if (!fontTexture_) 396 | ImGui_ImplSdlGlad_CreateDeviceObjects(); 397 | 398 | // Set up display size (to accommodate for window resizing) 399 | ImGuiIO &io = ImGui::GetIO(); 400 | int width, height, displayWidth, displayHeight; 401 | SDL_GetWindowSize(window, &width, &height); 402 | SDL_GL_GetDrawableSize(window, &displayWidth, &displayHeight); 403 | io.DisplaySize = ImVec2((float)width, (float)height); 404 | io.DisplayFramebufferScale = ImVec2(width > 0 ? ((float)displayWidth / width) : 0, 405 | height > 0 ? ((float)displayHeight / height) : 0); 406 | 407 | // Set up time step 408 | Uint32 time = SDL_GetTicks(); 409 | double currentTime = time / 1000.0; 410 | io.DeltaTime = time_ > 0.0 ? static_cast(currentTime - time_) : (float)(1.0f / 60.0f); 411 | time_ = currentTime; 412 | 413 | // Set up inputs 414 | int mouseX, mouseY; 415 | Uint32 mouseMask = SDL_GetMouseState(&mouseX, &mouseY); 416 | if (SDL_GetWindowFlags(window) & SDL_WINDOW_MOUSE_FOCUS) 417 | io.MousePos = ImVec2((float)mouseX, (float)mouseY); 418 | else 419 | io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); 420 | 421 | // If a mouse press event came, always pass it 422 | // as "mouse held this frame", so we don't miss 423 | // click-release events that are shorter than 1 424 | // frame. 425 | io.MouseDown[0] = mousePressed_[0] || (mouseMask & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0; 426 | io.MouseDown[1] = mousePressed_[1] || (mouseMask & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0; 427 | io.MouseDown[2] = mousePressed_[2] || (mouseMask & SDL_BUTTON(SDL_BUTTON_MIDDLE)) != 0; 428 | mousePressed_[0] = mousePressed_[1] = mousePressed_[2] = false; 429 | io.MouseWheel = mouseWheel_; 430 | mouseWheel_ = 0.0f; 431 | 432 | // Hide OS mouse cursor if ImGui is drawing it 433 | SDL_ShowCursor(io.MouseDrawCursor ? 0 : 1); 434 | 435 | // This call will update the io.WantCaptureMouse, io.WantCaptureKeyboard flag 436 | // that you can use to dispatch inputs to your application. 437 | ImGui::NewFrame(); 438 | } 439 | -------------------------------------------------------------------------------- /src/imgui/src/stb_rect_pack.h: -------------------------------------------------------------------------------- 1 | // stb_rect_pack.h - v0.10 - public domain - rectangle packing 2 | // Sean Barrett 2014 3 | // 4 | // Useful for e.g. packing rectangular textures into an atlas. 5 | // Does not do rotation. 6 | // 7 | // Not necessarily the awesomest packing method, but better than 8 | // the totally naive one in stb_truetype (which is primarily what 9 | // this is meant to replace). 10 | // 11 | // Has only had a few tests run, may have issues. 12 | // 13 | // More docs to come. 14 | // 15 | // No memory allocations; uses qsort() and assert() from stdlib. 16 | // Can override those by defining STBRP_SORT and STBRP_ASSERT. 17 | // 18 | // This library currently uses the Skyline Bottom-Left algorithm. 19 | // 20 | // Please note: better rectangle packers are welcome! Please 21 | // implement them to the same API, but with a different init 22 | // function. 23 | // 24 | // Credits 25 | // 26 | // Library 27 | // Sean Barrett 28 | // Minor features 29 | // Martins Mozeiko 30 | // Bugfixes / warning fixes 31 | // Jeremy Jaussaud 32 | // 33 | // Version history: 34 | // 35 | // 0.10 (2016-10-25) remove cast-away-const to avoid warnings 36 | // 0.09 (2016-08-27) fix compiler warnings 37 | // 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) 38 | // 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) 39 | // 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort 40 | // 0.05: added STBRP_ASSERT to allow replacing assert 41 | // 0.04: fixed minor bug in STBRP_LARGE_RECTS support 42 | // 0.01: initial release 43 | // 44 | // LICENSE 45 | // 46 | // This software is dual-licensed to the public domain and under the following 47 | // license: you are granted a perpetual, irrevocable license to copy, modify, 48 | // publish, and distribute this file as you see fit. 49 | 50 | ////////////////////////////////////////////////////////////////////////////// 51 | // 52 | // INCLUDE SECTION 53 | // 54 | 55 | #ifndef STB_INCLUDE_STB_RECT_PACK_H 56 | #define STB_INCLUDE_STB_RECT_PACK_H 57 | 58 | #define STB_RECT_PACK_VERSION 1 59 | 60 | #ifdef STBRP_STATIC 61 | #define STBRP_DEF static 62 | #else 63 | #define STBRP_DEF extern 64 | #endif 65 | 66 | #ifdef __cplusplus 67 | extern "C" { 68 | #endif 69 | 70 | typedef struct stbrp_context stbrp_context; 71 | typedef struct stbrp_node stbrp_node; 72 | typedef struct stbrp_rect stbrp_rect; 73 | 74 | #ifdef STBRP_LARGE_RECTS 75 | typedef int stbrp_coord; 76 | #else 77 | typedef unsigned short stbrp_coord; 78 | #endif 79 | 80 | STBRP_DEF void stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); 81 | // Assign packed locations to rectangles. The rectangles are of type 82 | // 'stbrp_rect' defined below, stored in the array 'rects', and there 83 | // are 'num_rects' many of them. 84 | // 85 | // Rectangles which are successfully packed have the 'was_packed' flag 86 | // set to a non-zero value and 'x' and 'y' store the minimum location 87 | // on each axis (i.e. bottom-left in cartesian coordinates, top-left 88 | // if you imagine y increasing downwards). Rectangles which do not fit 89 | // have the 'was_packed' flag set to 0. 90 | // 91 | // You should not try to access the 'rects' array from another thread 92 | // while this function is running, as the function temporarily reorders 93 | // the array while it executes. 94 | // 95 | // To pack into another rectangle, you need to call stbrp_init_target 96 | // again. To continue packing into the same rectangle, you can call 97 | // this function again. Calling this multiple times with multiple rect 98 | // arrays will probably produce worse packing results than calling it 99 | // a single time with the full rectangle array, but the option is 100 | // available. 101 | 102 | struct stbrp_rect 103 | { 104 | // reserved for your use: 105 | int id; 106 | 107 | // input: 108 | stbrp_coord w, h; 109 | 110 | // output: 111 | stbrp_coord x, y; 112 | int was_packed; // non-zero if valid packing 113 | 114 | }; // 16 bytes, nominally 115 | 116 | 117 | STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); 118 | // Initialize a rectangle packer to: 119 | // pack a rectangle that is 'width' by 'height' in dimensions 120 | // using temporary storage provided by the array 'nodes', which is 'num_nodes' long 121 | // 122 | // You must call this function every time you start packing into a new target. 123 | // 124 | // There is no "shutdown" function. The 'nodes' memory must stay valid for 125 | // the following stbrp_pack_rects() call (or calls), but can be freed after 126 | // the call (or calls) finish. 127 | // 128 | // Note: to guarantee best results, either: 129 | // 1. make sure 'num_nodes' >= 'width' 130 | // or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' 131 | // 132 | // If you don't do either of the above things, widths will be quantized to multiples 133 | // of small integers to guarantee the algorithm doesn't run out of temporary storage. 134 | // 135 | // If you do #2, then the non-quantized algorithm will be used, but the algorithm 136 | // may run out of temporary storage and be unable to pack some rectangles. 137 | 138 | STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); 139 | // Optionally call this function after init but before doing any packing to 140 | // change the handling of the out-of-temp-memory scenario, described above. 141 | // If you call init again, this will be reset to the default (false). 142 | 143 | 144 | STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); 145 | // Optionally select which packing heuristic the library should use. Different 146 | // heuristics will produce better/worse results for different data sets. 147 | // If you call init again, this will be reset to the default. 148 | 149 | enum 150 | { 151 | STBRP_HEURISTIC_Skyline_default=0, 152 | STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, 153 | STBRP_HEURISTIC_Skyline_BF_sortHeight 154 | }; 155 | 156 | 157 | ////////////////////////////////////////////////////////////////////////////// 158 | // 159 | // the details of the following structures don't matter to you, but they must 160 | // be visible so you can handle the memory allocations for them 161 | 162 | struct stbrp_node 163 | { 164 | stbrp_coord x,y; 165 | stbrp_node *next; 166 | }; 167 | 168 | struct stbrp_context 169 | { 170 | int width; 171 | int height; 172 | int align; 173 | int init_mode; 174 | int heuristic; 175 | int num_nodes; 176 | stbrp_node *active_head; 177 | stbrp_node *free_head; 178 | stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' 179 | }; 180 | 181 | #ifdef __cplusplus 182 | } 183 | #endif 184 | 185 | #endif 186 | 187 | ////////////////////////////////////////////////////////////////////////////// 188 | // 189 | // IMPLEMENTATION SECTION 190 | // 191 | 192 | #ifdef STB_RECT_PACK_IMPLEMENTATION 193 | #ifndef STBRP_SORT 194 | #include 195 | #define STBRP_SORT qsort 196 | #endif 197 | 198 | #ifndef STBRP_ASSERT 199 | #include 200 | #define STBRP_ASSERT assert 201 | #endif 202 | 203 | #ifdef _MSC_VER 204 | #define STBRP__NOTUSED(v) (void)(v) 205 | #else 206 | #define STBRP__NOTUSED(v) (void)sizeof(v) 207 | #endif 208 | 209 | enum 210 | { 211 | STBRP__INIT_skyline = 1 212 | }; 213 | 214 | STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) 215 | { 216 | switch (context->init_mode) { 217 | case STBRP__INIT_skyline: 218 | STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); 219 | context->heuristic = heuristic; 220 | break; 221 | default: 222 | STBRP_ASSERT(0); 223 | } 224 | } 225 | 226 | STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) 227 | { 228 | if (allow_out_of_mem) 229 | // if it's ok to run out of memory, then don't bother aligning them; 230 | // this gives better packing, but may fail due to OOM (even though 231 | // the rectangles easily fit). @TODO a smarter approach would be to only 232 | // quantize once we've hit OOM, then we could get rid of this parameter. 233 | context->align = 1; 234 | else { 235 | // if it's not ok to run out of memory, then quantize the widths 236 | // so that num_nodes is always enough nodes. 237 | // 238 | // I.e. num_nodes * align >= width 239 | // align >= width / num_nodes 240 | // align = ceil(width/num_nodes) 241 | 242 | context->align = (context->width + context->num_nodes-1) / context->num_nodes; 243 | } 244 | } 245 | 246 | STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) 247 | { 248 | int i; 249 | #ifndef STBRP_LARGE_RECTS 250 | STBRP_ASSERT(width <= 0xffff && height <= 0xffff); 251 | #endif 252 | 253 | for (i=0; i < num_nodes-1; ++i) 254 | nodes[i].next = &nodes[i+1]; 255 | nodes[i].next = NULL; 256 | context->init_mode = STBRP__INIT_skyline; 257 | context->heuristic = STBRP_HEURISTIC_Skyline_default; 258 | context->free_head = &nodes[0]; 259 | context->active_head = &context->extra[0]; 260 | context->width = width; 261 | context->height = height; 262 | context->num_nodes = num_nodes; 263 | stbrp_setup_allow_out_of_mem(context, 0); 264 | 265 | // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) 266 | context->extra[0].x = 0; 267 | context->extra[0].y = 0; 268 | context->extra[0].next = &context->extra[1]; 269 | context->extra[1].x = (stbrp_coord) width; 270 | #ifdef STBRP_LARGE_RECTS 271 | context->extra[1].y = (1<<30); 272 | #else 273 | context->extra[1].y = 65535; 274 | #endif 275 | context->extra[1].next = NULL; 276 | } 277 | 278 | // find minimum y position if it starts at x1 279 | static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) 280 | { 281 | stbrp_node *node = first; 282 | int x1 = x0 + width; 283 | int min_y, visited_width, waste_area; 284 | 285 | STBRP__NOTUSED(c); 286 | 287 | STBRP_ASSERT(first->x <= x0); 288 | 289 | #if 0 290 | // skip in case we're past the node 291 | while (node->next->x <= x0) 292 | ++node; 293 | #else 294 | STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency 295 | #endif 296 | 297 | STBRP_ASSERT(node->x <= x0); 298 | 299 | min_y = 0; 300 | waste_area = 0; 301 | visited_width = 0; 302 | while (node->x < x1) { 303 | if (node->y > min_y) { 304 | // raise min_y higher. 305 | // we've accounted for all waste up to min_y, 306 | // but we'll now add more waste for everything we've visted 307 | waste_area += visited_width * (node->y - min_y); 308 | min_y = node->y; 309 | // the first time through, visited_width might be reduced 310 | if (node->x < x0) 311 | visited_width += node->next->x - x0; 312 | else 313 | visited_width += node->next->x - node->x; 314 | } else { 315 | // add waste area 316 | int under_width = node->next->x - node->x; 317 | if (under_width + visited_width > width) 318 | under_width = width - visited_width; 319 | waste_area += under_width * (min_y - node->y); 320 | visited_width += under_width; 321 | } 322 | node = node->next; 323 | } 324 | 325 | *pwaste = waste_area; 326 | return min_y; 327 | } 328 | 329 | typedef struct 330 | { 331 | int x,y; 332 | stbrp_node **prev_link; 333 | } stbrp__findresult; 334 | 335 | static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) 336 | { 337 | int best_waste = (1<<30), best_x, best_y = (1 << 30); 338 | stbrp__findresult fr; 339 | stbrp_node **prev, *node, *tail, **best = NULL; 340 | 341 | // align to multiple of c->align 342 | width = (width + c->align - 1); 343 | width -= width % c->align; 344 | STBRP_ASSERT(width % c->align == 0); 345 | 346 | node = c->active_head; 347 | prev = &c->active_head; 348 | while (node->x + width <= c->width) { 349 | int y,waste; 350 | y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); 351 | if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL 352 | // bottom left 353 | if (y < best_y) { 354 | best_y = y; 355 | best = prev; 356 | } 357 | } else { 358 | // best-fit 359 | if (y + height <= c->height) { 360 | // can only use it if it first vertically 361 | if (y < best_y || (y == best_y && waste < best_waste)) { 362 | best_y = y; 363 | best_waste = waste; 364 | best = prev; 365 | } 366 | } 367 | } 368 | prev = &node->next; 369 | node = node->next; 370 | } 371 | 372 | best_x = (best == NULL) ? 0 : (*best)->x; 373 | 374 | // if doing best-fit (BF), we also have to try aligning right edge to each node position 375 | // 376 | // e.g, if fitting 377 | // 378 | // ____________________ 379 | // |____________________| 380 | // 381 | // into 382 | // 383 | // | | 384 | // | ____________| 385 | // |____________| 386 | // 387 | // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned 388 | // 389 | // This makes BF take about 2x the time 390 | 391 | if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { 392 | tail = c->active_head; 393 | node = c->active_head; 394 | prev = &c->active_head; 395 | // find first node that's admissible 396 | while (tail->x < width) 397 | tail = tail->next; 398 | while (tail) { 399 | int xpos = tail->x - width; 400 | int y,waste; 401 | STBRP_ASSERT(xpos >= 0); 402 | // find the left position that matches this 403 | while (node->next->x <= xpos) { 404 | prev = &node->next; 405 | node = node->next; 406 | } 407 | STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); 408 | y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); 409 | if (y + height < c->height) { 410 | if (y <= best_y) { 411 | if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { 412 | best_x = xpos; 413 | STBRP_ASSERT(y <= best_y); 414 | best_y = y; 415 | best_waste = waste; 416 | best = prev; 417 | } 418 | } 419 | } 420 | tail = tail->next; 421 | } 422 | } 423 | 424 | fr.prev_link = best; 425 | fr.x = best_x; 426 | fr.y = best_y; 427 | return fr; 428 | } 429 | 430 | static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) 431 | { 432 | // find best position according to heuristic 433 | stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); 434 | stbrp_node *node, *cur; 435 | 436 | // bail if: 437 | // 1. it failed 438 | // 2. the best node doesn't fit (we don't always check this) 439 | // 3. we're out of memory 440 | if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { 441 | res.prev_link = NULL; 442 | return res; 443 | } 444 | 445 | // on success, create new node 446 | node = context->free_head; 447 | node->x = (stbrp_coord) res.x; 448 | node->y = (stbrp_coord) (res.y + height); 449 | 450 | context->free_head = node->next; 451 | 452 | // insert the new node into the right starting point, and 453 | // let 'cur' point to the remaining nodes needing to be 454 | // stiched back in 455 | 456 | cur = *res.prev_link; 457 | if (cur->x < res.x) { 458 | // preserve the existing one, so start testing with the next one 459 | stbrp_node *next = cur->next; 460 | cur->next = node; 461 | cur = next; 462 | } else { 463 | *res.prev_link = node; 464 | } 465 | 466 | // from here, traverse cur and free the nodes, until we get to one 467 | // that shouldn't be freed 468 | while (cur->next && cur->next->x <= res.x + width) { 469 | stbrp_node *next = cur->next; 470 | // move the current node to the free list 471 | cur->next = context->free_head; 472 | context->free_head = cur; 473 | cur = next; 474 | } 475 | 476 | // stitch the list back in 477 | node->next = cur; 478 | 479 | if (cur->x < res.x + width) 480 | cur->x = (stbrp_coord) (res.x + width); 481 | 482 | #ifdef _DEBUG 483 | cur = context->active_head; 484 | while (cur->x < context->width) { 485 | STBRP_ASSERT(cur->x < cur->next->x); 486 | cur = cur->next; 487 | } 488 | STBRP_ASSERT(cur->next == NULL); 489 | 490 | { 491 | stbrp_node *L1 = NULL, *L2 = NULL; 492 | int count=0; 493 | cur = context->active_head; 494 | while (cur) { 495 | L1 = cur; 496 | cur = cur->next; 497 | ++count; 498 | } 499 | cur = context->free_head; 500 | while (cur) { 501 | L2 = cur; 502 | cur = cur->next; 503 | ++count; 504 | } 505 | STBRP_ASSERT(count == context->num_nodes+2); 506 | } 507 | #endif 508 | 509 | return res; 510 | } 511 | 512 | static int rect_height_compare(const void *a, const void *b) 513 | { 514 | const stbrp_rect *p = (const stbrp_rect *) a; 515 | const stbrp_rect *q = (const stbrp_rect *) b; 516 | if (p->h > q->h) 517 | return -1; 518 | if (p->h < q->h) 519 | return 1; 520 | return (p->w > q->w) ? -1 : (p->w < q->w); 521 | } 522 | 523 | static int rect_width_compare(const void *a, const void *b) 524 | { 525 | const stbrp_rect *p = (const stbrp_rect *) a; 526 | const stbrp_rect *q = (const stbrp_rect *) b; 527 | if (p->w > q->w) 528 | return -1; 529 | if (p->w < q->w) 530 | return 1; 531 | return (p->h > q->h) ? -1 : (p->h < q->h); 532 | } 533 | 534 | static int rect_original_order(const void *a, const void *b) 535 | { 536 | const stbrp_rect *p = (const stbrp_rect *) a; 537 | const stbrp_rect *q = (const stbrp_rect *) b; 538 | return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); 539 | } 540 | 541 | #ifdef STBRP_LARGE_RECTS 542 | #define STBRP__MAXVAL 0xffffffff 543 | #else 544 | #define STBRP__MAXVAL 0xffff 545 | #endif 546 | 547 | STBRP_DEF void stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) 548 | { 549 | int i; 550 | 551 | // we use the 'was_packed' field internally to allow sorting/unsorting 552 | for (i=0; i < num_rects; ++i) { 553 | rects[i].was_packed = i; 554 | #ifndef STBRP_LARGE_RECTS 555 | STBRP_ASSERT(rects[i].w <= 0xffff && rects[i].h <= 0xffff); 556 | #endif 557 | } 558 | 559 | // sort according to heuristic 560 | STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); 561 | 562 | for (i=0; i < num_rects; ++i) { 563 | if (rects[i].w == 0 || rects[i].h == 0) { 564 | rects[i].x = rects[i].y = 0; // empty rect needs no space 565 | } else { 566 | stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); 567 | if (fr.prev_link) { 568 | rects[i].x = (stbrp_coord) fr.x; 569 | rects[i].y = (stbrp_coord) fr.y; 570 | } else { 571 | rects[i].x = rects[i].y = STBRP__MAXVAL; 572 | } 573 | } 574 | } 575 | 576 | // unsort 577 | STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); 578 | 579 | // set was_packed flags 580 | for (i=0; i < num_rects; ++i) 581 | rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); 582 | } 583 | #endif 584 | --------------------------------------------------------------------------------