- Windows
- Linux
The engine tries to focus on things that other engines miss:
- Intuitive API
- Simplicity (not in terms of "make a game in one click" but in terms of "there are not much things to learn" (compared to the usual C++ development/gamedev knowledge), for example: there's no custom/unusual build system for the engine, just use your usual CMake files)
- Focus on KISS for engine internals
- Documentation (engine source code is fully documented, every code entity is documented, even private, also there are lots of documentation-comments in the implementation code, there's also a manual that you can read)
Currently the engine is in a very early development state and right now it's still impossible to make games with this engine.
Here is the list of base features that needs to be implemented in order for you to be able to make games:
- Reflection system
- Serialization/deserialization for game assets
- Garbage collector
- Handling user input events
- Config management (progress, settings, etc.)
- Node system (Godot-like ECS alternative)
- Profiler
- GLTF/GLB import
- Rendering (features for both DirectX 12 and Vulkan renderers)
- Automatically test and pick the most suitable renderer/GPU based on the OS/hardware capabilities/user preferences at runtime
- MSAA (Multisample anti-aliasing)
- Transparent materials
- Diffuse textures
- Multiple materials per mesh
- Frustum culling
- Simple API for using custom vertex/pixel/fragment/compute shaders
- Z-prepass
- Forward+ (Tiled Forward) rendering
- Light sources
- Directional light
- Spot light
- Point light
- Shadow mapping
- Directional lights
- Spot lights
- Point lights
- Cascading shadow maps
- Space partitioning and multi-threading for frustum culling
- Post-processing effects
- HDR (High dynamic range)
- Normal mapping
- Cube mapping
- Instancing
- SSAO (Screen space ambient occlusion)
- PBR (Physically based rendering)
- Decals
- Order independent transparency
- Occlusion culling
- Emissive materials
- Light culling improvements
- Multi-threaded command list/buffer generation
- Shadow caster culling
- GUI
- Skeletal animations
- Minimal scripting using AngelScript
- Basic editor
- Audio engine (integrate SoLoud)
- 2D audio
- 3D audio
- Audio streaming
- Sound effects
- Physics engine (integrate Jolt or Bullet3)
- Rigid body
- Ragdoll
- Soft body
- Joints
- Automatic LODs (Level of details)
- AI and pathfinding
- Particle effects
Once all base features will be implemented I will create a separate repository for examples and add a link to it here.
Documentation for this engine consists of 2 parts: API reference (generated from C++ comments) and the manual (at docs/Manual.md
), generated documentation includes both API reference and the manual (copied from docs/Manual.md
).
In order to generate the documentation you need to have Doxygen installed.
The documentation can be generated by executing the doxygen
command while being in the docs
directory. If Doxygen is installed, this will be done automatically on each build.
Generated documentation will be located at docs/gen/html
, open the index.html
file from this directory to view the documentation.
Prerequisites:
- compiler that supports C++23 (latest MSVC/Clang)
- CMake
- Doxygen
- LLVM
- Go
- Vulkan SDK
- prerequisites for Linux:
libtinfo.so
might not be installed on your system but is requiredlibclang
(needed for reflection code generator), after CMake is configuredext/Refureku/build/Bin/
will contain needed libraries, you would need to create the file/etc/ld.so.conf.d/nameless-engine.conf
with the path to this directory and runsudo ldconfig
so that these libraries will be found by the reflection generator
First, clone this repository:
git clone https://github.com/Flone-dnb/nameless-engine
cd nameless-engine
git submodule update --init --recursive
Then, if you've never used CMake before:
Create a build
directory next to this file, open created build
directory and type cmd
in Explorer's address bar. This will open up a console in which you need to type this:
cmake -DCMAKE_BUILD_TYPE=Debug .. // for debug mode
cmake -DCMAKE_BUILD_TYPE=Release .. // for release mode
This will generate project files that you will use for development.
To update this repository:
git pull
git submodule update --init --recursive
The following is a code style rules for engine developers not for game developers (although if you prefer you can follow these rules even if you're not writing engine code but making a game).
Mostly engine code style is controlled though clang-format
and clang-tidy
(although clang-tidy
is only enabled for release builds so make sure your changes compile in release mode), configuration for both of these is located in the root directory of this repository. Nevertheless, there are several things that those two can't control, which are:
Some prefixes are not controlled by clang-tidy
:
- for
bool
variables the prefix isb
, example:bIsEnabled
, - for integer variables (
int
,size_t
, etc.) the prefix isi
, example:iSwapChainBufferCount
, - for string variables (
std::string
,std::string_view
, etc.) the prefix iss
, example:sNodeName
, - for vector variables (
std::vector
,std::array
, etc.) the prefix isv
, example:vActionEvents
, - additionally, if you're using a mutex to guard specific field(s) use
std::pair
if possible, here are some examples:
std::pair<std::recursive_mutex, gc<Node>> mtxParentNode;
struct LocalSpaceInformation {
glm::mat4x4 relativeRotationMatrix = glm::identity<glm::mat4x4>();
glm::quat relativeRotationQuaternion = glm::identity<glm::quat>();
};
std::pair<std::recursive_mutex, LocalSpaceInformation> mtxLocalSpace;
Sort your #include
s and group them like this:
// Standard.
#include <variant>
#include <memory>
// Custom.
#include "render/general/resources/GpuResource.h"
// External.
#include "vulkan/vulkan.h"
where Standard
refers to headers from the standard library, Custom
refers to headers from the engine/game and External
refers to headers from external dependencies.
Directories in the src
directory are named with 1 lowercase word (preferably without underscores), for example:
render\general\resources
used to store render
-specific source code, general
means API-independent (DirectX/Vulkan) and resources
refers to GPU resources.
Files are named using CamelCase, for example: EditorGameInstance.h
.
- if you're using
friend class
specify it in the beginning of theclass
with a small description, for example:
/**
* Represents a descriptor (to a resource) that is stored in a descriptor heap.
* Automatically marked as unused in destructor.
*/
class DirectXDescriptor {
// We notify the heap about descriptor being no longer used in destructor.
friend class DirectXDescriptorHeap;
// ... then goes other code ...
generally friend class
is used to hide some internal object communication functions from public section to avoid public API user from shooting himself in the foot and causing unexpected behaviour.
- don't duplicate access modifiers in your class/struct, so don't write code like this:
class Foo{
public:
// ... some code here ...
private:
// ... some code here ...
public: // <- don't do that as `public` was already specified earlier
// ... some code here...
};
- specify access modifiers in the following order:
public
,protected
and finallyprivate
, for example:
class Foo{
public:
// ... some code here ...
protected:
// ... some code here ...
private:
// ... some code here...
};
- don't mix function with fields: specify all functions first and only then fields, for example:
class Foo{
public:
void foo();
protected:
void bar();
private:
void somefunction1();
void somefunction2();
// now all functions were specified and we can specify all fields
int iAnswer = 42;
static constexpr bool bEnable = false;
};
-
don't store fields in the
public
/protected
section, generally you should specify all fields in theprivate
section and provide getters/setters in other sections- but there are some exceptions to this rule such as
struct
s that just group a few variables - you don't need to hide them inprivate
section and provide getters although if you think this will help or you want to do something in your getters then you can do that - for inheritance generally you should still put all base class fields in the
private
section and for derived classes provide aprotected
getter/setter if you need, although there are also some exceptions to this
- but there are some exceptions to this rule such as
-
put all
static constexpr
/static inline const
fields in the bottom of your or class, for example:
private:
// ... some code here ...
/** Index of the root parameter that points to `cbuffer` with frame constants. */
static constexpr UINT iFrameConstantBufferRootParameterIndex = 0;
}; // end of class
- if your function can fail use engine's
Error
class as a return type (instead of logging an error and returning nothing), for example:
static std::variant<std::unique_ptr<VulkanResource>, Error> create(...);
[[nodiscard]] std::optional<Error> initialize(Renderer* pRenderer) override;
- if your function returns
std::optional<Error>
mark it as[[nodiscard]]
- avoid nesting, for example:
bad:
// Make sure the specified file exists.
if (std::filesystem::exists(shaderDescription.pathToShaderFile)) [[likely]] {
// Make sure the specified path is a file.
if (!std::filesystem::is_directory(shaderDescription.pathToShaderFile)) [[likely]] {
// Create shader cache directory if needed.
if (!std::filesystem::exists(shaderCacheDirectory)) {
std::filesystem::create_directory(shaderCacheDirectory);
}
// ... some other code ...
}else{
return Error(std::format(
"the specified shader path {} is not a file", shaderDescription.pathToShaderFile.string()));
}
}else{
return Error(std::format(
"the specified shader file {} does not exist", shaderDescription.pathToShaderFile.string()));
}
good:
// Make sure the specified file exists.
if (!std::filesystem::exists(shaderDescription.pathToShaderFile)) [[unlikely]] {
return Error(std::format(
"the specified shader file {} does not exist", shaderDescription.pathToShaderFile.string()));
}
// Make sure the specified path is a file.
if (std::filesystem::is_directory(shaderDescription.pathToShaderFile)) [[unlikely]] {
return Error(std::format(
"the specified shader path {} is not a file", shaderDescription.pathToShaderFile.string()));
}
// Create shader cache directory if needed.
if (!std::filesystem::exists(shaderCacheDirectory)) {
std::filesystem::create_directory(shaderCacheDirectory);
}
- prefer to group your implementation code into small chunks without empty lines with a comment in the beginning, all chunks should be separated by 1 empty line, for example:
// Specify defined macros for this shader.
auto vParameterNames = convertShaderMacrosToText(macros);
for (const auto& sParameter : vParameterNames) {
currentShaderDescription.vDefinedShaderMacros.push_back(sParameter);
}
// Add hash of the configuration to the shader name for logging.
const auto sConfigurationText = ShaderMacroConfigurations::convertConfigurationToText(macros);
currentShaderDescription.sShaderName += sConfigurationText;
// Add hash of the configuration to the compiled shader file name
// so that all shader variants will be stored in different files.
auto currentPathToCompiledShader = pathToCompiledShader;
currentPathToCompiledShader += sConfigurationText;
// Try to load the shader from cache.
auto result = Shader::createFromCache(
pRenderer,
currentPathToCompiledShader,
currentShaderDescription,
shaderDescription.sShaderName,
cacheInvalidationReason);
if (std::holds_alternative<Error>(result)) {
// Shader cache is corrupted or invalid. Delete invalid cache directory.
std::filesystem::remove_all(
ShaderFilesystemPaths::getPathToShaderCacheDirectory() / shaderDescription.sShaderName);
// Return error that specifies that cache is invalid.
auto err = std::get<Error>(std::move(result));
err.addCurrentLocationToErrorStack();
return err;
}
// Save loaded shader to shader pack.
pShaderPack->mtxInternalResources.second.shadersInPack[macros] =
std::get<std::shared_ptr<Shader>>(result);