Giter Site home page Giter Site logo

jessydl / paradigm Goto Github PK

View Code? Open in Web Editor NEW
17.0 1.0 2.0 4.31 MB

C++20 Vulkan and GLes rendering engine

Home Page: https://paradigmengine.dev

CMake 1.29% C++ 96.80% Python 1.90% Shell 0.01%
cpp vulkan graphics rendering-engine vulkan-sdk paradigm-engine paradigm gles cpp23 game-development

paradigm's Introduction

last git tag

Paradigm Engine

Paradigm Engine is a Vulkan first modern graphics rendering library written in C++23 (mostly c++17/20) with support for GLES (3.2), and future WebGPU support (see status section for more info). It concerns itself mostly with the heavy lifting of the rendering part of the engine, and supporting a toolchain that is flexible to your project's needs and structure. It stays away from dictating your code design, forcing you to use inheritence or the like for your logic, stays away from macro usage (unless in very rare instances), avoids globals, and provides simple bindings for you to implement your language of choice.

Some of the key reasons why this engine exists:

  • Showcasing an approach to serialization in C++ that does not rely on external compilers or macro magic.
  • Building a renderer that is fully integrated with a multithreaded ECS.
  • A focus on small binary size, for ease of distribution in a team.
  • Trying out new rendering engine designs that are more suited, and enabled by, modern graphics API's.
  • Flexible testbed for those who want to quickly prototype/validate something.
  • A trivial binding point so you can insert the language of your choice for your logic/extensions.
  • Minimize globals and service locator patterns that are present in many engines out there and instead use dependency injection.
  • And ofcourse brush up my skills in writing systems and architectures I'm not familiar with, and give myself new challenges in other problem domains. :D

For more detailed description about the engine itself, go to the readme of the core project, which is in the folder of the same name. Similarly all sub-projects that make up the engine all have their own readme's describing their intent, structure, status and dependencies. This readme is just to give a larger oversight over the entire project.

Status

Architecture Status Vulkan GLeS Metal WebGPU
windows 1.2 3.1 - 3.2 -
windows 1.2 3.1 - 3.2 -
ubuntu 1.2 3.2* -
- - -
- - -
1.2 3.1 - 3.2 -
ubuntu 1.2 3.1 - 3.2 -
see notes** - - - in progress

* GLES on GNU/Linux requires libegl1-mesa-dev and libgles2-mesa-dev to be installed.

** WASM/WebGPU currently in progress, the psl library compiles, but the WebGPU HAL still needs to be written in core.

Building

Prerequisites

All platforms

Python 3.9 or newer

CMake 3.22 or higher is required on all platforms.

A C++23 compliant compiler. See the Github Actions for up-to-date examples of supported compilers on different platforms.

Android

Android SDK Gradle (7.6 or later)

Creating the project files

You can build this project by either using the provided CMakePresets.json file (recommended), or by running paradigm.py with python 3.9+. See the CMakePresets section for more information.

So far MSVC (2022), CLang (12.0.0) with libc++, and GCC (12.1.0) are supported. The project will likely incorrectly generate for other compilers/setups. You can always verify the compiler version that was used by checking the github CI runners.

If lost, check the github actions workflow folder to see ideal platform setups.

CMakePresets

Various CMakePresets exists, they are specified to working combinations of graphics API's that are supported for the given platform. They can be used as normal, or used as guides for custom setups.

The cmake presets are structured in the following way {platform}-{type}-{graphics} unless the platform also supports different architectures (such as x86 and x86_64) in which case they are {platform}-{pointer_size}-{type}-{graphics}, see next section for more information. Following are the valid settings for each.

platform:

  • windows
  • gnu-linux
  • android

type:

  • debug
  • release

graphics:

  • vulkan
  • all (imples both vulkan and gles support).

paradigm.py

The paradigm script is a helper script that can invoke, amongst others, the builder script (tools/build.py). Invoke the builder script using --run build, this will set everything up quick and easy. It will generate a solution in the /project_file/{generator}/{architecture}/ folder by default, and when building it will output to /builds/{generator}/{architecture}/. You can tweak various settings and values, you'll find them at the top of the tools/build.py file.

As an example running py paradigm.py --run build --graphics vulkan gles --generator Ninja will set up the project as an executable, with all available graphics backends, using the Ninja build tool.

Examples

Following are some examples that showcase various usages of this project, click the image to go to the repository.

basic-app

Basic example app showcasing how to create a surface, loading a meta::library, create memory::region's and a resource::cache, and finally rendering a basic textured box.

assembler

The assembler project is a CLI toolchain that allows you to generate data files that can be used by the engine, as well as generating resource libraries. Its setup is a bit different due to the fact that it consumes the library mostly as a source.

Project Structure

