├── .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 |
--------------------------------------------------------------------------------