Version 0.3 - Initial Proposal
Prepared by Šimon Tupý 4.C
SSPŠaG
August 10, 2022
- 1 Introduction
- 1.1 Document Purpose
- 1.2 Definitions, Acronyms and Abbreviations
- 1.3 Target Audience
- 1.4 Additional Information
- 1.4.1 Navier-Stokes Equations
- 1.4.2 Rules
- 1.4.3 Eulerian simulation
- 1.4.4 Lagrangian simulation
- 1.5 Contacts
- 1.6 References
- 2 Product Overview
- 2.1 Product Perspective
- 2.1.1 SPH simulation
- 2.1.1 DFSPH simulation
- 2.2 Product Functions
- 2.3 User Groups
- 2.3.1 GPU compute newcomers
- 2.4 Product Environment
- 2.5 User Environment
- 2.6 Limitations and Implementation Details
- 2.7 Assumptions and Dependencies
- 2.7.1 Assumptions
- 2.7.2 Dependencies
- 2.1 Product Perspective
- 3 Interface Requirements
- 3.1 User Interface
- 3.1.1 Viewport Window
- 3.1.1.1 Saving and Loading Scenes
- 3.1.1.2 Camera Controls
- 3.1.2 Profiler Window
- 3.1.2.1 Frame Time Graph
- 3.1.2.2 Frame Time Counter
- 3.1.2.3 Renderer Statistics
- 3.1.3 Scene Hierarchy Window
- 3.1.3.1 Entity List
- 3.1.4 README Window
- 3.1.1 Viewport Window
- 3.2 Hardware Interface
- 3.3 Software Interface
- 3.1 User Interface
- 4 System properties
- 4.1 ECS-Based Scene System
- 4.1.1 Description and Importance
- 4.1.2 Inputs and Outputs
- 4.1.3 Function Specification
- 4.2 Event System
- 4.2.1 Description and Importance
- 4.2.2 Inputs and Outputs
- 4.2.3 Function Specification
- 4.3 Application
- 4.3.1 Description and Importance
- 4.3.2 Inputs and Outputs
- 4.3.3 Function Specification
- 4.4 Ref
- 4.4.1 Description and Importance
- 4.4.2 Inputs and Outputs
- 4.4.3 Function Specification
- 4.5 Debugger
- 4.5.1 Description and Importance
- 4.5.2 Inputs and Outputs
- 4.5.3 Function Specification
- 4.6 Editor
- 4.6.1 Description and Importance
- 4.6.2 Inputs and Outputs
- 4.6.3 Function Specification
- 4.7 Renderer
- 4.7.1 Buffers
- 4.7.1.1 Frame Buffer
- 4.7.1.1.1 Description and Importance
- 4.7.1.1.2 Inputs and Outputs
- 4.7.1.1.3 Function Specification
- 4.7.1.2 Vertex Buffer
- 4.7.1.2.1 Description and Importance
- 4.7.1.2.2 Inputs and Outputs
- 4.7.1.2.3 Function Specification
- 4.7.1.3 Index Buffer
- 4.7.1.3.1 Description and Importance
- 4.7.1.3.2 Inputs and Outputs
- 4.7.1.3.3 Function Specification
- 4.7.1.4 Uniform Buffer
- 4.7.1.4.1 Description and Importance
- 4.7.1.4.2 Inputs and Outputs
- 4.7.1.4.3 Function Specification
- 4.7.1.1 Frame Buffer
- 4.7.2 Triangle Mesh
- 4.7.2.1 Description and Importance
- 4.7.2.2 Inputs and Outputs
- 4.7.2.3 Function Specification
- 4.7.3 Camera
- 4.7.3.1 Description and Importance
- 4.7.3.2 Inputs and Outputs
- 4.7.3.3 Function Specification
- 4.7.4 Material
- 4.7.4.1 Description and Importance
- 4.7.4.2 Inputs and Outputs
- 4.7.4.3 Function Specification
- 4.7.5 Shader
- 4.7.5.1 Description and Importance
- 4.7.5.2 Inputs and Outputs
- 4.7.5.3 Function Specification
- 4.7.6 Vertex Array Object
- 4.7.6.1 Description and Importance
- 4.7.6.2 Inputs and Outputs
- 4.7.6.3 Function Specification
- 4.7.1 Buffers
- 4.1 ECS-Based Scene System
- 5 Non-Functional Requirements
- 5.1 Performance
- 5.2 Security
- 5.3 Reliability
- 5.4 Project Documentation
- 5.5 User Documentation
The purpose of this document is to present a detailed description of the application. It will explain its purposes, features, interface, and what the application and its accompanying systems will do.
Term | Definition |
---|---|
Software Requirements Specification | A document that completely describes all of the functions of a proposed system and the constraints under which it must operate. For example, this document. |
CFD | Computational fluid dynamics - the use of applied mathematics, physics and computational software to visualize how a gas or liquid flows, as well as how it affects objects as it flows past. |
Advection | The evolution of mass forwards in time using a velocity field. |
Convection | The process of transferring heat via circulation of movement of the fluid. |
Lagrangian (methods) | Methods that move a fluid volume (ie. by using advection), most commonly used with particles. |
Eulerian (methods) | Methods utilizing a grid-based approach to fluid simulation. |
SPH | Smoothed particle hydrodynamics - the most common form of CFD. |
DFSPH | Divergence-free SPH. |
Device | A device capable of running CUDA code (ie. an Nvidia GPU) |
Host | The CPU and CPU related code |
Key | A keyboard key or mouse button declaration. |
This document is intended for both stakeholders and the developers of the application.
Fluids are governed by the incompressible Navier-Stokes equations, which look like this:
Equation | Description |
---|---|
Velocity of the fluid |
|
Density of the fluid |
|
Pressure exerted by the fluid |
|
Gravitational force applied to the fluid |
|
Viscosity of the fluid. |
There are also several rules the fluid simulation has to abide by:
Rule | Description |
---|---|
Conservation of Energy | The amount of energy in our fluid must remain the same over time (or at the very least not deviate by a large margin). |
Conservation of Mass | The total mass of our fluid must remain the same over time (or at the very least not deviate by a large margin). |
Balance of Momentum | The momentum of the fluid can only be changed by external force. |
Incompressibility | The fluid's volume should never change on its own. ( |
This method revolves around a global grid containing and representing the fluid volume, forces, density, pressure and viscosity. It provides an easy and performant way of computing the pressure of a given cell. For non-sparse volumes the sampling is also constant, due to array access times. The simulation also provides great stability at relatively low cost.
This method represents the fluid volume as particles - this provides us with an easy method of calculating advection, however, the method, by itself isn't very stable and needs small timesteps, increasing its computational cost.
E-mail: simontupy64@gmail.com
- Fluid Simulation Terminology Some Terms and Equations (https://www.cs.purdue.edu/cgvlab/courses/434/434Spring2022/lectures/CS434-14-Fluids.pdf)
- Review of smoothed particle hydrodynamics - Journals (https://royalsocietypublishing.org/doi/10.1098/rspa.2019.0801)
- The Rust Graphics Meetup (https://github.com/gfx-rs/meetup)
- FLIPViscosity3D (https://github.com/rlguy/FLIPViscosity3D)
This piece of software will be simple fluid simulation tool utilizing GPU-based CFD. The user will by provided with a simple, minimal interface that will enable them to manipulate the given scene, load, save and create new scenes, toggle various simulation parameters and visualize the given simulation. As noted in the initial proposal, the project will contain at least one example of a fluid simulation, however, at the current rate of progress, it is expected that two instances will be implemented.
The first (and most basic) fluid simulation that will be implemented will be a simple SPH simulation, that will provide the users with basic knowledge of CFD. This implementation will utilize both Lagrangian and Eulerian methods of simulation (this way we can get the best of both worlds and increase the overall performance, albeit at the cost of simulation accuracy).
A more advanced SPH implementation with a dedicated divergence solver. This method provides us with improved stability at a negligible cost to performance. Since DFSPH is suitable for high-viscosity simulations a modern viscosity solver will also be implemented.
The main goal of this project is to enable users to quickly prototype and create, at this point in development, small-scale fluid simulations. Furthermore, the project will be used in the future as a showcase-style application for different CFD methods.
The project can serve as a (hopefully) decent learning tool for programmers entering the world of GPU compute and CUDA showcasing different methods of manipulating memory on the device (ie. deep copying) or working with kernels. The project will provide a concise explanation of most fluid simulation methods and CUDA related code.
The application will run on any system capable of running CUDA (the target system has to have an Nvidia GPU, and be considered as a CUDA-compliant device) and compiling the project.
The application will provide the user with a simple, single-window interface.
The main limitation of the application will be performance, which directly correlates to the amount of CUDA cores and memory speed of the target device. For the sake of keeping the simulation running in real (or semi-real) time all the necessary data (particle positions, velocity, density, viscosity etc.) will be kept on the device in the format of buffers - this means that the simulation size is directly capped by the amount device memory.
It is expected that the user will be able to download, and get the application running by using the Getting up and running section of the readme file.
The list of currently used dependencies can be found here. Every dependency, excluding the CUDA toolkit and Vulkan SDK will be installed when the user clones the repository.
Even though OpenCL provides a unified development environment for creating applications utilizing the power of GPU compute CUDA was chosen due to its (more often than not) superior performance and due to the fact that glm (the math library that is used across the project) natively supports it, thus providing us with very useful and performant functions for vector calculations - pushing the performance even further.
The user will be provided with a simple, window-based interface, that will enable them to profile, save, load and edit scenes. In the beginning, most of the operations will be located inside simple context menus, however, later on a proper UI system will be implemented. The interface can be freely edited and transformed to the user's liking thanks to the window-panel system. The UI will be rendered using the OpenGL branch of ImGui.
The viewport window contains an OpenGL framebuffer texture, that displays the current scene.
To save and load scenes the user can right click the viewport and select either the "Save Scene" or "Load Scene" option. The save scene option also provides a simple shortcut - Ctrl + S - which will save the currently loaded scene, if a default filepath is provided. Scenes will first be saved in the JSON format.
The built-in arc ball camera has three movement functions: orbit (MMB), pan (MMB+Shift) and zoom (Scroll)
The profiler window displays useful information about the current scene.
The frame time graph is a basic graph UI component that displays the time each frame took to compute. The graph is comprised of many rectangular shapes, that are scaled by the current delta time value.
Since the frame time graph by itself does not provide exact information we need another UI component - the frame time counter displays 3 values: max, min and current delta time values in milliseconds.
The profiler will additionally provide basic renderer statistics: the current count of all vertices that are being renderer in this frame, the draw call count and whether VSync is enabled.
The scene hierarchy window displays a list of all entities in the current scene.
The individual entities are displayed using a tree diagram. The individual tree nodes respond to M2 events and produce a simple context menu containing the following options:
- Delete - Deletes the entity.
- Rename - Creates a rename input field and renames the entity.
- Create Empty - Creates an empty child entity parented to the entity.
In the case of the RMB event not being handled by any specific tree node, the list creates a different context menu containing the following options:
- Create Empty - Creates an empty entity.
- Save Scene - Opens a save file dialog window and saves the current scene.
- Load Scene - Opens a load file dialog window and loads the selected scene.
Some scenes may also provide a simple README panel containing relevant information about the active scene.
Due to the project being based on CUDA a compliant device is required to run the simulations (NVidia GPU).
N/A
The core of the application is the scene system. Due to it's high performance and easy extensibility we've chosen to use and ECS-based system (entity component system), where every entity has a certain amount of components (mesh component, material component, simulation component etc.). The Scene itself is a registry containing various entities. Each entity has its ID (handle) and a reference to its parent scene.
Entity creation and deletion, the ability to change the parent/child of a certain entity. Entity ID getters, transform conversion functions (Local -> World space and vice versa). Entity queries (entity count, entity views). Furthermore the scene implements update and render methods for updating and rendering the entire scene (note that the render method will probably be moved to a separate scene renderer class in the future).
Most of the methods are wrappers for the respective entt functions. Currently available components:
- DPSHPSimulationComponent
- SPHSimulationComponent
- IDComponent
- MaterialComponent
- MeshComponent
- TagComponent
- TransformComponent
- RelationshipComponent
Every component implements a cereal serialization function that is used to save and load the specific component.
The application's input system relies on a simple, blocking event system. Every "layer" (Editor, editor panels etc.) that intercepts any event implements an OnEvent method and decides whether or not the event is considered as "Processed" or if it gets to bubble further.
The inputs are generated by the GLFW polling system. The outputs are then "signals" sent to all listening layers.
The system comprises of 2 elements: an abstract Event class and an EventDispatcher class. The event class holds information about the specific event, and the EventDispatcher dispatches the specified events inside OnEvent methods to other, more specific functions that deal with the event on their own. Currently, There are 14 event types:
// Window
WindowClose
WindowMinimize
WindowResize
WindowFocus
WindowLostFocus
WindowMoved
WindowTitleBarHitTest
// Keyboard
KeyPressed
KeyReleased
KeyTyped
// Mouse
MouseButtonPressed
MouseButtonReleased
MouseMoved
MouseScrolled
// Scene
OnSceneLoaded
OnSceneSaved
The application class is the core of the project. It handles every event and functions as the main entry point.
Since this is the highest-level object in the entire project all inputs and outputs pass through/originate from it.
The application class, in its essence, is a wrapper for the main loop. It is also the only object that can directly interact with it (however, utility functions such as Run and Close are provided). The application class manages the application window and the editor (Window events get passed down to the editor through the application and then bubble further). The application is a singleton class and all that is needed to do to start the application is to create an instance of it.
graph LR;
1[Update]-->ProcessEvents
subgraph Main loop
ProcessEvents-->UpdateSceneContext
UpdateSceneContext-->UpdateEditor
UpdateEditor-->SwapWindowBuffers
end
SwapWindowBuffers-->2[Update]
- ProcessEvents: Processes all events that do not have the
dispatchImmediately
flag set totrue
. - UpdateSceneContext: Updates the active scene, this only includes logical operations.
- UpdateEditor: Updates the editor, this includes updating the viewport panel, which then renders the scene from its point of view.
- SwapWindowBuffers: Swaps the window buffers.
The application's core data structure is the Ref. It is used as opposed to / as a replacement to shared pointers.
N/A
Every class that we want to become/be used as a Ref has to publicly derive from the RefCounted class. This class stores the current reference count to that object, whenever a Ref is created from it. The Ref class enables us to intrusively access the same object from multiple places at the same time and it behaves very similarly to std::shared_ptr.
The project has its own basic debugger/logger.
The debugger provides basic LOG, ERR, WARN and ASSERT macros, furthermore, it implements COMPUTE_SAFE and COMPUTE_CHECK macros utilized in CUDA/C++ code.
The debugger only works in the Debug configuration, however, a ENABLE_DEBUG_MACROS_RELEASE is available, and if it is implemented, the configuration check is ignored and debug macros remain active even in the release configuration.
The editor provides a basic user interface. It's core is the EditorPanel class, which then functions as a base class for other windows, namely the ViewportPanel and SystemInfo classes.
The editor continually receives events from the application class and produces relevant results (see User Interface for more information).
The editor implements OnEvent and update functions. It also contains references to the current scene (scene context), the currently focused entity (selection context), and its very own panel manager. It also implements utility functions for setting and getting the scene and selection contexts, and for loading and saving the current scene context. The editor is a singleton class. The editor gets updated every frame, see the update tree below:
graph LR;
1[Update]-->UpdateImGui
subgraph Update loop
UpdateImGui-->UpdatePanelManager
UpdatePanelManager-->UpdateProfilerPanel
UpdatePanelManager-->UpdateReadMePanel
UpdatePanelManager-->UpdateSceneHierarchyPanel
UpdatePanelManager-->UpdateViewportPanel
UpdateViewportPanel-->RenderScene
end
RenderScene-->2[Update]
UpdateProfilerPanel-->2[Update]
UpdateReadMePanel-->2[Update]
UpdateSceneHierarchyPanel-->2[Update]
UpdateViewportPanel-->2[Update]
The renderer system has several components and provides utility functions and classes for drawing on the screen and managing the OpenGL context.
graph LR;
1[Update]-->SetClearColor
subgraph Render loop
SetClearColor-->Clear
Clear-->BeginScene
BeginScene-->Draw
Draw-->EndScene
end
EndScene-->2[Update]
The frame buffer is a replacement for the default GLFW frame buffer and provides a way to draw to a set of textures.
To create a frame buffer the user has to provide a frame buffer description struct containing the sample count, width, height and a list of attached textures.
Once the frame buffer gets bound every draw call gets directed into it and its texture attachments.
The vertex buffer provides a way of interfacing with the OpenGL vertex buffer.
The vertex buffer can be either constructed by providing the size and data, or just the data by itself. The data can be updated later.
Once constructed, the vertex buffer can be provided to a vertex array object for drawing.
The index buffer provides a way of interfacing with the OpenGL index buffer.
The vertex buffer can be constructed by providing the necessary index data.
Once constructed, the index buffer can be provided to a vertex array object for drawing.
The uniform buffer provides a way of interfacing with the OpenGL uniform buffer.
The uniform buffer can be constructed by providing a size in bytes and a binding index.
Once constructed, the uniform buffer can be provided to material, where it will represent the uniform data of a specific shader.
The triangle mesh class is the default way of rendering meshes. It provides a simple, triangle-based representation of the specified mesh.
To construct a triangle mesh the user has to provide a path to a .obj mesh file.
N/A
The camera contains a view and projection matrix and can be position, rotated and scaled.
The camera can be constructed by providing the size, fov, near and far clip planes.
N/A
The material class provides a way to set the uniforms of a shader in a persistent way.
The material can be constructed using a shader.
Once a shader is provided the material analyzes it and creates a uniform buffer.
The index buffer provides a way of interfacing with the OpenGL shader program.
To create a shader a file path to the shader source file has to be provided. After the shader gets constructed a uniform buffer containing all the uniform values gets created.
To aid with generating the uniform buffer SPIR-V generates the shader binaries - this also enable shader caching.
The index buffer provides a way of interfacing with the OpenGL vertex array object.
After construction, the user can bind a vertex and index buffer to it, after this, the vertex array can be bound and used by OpenGL to draw the item.
N/A
A basic implementation of the SPH algorithm described here.
The simulation constructor takes in a simple description struct. After the simulation updates the user can retrieve a vertex array containing all relevant data or individual arrays.
The simulation loop follows the graph below:
graph LR;
1[Update]-->Integrate
subgraph Simulation loop
Integrate-->CalculateHash;
CalculateHash-->Sort;
Sort-->Reorder;
Reorder-->Collide;
end
Collide-->2[Update];
- Integrate: Combines velocities from the current and last frame. Clamps the particle positions to the bounding box.
- CalculateHash: Calculates the hash of every particle, this hash is then used as a key in the sort function, each hash value is derived from the particle's current position.
- Sort: Sorts all particles and theird hash based on the hash value.
- Reorder: Reorders the particles based on the sorted hash.
- Collide: Updates the velocity and position of the particles. Calculates the density and force affecting the particles.
It is of vital importance that the application and the included fluid simulations run at a reasonable framerate on aptly equipped systems.
N/A
The application should run without crashing or uncalled-for stuttering. The risk of memory leaks should also be minimized.
The project will eventually provide a Github wiki page that will explain its core concepts and inner workings.
The project's wiki page will also include a section on proper usage of the application.