Sub-Projects

All the sub-project's code resides in the root in a folder that shares the same name. Each one of them contains both the src and inc folder as well as a readme that describes that specific project's intended goals.

Tools

For tools that can aid in generating shaders, importing models, and generating resource libraries, check out the assembler repository.

Versioning

Version information can be found in core/gen/core/paradigm.hpp (note this file is generated by tools/generate_header_info.py which auto runs every build). This project uses versioning of the format MAJOR.MINOR.PATCH.{GIT SHA1}. Release type can be one of the following:

  • i - internal
  • r - release

release versions are determined if the SHA1 is pointing to a tagged commit, otherwise it's considered an internal build.

External Libraries

The following external libraries may be used in one, or many of the sub projects. The specific project readme's will describe their dependencies in more detail so you can always verify which project uses what.

  • GLI image loading and saving
  • GLM mathematics library used only by GLI
  • litmus used for running tests
  • spdlog used for logging
  • fmt modern formatting library for C++
  • utfcpp utf8 string parsing
  • strype support for stringifying typenames and enums on supported compilers

note that dependencies might pull in further dependencies that are not listed here.

Conditional dependencies

The following dependencies are conditionally included:

  • Vulkan-hpp when enabling the Vulkan backend Vulkan-hpp is used to generated C++ like headers for Vulkan
  • GLAD when enabling the GL backend GLAD is used to generate the GL headers
  • google/benchmark when enabling PE_BENCHMARKS

Documentation

A reference documentation is available at https://paradigmengine.github.io/. API examples, tutorials, and best practices guide will be written at a later time. For the time being, you can look at the example projects provided in the examples section.

You can also find further documentation in the docs directory, such as information on how to use the Entity Component System.

Tests

building

Tests are on by default when compiling the project as a library, you can disable this by toggline PE_MODE in cmake from LIB to LIB_NO_TESTS. Tests will not be included when building the project in EXE mode.

When using CMake directly (or with CMakePresets), you'll have the project files output in project_files, from there out you can either boot it up in your editor of choice, or if your generator isn't also an IDE, then build it from there. Build outputs by default will appear in /builds/.

When using the paradigm.py or tools/build.py script, you can set this value like this --cmake_params="-DPE_MODE=LIB". LIB is the default value for this project.

Tests use Google Benchmark, these will be fetched automatically when the CMake script detects testing to be true.

Future

Language

This project will keep up with the latest C++ language improvements till atleast C++2a, as we require some of the newer features to create safer and easier to reason about interfaces for, amongst other things, serialization. After C++2a we will freeze the language to that version and keep it stable.

Graphics Backends

After fully completing support for GLES 3.1+ and Vulkan, and a graphical toolset for editing has been finished, focus will be shifted on a Metal backend. A WebGPU backend might be looked into to support more platforms as well if it has reached MVP/1.0 status.

Extras

Build as executable

If you wish to build the engine, not as a library, but instead as an executable, you can enable this behaviour by passing -DPE_MODE=EXE as a --cmake_param in the build.py script, or directly in your cmake invocation. This will set the CORE_EXECUTABLE define in the compiler, and will trigger the core project to be built as an executable instead of being a library.

Benchmarks

Rudimentary benchmarks (heavily WIP) have been added to the project, you can enable this by setting the cmake value PE_MODE to anything but EXE, and toggling on PE_BENCHMARKS.

License

This project is dual-licensed under commercial and open source licenses. Licensed under GNU AGPLv3 for free usage, and licensed under a commercial license you can purchase or request for commercial usage.

  • GNU AGPLv3: You may use Paradigm Engine as a Free Open Source Software as outlined in the terms found here: https://www.gnu.org/licenses/agpl-3.0.en.html

  • Commercial license: If you do not want to disclose the source of your application you have the option to request a commercial license. The commercial license gives you the full rights to create and distribute software on your own terms without any open source license obligations. For more information, please contact Jessy De Lannoit directly here.

This license (and its setup) only applies to the current major version of this software. So if you are getting the license on version 1.0.0, you will be able to use all upgrades that carry the same major version number (in this case 1.x.x). Once the switch is made to a newer version such as 2.x.x of the project, you will need to upgrade your license to use that major version's updated license (in case there is a new license), or the updated license setup.

Of course you may continue to use any version of this project you currently have licensed/have a license for, without needing to change your license as long as you do not pull new updates from major versions you do not have a license for.

This license, and its setup applies to all sub-projects in this repository unless explicitly stated otherwise.

paradigm's People

Contributors

jessydl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

Forkers

zeta1999 sarvex

paradigm's Issues

Vulkan texture format hardening

Current codepath for vulkan texture loading doesn't check for supported formats. Resolve this and gracefully handle, or error out.

Skinned mesh support

Support basic animated models using skeletal animation keyframes. Dealing with import is left for the assembler project.

Preferably the implementation is done through compute shaders for the transformations, but is not a requirement. If not implemented through compute, it is considered a defect that has to be resolved before 1.0.0

note: delay this until #28 and #32 are resolved

ECS improvements

Following are a list of features that should be investigated or added to the ECS implementation:

Extra:

  • Look into alias components. These components could shadow, or alias one-another (see core::ecs::components::transform issue in internal docs)
  • Look into duplicate component support (i.e. multiple same components on same entity). This could be handled as a compile time setting with a set max size, or potentially unbounded size.
  • Alternatively from alias components, look into "concept" types, i.e. components that satisfy a specific concept (like TransformConcept for anything that has a world space position) being grouped and sent as their shared trait data.
  • indirect_t packs spend a large amount of time in lookups even when the underlying storage has not changed. We could consider a "generation_version" variable to mark when the underlying storage has not changed in meaningfull ways so that previous lookups can be re-used

Component storage should be generic memory

Currently the underlying storage knows about the type. This allows us to handle non-trivial types, but disallows us from trivially deserializing these types without knowing the type up front. Ideally we solve this by making the underlying storage itself generic allowing the sidestepping of this problem, and not with type registration patterns.

This issue interacts with #63 and #65 , particularly dealing with complexer types such as reference tracked handles should be considered when solving this problem.

  • #75
  • design a new psl::ecs::details::component_info to utilize this. Preferably figure out a correct way of dealing with flag types that don't incur overhead. Fetching data from this new type should be type-safe (i.e. upper types shouldn't be the one dealing with type safety concerns).

Note: we could consider a hybrid system where any serializable component has to use the untyped storage, while serializable components can have non-trivial types.

Resource System Rewrite

Investigate an alternative approach, and then wholesale replace the existing resource loading system.

The alternative will have these constraints and goals:

  • support async loading
  • protect disk based assets (i.e. fresh copy should always be possible or immutable authoritive source)*
  • complex interaction (such as waiting for a GPU based event to signal) before the resource is available
  • support both reference tracking, as well as manual tracked modes (not needed to be mixed support)
  • safe resource destruction (i.e. resources are protected as long as they are in use)
  • support both shared and weak handles
  • alias handles, we should be able to contain all graphics API related resources in a single handle, which forwards into the actual active handle. Support for multiple to be active is unnecesairy. Support for mixed operations is unnecesairy as well (i.e. we can pre-assume the active index)
  • internally protected from race conditions from scheduled operations (but not externally, i.e. all public methods make no threading guarantees)
  • ability to forward arbitrary arguments to the to-be-constructed type.
  • all points need to have unit tests verifying these constraints and goals

* immutable authoritive source seems to be impossible without large efforts in architecture as when f.e. a buffer is loaded we obviously need to be able to write to it if it's used for those purposes.

Notes:

protect disk based assets:

The current approach selects the "last loaded" to be the authoritive source of what is the disk based assets (for the find method), but allows load to be called N times and get a fresh version as if just loaded from disk

complex interaction:

As the system has been implemented using coroutines, you can simply use co_await in the construct method and await for you relevant signal to fire, or to switch to other threads.

reference tracked/manual resource management:

All resources are always tracked by default. This isn't toggleable for now (this is to satisfy the safe resource destruction requirement). However, you can control when resources get destroyed if it's safe to do so (i.e. the last references are held by the cache, and the caller). The try_destroy method will verify if it's safe, and will destroy the resource within the given scope.

Alternatively, the unsafe_destroy will forcibly destroy the resource. This functionality is hidden behind the RESOURCE_ALLOW_UNSAFE define.

consider renaming construct to co_construct /s

Android app sleep/background support

App currently doesn't recover from going to sleep/background on the Android platform.

goal: app gracefully can go to background for any time, any duration, and either reboots, or continues execution.

[ECS] indirect packs should use the internal cache

The way the packs are handled internally in the ecs state results in some copying; as the indirect packs are managing their own memory (the indices only), this results in some overhead for copying. If we make the indirect packs as lightweight as the direct packs (by using the state's internal cache), then we can remove quite a bit from this overhead.

Improve `serialization_name`

Currently the psl::serialization system requires a serialization_name to be present in the serialization supported objects. This currently is a const char[] where users need to explicitly write the size of the buffer. Modify this so the user either doesn't need it (unlikely), or where the size of this is known automatically.

Android support in the CI

Currently no method of automated builds for Android exist, resolve this.

What is expected:

  • Github CI builds android artefacts
  • Possible some form of basic tests is done on the artifact (docker image of Android perhaps?)

Support serialization of the `psl::ecs::state_t`

The ECS state should be fully de/serializable, this includes all the components, and the to-be-processed state. Potentially we serialize the filters as well, but these are less important as that's internal optimization.

Registered systems are not to be serialized.

Note that support for deserializing into an already modified ECS state is considered an "extra" and not necessary.

ECS filtering use cached component containers

Currently filters use the component key, and do a lookup just when the filtering operation is done. This could be improved by having the component container be used instead (when available), and fall back to the key behaviour.

Support "prototype" functions for trivial component types

Due to the requirements of std::is_trivially_constructible we cannot have constructors with side effects for serializable types. Add the ability to have a "prototype" function of some sort allowing the user to specify a "good default value" for a new instance.

This works as the only time new objects get created is either from a known position (from the type system's perspective), or from known data (deserialization and internal stack).

Automate doxygen & github.io

Documentation should be generated through the CI every PR merge (or every commit to develop)

Ideally the documentation would be accessible online versioned. This could be achieved by having versioned folders based on tag on gh-pages branch i.e.:

root
  0.1.0/**
  0.1.1/**
  all_files_current_develop

`ecs` templated component types should be rejected

The stringifying of typenames cannot deal with templated types correctly cross platform (issue related to NTTP & defaulted template args). Either we reject NTTPs (would require considerable parsing), and figure out a solution to sidestep defaulted template args (MSVC differs from GCC/CLang), or reject all templated names for the time being.

Fully implement compute support

Compute shaders can already be loaded, but have some edge cases in resource handling and dependency tracking.

Finalize *::computepass and *::computecall to offer the full functionality set as is expected.

At the end we should be able to effortlessly load a "compute" material that outputs to a texture (or other resource), which is then consumed by another "draw" or "compute" material. Preferably this has almost no user interactions needed (i.e. no code to map to one another if everything is described in data files).

  • GLES backend
  • VK backend
  • GFX backend

Investigate why alignas + ecs component leads to segfault in GCC

When storing a type that has been aligned using alignas GCC will generate faulty code when fetching the data from a component pack.

example:

struct alignas(16) foo {
    float data[4];
};

state_t state{};

state.create<foo>(1);

state.declare([](info_t& info, pack<foo> pack){
    for(auto [value] : pack) {
        volatile auto copy = value; // <- segfault
    }
});

state.tick(std::chrono::duration<float>(1.0f));

[non-fatal][recovers] Trashing of GPU memory due to syncing

Severity: High (causes non-fatal issue, and recovers next frame)

Issue:
GPU memory can get trashed when realloc happens in a buffer when, in a single frame, range A moves, followed by range B reallocs onto the old range A location. This results in a frame where the affected commands interacting with that region of memory can get incorrect results (f.e. in the case of rendering geometry, usually a "jitter" is observed). This recovers the next frame as the correct offsets are recorded.

Possible resolution:
Implement a mechanism to mark regions of memory as locked while they are in flight, deferring their dealloc. Let these locks be attached to preferably a user controllable solution, so that we can solve this using fences for this instance, and potentially barriers/others in case of other user defined use cases.

Constrain ECS components to `std::is_trivial`

This means finding a solution for reference tracked resources (f.e. core::resource::handle), and resources that contain indirections (f.e. std::string). This likely means these resources would need to persist elsewhere in the ecs::state rather than the typical component storage structure, and the storage structure holding indirections to.

Currently we ignore this constraint resulting in being allowed to use these non-conforming elements, and thanks to the way the internal memory storage is handled this is still valid. But as we need to support std::is_trivial in the future for other ECS features, this should be handled first before proceeding.

Make MaterialData dynamically resizeable

Currently the MaterialData binding's backing storage is "set once, change never". This can work for toy examples but is bad for actual production.

Proposed changes (choose one):

1. Support growing buffers, and alert materials through callback when realloc happens.

Advantage:

  • one ground truth location
  • easy to access anywhere.

Cons:

  • double buffering issues/complexities
  • possible large amount of materials to update on realloc
  • unstable
  • defrag issues, though due to the presence of realloc functionality this should be trivial to resolve

2. materials/bundles are in "charge" of their own buffers.

Implementing this in bundles has the distinct advantage that materials already store their instance data there

Pros:

  • stable
  • extremely flexible
  • safe (no chance of overwriting other's data)

Cons:

  • Lots of small allocations, which we want to avoid

3. mixed approach

Implement material data handling in either bundles/material (prefer bundles), defer to a global buffer unless size exceeds N bytes, or global buffer runs out of size. When global buffer runs out of size, either allocate 2nd global buffer or do smaller self managed allocations.

Pros:

  • stable, other buffers aren't affected
  • flexible

Cons:

  • defrag issue potential (when multiple global buffers exist, but all are underutilized. This can be alleviated with realloc

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.