Compare commits

..

20 commits

Author SHA1 Message Date
63cb6dfe92
wip: convert from include style to module import style :D
Some checks are pending
continuous-integration/drone/push Build is running
2025-11-04 18:50:59 +03:30
14c2512202
add breathe & remove conan
Some checks are pending
continuous-integration/drone/push Build is running
2025-11-03 16:31:15 +03:30
599f28fe73
add breathe extension for Doxygen doc generation
Some checks are pending
continuous-integration/drone/push Build is running
2025-11-03 16:09:14 +03:30
0a1bda9573
remove generated directories 2025-11-03 16:08:07 +03:30
311b20bdf2
refactor: adjust for recent logger changes
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-11-01 03:10:33 +03:30
6a257566bd
refactor(logger): colors & source location! 2025-11-01 02:36:05 +03:30
4534ed11d2
refactor(test): replace all std::couts with std::println
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-11-01 00:22:56 +03:30
d029c0e473 build: add /EHsc and DriveMode: cl to .clangd file
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-30 18:09:40 +03:30
604ee5e6a1 fix(renderer/vk/raii): special member function issues
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-30 13:45:48 +03:30
7ee4381bbf feat(renderer/vk): add some vulkan functions 2025-10-30 13:45:48 +03:30
8730d31e2f refactor(renderer/vk/gpu): quality of life modifications 2025-10-30 13:45:48 +03:30
f50208653e build: add .clangd to fix clangd lsp issues on Windows
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-29 23:17:37 +03:30
5422792705
feat(renderer): storage & staging buffer types
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-27 23:29:08 +03:30
2ddb90faff
chore: remove code of conduct
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-26 16:44:12 +03:30
736c37d2f1
feat(renderer/vk): dynamic rendering
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-26 16:43:56 +03:30
97ca429d38 feat: frame constants & camera component (#62)
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #62
Co-authored-by: light7734 <light7734@tuta.io>
Co-committed-by: light7734 <light7734@tuta.io>
2025-10-26 06:56:18 +00:00
5a404d5269 feat(renderer): buffer (#61)
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #61
Co-authored-by: light7734 <light7734@tuta.io>
Co-committed-by: light7734 <light7734@tuta.io>
2025-10-25 12:56:14 +00:00
a9e27d6935
fix: old baked shaders
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-22 06:08:10 +03:30
80662983a3
ci: revert commenting the entire .drone.yml
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-22 05:58:48 +03:30
c39ce89a9b
chore(ci): bump c++ standard version to 26
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-22 05:55:20 +03:30
161 changed files with 3836 additions and 2799 deletions

5
.clangd Normal file
View file

@ -0,0 +1,5 @@
CompileFlags:
DriverMode: cl
Add:
- /EHsc
- /std:c++latest

View file

@ -1,42 +1,42 @@
# ---
# kind: pipeline
# type: exec
# name: amd64 — msvc
# trigger:
# branch:
# - main
# platform:
# os: windows
# arch: amd64
#
# steps:
# - name: unit tests
# shell: powershell
# commands:
# - ./tools/ci/amd64/msvc/unit_tests.ps1
#
# ---
# kind: pipeline
# type: docker
# name: amd64 — gcc
# trigger:
# branch:
# - main
#
# steps:
# - name: unit tests
# image: ci:latest
# pull: if-not-exists
# commands:
# - ./tools/ci/amd64/gcc/unit_tests.sh
#
# - name: valgrind
# image: ci:latest
# pull: if-not-exists
# commands:
# - ./tools/ci/amd64/gcc/valgrind.sh
#
# ---
---
kind: pipeline
type: exec
name: amd64 — msvc
trigger:
branch:
- main
platform:
os: windows
arch: amd64
steps:
- name: unit tests
shell: powershell
commands:
- ./tools/ci/amd64/msvc/unit_tests.ps1
---
kind: pipeline
type: docker
name: amd64 — gcc
trigger:
branch:
- main
steps:
- name: unit tests
image: ci:latest
pull: if-not-exists
commands:
- ./tools/ci/amd64/gcc/unit_tests.sh
- name: valgrind
image: ci:latest
pull: if-not-exists
commands:
- ./tools/ci/amd64/gcc/valgrind.sh
---
kind: pipeline
type: docker
name: amd64 — clang
@ -45,114 +45,110 @@ trigger:
- main
steps:
# - name: code coverage
# image: ci:latest
# pull: if-not-exists
# environment:
# CODECOV_TOKEN:
# from_secret: CODECOV_TOKEN
# commands:
# - ./tools/ci/amd64/clang/coverage.sh
#
# - name: leak sanitizer
# image: ci:latest
# pull: if-not-exists
# commands:
# - ./tools/ci/amd64/clang/lsan.sh
#
- name: code coverage
image: ci:latest
pull: if-not-exists
environment:
CODECOV_TOKEN:
from_secret: CODECOV_TOKEN
commands:
- ./tools/ci/amd64/clang/coverage.sh
- name: leak sanitizer
image: ci:latest
pull: if-not-exists
commands:
- ./tools/ci/amd64/clang/lsan.sh
- name: memory sanitizer
image: ci:latest
pull: if-not-exists
commands:
- ./tools/ci/amd64/clang/msan.sh
#
# ---
# kind: pipeline
# type: docker
# name: static analysis
# trigger:
# branch:
# - main
#
# steps:
# - name: clang tidy
# image: ci:latest
# pull: if-not-exists
# privileged: true
# commands:
# - ./tools/ci/static_analysis/clang_tidy.sh
#
# - name: shell check
# image: ci:latest
# pull: if-not-exists
# commands:
# - ./tools/ci/static_analysis/shell_check.sh
#
# - name: clang format
# image: ci:latest
# pull: if-not-exists
# commands:
# - ./tools/ci/static_analysis/clang_format.sh
#
# - name: cmake format
# image: ci:latest
# pull: if-not-exists
# commands:
# - ./tools/ci/static_analysis/cmake_format.sh
#
# - name: shell format
# image: ci:latest
# pull: if-not-exists
# commands:
# - ./tools/ci/static_analysis/shell_format.sh
#
# ---
# kind: pipeline
# type: docker
# name: documentation — development
# node:
# environment: ryali
# trigger:
# branch:
# - main
#
# steps:
# - name: build and deploy
# image: documentation:latest
# pull: if-not-exists
# commands:
# - pwd
# - cd docs
# - mkdir generated
# - touch generated/changelogs.rst
# - touch generated/api.rst
# - sphinx-build -M html . .
#
# - rm -rf /light_docs_dev/*
# - mv ./html/* /light_docs_dev/
#
# ---
#
# kind: pipeline
# type: docker
# name: documentation — production
# node:
# environment: ryali
# trigger:
# event:
# - tag
#
# steps:
# - name: build and deploy
# image: documentation:latest
# pull: if-not-exists
# commands:
# - cd docs
# - mkdir generated
# - touch generated/changelogs.rst
# - touch generated/api.rst
# - sphinx-build -M html . .
#
# - rm -rf /light_docs/*
# - mv ./html/* /light_docs/
#
---
kind: pipeline
type: docker
name: static analysis
trigger:
branch:
- main
steps:
- name: clang tidy
image: ci:latest
pull: if-not-exists
privileged: true
commands:
- ./tools/ci/static_analysis/clang_tidy.sh
- name: shell check
image: ci:latest
pull: if-not-exists
commands:
- ./tools/ci/static_analysis/shell_check.sh
- name: clang format
image: ci:latest
pull: if-not-exists
commands:
- ./tools/ci/static_analysis/clang_format.sh
- name: cmake format
image: ci:latest
pull: if-not-exists
commands:
- ./tools/ci/static_analysis/cmake_format.sh
- name: shell format
image: ci:latest
pull: if-not-exists
commands:
- ./tools/ci/static_analysis/shell_format.sh
---
kind: pipeline
type: docker
name: documentation — development
node:
environment: ryali
trigger:
branch:
- main
steps:
- name: build and deploy
image: documentation:latest
pull: if-not-exists
commands:
- cd docs
- sphinx-build -M html . .
- rm -rf /light_docs_dev/*
- mv ./html/* /light_docs_dev/
---
kind: pipeline
type: docker
name: documentation — production
node:
environment: ryali
trigger:
event:
- tag
steps:
- name: build and deploy
image: documentation:latest
pull: if-not-exists
commands:
- cd docs
- mkdir generated
- touch generated/changelogs.rst
- touch generated/api.rst
- sphinx-build -M html . .
- rm -rf /light_docs/*
- mv ./html/* /light_docs/

View file

@ -1,128 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
Discord: Light7734#4652.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

Binary file not shown.

View file

@ -1,21 +1,26 @@
#version 450 core
vec2 positions[3] = vec2[](
vec2(0.0, -0.5),
vec2(0.5, 0.5),
vec2(-0.5, 0.5)
layout(push_constant ) uniform pc {
mat4 view_projection;
};
vec3 positions[3] = vec3[](
vec3(0.0, -0.5, 0.5),
vec3(0.5, 0.5, 0.5),
vec3(-0.5, 0.5, 0.5)
);
vec3 colors[3] = vec3[](
vec3(1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0)
vec3(0.0, 0.0, 0.0),
vec3(0.0, 0.0, 0.0),
vec3(0.0, 0.0, 0.0)
);
layout(location = 0) out vec3 out_frag_color;
void main()
{
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
gl_Position = view_projection * vec4(positions[gl_VertexIndex], 1.0);
out_frag_color = colors[gl_VertexIndex];
}

Binary file not shown.

2
docs/.gitignore vendored
View file

@ -1,3 +1,5 @@
_build/
generated/
html/
xml/

86
docs/Doxyfile Normal file
View file

@ -0,0 +1,86 @@
TARGET = ./
INPUT = "../modules"
RECURSIVE = YES
PROJECT_NAME = "Light"
JAVADOC_AUTOBRIEF = YES
JAVADOC_BANNER = YES
GENERATE_XML = YES
EXTRACT_PRIVATE = NO
EXTRACT_STATIC = NO
EXTRACT_LOCAL_CLASSES = NO
HIDE_UNDOC_RELATIONS = YES
HAVE_DOT = NO
GENERATE_TODOLIST = NO
GENERATE_HTML = NO
GENERATE_DOCSET = NO
GENERATE_HTMLHELP = NO
GENERATE_CHI = NO
GENERATE_QHP = NO
GENERATE_ECLIPSEHELP = NO
GENERATE_TREEVIEW = NO
GENERATE_LATEX = NO
GENERATE_RTF = NO
GENERATE_MAN = NO
GENERATE_DOCBOOK = NO
GENERATE_AUTOGEN_DEF = NO
GENERATE_SQLITE3 = NO
GENERATE_PERLMOD = NO
GENERATE_TAGFILE = NO
GENERATE_LEGEND = NO
GENERATE_TESTLIST = NO
GENERATE_BUGLIST = NO
GENERATE_DEPRECATEDLIST= NO
FILE_PATTERNS = *.c \
*.cc \
*.cxx \
*.cxxm \
*.cpp \
*.cppm \
*.ccm \
*.c++ \
*.c++m \
*.java \
*.ii \
*.ixx \
*.ipp \
*.i++ \
*.inl \
*.idl \
*.ddl \
*.odl \
*.h \
*.hh \
*.hxx \
*.hpp \
*.h++ \
*.l \
*.cs \
*.d \
*.php \
*.php4 \
*.php5 \
*.phtml \
*.inc \
*.m \
*.markdown \
*.md \
*.mm \
*.dox \
*.py \
*.pyw \
*.f90 \
*.f95 \
*.f03 \
*.f08 \
*.f18 \
*.f \
*.for \
*.vhd \
*.vhdl \
*.ucf \
*.qsf \
*.ice

17
docs/api/app.rst Normal file
View file

@ -0,0 +1,17 @@
Application
===================================================================================================
.. toctree::
:maxdepth: 3
:caption: App
Functions
---------------------------------------------------------------------------------------------------
.. doxygenfunction:: main
Classes
---------------------------------------------------------------------------------------------------
.. doxygenclass:: lt::app::ISystem
.. doxygenstruct:: lt::app::TickInfo
.. doxygenstruct:: lt::app::TickResult

13
docs/api/renderer.rst Normal file
View file

@ -0,0 +1,13 @@
Renderer
===================================================================================================
.. toctree::
:maxdepth: 3
:caption: App
Classes
---------------------------------------------------------------------------------------------------
.. doxygenenum:: lt::renderer::Api
.. doxygenclass:: lt::renderer::System
.. doxygenstruct:: lt::renderer::components::Sprite

View file

@ -13,13 +13,21 @@ author = 'light7734'
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = []
extensions = ['breathe']
breathe_projects = {"Light": "./xml"}
breathe_default_project = "Light"
breathe_default_members = ()
# Tell sphinx what the primary language being documented is.
primary_domain = 'cpp'
# Tell sphinx what the pygments highlight language should be.
highlight_language = 'cpp'
templates_path = ['_templates']
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output

View file

@ -1,68 +0,0 @@
from git import Repo
import re
repo = Repo(search_parent_directories=True)
assert not repo.bare
file_path = "generated/changelog.rst"
messages = []
short_shas = []
hex_shas = []
logs = []
remote_url = "https://git.light7734.com/light7734/light/commit"
def format_log(commit_type, message, major, minor, patch, short_sha, hex_sha):
href = f"{remote_url}/{hex_sha}"
version = f"{major}.{minor}.{patch}-kitten+{short_sha}";
link = f"`{version} <{remote_url}/{hex_sha}>`__"
return f"| **{message}** ({link})"
for commit in repo.iter_commits():
messages.append(commit.summary)
short_shas.append(repo.git.rev_parse(commit.hexsha, short=5))
hex_shas.append(commit.hexsha)
ver_major = 0
ver_minor = 0
ver_patch = 0
idx = len(messages)
for message in reversed(messages):
idx = idx - 1;
commit_type = re.match("^(feat|fix|refactor|perf|build|asset|test|chore|ci|docs)", message)
if not commit_type:
continue
match commit_type.group(0):
case "feat":
ver_minor = ver_minor + 1
ver_patch = 0
case "fix":
ver_patch = ver_patch + 1
case "refactor":
ver_patch = ver_patch + 1
case "perf":
ver_patch = ver_patch + 1
case "build":
ver_patch = ver_patch + 1
case "asset":
ver_patch = ver_patch + 1
logs.append(format_log(commit_type, message, ver_major, ver_minor, ver_patch, short_shas[idx], hex_shas[idx]))
with open(file_path, "w") as f:
f.write(".. changelogs\n\n\n")
f.write("Changelogs\n")
f.write("==================================================\n\n")
f.write("KITTEN\n")
f.write("--------------------------------------------------\n\n")
for log in reversed(logs):
f.write(log + '\n')

View file

@ -23,10 +23,10 @@
guidelines/conventions.rst
.. toctree::
:maxdepth: 2
:caption: Generated Docs
:maxdepth: 3
:caption: API
generated/api.rst
generated/changelog.rst
api/app.rst
api/renderer.rst

View file

@ -1,35 +0,0 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)
if "%1" == "" goto help
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

View file

@ -1,26 +1,37 @@
# engine
add_subdirectory(./std)
add_subdirectory(./bitwise)
add_subdirectory(./env)
add_subdirectory(./memory)
add_subdirectory(./time)
add_subdirectory(./logger)
add_subdirectory(./debug)
add_subdirectory(./math)
#
add_subdirectory(./asset_baker)
add_subdirectory(./assets)
#
add_subdirectory(./camera)
add_subdirectory(./input)
# add_subdirectory(./ui)
#
add_subdirectory(./surface)
add_subdirectory(./renderer)
add_subdirectory(./ecs)
#
add_subdirectory(./app)
# engine add_subdirectory(./std)
# apps
add_subdirectory(./mirror)
add_subdirectory(test)
add_subdirectory(./logger)
add_subdirectory(./bitwise)
add_subdirectory(./env)
add_subdirectory(./memory)
add_subdirectory(./time)
add_subdirectory(./debug)
add_subdirectory(./math)
add_subdirectory(./assets)
add_subdirectory(./asset_baker)
add_subdirectory(./camera)
add_subdirectory(./app)
add_subdirectory(./ecs)
add_subdirectory(./surface)
add_subdirectory(./input)
# add_subdirectory(./ui)
# add_subdirectory(./renderer)
#
# add_subdirectory(./mirror)

View file

@ -1,4 +1,12 @@
add_library_module(app application.cpp)
add_library_module(
NAME
app
INTERFACES
application.cppm
system.cppm
SOURCES
entrypoint.cpp)
target_link_libraries(
app
PUBLIC memory

View file

@ -0,0 +1,98 @@
export module app;
import app.system;
import memory.reference;
import memory.scope;
import std;
namespace lt::app {
/** The main application class.
* Think of this like an aggregate of systems, you register systems through this interface.
* Then they'll tick every "application frame".
*/
export class Application
{
public:
Application(const Application &) = delete;
Application(Application &&) = delete;
auto operator=(const Application &) -> Application & = delete;
auto operator=(Application &&) -> Application & = delete;
virtual ~Application() = default;
void game_loop();
void register_system(memory::Ref<app::ISystem> system);
void unregister_system(memory::Ref<app::ISystem> system);
protected:
Application() = default;
private:
std::vector<memory::Ref<app::ISystem>> m_systems;
std::vector<memory::Ref<app::ISystem>> m_systems_to_be_unregistered;
std::vector<memory::Ref<app::ISystem>> m_systems_to_be_registered;
};
export extern memory::Scope<class Application> create_application();
} // namespace lt::app
module :private;
namespace lt::app {
void Application::game_loop()
{
while (true)
{
for (auto &system : m_systems)
{
const auto &last_tick = system->get_last_tick_result();
const auto now = std::chrono::steady_clock::now();
system->tick(
TickInfo {
.delta_time = now - last_tick.end_time,
.budget = std::chrono::milliseconds { 10 },
.start_time = now,
}
);
}
for (auto &system : m_systems_to_be_registered)
{
m_systems.emplace_back(system)->on_register();
}
for (auto &system : m_systems_to_be_unregistered)
{
m_systems.erase(
std::remove(m_systems.begin(), m_systems.end(), system),
m_systems.end()
);
}
if (m_systems.empty())
{
return;
}
}
}
void Application::register_system(memory::Ref<app::ISystem> system)
{
m_systems.emplace_back(std::move(system));
}
void Application::unregister_system(memory::Ref<app::ISystem> system)
{
m_systems_to_be_unregistered.emplace_back(std::move(system));
}
} // namespace lt::app

View file

@ -0,0 +1,31 @@
import memory.scope;
import logger;
import app;
import std;
/** The ultimate entrypoint. */
auto main(int argc, char *argv[]) -> std::int32_t
{
try
{
std::ignore = argc;
std::ignore = argv;
auto application = lt::memory::Scope<lt::app::Application> {};
application = lt::app::create_application();
if (!application)
{
throw std::runtime_error { "Failed to create application\n" };
}
application->game_loop();
return 0;
}
catch (const std::exception &exp)
{
lt::log::critical("Terminating due to uncaught exception:");
lt::log::critical("\texception.what(): {}", exp.what());
return 1;
}
}

View file

@ -1,55 +0,0 @@
#include <app/application.hpp>
#include <app/system.hpp>
#include <memory/reference.hpp>
namespace lt::app {
void Application::game_loop()
{
while (true)
{
for (auto &system : m_systems)
{
const auto &last_tick = system->get_last_tick_result();
const auto now = std::chrono::steady_clock::now();
system->tick(
TickInfo {
.delta_time = now - last_tick.end_time,
.budget = std::chrono::milliseconds { 10 },
.start_time = now,
}
);
}
for (auto &system : m_systems_to_be_registered)
{
m_systems.emplace_back(system)->on_register();
}
for (auto &system : m_systems_to_be_unregistered)
{
m_systems.erase(
std::remove(m_systems.begin(), m_systems.end(), system),
m_systems.end()
);
}
if (m_systems.empty())
{
return;
}
}
}
void Application::register_system(memory::Ref<app::ISystem> system)
{
m_systems.emplace_back(std::move(system));
}
void Application::unregister_system(memory::Ref<app::ISystem> system)
{
m_systems_to_be_unregistered.emplace_back(std::move(system));
}
} // namespace lt::app

View file

@ -1,47 +0,0 @@
#pragma once
#include <memory/reference.hpp>
#include <memory/scope.hpp>
namespace lt::app {
class ISystem;
extern memory::Scope<class Application> create_application();
/** The main application class.
* Think of this like an aggregate of systems, you register systems through this interface.
* Then they'll tick every "application frame".
*/
class Application
{
public:
Application(const Application &) = delete;
Application(Application &&) = delete;
auto operator=(const Application &) -> Application & = delete;
auto operator=(Application &&) -> Application & = delete;
virtual ~Application() = default;
void game_loop();
void register_system(memory::Ref<app::ISystem> system);
void unregister_system(memory::Ref<app::ISystem> system);
protected:
Application() = default;
private:
std::vector<memory::Ref<app::ISystem>> m_systems;
std::vector<memory::Ref<app::ISystem>> m_systems_to_be_unregistered;
std::vector<memory::Ref<app::ISystem>> m_systems_to_be_registered;
};
} // namespace lt::app

View file

@ -1,27 +0,0 @@
#pragma once
#include <app/application.hpp>
#include <memory/scope.hpp>
auto main(int argc, char *argv[]) -> int32_t
try
{
std::ignore = argc;
std::ignore = argv;
auto application = lt::memory::Scope<lt::app::Application> {};
application = lt::app::create_application();
if (!application)
{
throw std::runtime_error { "Failed to create application\n" };
}
application->game_loop();
return EXIT_SUCCESS;
}
catch (const std::exception &exp)
{
log_crt("Terminating due to uncaught exception:");
log_crt("\texception.what(): {}", exp.what());
return EXIT_FAILURE;
}

View file

@ -1,13 +1,13 @@
#pragma once
#include <chrono>
export module app.system;
import logger;
import std;
namespace lt::app {
/** Information required to tick a system.
* @note May be used across an entire application-frame (consisting of multiple systems ticking)
*/
struct TickInfo
export struct TickInfo
{
using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>;
@ -30,7 +30,7 @@ struct TickInfo
};
/** Information about how a system's tick performed */
struct TickResult
export struct TickResult
{
using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>;
@ -46,10 +46,9 @@ struct TickResult
Timepoint_T end_time;
};
struct SystemDiagnosis
export struct SystemDiagnosis
{
enum class Severity : uint8_t
enum class Severity : std::uint8_t
{
verbose,
info,
@ -65,14 +64,14 @@ struct SystemDiagnosis
Severity severity;
};
class SystemStats
export class SystemStats
{
public:
void push_diagnosis(SystemDiagnosis &&diagnosis)
{
auto diag = m_diagnosis.emplace_back(std::move(diagnosis));
log_dbg("message: {}", diag.message);
log::debug("message: {}", diag.message);
}
[[nodiscard]] auto empty_diagnosis() const -> bool
@ -84,7 +83,7 @@ private:
std::vector<SystemDiagnosis> m_diagnosis;
};
class ISystem
export class ISystem
{
public:
ISystem() = default;

View file

@ -1,6 +1,5 @@
add_library_module(libasset_baker bakers.cpp)
target_link_libraries(libasset_baker PUBLIC assets logger lt_debug tbb)
add_test_module(libasset_baker bakers.test.cpp)
add_library_module(NAME libasset_baker INTERFACES bakers.cppm)
target_link_libraries(libasset_baker PUBLIC assets logger lt_debug)
add_executable_module(asset_baker entrypoint/baker.cpp)
add_executable(asset_baker entrypoint.cpp)
target_link_libraries(asset_baker PRIVATE libasset_baker)

View file

@ -0,0 +1,68 @@
export module bakers;
import debug.assertions;
import assets.metadata;
import assets.shader;
import logger;
import std;
export void bake_shader(
const std::filesystem::path &in_path,
const std::filesystem::path &out_path,
lt::assets::ShaderAsset::Type type
)
{
using lt::assets::ShaderAsset;
using enum lt::assets::ShaderAsset::Type;
auto glsl_path = in_path.string();
auto spv_path = std::format("{}.spv", glsl_path);
lt::log::trace(
"Compiling {} shader {} -> {}",
type == vertex ? "vertex" : "fragment",
glsl_path,
spv_path
);
// Don't bother linking to shaderc, just invoke the command with a system call.
// NOLINTNEXTLINE(concurrency-mt-unsafe)
std::system(
std::format(
"glslc --target-env=vulkan1.4 -std=450core -fshader-stage={} {} -o {}",
type == vertex ? "vert" : "frag",
glsl_path,
spv_path
)
.c_str()
);
auto stream = std::ifstream(spv_path, std::ios::binary);
lt::debug::ensure(
stream.is_open(),
"Failed to open compiled {} shader at: {}",
type == vertex ? "vert" : "frag",
spv_path
);
stream.seekg(0, std::ios::end);
const auto size = stream.tellg();
auto bytes = std::vector<std::byte>(size);
stream.seekg(0, std::ios::beg);
stream.read((char *)bytes.data(), size); // NOLINT
lt::log::debug("BYTES: {}", bytes.size());
stream.close();
std::filesystem::remove(spv_path);
ShaderAsset::pack(
out_path,
lt::assets::AssetMetadata {
.version = lt::assets::current_version,
.type = ShaderAsset::asset_type_identifier,
},
ShaderAsset::Metadata {
.type = type,
},
std::move(bytes)
);
}

View file

@ -1,7 +1,10 @@
#include <asset_baker/bakers.hpp>
#include <assets/shader.hpp>
import assets.shader;
import logger;
import bakers;
import std;
auto main(int argc, char *argv[]) -> int32_t
auto main(int argc, char *argv[]) -> std::int32_t
try
{
if (argc != 2)
@ -30,12 +33,12 @@ try
}
}
return EXIT_SUCCESS;
return 0;
}
catch (const std::exception &exp)
{
log_crt("Terminating due to uncaught exception:");
log_crt("\texception.what: {}:", exp.what());
lt::log::critical("Terminating due to uncaught exception:");
lt::log::critical("\texception.what: {}:", exp.what());
return EXIT_FAILURE;
return 1;
}

View file

@ -1,2 +0,0 @@
#include <asset_baker/bakers.hpp>
#include <test/test.hpp>

View file

@ -1,6 +1,5 @@
#pragma once
#include <assets/shader.hpp>
inline void bake_shader(
const std::filesystem::path &in_path,
@ -13,7 +12,7 @@ inline void bake_shader(
auto glsl_path = in_path.string();
auto spv_path = std::format("{}.spv", glsl_path);
log_trc(
lt::log::trace(
"Compiling {} shader {} -> {}",
type == vertex ? "vertex" : "fragment",
glsl_path,
@ -33,7 +32,7 @@ inline void bake_shader(
);
auto stream = std::ifstream(spv_path, std::ios::binary);
lt::ensure(
lt::debug::ensure(
stream.is_open(),
"Failed to open compiled {} shader at: {}",
type == vertex ? "vert" : "frag",
@ -46,7 +45,7 @@ inline void bake_shader(
auto bytes = std::vector<std::byte>(size);
stream.seekg(0, std::ios::beg);
stream.read((char *)bytes.data(), size); // NOLINT
log_dbg("BYTES: {}", bytes.size());
lt::log::debug("BYTES: {}", bytes.size());
stream.close();
std::filesystem::remove(spv_path);

View file

@ -1,5 +1,3 @@
add_library_module(assets shader.cpp)
add_library_module(NAME assets INTERFACES shader.cppm metadata.cppm)
target_link_libraries(assets PUBLIC logger lt_debug)
add_test_module(assets shader.test.cpp)

View file

@ -1,18 +1,19 @@
#pragma once
export module assets.metadata;
import std;
namespace lt::assets {
export namespace lt::assets {
using Type_T = std::array<const char, 16>;
using Tag_T = uint8_t;
using Tag_T = std::uint8_t;
using Version = uint8_t;
using Version = std::uint8_t;
using Blob = std::vector<std::byte>;
constexpr auto current_version = Version { 1u };
enum class CompressionType : uint8_t
enum class CompressionType : std::uint8_t
{
none,
lz4,
@ -30,13 +31,13 @@ struct BlobMetadata
{
Tag_T tag;
size_t offset;
std::size_t offset;
CompressionType compression_type;
size_t compressed_size;
std::size_t compressed_size;
size_t uncompressed_size;
std::size_t uncompressed_size;
};
} // namespace lt::assets

View file

@ -1,3 +0,0 @@
#pragma once
// TO BE DOOO

View file

@ -1,74 +0,0 @@
#pragma once
#include <assets/metadata.hpp>
namespace lt::assets {
class ShaderAsset
{
public:
static constexpr auto asset_type_identifier = Type_T { "SHADER_________" };
enum class BlobTag : Tag_T
{
code,
};
enum class Type : uint8_t
{
vertex,
fragment,
geometry,
compute,
};
struct Metadata
{
Type type;
};
static void pack(
const std::filesystem::path &destination,
AssetMetadata asset_metadata,
Metadata metadata,
Blob code_blob
);
ShaderAsset(const std::filesystem::path &path);
void unpack_to(BlobTag tag, std::span<std::byte> destination) const;
[[nodiscard]] auto unpack(BlobTag tag) const -> Blob;
[[nodiscard]] auto get_asset_metadata() const -> const AssetMetadata &
{
return m_asset_metadata;
}
[[nodiscard]] auto get_metadata() const -> const Metadata &
{
return m_metadata;
}
[[nodiscard]] auto get_blob_metadata(BlobTag tag) const -> const BlobMetadata &
{
ensure(
tag == BlobTag::code,
"Invalid blob tag for shader asset: {}",
std::to_underlying(tag)
);
return m_code_blob_metadata;
}
private:
AssetMetadata m_asset_metadata {};
Metadata m_metadata {};
BlobMetadata m_code_blob_metadata {};
mutable std::ifstream m_stream;
};
} // namespace lt::assets

View file

@ -1,4 +1,80 @@
#include <assets/shader.hpp>
export module assets.shader;
import assets.metadata;
import debug.assertions;
import std;
export namespace lt::assets {
class ShaderAsset
{
public:
static constexpr auto asset_type_identifier = Type_T { "SHADER_________" };
enum class BlobTag : Tag_T
{
code,
};
enum class Type : std::uint8_t
{
vertex,
fragment,
geometry,
compute,
};
struct Metadata
{
Type type;
};
static void pack(
const std::filesystem::path &destination,
AssetMetadata asset_metadata,
Metadata metadata,
Blob code_blob
);
ShaderAsset(const std::filesystem::path &path);
void unpack_to(BlobTag tag, std::span<std::byte> destination) const;
[[nodiscard]] auto unpack(BlobTag tag) const -> Blob;
[[nodiscard]] auto get_asset_metadata() const -> const AssetMetadata &
{
return m_asset_metadata;
}
[[nodiscard]] auto get_metadata() const -> const Metadata &
{
return m_metadata;
}
[[nodiscard]] auto get_blob_metadata(BlobTag tag) const -> const BlobMetadata &
{
debug::ensure(
tag == BlobTag::code,
"Invalid blob tag for shader asset: {}",
std::to_underlying(tag)
);
return m_code_blob_metadata;
}
private:
AssetMetadata m_asset_metadata {};
Metadata m_metadata {};
BlobMetadata m_code_blob_metadata {};
mutable std::ifstream m_stream;
};
} // namespace lt::assets
namespace lt::assets {
@ -14,14 +90,14 @@ constexpr auto total_metadata_size = //
ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path)
{
ensure(m_stream.is_open(), "Failed to open shader asset at: {}", path.string());
debug::ensure(m_stream.is_open(), "Failed to open shader asset at: {}", path.string());
const auto read = [this](auto &field) {
m_stream.read(std::bit_cast<char *>(&field), sizeof(field));
};
m_stream.seekg(0, std::ifstream::end);
const auto file_size = static_cast<size_t>(m_stream.tellg());
ensure(
const auto file_size = static_cast<std::size_t>(m_stream.tellg());
debug::ensure(
file_size > total_metadata_size,
"Failed to open shader asset at: {}, file smaller than metadata: {} < {}",
path.string(),
@ -39,7 +115,7 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path)
read(m_code_blob_metadata.compressed_size);
read(m_code_blob_metadata.uncompressed_size);
ensure(
debug::ensure(
m_asset_metadata.type == asset_type_identifier,
"Failed to open shader asset at: {}, incorrect asset type: {} != {}",
path.string(),
@ -47,7 +123,7 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path)
asset_type_identifier
);
ensure(
debug::ensure(
m_asset_metadata.version == current_version,
"Failed to open shader asset at: {}, version mismatch: {} != {}",
path.string(),
@ -55,21 +131,21 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path)
current_version
);
ensure(
debug::ensure(
std::to_underlying(m_metadata.type) <= std::to_underlying(Type::compute),
"Failed to open shader asset at: {}, invalid shader type: {}",
path.string(),
std::to_underlying(m_metadata.type)
);
ensure(
debug::ensure(
m_code_blob_metadata.tag == std::to_underlying(BlobTag::code),
"Failed to open shader asset at: {}, invalid blob tag: {}",
path.string(),
m_code_blob_metadata.tag
);
ensure(
debug::ensure(
m_code_blob_metadata.offset + m_code_blob_metadata.compressed_size <= file_size,
"Failed to open shader asset at: {}, file smaller than blob: {} > {} + {}",
path.string(),
@ -99,7 +175,7 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path)
.uncompressed_size = code_blob.size(),
};
ensure(stream.is_open(), "Failed to pack shader asset to {}", destination.string());
debug::ensure(stream.is_open(), "Failed to pack shader asset to {}", destination.string());
const auto write = [&stream](auto &field) {
stream.write(std::bit_cast<char *>(&field), sizeof(field));
};
@ -116,14 +192,18 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path)
void ShaderAsset::unpack_to(BlobTag tag, std::span<std::byte> destination) const
{
ensure(tag == BlobTag::code, "Invalid blob tag for shader asset: {}", std::to_underlying(tag));
debug::ensure(
tag == BlobTag::code,
"Invalid blob tag for shader asset: {}",
std::to_underlying(tag)
);
ensure(
debug::ensure(
destination.size() >= m_code_blob_metadata.uncompressed_size,
"Failed to unpack shader blob {} to destination ({}) of size {} since it's smaller "
"than the blobl's uncompressed size: {}",
std::to_underlying(tag),
std::bit_cast<size_t>(destination.data()),
std::bit_cast<std::size_t>(destination.data()),
destination.size(),
m_code_blob_metadata.uncompressed_size
);
@ -137,7 +217,11 @@ void ShaderAsset::unpack_to(BlobTag tag, std::span<std::byte> destination) const
[[nodiscard]] auto ShaderAsset::unpack(BlobTag tag) const -> Blob
{
ensure(tag == BlobTag::code, "Invalid blob tag for shader asset: {}", std::to_underlying(tag));
debug::ensure(
tag == BlobTag::code,
"Invalid blob tag for shader asset: {}",
std::to_underlying(tag)
);
auto blob = Blob(m_code_blob_metadata.uncompressed_size);
unpack_to(tag, blob);

View file

@ -1,6 +1,8 @@
#include <assets/shader.hpp>
#include <ranges>
#include <test/test.hpp>
import assets.metadata;
import assets.shader;
import test.test;
import test.expects;
import std;
using ::lt::assets::AssetMetadata;
using ::lt::assets::BlobMetadata;
@ -10,6 +12,7 @@ using ::lt::test::expect_eq;
using ::lt::test::expect_throw;
using ::lt::test::expect_true;
using ::lt::test::Suite;
using ::lt::test::operator""_suite;
const auto test_data_path = std::filesystem::path { "./data/test_assets" };
const auto tmp_path = std::filesystem::path { "/tmp/lt_assets_tests/" };
@ -70,7 +73,7 @@ Suite packing = "shader_pack"_suite = [] {
expect_true(stream.is_open());
stream.seekg(0, std::ios::end);
const auto file_size = static_cast<size_t>(stream.tellg());
const auto file_size = static_cast<std::size_t>(stream.tellg());
expect_eq(file_size, expected_size);
stream.close();

View file

@ -1 +1 @@
add_library_module(bitwise)
add_library_module(NAME bitwise INTERFACES operations.cppm)

View file

@ -1,11 +1,10 @@
#pragma once
#include <cstdint>
export module bitwise;
import std;
namespace lt::bitwise {
/* bit-wise */
constexpr auto bit(uint32_t x) -> uint32_t
constexpr auto bit(std::uint32_t x) -> std::uint32_t
{
return 1u << x;
}

View file

@ -1,3 +1,3 @@
add_library_module(camera camera.cpp scene.cpp)
add_library_module(NAME camera INTERFACES components.cppm)
target_link_libraries(camera PUBLIC math)

View file

@ -0,0 +1,21 @@
export module camera.components;
import math.vec4;
namespace lt::camera::components {
export struct PerspectiveCamera
{
float vertical_fov {};
float near_plane {};
float far_plane {};
float aspect_ratio {};
math::vec4 background_color;
bool is_primary {};
};
} // namespace lt::camera::components

View file

@ -1,6 +0,0 @@
#include <camera/camera.hpp>
namespace lt {
}

View file

@ -1,84 +0,0 @@
#include <camera/camera.hpp>
#include <camera/component.hpp>
#include <math/algebra.hpp>
#include <math/trig.hpp>
namespace lt {
SceneCamera::SceneCamera()
: m_orthographic_specification { .size = 1000.0f, .near_plane = -1.0f, .far_plane = 10000.0f }
, m_perspective_specification { .vertical_fov = math::radians(45.0f),
.near_plane = 0.01f,
.far_plane = 10000.0f }
, m_aspect_ratio(16.0f / 9.0f)
{
calculate_projection();
}
void SceneCamera::set_viewport_size(unsigned int width, unsigned int height)
{
m_aspect_ratio = static_cast<float>(width) / static_cast<float>(height);
calculate_projection();
}
void SceneCamera::set_projection_type(ProjectionType projection_type)
{
m_projection_type = projection_type;
calculate_projection();
}
void SceneCamera::set_orthographic_size(float size)
{
m_orthographic_specification.size = size;
calculate_projection();
}
void SceneCamera::set_orthographic_far_plane(float far_plane)
{
m_orthographic_specification.far_plane = far_plane;
calculate_projection();
}
void SceneCamera::set_orthographic_near_plane(float near_plane)
{
m_orthographic_specification.near_plane = near_plane;
calculate_projection();
}
void SceneCamera::set_perspective_vertical_fov(float vertical_fov)
{
m_perspective_specification.vertical_fov = vertical_fov;
calculate_projection();
}
void SceneCamera::set_perspective_far_plane(float far_plane)
{
m_perspective_specification.far_plane = far_plane;
calculate_projection();
}
void SceneCamera::set_perspective_near_plane(float near_plane)
{
m_perspective_specification.near_plane = near_plane;
calculate_projection();
}
void SceneCamera::calculate_projection()
{
// TODO(Light): implement ortho perspective
if (m_projection_type == ProjectionType::Orthographic)
{
// throw std::runtime_error { "ortho perspective not supported yet" };
}
// defaults to perspective for now...
m_projection = math::perspective(
m_perspective_specification.vertical_fov,
m_aspect_ratio,
m_perspective_specification.near_plane,
m_perspective_specification.far_plane
);
}
} // namespace lt

View file

@ -1,35 +0,0 @@
#pragma once
#include <math/mat4.hpp>
#include <math/vec4.hpp>
namespace lt {
class Camera
{
public:
Camera() = default;
[[nodiscard]] auto get_projection() const -> const math::mat4 &
{
return m_projection;
}
[[nodiscard]] auto get_background_color() const -> const math::vec4 &
{
return m_background_color;
}
void set_background_color(const math::vec4 &color)
{
m_background_color = color;
}
protected:
math::mat4 m_projection;
private:
math::vec4 m_background_color = math::vec4(1.0f, 0.0f, 0.0f, 1.0f);
};
} // namespace lt

View file

@ -1,29 +0,0 @@
#pragma once
#include <camera/scene.hpp>
namespace lt {
struct CameraComponent
{
CameraComponent() = default;
CameraComponent(const CameraComponent &) = default;
CameraComponent(SceneCamera _camera, bool _isPrimary = false)
: camera(_camera)
, isPrimary(_isPrimary)
{
}
operator SceneCamera() const
{
return camera;
}
SceneCamera camera;
bool isPrimary {};
};
} // namespace lt

View file

@ -1,100 +0,0 @@
#pragma once
#include <camera/camera.hpp>
namespace lt {
class SceneCamera: public Camera
{
public:
enum class ProjectionType
{
Orthographic = 0,
Perspetcive = 1
};
struct OrthographicSpecification
{
float size;
float near_plane;
float far_plane;
};
struct PerspectiveSpecification
{
float vertical_fov;
float near_plane;
float far_plane;
};
SceneCamera();
void set_viewport_size(unsigned int width, unsigned int height);
void set_projection_type(ProjectionType projection_type);
void set_orthographic_size(float size);
void set_orthographic_far_plane(float far_plane);
void set_orthographic_near_plane(float near_plane);
void set_perspective_vertical_fov(float vertical_fov);
void set_perspective_far_plane(float far_plane);
void set_perspective_near_plane(float near_plane);
[[nodiscard]] auto get_orthographic_size() const -> float
{
return m_orthographic_specification.size;
}
[[nodiscard]] auto get_orthographic_far_plane() const -> float
{
return m_orthographic_specification.far_plane;
}
[[nodiscard]] auto get_orthographic_near_plane() const -> float
{
return m_orthographic_specification.near_plane;
}
[[nodiscard]] auto get_perspective_vertical_fov() const -> float
{
return m_perspective_specification.vertical_fov;
}
[[nodiscard]] auto get_perspective_far_plane() const -> float
{
return m_perspective_specification.far_plane;
}
[[nodiscard]] auto get_perspective_near_plane() const -> float
{
return m_perspective_specification.near_plane;
}
[[nodiscard]] auto get_projection_type() const -> ProjectionType
{
return m_projection_type;
}
private:
OrthographicSpecification m_orthographic_specification;
PerspectiveSpecification m_perspective_specification;
float m_aspect_ratio;
ProjectionType m_projection_type { ProjectionType::Orthographic };
void calculate_projection();
};
} // namespace lt

View file

@ -1,4 +1,2 @@
add_library_module(lt_debug instrumentor.cpp)
add_library_module(NAME lt_debug INTERFACES instrumentor.cppm assertions.cppm)
target_link_libraries(lt_debug PUBLIC logger)
target_precompile_headers(lt_debug PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/private/pch.hpp)

View file

@ -0,0 +1,47 @@
export module debug.assertions;
import std;
namespace lt::debug {
///////////////////////////////////////
// ----------* INTERFACE *--------- //
/////////////////////////////////////
export template<typename Expression_T, typename... Args_T>
struct ensure
{
ensure(
const Expression_T &expression,
std::format_string<Args_T...> fmt,
Args_T &&...args,
const std::source_location &location = std::source_location::current()
);
};
export template<typename Expression_T, typename... Args_T>
ensure(Expression_T, std::format_string<Args_T...>, Args_T &&...)
-> ensure<Expression_T, Args_T...>;
///////////////////////////////////////
// * IMPLEMENTATION -- TEMPLATES * //
/////////////////////////////////////
template<typename Expression_T, typename... Args_T>
ensure<Expression_T, Args_T...>::ensure(
const Expression_T &expression,
std::format_string<Args_T...> fmt,
Args_T &&...args,
const std::source_location &location
)
{
if (!static_cast<bool>(expression))
{
throw std::runtime_error { std::format(
"exception: {}\nlocation: {}:{}",
std::format(fmt, std::forward<Args_T>(args)...),
location.file_name(),
location.line()
) };
}
}
} // namespace lt::debug

View file

@ -0,0 +1,145 @@
export module debug.instrumentor;
import std;
import logger;
namespace lt::debug {
struct ScopeProfileResult
{
std::string name;
long long start, duration;
std::uint32_t threadID;
};
class Instrumentor
{
public:
static auto instance() -> Instrumentor &
{
static auto instance = Instrumentor {};
return instance;
}
static void begin_session(const std::string &outputPath)
{
instance().begin_session_impl(outputPath);
}
static void end_session()
{
instance().end_session_impl();
}
static void submit_scope_profile(const ScopeProfileResult &profileResult)
{
instance().submit_scope_profile_impl(profileResult);
}
private:
std::ofstream m_output_file_stream;
unsigned int m_current_session_count { 0u };
Instrumentor() = default;
void begin_session_impl(const std::string &outputPath);
void end_session_impl();
void submit_scope_profile_impl(const ScopeProfileResult &profileResult);
};
class InstrumentorTimer
{
public:
InstrumentorTimer(const std::string &scopeName);
~InstrumentorTimer();
private:
ScopeProfileResult m_result;
std::chrono::time_point<std::chrono::steady_clock> m_start;
};
} // namespace lt::debug
/* scope */
#define lt_profile_scope(name) lt_profile_scope_no_redifinition(name, __LINE__)
#define lt_profile_scope_no_redifinition(name, line) lt_profile_scope_no_redifinition2(name, line)
#define lt_profile_scope_no_redifinition2(name, line) InstrumentorTimer timer##line(name)
/* function */
#define LT_PROFILE_FUNCTION lt_profile_scope(__FUNCSIG__)
/* session */
#define lt_profile_begin_session(outputPath) ::lt::Instrumentor::begin_session(outputPath)
#define lt_profile_end_session() ::lt::Instrumentor::end_session()
module :private;
using namespace lt::debug;
void Instrumentor::begin_session_impl(const std::string &outputPath)
{
std::filesystem::create_directory(outputPath.substr(0, outputPath.find_last_of('/') + 1));
m_output_file_stream.open(outputPath);
m_output_file_stream << "{\"traceEvents\":[";
}
void Instrumentor::end_session_impl()
{
if (m_current_session_count == 0u)
{
log::warn("0 profiling for the ended session");
}
m_current_session_count = 0u;
m_output_file_stream << "]}";
m_output_file_stream.flush();
m_output_file_stream.close();
}
void Instrumentor::submit_scope_profile_impl(const ScopeProfileResult &profileResult)
{
if (m_current_session_count++ == 0u)
{
m_output_file_stream << "{";
}
else
{
m_output_file_stream << ",{";
}
m_output_file_stream << R"("name":")" << profileResult.name << "\",";
m_output_file_stream << R"("cat": "scope",)";
m_output_file_stream << R"("ph": "X",)";
m_output_file_stream << "\"ts\":" << profileResult.start << ",";
m_output_file_stream << "\"dur\":" << profileResult.duration << ",";
m_output_file_stream << "\"pid\":0,";
m_output_file_stream << "\"tid\":" << profileResult.threadID << "";
m_output_file_stream << "}";
}
InstrumentorTimer::InstrumentorTimer(const std::string &scopeName)
: m_result({ .name = scopeName, .start = 0, .duration = 0, .threadID = 0 })
, m_start(std::chrono::steady_clock::now())
{
}
InstrumentorTimer::~InstrumentorTimer()
{
auto end = std::chrono::steady_clock::now();
m_result.start = std::chrono::time_point_cast<std::chrono::microseconds>(m_start)
.time_since_epoch()
.count();
m_result.duration = std::chrono::time_point_cast<std::chrono::microseconds>(end)
.time_since_epoch()
.count()
- m_result.start;
Instrumentor::submit_scope_profile(m_result);
}

View file

@ -1,71 +0,0 @@
#include <logger/logger.hpp>
#include <lt_debug/instrumentor.hpp>
namespace lt {
void Instrumentor::begin_session_impl(const std::string &outputPath)
{
std::filesystem::create_directory(outputPath.substr(0, outputPath.find_last_of('/') + 1));
m_output_file_stream.open(outputPath);
m_output_file_stream << "{\"traceEvents\":[";
}
void Instrumentor::end_session_impl()
{
if (m_current_session_count == 0u)
{
log_wrn("0 profiling for the ended session");
}
m_current_session_count = 0u;
m_output_file_stream << "]}";
m_output_file_stream.flush();
m_output_file_stream.close();
}
void Instrumentor::submit_scope_profile_impl(const ScopeProfileResult &profileResult)
{
if (m_current_session_count++ == 0u)
{
m_output_file_stream << "{";
}
else
{
m_output_file_stream << ",{";
}
m_output_file_stream << R"("name":")" << profileResult.name << "\",";
m_output_file_stream << R"("cat": "scope",)";
m_output_file_stream << R"("ph": "X",)";
m_output_file_stream << "\"ts\":" << profileResult.start << ",";
m_output_file_stream << "\"dur\":" << profileResult.duration << ",";
m_output_file_stream << "\"pid\":0,";
m_output_file_stream << "\"tid\":" << profileResult.threadID << "";
m_output_file_stream << "}";
}
InstrumentorTimer::InstrumentorTimer(const std::string &scopeName)
: m_result({ .name = scopeName, .start = 0, .duration = 0, .threadID = 0 })
, m_start(std::chrono::steady_clock::now())
{
}
InstrumentorTimer::~InstrumentorTimer()
{
auto end = std::chrono::steady_clock::now();
m_result.start = std::chrono::time_point_cast<std::chrono::microseconds>(m_start)
.time_since_epoch()
.count();
m_result.duration = std::chrono::time_point_cast<std::chrono::microseconds>(end)
.time_since_epoch()
.count()
- m_result.start;
Instrumentor::submit_scope_profile(m_result);
}
} // namespace lt

View file

@ -1,3 +0,0 @@
#pragma once
#include <lt_debug/assertions.hpp>

View file

@ -1,36 +0,0 @@
#pragma once
#include <format>
#include <logger/logger.hpp>
#include <source_location>
namespace lt {
template<typename Expression_T, typename... Args_T>
struct ensure
{
ensure(
const Expression_T &expression,
std::format_string<Args_T...> fmt,
Args_T &&...args,
const std::source_location &location = std::source_location::current()
)
{
if (!static_cast<bool>(expression))
{
throw std::runtime_error { std::format(
"exception: {}\nlocation: {}:{}",
std::format(fmt, std::forward<Args_T>(args)...),
location.file_name(),
location.line()
) };
}
}
};
template<typename Expression_T, typename... Args_T>
ensure(Expression_T, std::format_string<Args_T...>, Args_T &&...)
-> ensure<Expression_T, Args_T...>;
} // namespace lt

View file

@ -1,77 +0,0 @@
#pragma once
#include <chrono>
#include <fstream>
namespace lt {
struct ScopeProfileResult
{
std::string name;
long long start, duration;
uint32_t threadID;
};
class Instrumentor
{
public:
static auto instance() -> Instrumentor &
{
static auto instance = Instrumentor {};
return instance;
}
static void begin_session(const std::string &outputPath)
{
instance().begin_session_impl(outputPath);
}
static void end_session()
{
instance().end_session_impl();
}
static void submit_scope_profile(const ScopeProfileResult &profileResult)
{
instance().submit_scope_profile_impl(profileResult);
}
private:
std::ofstream m_output_file_stream;
unsigned int m_current_session_count { 0u };
Instrumentor() = default;
void begin_session_impl(const std::string &outputPath);
void end_session_impl();
void submit_scope_profile_impl(const ScopeProfileResult &profileResult);
};
class InstrumentorTimer
{
public:
InstrumentorTimer(const std::string &scopeName);
~InstrumentorTimer();
private:
ScopeProfileResult m_result;
std::chrono::time_point<std::chrono::steady_clock> m_start;
};
} // namespace lt
/* scope */
#define lt_profile_scope(name) lt_profile_scope_no_redifinition(name, __LINE__)
#define lt_profile_scope_no_redifinition(name, line) lt_profile_scope_no_redifinition2(name, line)
#define lt_profile_scope_no_redifinition2(name, line) InstrumentorTimer timer##line(name)
/* function */
#define LT_PROFILE_FUNCTION lt_profile_scope(__FUNCSIG__)
/* session */
#define lt_profile_begin_session(outputPath) ::lt::Instrumentor::begin_session(outputPath)
#define lt_profile_end_session() ::lt::Instrumentor::end_session()

View file

@ -1,4 +1,5 @@
add_library_module(ecs sparse_set.cpp)
add_library_module(NAME ecs INTERFACES sparse_set.cppm registry.cppm
entity.cppm)
target_link_libraries(ecs PUBLIC logger lt_debug memory)
add_test_module(ecs sparse_set.test.cpp registry.test.cpp)

View file

@ -1,19 +1,20 @@
#pragma once
#include <ecs/registry.hpp>
#include <memory/reference.hpp>
export module ecs.entity;
import debug.assertions;
import memory.reference;
import ecs.registry;
import std;
namespace lt::ecs {
/** High-level entity convenience wrapper */
class Entity
export class Entity
{
public:
Entity(memory::Ref<Registry> registry, EntityId identifier)
: m_registry(std::move(registry))
, m_identifier(identifier)
{
ensure(m_registry, "Failed to create Entity ({}): null registry", m_identifier);
debug::ensure(m_registry, "Failed to create Entity ({}): null registry", m_identifier);
}
template<typename Component_T>

View file

@ -1,13 +1,14 @@
#pragma once
#include <ecs/sparse_set.hpp>
#include <memory/scope.hpp>
export module ecs.registry;
import debug.assertions;
import ecs.sparse_set;
import memory.scope;
import std;
namespace lt::ecs {
using EntityId = uint32_t;
export using EntityId = std::uint32_t;
constexpr auto null_entity = std::numeric_limits<EntityId>::max();
export constexpr auto null_entity = std::numeric_limits<EntityId>::max();
/** A registry of components, the heart of an ECS architecture.
*
@ -22,7 +23,7 @@ constexpr auto null_entity = std::numeric_limits<EntityId>::max();
* @ref https://github.com/skypjack/entt
* @ref https://github.com/SanderMertens/flecs
*/
class Registry
export class Registry
{
public:
using UnderlyingSparseSet_T = TypeErasedSparseSet<EntityId>;
@ -189,25 +190,25 @@ public:
}
};
[[nodiscard]] auto get_entity_count() const -> size_t
[[nodiscard]] auto get_entity_count() const -> std::size_t
{
return static_cast<size_t>(m_entity_count);
return static_cast<std::size_t>(m_entity_count);
}
private:
using TypeId = size_t;
using TypeId = std::size_t;
static consteval auto hash_cstr(const char *str) -> TypeId
{
constexpr auto fnv_offset_basis = size_t { 14695981039346656037ull };
constexpr auto fnv_prime = size_t { 1099511628211ull };
constexpr auto fnv_offset_basis = std::size_t { 14695981039346656037ull };
constexpr auto fnv_prime = std::size_t { 1099511628211ull };
auto hash = fnv_offset_basis;
for (const auto &ch : std::string_view { str })
{
hash *= fnv_prime;
hash ^= static_cast<uint8_t>(ch);
hash ^= static_cast<std::uint8_t>(ch);
}
return hash;
@ -241,7 +242,7 @@ private:
auto *base_set = m_sparsed_sets[type_id].get();
auto *derived_set = dynamic_cast<SparseSet<T, EntityId> *>(base_set);
ensure(derived_set, "Failed to downcast to derived set");
debug::ensure(derived_set, "Failed to downcast to derived set");
return *derived_set;
}

View file

@ -1,19 +1,17 @@
#include <ecs/registry.hpp>
#include <ranges>
#include <test/expects.hpp>
#include <test/test.hpp>
import ecs.registry;
import test.test;
import test.expects;
import std;
using lt::test::Case;
using lt::test::expect_unreachable;
using lt::test::Suite;
using lt::test::expect_eq;
using lt::test::expect_false;
using lt::test::expect_true;
using lt::ecs::EntityId;
using lt::ecs::Registry;
using ::lt::ecs::EntityId;
using ::lt::ecs::Registry;
using ::lt::test::Case;
using ::lt::test::expect_eq;
using ::lt::test::expect_false;
using ::lt::test::expect_true;
using ::lt::test::expect_unreachable;
using ::lt::test::Suite;
using ::lt::test::operator""_suite;
struct Component
{

View file

@ -1,12 +1,13 @@
#pragma once
export module ecs.sparse_set;
import debug.assertions;
import std;
namespace lt::ecs {
/**
*
* @ref https://programmingpraxis.com/2012/03/09/sparse-sets/
*/
template<typename Identifier_T = uint32_t>
export template<typename Identifier_T = std::uint32_t>
class TypeErasedSparseSet
{
public:
@ -25,19 +26,19 @@ public:
virtual void remove(Identifier_T identifier) = 0;
};
template<typename Value_T, typename Identifier_T = uint32_t>
export template<typename Value_T, typename Identifier_T = std::uint32_t>
class SparseSet: public TypeErasedSparseSet<Identifier_T>
{
public:
using Dense_T = std::pair<Identifier_T, Value_T>;
static constexpr auto max_capacity = size_t { 1'000'000 };
static constexpr auto max_capacity = std::size_t { 1'000'000 };
static constexpr auto null_identifier = std::numeric_limits<Identifier_T>().max();
explicit SparseSet(size_t initial_capacity = 1)
explicit SparseSet(std::size_t initial_capacity = 1)
{
ensure(
debug::ensure(
initial_capacity <= max_capacity,
"Failed to create SparseSet: capacity too large ({} > {})",
initial_capacity,
@ -52,13 +53,16 @@ public:
{
if (m_sparse.size() < identifier + 1)
{
auto new_capacity = std::max(static_cast<size_t>(identifier + 1), m_sparse.size() * 2);
auto new_capacity = std::max(
static_cast<std::size_t>(identifier + 1),
m_sparse.size() * 2
);
new_capacity = std::min(new_capacity, max_capacity);
// log_dbg("Increasing sparse vector size:", m_dead_count);
// log_dbg("\tdead_count: {}", m_dead_count);
// log_dbg("\talive_count: {}", m_alive_count);
// log_dbg("\tsparse.size: {} -> {}", m_sparse.size(), new_capacity);
// log::debug("Increasing sparse vector size:", m_dead_count);
// log::debug("\tdead_count: {}", m_dead_count);
// log::debug("\talive_count: {}", m_alive_count);
// log::debug("\tsparse.size: {} -> {}", m_sparse.size(), new_capacity);
m_sparse.resize(new_capacity, null_identifier);
}
@ -145,12 +149,12 @@ public:
return std::forward<Self_T>(self).m_dense[std::forward<Self_T>(self).m_sparse[identifier]];
}
[[nodiscard]] auto get_size() const noexcept -> size_t
[[nodiscard]] auto get_size() const noexcept -> std::size_t
{
return m_alive_count;
}
[[nodiscard]] auto get_capacity() const noexcept -> size_t
[[nodiscard]] auto get_capacity() const noexcept -> std::size_t
{
return m_sparse.capacity();
}
@ -165,9 +169,9 @@ private:
std::vector<Identifier_T> m_sparse;
size_t m_alive_count {};
std::size_t m_alive_count {};
size_t m_dead_count {};
std::size_t m_dead_count {};
};
} // namespace lt::ecs

View file

@ -1,16 +1,16 @@
#include <ecs/sparse_set.hpp>
#include <ranges>
#include <test/expects.hpp>
#include <test/test.hpp>
import ecs.sparse_set;
import test.test;
import test.expects;
import std;
using lt::test::Case;
using lt::test::Suite;
using lt::test::expect_eq;
using lt::test::expect_false;
using lt::test::expect_ne;
using lt::test::expect_throw;
using lt::test::expect_true;
using ::lt::test::Case;
using ::lt::test::expect_eq;
using ::lt::test::expect_false;
using ::lt::test::expect_ne;
using ::lt::test::expect_throw;
using ::lt::test::expect_true;
using ::lt::test::Suite;
using ::lt::test::operator""_suite;
using Set = lt::ecs::SparseSet<int>;
constexpr auto capacity = 100;

View file

@ -1 +1 @@
add_library_module(env)
add_library_module(NAME env INTERFACES constants.cppm)

View file

@ -1,8 +1,10 @@
#pragma once
export module env;
import std;
namespace lt {
enum class Platform : uint8_t
enum class Platform : std::uint8_t
{
/** The GNU/Linux platform.
* Tested on the following distros: arch-x86_64
@ -24,7 +26,7 @@ enum class Platform : uint8_t
};
/** The compiler that was used for compiling the project. */
enum class Compiler : uint8_t
enum class Compiler : std::uint8_t
{
clang,
gcc,

View file

@ -1,4 +1,4 @@
add_library_module(input system.cpp)
add_library_module(NAME input INTERFACES system.cpp)
target_link_libraries(input PUBLIC surface math logger tbb)
add_test_module(input system.test.cpp)

View file

@ -2,7 +2,6 @@
#include <cstdint>
namespace lt::Key {
enum : uint16_t
{
@ -177,4 +176,21 @@ enum : uint16_t
};
}
enum : uint8_t
{
Button1 = 0,
Button2 = 1,
Button3 = 2,
Button4 = 3,
Button5 = 4,
Button6 = 5,
Button7 = 6,
Button8 = 7,
LButton = Button1,
RButton = Button2,
MButton = Button3,
};
} // namespace lt::Key

View file

@ -1,4 +1,4 @@
#pragma once
export module input.components;
#include <vector>

View file

@ -1,23 +0,0 @@
#pragma once
#include <cstdint>
namespace lt::Mouse {
enum : uint8_t
{
Button1 = 0,
Button2 = 1,
Button3 = 2,
Button4 = 3,
Button5 = 4,
Button6 = 5,
Button7 = 6,
Button8 = 7,
LButton = Button1,
RButton = Button2,
MButton = Button3,
};
}

View file

@ -1,55 +0,0 @@
#pragma once
#include <app/system.hpp>
#include <ecs/registry.hpp>
#include <memory/reference.hpp>
#include <surface/components.hpp>
#include <surface/events/keyboard.hpp>
#include <surface/events/mouse.hpp>
namespace lt::input {
class System: public app::ISystem
{
public:
System(memory::Ref<ecs::Registry> registry);
void tick(app::TickInfo tick) override;
void on_register() override;
void on_unregister() override;
[[nodiscard]] auto get_last_tick_result() const -> const app::TickResult & override
{
return m_last_tick_result;
}
private:
void handle_event(const surface::SurfaceComponent::Event &event);
void on_surface_lost_focus();
void on_key_press(const lt::surface::KeyPressedEvent &event);
void on_key_release(const lt::surface::KeyReleasedEvent &event);
void on_pointer_move(const lt::surface::MouseMovedEvent &event);
void on_button_press(const lt::surface::ButtonPressedEvent &event);
void on_button_release(const lt::surface::ButtonReleasedEvent &event);
memory::Ref<ecs::Registry> m_registry;
std::array<bool, 512> m_keys {};
std::array<bool, 512> m_buttons {};
math::vec2 m_pointer_position;
app::TickResult m_last_tick_result {};
};
} // namespace lt::input

View file

@ -1,3 +1,62 @@
#pragma once
#include <app/system.hpp>
#include <ecs/registry.hpp>
#include <memory/reference.hpp>
#include <surface/components.hpp>
#include <surface/events/keyboard.hpp>
#include <surface/events/mouse.hpp>
namespace lt::input {
class System: public app::ISystem
{
public:
System(memory::Ref<ecs::Registry> registry);
void tick(app::TickInfo tick) override;
void on_register() override;
void on_unregister() override;
[[nodiscard]] auto get_last_tick_result() const -> const app::TickResult & override
{
return m_last_tick_result;
}
private:
void handle_event(const surface::SurfaceComponent::Event &event);
void on_surface_lost_focus();
void on_key_press(const lt::surface::KeyPressedEvent &event);
void on_key_release(const lt::surface::KeyReleasedEvent &event);
void on_pointer_move(const lt::surface::MouseMovedEvent &event);
void on_button_press(const lt::surface::ButtonPressedEvent &event);
void on_button_release(const lt::surface::ButtonReleasedEvent &event);
memory::Ref<ecs::Registry> m_registry;
std::array<bool, 512> m_keys {};
std::array<bool, 512> m_buttons {};
math::vec2 m_pointer_position;
app::TickResult m_last_tick_result {};
};
} // namespace lt::input
module :private;
#include <input/components.hpp>
#include <input/system.hpp>
#include <memory/reference.hpp>
@ -101,7 +160,7 @@ void System::on_key_press(const lt::surface::KeyPressedEvent &event)
{
if (event.get_key() > m_keys.size())
{
log_dbg(
log::debug(
"Key code larger than key container size, implement platform-dependant "
"key-code-mapping!"
);
@ -116,7 +175,7 @@ void System::on_key_release(const lt::surface::KeyReleasedEvent &event)
{
if (event.get_key() > m_keys.size())
{
log_dbg(
log::debug(
"Key code larger than key container size, implement platform-dependant "
"key-code-mapping!"
);

View file

@ -1 +1,2 @@
add_library_module(logger logger.cpp)
add_library_module(NAME logger INTERFACES logger.cppm)
# add_test_module(logger logger.test.cpp)

180
modules/logger/logger.cppm Normal file
View file

@ -0,0 +1,180 @@
export module logger;
import std;
namespace lt::log {
/** Severity of a log message. */
enum class Level : std::uint8_t
{
/** Lowest and most vebose log level, for tracing execution paths and events */
trace = 0,
/** Vebose log level, for enabling temporarily to debug */
debug = 1,
/** General information */
info = 2,
/** Things we should to be aware of and edge cases */
warn = 3,
/** Defects, bugs and undesired behaviour */
error = 4,
/** Unrecoverable errors */
critical = 5,
/** No logging */
off = 6,
};
namespace details {
inline auto thread_hash_id() noexcept -> std::uint64_t
{
return static_cast<std::uint64_t>(std::hash<std::thread::id> {}(std::this_thread::get_id()));
}
} // namespace details
template<typename... Args>
struct [[maybe_unused]] print
{
[[maybe_unused]] print(
Level level,
const std::source_location &location,
std::format_string<Args...> format,
Args &&...arguments
) noexcept
{
constexpr auto to_string = [](Level level, auto location) {
// clang-format off
switch (level)
{
using enum Level;
case trace : return "\033[1;37m| trc |\033[0m";
case debug : return "\033[1;36m| dbg |\033[0m";
case info : return "\033[1;32m| inf |\033[0m";
case warn : return "\033[1;33m| wrn |\033[0m";
case error : return "\033[1;31m| err |\033[0m";
case critical: return "\033[1;41m| crt |\033[0m";
case off: return "off";
}
// clang-format on
std::unreachable();
};
const auto path = std::filesystem::path { location.file_name() };
std::println(
"{} {} ==> {}",
to_string(level, location),
std::format("{}:{}", path.filename().c_str(), location.line()),
std::format(format, std::forward<Args>(arguments)...)
);
}
};
template<typename... Args>
print(Level, const std::source_location &, std::format_string<Args...>, Args &&...) noexcept
-> print<Args...>;
export template<typename... Args>
struct [[maybe_unused]] trace
{
[[maybe_unused]] trace(
std::format_string<Args...> format,
Args &&...arguments,
const std::source_location &location = std::source_location::current()
) noexcept
{
print(Level::trace, location, format, std::forward<Args>(arguments)...);
}
};
export template<typename... Args>
trace(std::format_string<Args...>, Args &&...) noexcept -> trace<Args...>;
export template<typename... Args>
struct [[maybe_unused]] debug
{
[[maybe_unused]] debug(
std::format_string<Args...> format,
Args &&...arguments,
const std::source_location &location = std::source_location::current()
) noexcept
{
print(Level::debug, location, format, std::forward<Args>(arguments)...);
}
};
export template<typename... Args>
debug(std::format_string<Args...>, Args &&...) noexcept -> debug<Args...>;
export template<typename... Args>
struct [[maybe_unused]] info
{
[[maybe_unused]] info(
std::format_string<Args...> format,
Args &&...arguments,
const std::source_location &location = std::source_location::current()
) noexcept
{
print(Level::info, location, format, std::forward<Args>(arguments)...);
}
};
export template<typename... Args>
info(std::format_string<Args...>, Args &&...) noexcept -> info<Args...>;
export template<typename... Args>
struct [[maybe_unused]] warn
{
[[maybe_unused]] warn(
std::format_string<Args...> format,
Args &&...arguments,
const std::source_location &location = std::source_location::current()
) noexcept
{
print(Level::warn, location, format, std::forward<Args>(arguments)...);
}
};
export template<typename... Args>
warn(std::format_string<Args...>, Args &&...) noexcept -> warn<Args...>;
export template<typename... Args>
struct [[maybe_unused]] error
{
[[maybe_unused]] error(
std::format_string<Args...> format,
Args &&...arguments,
const std::source_location &location = std::source_location::current()
) noexcept
{
print(Level::error, location, format, std::forward<Args>(arguments)...);
}
};
export template<typename... Args>
error(std::format_string<Args...>, Args &&...) noexcept -> error<Args...>;
export template<typename... Args>
struct [[maybe_unused]] critical
{
[[maybe_unused]] critical(
std::format_string<Args...> format,
Args &&...arguments,
const std::source_location &location = std::source_location::current()
) noexcept
{
print(Level::critical, location, format, std::forward<Args>(arguments)...);
}
};
export template<typename... Args>
critical(std::format_string<Args...>, Args &&...) noexcept -> critical<Args...>;
} // namespace lt::log

View file

@ -0,0 +1,25 @@
import logger;
import test;
using ::lt::test::Case;
using ::lt::test::Suite;
Suite suite = [] {
Case { "no format" } = [] {
lt::log::trace("trace");
lt::log::debug("debug");
lt::log::info("info");
lt::log::warn("warn");
lt::log::error("error");
lt::log::critical("critical");
};
Case { "formatted" } = [] {
lt::log::trace("trace {}", 69);
lt::log::debug("debug {}", 69);
lt::log::info("info {}", 69);
lt::log::warn("warn {}", 69);
lt::log::error("error {}", 69);
lt::log::critical("critical {}", 69);
};
};

View file

@ -1 +0,0 @@
#include <logger/logger.hpp>

View file

@ -1,89 +0,0 @@
#pragma once
#include <format>
#include <print>
/** Severity of a log message. */
enum class LogLvl : uint8_t
{
/** Lowest and most vebose log level, for tracing execution paths and events */
trace = 0,
/** Vebose log level, for enabling temporarily to debug */
debug = 1,
/** General information */
info = 2,
/** Things we should to be aware of and edge cases */
warn = 3,
/** Defects, bugs and undesired behaviour */
error = 4,
/** Unrecoverable errors */
critical = 5,
/** No logging */
off = 6,
};
/** Simple console logger */
class Logger
{
public:
void static show_imgui_window();
template<typename... Args>
void static log(LogLvl lvl, std::format_string<Args...> fmt, Args &&...args) noexcept
{
std::ignore = lvl;
std::println(fmt, std::forward<Args>(args)...);
}
void static log(LogLvl lvl, const char *message) noexcept
{
std::ignore = lvl;
std::println("{}", message);
}
private:
Logger() = default;
};
template<typename... Args>
void log_trc(std::format_string<Args...> fmt, Args &&...args) noexcept
{
Logger::log(LogLvl::trace, fmt, std::forward<Args>(args)...);
}
template<typename... Args>
void log_dbg(std::format_string<Args...> fmt, Args &&...args) noexcept
{
Logger::log(LogLvl::debug, fmt, std::forward<Args>(args)...);
}
template<typename... Args>
void log_inf(std::format_string<Args...> fmt, Args &&...args) noexcept
{
Logger::log(LogLvl::info, fmt, std::forward<Args>(args)...);
}
template<typename... Args>
void log_wrn(std::format_string<Args...> fmt, Args &&...args) noexcept
{
Logger::log(LogLvl::warn, fmt, std::forward<Args>(args)...);
}
template<typename... Args>
void log_err(std::format_string<Args...> fmt, Args &&...args) noexcept
{
Logger::log(LogLvl::error, fmt, std::forward<Args>(args)...);
}
template<typename... Args>
void log_crt(std::format_string<Args...> fmt, Args &&...args) noexcept
{
Logger::log(LogLvl::critical, fmt, std::forward<Args>(args)...);
}

View file

@ -1 +1,11 @@
add_library_module(math)
add_library_module(
NAME
math
INTERFACES
algebra.cppm
mat4.cppm
trig.cppm
vec2.cppm
vec3.cppm
vec4.cppm
components/transform.cppm)

View file

@ -1,6 +1,6 @@
#pragma once
#include <math/mat4.hpp>
export module math.algebra;
import math.mat4;
import std;
namespace lt::math {
@ -31,25 +31,29 @@ namespace lt::math {
*
* the 1 at [z][3] is to save the Z axis into the resulting W for perspective division.
*
* thanks to pikuma: https://www.youtube.com/watch?v=EqNcqBdrNyI
* @ref Thanks to pikuma for explaining the math behind this:
* https://www.youtube.com/watch?v=EqNcqBdrNyI
*/
template<typename T>
constexpr auto perspective(T field_of_view, T aspect_ratio, T z_near, T z_far)
{
const T half_fov_tan = std::tan(field_of_view / static_cast<T>(2));
auto result = mat4_impl<T> { T { 0 } };
auto result = mat4_impl<T>::identity();
result[0][0] = T { 1 } / (aspect_ratio * half_fov_tan);
//
result[1][1] = T { 1 } / (half_fov_tan);
result[2][2] = -(z_far + z_near) / (z_far - z_near);
//
// result[2][2] = -(z_far + z_near) / (z_far - z_near);
//
result[2][2] = z_far / (z_far - z_near);
//
result[2][3] = -T { 1 };
result[3][2] = -(T { 2 } * z_far * z_near) / (z_far - z_near);
//
// result[3][2] = -(T { 2 } * z_far * z_near) / (z_far - z_near);
result[3][2] = -(z_far * z_near) / (z_far - z_near);
//
return result;
}

View file

@ -0,0 +1,16 @@
export module math.components;
import math.vec3;
namespace lt::math::components {
export struct Transform
{
math::vec3 translation;
math::vec3 scale;
math::vec3 rotation;
};
} // namespace lt::math::components

View file

@ -1,14 +1,15 @@
#pragma once
#include <math/vec3.hpp>
#include <math/vec4.hpp>
export module math.mat4;
import math.vec3;
import math.vec4;
import std;
namespace lt::math {
template<typename T = float>
export template<typename T = float>
struct mat4_impl
{
using Column_T = vec4_impl<T>;
constexpr explicit mat4_impl(T scalar = 0)
: values(
{
@ -43,7 +44,7 @@ struct mat4_impl
{
}
[[nodiscard]] constexpr auto identity() -> mat4_impl<T>
[[nodiscard]] static constexpr auto identity() -> mat4_impl<T>
{
return mat4_impl<T> {
{ 1 }, {}, {}, {}, //
@ -53,12 +54,12 @@ struct mat4_impl
};
}
[[nodiscard]] constexpr auto operator[](size_t idx) -> Column_T &
[[nodiscard]] constexpr auto operator[](std::size_t idx) -> Column_T &
{
return values[idx];
}
[[nodiscard]] constexpr auto operator[](size_t idx) const -> const Column_T &
[[nodiscard]] constexpr auto operator[](std::size_t idx) const -> const Column_T &
{
return values[idx];
}
@ -76,34 +77,34 @@ struct mat4_impl
std::array<Column_T, 4> values; // NOLINT
};
template<typename T>
[[nodiscard]] inline auto translate(const vec3_impl<T> &value) -> mat4_impl<T>
export template<typename T>
[[nodiscard]] auto translate(const vec3_impl<T> &value) -> mat4_impl<T>
{
return mat4_impl<T> {};
}
template<typename T>
[[nodiscard]] inline auto rotate(float value, const vec3_impl<T> &xyz) -> mat4_impl<T>
export template<typename T>
[[nodiscard]] auto rotate(float value, const vec3_impl<T> &xyz) -> mat4_impl<T>
{
return mat4_impl<T> {};
}
template<typename T>
[[nodiscard]] inline auto scale(const vec3_impl<T> &value) -> mat4_impl<T>
export template<typename T>
[[nodiscard]] auto scale(const vec3_impl<T> &value) -> mat4_impl<T>
{
return mat4_impl<T> {};
}
template<typename T>
[[nodiscard]] inline auto inverse(const mat4_impl<T> &value) -> mat4_impl<T>
export template<typename T>
[[nodiscard]] auto inverse(const mat4_impl<T> &value) -> mat4_impl<T>
{
return mat4_impl<T> {};
}
using mat4 = mat4_impl<float>;
export using mat4 = mat4_impl<float>;
using imat4 = mat4_impl<int32_t>;
export using imat4 = mat4_impl<std::int32_t>;
using umat4 = mat4_impl<uint32_t>;
export using umat4 = mat4_impl<std::uint32_t>;
} // namespace lt::math

View file

@ -1,4 +1,4 @@
#pragma once
export module math.trig;
namespace lt::math {

View file

@ -1,8 +1,10 @@
#pragma once
export module math.vec2;
import std;
namespace lt::math {
template<typename T = float>
export template<typename T = float>
struct vec2_impl
{
constexpr vec2_impl(): x(), y()
@ -57,15 +59,15 @@ struct vec2_impl
};
using vec2 = vec2_impl<float>;
export using vec2 = vec2_impl<float>;
using ivec2 = vec2_impl<int32_t>;
export using ivec2 = vec2_impl<std::int32_t>;
using uvec2 = vec2_impl<uint32_t>;
export using uvec2 = vec2_impl<std::uint32_t>;
} // namespace lt::math
template<typename T>
export template<typename T>
struct std::formatter<lt::math::vec2_impl<T>>
{
constexpr auto parse(std::format_parse_context &context)

View file

@ -1,11 +1,11 @@
#pragma once
export module math.vec3;
#include <cstdint>
#include <math/vec2.hpp>
import math.vec2;
import std;
namespace lt::math {
template<typename T = float>
export template<typename T = float>
struct vec3_impl
{
constexpr vec3_impl(): x(), y(), z()
@ -61,11 +61,11 @@ struct vec3_impl
T z; // NOLINT
};
using vec3 = vec3_impl<float>;
export using vec3 = vec3_impl<float>;
using ivec3 = vec3_impl<int32_t>;
export using ivec3 = vec3_impl<std::int32_t>;
using uvec3 = vec3_impl<uint32_t>;
export using uvec3 = vec3_impl<std::uint32_t>;
} // namespace lt::math

View file

@ -1,11 +1,11 @@
#pragma once
#include <array>
#include <cstdint>
export module math.vec4;
import math.vec2;
import math.vec3;
import std;
namespace lt::math {
template<typename T = float>
export template<typename T = float>
struct vec4_impl
{
constexpr vec4_impl(): x(), y(), z(), w()
@ -40,12 +40,12 @@ struct vec4_impl
};
}
[[nodiscard]] constexpr auto operator[](size_t idx) -> T &
[[nodiscard]] constexpr auto operator[](std::size_t idx) -> T &
{
return values[idx];
}
[[nodiscard]] constexpr auto operator[](size_t idx) const -> const T &
[[nodiscard]] constexpr auto operator[](std::size_t idx) const -> const T &
{
return values[idx];
}
@ -86,15 +86,15 @@ struct vec4_impl
};
};
using vec4 = vec4_impl<float>;
export using vec4 = vec4_impl<float>;
using ivec4 = vec4_impl<int32_t>;
export using ivec4 = vec4_impl<std::int32_t>;
using uvec4 = vec4_impl<uint32_t>;
export using uvec4 = vec4_impl<std::uint32_t>;
} // namespace lt::math
template<typename T>
export template<typename T>
struct std::formatter<lt::math::vec4_impl<T>>
{
constexpr auto parse(std::format_parse_context &context)

View file

@ -1 +1,2 @@
add_library_module(memory)
add_library_module(NAME memory INTERFACES null_on_move.cppm reference.cppm
scope.cppm)

View file

@ -1,11 +1,13 @@
#pragma once
export module memory.null_on_move;
import std;
namespace lt::memory {
/** Holds an `Underlying_T`, assigns it to `null_value` when this object is moved.
*
* @note For avoiding the need to explicitly implement the move constructor for objects that hold
* Vulkan objects. But may server other purposes, hence why I kept the implementation generic.
* Vulkan objects. But may serve other purposes, hence why I kept the implementation generic.
*/
template<typename Underlying_T, Underlying_T null_value = nullptr>
class NullOnMove
@ -77,9 +79,9 @@ public:
return m_value;
}
operator uint64_t() const
operator std::uint64_t() const
{
return (uint64_t)m_value;
return (std::uint64_t)m_value;
}
[[nodiscard]] auto get() -> Underlying_T

View file

@ -1,7 +1,6 @@
#pragma once
export module memory.reference;
#include <memory/reference.hpp>
#include <memory>
import std;
namespace lt::memory {
@ -10,21 +9,21 @@ namespace lt::memory {
* @note Currently just an alias, might turn into an implementation later.
* @ref https://en.cppreference.com/w/cpp/memory/shared_ptr.html
*/
template<typename t>
using Ref = std::shared_ptr<t>;
export template<typename T>
using Ref = std::shared_ptr<T>;
/** Allocates memory for an `Underlying_T` and directly constructs it there.
*
* @return A Ref<Underlying_T> to the constructed object.
*/
template<typename Underlying_T, typename... Args>
export template<typename Underlying_T, typename... Args>
constexpr Ref<Underlying_T> create_ref(Args &&...args)
{
return std::make_shared<Underlying_T>(std::forward<Args>(args)...);
}
/** Converts c-style pointer of type `Underlying_T` to a `Ref<Underlying_T>`. */
template<typename Underlying_T>
export template<typename Underlying_T>
constexpr Ref<Underlying_T> make_ref(Underlying_T *raw_pointer)
{
return Ref<Underlying_T>(raw_pointer);

View file

@ -1,30 +1,29 @@
#pragma once
export module memory.scope;
#include <memory/scope.hpp>
#include <memory>
import std;
namespace lt::memory {
/** Wrapper around std::unique_ptr.
/** @brief Wrapper around std::unique_ptr.
*
* @note Currently just an alias, might turn into an implementation later.
* @ref https://en.cppreference.com/w/cpp/memory/unique_ptr.html
*/
template<typename t>
export template<typename t>
using Scope = std::unique_ptr<t>;
/** Allocates memory for an `Underlying_T` and directly constructs it there.
*
* @return A Scope<Underlying_T> to the constructed object.
*/
template<typename Underlying_T, typename... Args>
export template<typename Underlying_T, typename... Args>
constexpr Scope<Underlying_T> create_scope(Args &&...args)
{
return std::make_unique<Underlying_T>(std::forward<Args>(args)...);
}
/** Converts c-style pointer of type `Underlying_T` to a `Scope<Underlying_T>`. */
template<typename Underlying_T>
export template<typename Underlying_T>
constexpr Scope<Underlying_T> make_scope(Underlying_T *raw_pointer)
{
return Scope<Underlying_T>(raw_pointer);

View file

@ -1,5 +1,6 @@
add_library_module(libmirror)
target_link_libraries(libmirror INTERFACE app time input surface renderer)
target_link_libraries(libmirror INTERFACE app time input surface renderer
camera)
add_test_module(
libmirror layers/editor_layer.test.cpp panels/asset_browser.test.cpp

View file

@ -2,13 +2,17 @@
#include <app/application.hpp>
#include <app/entrypoint.hpp>
#include <app/system.hpp>
#include <camera/components.hpp>
#include <ecs/entity.hpp>
#include <input/components.hpp>
#include <input/system.hpp>
#include <math/components/transform.hpp>
#include <math/trig.hpp>
#include <math/vec2.hpp>
#include <memory/reference.hpp>
#include <memory/scope.hpp>
#include <renderer/components/messenger.hpp>
#include <renderer/components/sprite.hpp>
#include <renderer/system.hpp>
#include <surface/events/keyboard.hpp>
#include <surface/events/surface.hpp>
@ -28,7 +32,7 @@ void renderer_callback(
std::ignore = message_type;
std::ignore = user_data;
log_dbg("RENDERER CALLBACK: {}", data.message);
log::debug("RENDERER CALLBACK: {}", data.message);
}
class MirrorSystem: public lt::app::ISystem
@ -66,13 +70,18 @@ public:
const auto &[x, y] = surface.get_position();
const auto &[width, height] = surface.get_resolution();
if (input.get_action(m_quit_action_key).state == State::active)
{
should_quit = true;
}
if (input.get_action(m_debug_action_keys[0]).state == State::active)
{
surface.push_request(surface::ModifyPositionRequest({ x + 5, y + 5 }));
for (auto &[id, camera] :
m_registry->view<lt::camera::components::PerspectiveCamera>())
{
camera.vertical_fov += (static_cast<float>(tick.delta_time.count()) * 40.0f);
}
}
if (input.get_action(m_debug_action_keys[1]).state == State::active)
@ -138,7 +147,7 @@ public:
void on_window_close()
{
log_inf("Window close requested...");
log::info("Window close requested...");
unregister_system(m_input_system);
unregister_system(m_surface_system);
@ -219,6 +228,35 @@ public:
.callback = &renderer_callback,
.user_data = this,
} });
m_sprite_id = m_editor_registry->create_entity();
m_editor_registry->add(
m_sprite_id,
renderer::components::Sprite { .color = lt::math::vec3 { 1.0f, 0.0f, 0.0f } }
);
m_editor_registry->add(
m_sprite_id,
math::components::Transform {
.translation = { -5.0, -5.0, 0.5 },
.scale = { 5.0, 5.0, 1.0 },
.rotation = {},
}
);
m_camera_id = m_editor_registry->create_entity();
m_editor_registry->add(
m_camera_id,
camera::components::PerspectiveCamera {
.vertical_fov = math::radians(90.0f),
.near_plane = 0.1f,
.far_plane = 30.0,
.aspect_ratio = 1.0f,
.background_color = math::vec4(1.0, 0.0, 0.0, 1.0),
.is_primary = true,
}
);
}
void setup_input_system()
@ -245,6 +283,10 @@ private:
memory::Ref<MirrorSystem> m_mirror_system;
lt::ecs::EntityId m_window = lt::ecs::null_entity;
lt::ecs::EntityId m_camera_id = lt::ecs::null_entity;
lt::ecs::EntityId m_sprite_id = lt::ecs::null_entity;
};
auto app::create_application() -> memory::Scope<app::Application>

View file

@ -109,7 +109,7 @@ void AssetBrowserPanel::on_user_interface_update()
))
{
auto serializer = SceneSerializer { m_active_scene };
log_inf("Attempting to deserialize: {}", path.string());
log::info("Attempting to deserialize: {}", path.string());
serializer.deserialize(path.string());
}
break;

View file

@ -8,21 +8,29 @@ add_library_module(
backend/vk/context/instance.cpp
backend/vk/context/surface.cpp
backend/vk/context/swapchain.cpp
backend/vk/data/buffer.cpp
backend/vk/renderer/pass.cpp
backend/vk/renderer/renderer.cpp
# Vulkan - frontend
# frontend
frontend/messenger.cpp
frontend/context/device.cpp
frontend/context/gpu.cpp
frontend/context/instance.cpp
frontend/context/surface.cpp
frontend/context/swapchain.cpp
frontend/data/buffer.cpp
frontend/renderer/renderer.cpp
frontend/renderer/pass.cpp)
target_link_libraries(
renderer
PUBLIC app ecs memory assets time bitwise
PUBLIC app
ecs
memory
assets
time
bitwise
camera
PRIVATE surface pthread)
add_test_module(
@ -34,11 +42,10 @@ add_test_module(
frontend/context/surface.test.cpp
frontend/context/device.test.cpp
frontend/context/swapchain.test.cpp
frontend/renderer/pass.test.cpp
frontend/data/buffer.test.cpp
# frontend/renderer/pass.test.cpp
frontend/renderer/renderer.test.cpp
# backend specific tests -- vk
backend/vk/context/instance.test.cpp
# backend specific tests -- dx backend specific tests -- mt
)
backend/vk/context/instance.test.cpp)
target_link_libraries(renderer_tests PRIVATE surface pthread)

View file

@ -1,4 +1,5 @@
//
#include <logger/logger.hpp>
#include <renderer/backend/vk/context/device.hpp>
#include <renderer/backend/vk/context/gpu.hpp>
#include <renderer/backend/vk/context/surface.hpp>
@ -47,8 +48,8 @@ Device::~Device()
}
catch (const std::exception &exp)
{
log_err("Failed to destroy vk device:");
log_err("\twhat: {}", exp.what());
log::error("Failed to destroy vk device:");
log::error("\twhat: {}", exp.what());
}
}
@ -75,20 +76,107 @@ void Device::initialize_logical_device()
auto extensions = std::vector<const char *> {
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME,
VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME,
};
auto device_info = VkDeviceCreateInfo {
auto descriptor_indexing_features = m_gpu->get_descriptor_indexing_features();
log::debug("");
log::debug(
"shaderInputAttachmentArrayDynamicIndexing: {}",
descriptor_indexing_features.shaderInputAttachmentArrayDynamicIndexing
);
log::debug(
" shaderUniformTexelBufferArrayDynamicIndexing: {}",
descriptor_indexing_features.shaderUniformTexelBufferArrayDynamicIndexing
);
log::debug(
" shaderStorageTexelBufferArrayDynamicIndexing: {}",
descriptor_indexing_features.shaderStorageTexelBufferArrayDynamicIndexing
);
log::debug(
" shaderUniformBufferArrayNonUniformIndexing: {}",
descriptor_indexing_features.shaderUniformBufferArrayNonUniformIndexing
);
log::debug(
" shaderSampledImageArrayNonUniformIndexing: {}",
descriptor_indexing_features.shaderSampledImageArrayNonUniformIndexing
);
log::debug(
" shaderStorageBufferArrayNonUniformIndexing: {}",
descriptor_indexing_features.shaderStorageBufferArrayNonUniformIndexing
);
log::debug(
" shaderStorageImageArrayNonUniformIndexing: {}",
descriptor_indexing_features.shaderStorageImageArrayNonUniformIndexing
);
log::debug(
" shaderInputAttachmentArrayNonUniformIndexing: {}",
descriptor_indexing_features.shaderInputAttachmentArrayNonUniformIndexing
);
log::debug(
" shaderUniformTexelBufferArrayNonUniformIndexing: {}",
descriptor_indexing_features.shaderUniformTexelBufferArrayNonUniformIndexing
);
log::debug(
" shaderStorageTexelBufferArrayNonUniformIndexing: {}",
descriptor_indexing_features.shaderStorageTexelBufferArrayNonUniformIndexing
);
log::debug(
" descriptorBindingUniformBufferUpdateAfterBind: {}",
descriptor_indexing_features.descriptorBindingUniformBufferUpdateAfterBind
);
log::debug(
" descriptorBindingSampledImageUpdateAfterBind: {}",
descriptor_indexing_features.descriptorBindingSampledImageUpdateAfterBind
);
log::debug(
" descriptorBindingStorageImageUpdateAfterBind: {}",
descriptor_indexing_features.descriptorBindingStorageImageUpdateAfterBind
);
log::debug(
" descriptorBindingStorageBufferUpdateAfterBind: {}",
descriptor_indexing_features.descriptorBindingStorageBufferUpdateAfterBind
);
log::debug(
" descriptorBindingUniformTexelBufferUpdateAfterBind: {}",
descriptor_indexing_features.descriptorBindingUniformTexelBufferUpdateAfterBind
);
log::debug(
" descriptorBindingStorageTexelBufferUpdateAfterBind: {}",
descriptor_indexing_features.descriptorBindingStorageTexelBufferUpdateAfterBind
);
log::debug(
" descriptorBindingUpdateUnusedWhilePending: {}",
descriptor_indexing_features.descriptorBindingUpdateUnusedWhilePending
);
log::debug(
" descriptorBindingPartiallyBound: {}",
descriptor_indexing_features.descriptorBindingPartiallyBound
);
log::debug(
" descriptorBindingVariableDescriptorCount: {}",
descriptor_indexing_features.descriptorBindingVariableDescriptorCount
);
log::debug(" runtimeDescriptorArray: {}", descriptor_indexing_features.runtimeDescriptorArray);
const auto dynamic_rendering_features = VkPhysicalDeviceDynamicRenderingFeatures {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
.pNext = &descriptor_indexing_features,
.dynamicRendering = true,
};
m_device = m_gpu->create_device(
VkDeviceCreateInfo {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.pNext = &dynamic_rendering_features,
.queueCreateInfoCount = static_cast<uint32_t>(queue_infos.size()),
.pQueueCreateInfos = queue_infos.data(),
.enabledExtensionCount = static_cast<uint32_t>(extensions.size()),
.ppEnabledExtensionNames = extensions.data(),
.pEnabledFeatures = &physical_device_features,
};
ensure(
!vk_create_device(m_gpu->vk(), &device_info, nullptr, &m_device),
"Failed to create logical vulkan device"
}
);
}
@ -207,6 +295,31 @@ void Device::wait_for_fences(std::span<VkFence> fences) const
return images;
}
[[nodiscard]] auto Device::get_memory_requirements(VkBuffer buffer) const -> VkMemoryRequirements
{
auto requirements = VkMemoryRequirements {};
vk_get_buffer_memory_requirements(m_device, buffer, &requirements);
return requirements;
}
void Device::bind_memory(VkBuffer buffer, VkDeviceMemory memory, size_t offset /* = 0u */) const
{
vkc(vk_bind_buffer_memory(m_device, buffer, memory, offset));
}
[[nodiscard]] auto Device::map_memory(VkDeviceMemory memory, size_t size, size_t offset) const
-> std::span<std::byte>
{
void *data = {};
vkc(vk_map_memory(m_device, memory, offset, size, {}, &data));
return { std::bit_cast<std::byte *>(data), size };
}
void Device::unmap_memory(VkDeviceMemory memory)
{
vk_unmap_memory(m_device, memory);
}
[[nodiscard]] auto Device::create_swapchain(VkSwapchainCreateInfoKHR info) const -> VkSwapchainKHR
{
auto *swapchain = VkSwapchainKHR {};
@ -251,9 +364,19 @@ void Device::wait_for_fences(std::span<VkFence> fences) const
return pass;
}
[[nodiscard]] auto Device::create_pipeline_layout(VkPipelineLayoutCreateInfo info) const
-> VkPipelineLayout
[[nodiscard]] auto Device::create_pipeline_layout(
std::vector<VkDescriptorSetLayout> descriptor_set_layout,
std::vector<VkPushConstantRange> push_constant_ranges
) const -> VkPipelineLayout
{
auto info = VkPipelineLayoutCreateInfo {
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.setLayoutCount = static_cast<uint32_t>(descriptor_set_layout.size()),
.pSetLayouts = descriptor_set_layout.data(),
.pushConstantRangeCount = static_cast<uint32_t>(push_constant_ranges.size()),
.pPushConstantRanges = push_constant_ranges.data(),
};
auto *pipeline_layout = VkPipelineLayout {};
vkc(vk_create_pipeline_layout(m_device, &info, nullptr, &pipeline_layout));
return pipeline_layout;
@ -299,6 +422,28 @@ void Device::wait_for_fences(std::span<VkFence> fences) const
return fences;
}
[[nodiscard]] auto Device::create_buffer(VkBufferCreateInfo info) const -> VkBuffer
{
auto *buffer = VkBuffer {};
vkc(vk_create_buffer(m_device, &info, nullptr, &buffer));
return buffer;
}
[[nodiscard]] auto Device::create_desscriptor_pool(VkDescriptorPoolCreateInfo info) const
-> VkDescriptorPool
{
auto *pool = VkDescriptorPool {};
vkc(vk_create_descriptor_pool(m_device, &info, nullptr, &pool));
return pool;
}
[[nodiscard]] auto Device::create_descriptor_set_layout(VkDescriptorSetLayoutCreateInfo info) const
-> VkDescriptorSetLayout
{
auto *layout = VkDescriptorSetLayout {};
vkc(vk_create_descriptor_set_layout(m_device, &info, nullptr, &layout));
return layout;
}
[[nodiscard]] auto Device::allocate_command_buffers(VkCommandBufferAllocateInfo info) const
-> std::vector<VkCommandBuffer>
{
@ -307,6 +452,34 @@ void Device::wait_for_fences(std::span<VkFence> fences) const
return command_buffers;
}
[[nodiscard]] auto Device::allocate_memory(VkMemoryAllocateInfo info) const -> VkDeviceMemory
{
auto *memory = VkDeviceMemory {};
vkc(vk_allocate_memory(m_device, &info, nullptr, &memory));
return memory;
}
[[nodiscard]] auto Device::allocate_descriptor_set(VkDescriptorSetAllocateInfo info) const
-> VkDescriptorSet
{
auto *descriptor_set = VkDescriptorSet {};
vkc(vk_allocate_descriptor_sets(m_device, &info, &descriptor_set));
return descriptor_set;
}
void Device::free_memory(VkDeviceMemory memory) const
{
vk_free_memory(m_device, memory, nullptr);
}
void Device::free_descriptor_set(
VkDescriptorPool descriptor_pool,
VkDescriptorSet descriptor_set
) const
{
vkc(vk_free_descriptor_sets(m_device, descriptor_pool, 1, &descriptor_set));
}
void Device::destroy_swapchain(VkSwapchainKHR swapchain) const
{
vk_destroy_swapchain_khr(m_device, swapchain, m_allocator);
@ -389,4 +562,19 @@ void Device::destroy_fences(std::span<VkFence> fences) const
}
}
void Device::destroy_buffer(VkBuffer buffer) const
{
vk_destroy_buffer(m_device, buffer, nullptr);
}
void Device::destroy_descriptor_set_layout(VkDescriptorSetLayout layout) const
{
vk_destroy_descriptor_set_layout(m_device, layout, nullptr);
}
void Device::destroy_descriptor_pool(VkDescriptorPool pool) const
{
vk_destroy_descriptor_pool(m_device, pool, nullptr);
}
} // namespace lt::renderer::vk

View file

@ -96,6 +96,16 @@ public:
[[nodiscard]] auto get_swapchain_images(VkSwapchainKHR swapchain) const -> std::vector<VkImage>;
[[nodiscard]] auto get_memory_requirements(VkBuffer buffer) const -> VkMemoryRequirements;
/** binders / mappers */
void bind_memory(VkBuffer buffer, VkDeviceMemory memory, size_t offset = 0u) const;
[[nodiscard]] auto map_memory(VkDeviceMemory memory, size_t size, size_t offset) const
-> std::span<std::byte>;
void unmap_memory(VkDeviceMemory memory);
/** create functions */
[[nodiscard]] auto create_swapchain(VkSwapchainCreateInfoKHR info) const -> VkSwapchainKHR;
@ -108,8 +118,10 @@ public:
[[nodiscard]] auto create_pass(VkRenderPassCreateInfo info) const -> VkRenderPass;
[[nodiscard]] auto create_pipeline_layout(VkPipelineLayoutCreateInfo info) const
-> VkPipelineLayout;
[[nodiscard]] auto create_pipeline_layout(
std::vector<VkDescriptorSetLayout> descriptor_set_layout,
std::vector<VkPushConstantRange> push_constant_ranges
) const -> VkPipelineLayout;
[[nodiscard]] auto create_shader_module(VkShaderModuleCreateInfo info) const -> VkShaderModule;
@ -120,10 +132,31 @@ public:
[[nodiscard]] auto create_fences(VkFenceCreateInfo info, uint32_t count) const
-> std::vector<VkFence>;
[[nodiscard]] auto create_buffer(VkBufferCreateInfo info) const -> VkBuffer;
[[nodiscard]] auto create_descriptor_set_layout(VkDescriptorSetLayoutCreateInfo info) const
-> VkDescriptorSetLayout;
[[nodiscard]] auto create_desscriptor_pool(VkDescriptorPoolCreateInfo info) const
-> VkDescriptorPool;
/** allocation functions */
[[nodiscard]] auto allocate_memory(VkMemoryAllocateInfo info) const -> VkDeviceMemory;
[[nodiscard]] auto allocate_command_buffers(VkCommandBufferAllocateInfo info) const
-> std::vector<VkCommandBuffer>;
[[nodiscard]] auto allocate_descriptor_set(VkDescriptorSetAllocateInfo info) const
-> VkDescriptorSet;
/** de-allocation functions */
void free_memory(VkDeviceMemory memory) const;
void free_descriptor_set(
VkDescriptorPool descriptor_pool,
VkDescriptorSet descriptor_set
) const;
/** destroy functions */
void destroy_swapchain(VkSwapchainKHR swapchain) const;
@ -153,6 +186,12 @@ public:
void destroy_fences(std::span<VkFence> fences) const;
void destroy_buffer(VkBuffer buffer) const;
void destroy_descriptor_set_layout(VkDescriptorSetLayout layout) const;
void destroy_descriptor_pool(VkDescriptorPool pool) const;
private:
template<typename T>
static auto get_object_type(const T &object) -> VkObjectType

View file

@ -5,35 +5,38 @@
namespace lt::renderer::vk {
Gpu::Gpu(IInstance *instance)
: m_descriptor_indexing_features(
{ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES }
)
{
auto gpus = static_cast<Instance *>(instance)->enumerate_gpus();
for (auto &gpu : gpus)
{
auto properties = VkPhysicalDeviceProperties {};
auto features = VkPhysicalDeviceFeatures {};
auto features = VkPhysicalDeviceFeatures2 {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
.pNext = &m_descriptor_indexing_features
};
vk_get_physical_device_properties(gpu, &properties);
vk_get_physical_device_features(gpu, &features);
if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
&& features.geometryShader)
&& features.features.geometryShader)
{
m_gpu = gpu;
}
}
ensure(m_gpu, "Failed to find any suitable Vulkan physical device");
}
[[nodiscard]] auto Gpu::get_queue_family_properties() const -> std::vector<VkQueueFamilyProperties>
{
vk_get_physical_device_memory_properties(m_gpu, &m_memory_properties);
auto count = uint32_t { 0u };
vk_get_physical_device_queue_family_properties(m_gpu, &count, nullptr);
auto properties = std::vector<VkQueueFamilyProperties>(count);
vk_get_physical_device_queue_family_properties(m_gpu, &count, properties.data());
return properties;
m_queue_family_properties.resize(count);
vk_get_physical_device_queue_family_properties(m_gpu, &count, m_queue_family_properties.data());
}
[[nodiscard]] auto Gpu::queue_family_supports_presentation(
@ -67,5 +70,11 @@ Gpu::Gpu(IInstance *instance)
return formats;
}
[[nodiscard]] auto Gpu::create_device(VkDeviceCreateInfo info) const -> VkDevice
{
auto *device = VkDevice {};
vkc(vk_create_device(m_gpu, &info, nullptr, &device));
return device;
}
} // namespace lt::renderer::vk

View file

@ -12,13 +12,6 @@ class Gpu: public IGpu
public:
Gpu(IInstance *instance);
[[nodiscard]] auto vk() const -> VkPhysicalDevice
{
return m_gpu;
}
[[nodiscard]] auto get_queue_family_properties() const -> std::vector<VkQueueFamilyProperties>;
[[nodiscard]] auto queue_family_supports_presentation(
VkSurfaceKHR surface,
uint32_t queue_family_idx
@ -30,8 +23,39 @@ public:
[[nodiscard]] auto get_surface_formats(VkSurfaceKHR surface) const
-> std::vector<VkSurfaceFormatKHR>;
[[nodiscard]] auto create_device(VkDeviceCreateInfo info) const -> VkDevice;
[[nodiscard]] auto get_properties() const -> VkPhysicalDeviceProperties
{
return m_properties;
}
[[nodiscard]] auto get_descriptor_indexing_features() const
-> VkPhysicalDeviceDescriptorIndexingFeatures
{
return m_descriptor_indexing_features;
}
[[nodiscard]] auto get_memory_properties() const -> VkPhysicalDeviceMemoryProperties
{
return m_memory_properties;
}
[[nodiscard]] auto get_queue_family_properties() const -> std::vector<VkQueueFamilyProperties>
{
return m_queue_family_properties;
}
private:
memory::NullOnMove<VkPhysicalDevice> m_gpu = VK_NULL_HANDLE;
VkPhysicalDeviceProperties m_properties {};
VkPhysicalDeviceDescriptorIndexingFeatures m_descriptor_indexing_features;
VkPhysicalDeviceMemoryProperties m_memory_properties {};
std::vector<VkQueueFamilyProperties> m_queue_family_properties;
};
} // namespace lt::renderer::vk

View file

@ -29,8 +29,9 @@ PFN_vkGetPhysicalDeviceQueueFamilyProperties vk_get_physical_device_queue_family
PFN_vkCreateDevice vk_create_device {};
PFN_vkGetDeviceProcAddr vk_get_device_proc_address {};
PFN_vkDestroyDevice vk_destroy_device {};
PFN_vkGetPhysicalDeviceFeatures vk_get_physical_device_features {};
PFN_vkGetPhysicalDeviceFeatures2 vk_get_physical_device_features {};
PFN_vkEnumerateDeviceExtensionProperties vk_enumerate_device_extension_properties {};
PFN_vkGetPhysicalDeviceMemoryProperties vk_get_physical_device_memory_properties {};
// extension instance functions
PFN_vkCmdBeginDebugUtilsLabelEXT vk_cmd_begin_debug_label {};
@ -86,15 +87,37 @@ PFN_vkCmdBindPipeline vk_cmd_bind_pipeline {};
PFN_vkCmdDraw vk_cmd_draw {};
PFN_vkCmdSetViewport vk_cmd_set_viewport {};
PFN_vkCmdSetScissor vk_cmd_set_scissors {};
PFN_vkCmdPushConstants vk_cmd_push_constants {};
PFN_vkCmdCopyBuffer vk_cmd_copy_buffer {};
PFN_vkCreateDescriptorSetLayout vk_create_descriptor_set_layout {};
PFN_vkDestroyDescriptorSetLayout vk_destroy_descriptor_set_layout {};
PFN_vkCreateDescriptorPool vk_create_descriptor_pool {};
PFN_vkDestroyDescriptorPool vk_destroy_descriptor_pool {};
PFN_vkAllocateDescriptorSets vk_allocate_descriptor_sets {};
PFN_vkFreeDescriptorSets vk_free_descriptor_sets {};
PFN_vkCreateBuffer vk_create_buffer {};
PFN_vkDestroyBuffer vk_destroy_buffer {};
PFN_vkGetBufferMemoryRequirements vk_get_buffer_memory_requirements {};
PFN_vkAllocateMemory vk_allocate_memory {};
PFN_vkBindBufferMemory vk_bind_buffer_memory {};
PFN_vkMapMemory vk_map_memory {};
PFN_vkUnmapMemory vk_unmap_memory {};
PFN_vkFreeMemory vk_free_memory {};
PFN_vkResetCommandBuffer vk_reset_command_buffer {};
PFN_vkCmdBeginRendering vk_cmd_begin_rendering {};
PFN_vkCmdEndRendering vk_cmd_end_rendering {};
PFN_vkGetPhysicalDeviceSurfaceSupportKHR vk_get_physical_device_surface_support {};
PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vk_get_physical_device_surface_capabilities {};
PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vk_get_physical_device_surface_formats {};
auto vk_create_xlib_surface_khr = PFN_vkCreateXlibSurfaceKHR {};
auto vk_destroy_surface_khr = PFN_vkDestroySurfaceKHR {};
// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
Instance::Instance()
@ -127,6 +150,7 @@ void Instance::initialize_instance()
VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
VK_KHR_SURFACE_EXTENSION_NAME,
VK_KHR_XLIB_SURFACE_EXTENSION_NAME,
VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
};
const char *layer_name = "VK_LAYER_KHRONOS_validation";
@ -220,10 +244,10 @@ void Instance::initialize_instance()
memset(extensions.data(), 0, extensions.size() * sizeof(VkExtensionProperties));
vkc(vk_enumerate_instance_extension_properties(nullptr, &count, extensions.data()));
// log_inf("Available vulkan instance extensions:");
// log::info("Available vulkan instance extensions:");
for (auto &ext : extensions)
{
// log_inf("\t{} @ {}", ext.extensionName, ext.specVersion);
// log::info("\t{} @ {}", ext.extensionName, ext.specVersion);
}
}
@ -269,7 +293,7 @@ void Instance::load_global_functions()
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
pfn = reinterpret_cast<T>(vk_get_instance_proc_address(nullptr, fn_name));
ensure(pfn, "Failed to load vulkan global function: {}", fn_name);
// log_trc("Loaded global function: {}", fn_name);
// log::trace("Loaded global function: {}", fn_name);
};
load_fn(vk_create_instance, "vkCreateInstance");
@ -283,7 +307,7 @@ void Instance::load_instance_functions()
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
pfn = reinterpret_cast<T>(vk_get_instance_proc_address(m_instance, fn_name));
ensure(pfn, "Failed to load vulkan instance function: {}", fn_name);
// log_trc("Loaded instance function: {}", fn_name);
// log::trace("Loaded instance function: {}", fn_name);
};
load_fn(vk_destroy_instance, "vkDestroyInstance");
@ -298,6 +322,7 @@ void Instance::load_instance_functions()
load_fn(vk_destroy_device, "vkDestroyDevice");
load_fn(vk_get_physical_device_features, "vkGetPhysicalDeviceFeatures");
load_fn(vk_enumerate_device_extension_properties, "vkEnumerateDeviceExtensionProperties");
load_fn(vk_get_physical_device_memory_properties, "vkGetPhysicalDeviceMemoryProperties");
load_fn(vk_cmd_begin_debug_label, "vkCmdBeginDebugUtilsLabelEXT");
load_fn(vk_cmd_end_debug_label, "vkCmdEndDebugUtilsLabelEXT");
@ -327,7 +352,7 @@ void Instance::load_device_functions_impl(VkDevice device)
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
pfn = reinterpret_cast<T>(vk_get_device_proc_address(device, fn_name));
ensure(pfn, "Failed to load vulkan device function: {}", fn_name);
// log_trc("Loaded device function: {}", fn_name);
// log::trace("Loaded device function: {}", fn_name);
};
load_fn(vk_get_device_queue, "vkGetDeviceQueue");
@ -370,7 +395,26 @@ void Instance::load_device_functions_impl(VkDevice device)
load_fn(vk_cmd_draw, "vkCmdDraw");
load_fn(vk_cmd_set_viewport, "vkCmdSetViewport");
load_fn(vk_cmd_set_scissors, "vkCmdSetScissor");
load_fn(vk_cmd_push_constants, "vkCmdPushConstants");
load_fn(vk_cmd_copy_buffer, "vkCmdCopyBuffer");
load_fn(vk_create_descriptor_set_layout, "vkCreateDescriptorSetLayout");
load_fn(vk_destroy_descriptor_set_layout, "vkDestroyDescriptorSetLayout");
load_fn(vk_create_descriptor_pool, "vkCreateDescriptorPool");
load_fn(vk_destroy_descriptor_pool, "vkDestroyDescriptorPool");
load_fn(vk_allocate_descriptor_sets, "vkAllocateDescriptorSets");
load_fn(vk_free_descriptor_sets, "vkFreeDescriptorSets");
load_fn(vk_create_buffer, "vkCreateBuffer");
load_fn(vk_destroy_buffer, "vkDestroyBuffer");
load_fn(vk_allocate_memory, "vkAllocateMemory");
load_fn(vk_bind_buffer_memory, "vkBindBufferMemory");
load_fn(vk_map_memory, "vkMapMemory");
load_fn(vk_unmap_memory, "vkUnmapMemory");
load_fn(vk_free_memory, "vkFreeMemory");
load_fn(vk_get_buffer_memory_requirements, "vkGetBufferMemoryRequirements");
load_fn(vk_reset_command_buffer, "vkResetCommandBuffer");
load_fn(vk_cmd_begin_rendering, "vkCmdBeginRendering");
load_fn(vk_cmd_end_rendering, "vkCmdEndRendering");
}
auto Instance::enumerate_gpus() const -> std::vector<VkPhysicalDevice>

View file

@ -1,3 +1,4 @@
#include <logger/logger.hpp>
#include <ranges>
#include <renderer/backend/vk/context/device.hpp>
#include <renderer/backend/vk/context/gpu.hpp>
@ -95,8 +96,8 @@ Swapchain::~Swapchain()
}
catch (const std::exception &exp)
{
log_err("Failed to destroy swapchain:");
log_err("\twhat: {}", exp.what());
log::error("Failed to destroy swapchain:");
log::error("\twhat: {}", exp.what());
}
}

View file

@ -49,6 +49,17 @@ public:
return m_images.size();
}
[[nodiscard]] auto get_image_view(uint32_t idx) -> VkImageView
{
return m_image_views[idx];
}
[[nodiscard]] auto get_image(uint32_t idx) -> VkImage
{
return m_images[idx];
}
[[nodiscard]] auto create_framebuffers_for_pass(VkRenderPass pass) const
-> std::vector<VkFramebuffer>;

View file

@ -0,0 +1,106 @@
#include <renderer/backend/vk/context/device.hpp>
#include <renderer/backend/vk/context/gpu.hpp>
#include <renderer/backend/vk/data/buffer.hpp>
namespace lt::renderer::vk {
Buffer::Buffer(IDevice *device, IGpu *gpu, const CreateInfo &info)
: m_device(static_cast<Device *>(device))
, m_gpu(static_cast<Gpu *>(gpu))
, m_buffer(
m_device,
VkBufferCreateInfo {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = info.size,
.usage = to_native_usage_flags(info.usage),
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
}
)
, m_memory(m_device, m_buffer, determine_allocation_info(info.usage))
, m_size(info.size)
{
}
[[nodiscard]] auto Buffer::map() -> std::span<std::byte> /* override */
{
return m_device->map_memory(m_memory, m_size, 0ul);
}
void Buffer::unmap() /* override */
{
m_device->unmap_memory(m_memory);
}
[[nodiscard]] auto Buffer::determine_allocation_info(Usage usage) const -> VkMemoryAllocateInfo
{
const auto requirements = m_device->get_memory_requirements(m_buffer);
auto memory_properties = m_gpu->get_memory_properties();
const auto required_properties = to_native_memory_properties(usage);
auto type = 0u;
for (auto idx = 0; idx < memory_properties.memoryTypeCount; ++idx)
{
const auto property_flags = memory_properties.memoryTypes[idx].propertyFlags;
if (has_correct_memory_type_bit(requirements.memoryTypeBits, idx)
&& has_required_memory_properties(required_properties, property_flags))
{
type = idx;
break;
}
}
return VkMemoryAllocateInfo {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = requirements.size,
.memoryTypeIndex = type,
};
}
[[nodiscard]] auto Buffer::to_native_usage_flags(Usage usage) const -> VkBufferUsageFlags
{
switch (usage)
{
case Usage::vertex: return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
case Usage::index: return VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
case Usage::storage:
return VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
case Usage::staging: return VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
}
std::unreachable();
}
[[nodiscard]] auto Buffer::to_native_memory_properties(Usage usage) const -> VkMemoryPropertyFlags
{
switch (usage)
{
case Usage::vertex:
case Usage::index:
case Usage::storage: return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
case Usage::staging:
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
}
std::unreachable();
}
[[nodiscard]] auto Buffer::has_correct_memory_type_bit(uint32_t type_bits, uint32_t type_idx) const
-> bool
{
return type_bits & (1 << type_idx);
}
[[nodiscard]] auto Buffer::has_required_memory_properties(
uint32_t required_properties,
uint32_t property_flags
) const -> bool
{
return (property_flags & required_properties) == required_properties;
}
} // namespace lt::renderer::vk

View file

@ -0,0 +1,60 @@
#pragma once
#include <renderer/backend/vk/raii/raii.hpp>
#include <renderer/frontend/data/buffer.hpp>
namespace lt::renderer::vk {
class Buffer: public IBuffer
{
public:
Buffer(class IDevice *device, class IGpu *gpu, const CreateInfo &info);
[[nodiscard]] auto map() -> std::span<std::byte> override;
void unmap() override;
// TODO(Light): this is to make copying possible.
// But it should be removed in the future,
// Right now it's not possible because: buffers can't understand CommandBuffers.
// And I'm not sure how to properly abstract over command buffers,
// before using other APIs...
[[nodiscard]] auto vk()
{
return *m_buffer;
}
[[nodiscard]] auto get_size() const -> size_t override
{
return m_size;
}
private:
[[nodiscard]] auto determine_allocation_info(Usage usage) const -> VkMemoryAllocateInfo;
[[nodiscard]] auto to_native_usage_flags(Usage usage) const -> VkBufferUsageFlags;
[[nodiscard]] auto to_native_memory_properties(Usage usage) const -> VkMemoryPropertyFlags;
[[nodiscard]] auto has_correct_memory_type_bit(uint32_t type_bits, uint32_t type_idx) const
-> bool;
[[nodiscard]] auto has_required_memory_properties(
uint32_t required_properties,
uint32_t property_flags
) const -> bool;
Device *m_device {};
Gpu *m_gpu {};
raii::Buffer m_buffer;
raii::Memory m_memory;
// TODO(Light): should this reflect the allocation size instead?
size_t m_size {};
};
} // namespace lt::renderer::vk

View file

@ -0,0 +1,9 @@
#pragma once
namespace lt::renderer::vk {
class Descriptors
{
};
} // namespace lt::renderer::vk

View file

@ -1,3 +1,4 @@
#include <logger/logger.hpp>
#include <renderer/backend/vk/messenger.hpp>
namespace lt::renderer::vk {
@ -42,8 +43,8 @@ Messenger::Messenger(IInstance *instance, CreateInfo info)
}
catch (const std::exception &exp)
{
log_err("Uncaught exception in messenger callback:");
log_err("\twhat: {}", exp.what());
log::error("Uncaught exception in messenger callback:");
log::error("\twhat: {}", exp.what());
}
return VK_FALSE;

View file

@ -1,10 +1,11 @@
#include <memory/pointer_types/null_on_move.hpp>
#include <renderer/backend/vk/context/device.hpp>
#include <renderer/backend/vk/context/instance.hpp>
#include <renderer/backend/vk/vulkan.hpp>
namespace lt::renderer::vk::raii {
// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions)
class DebugMessenger
{
public:
@ -16,13 +17,19 @@ public:
~DebugMessenger()
{
if (!m_instance)
if (m_instance)
{
return;
}
m_instance->destroy_messenger(m_object);
}
}
DebugMessenger(DebugMessenger &&) = default;
DebugMessenger(const DebugMessenger &) = delete;
auto operator=(DebugMessenger &&) -> DebugMessenger & = default;
auto operator=(const DebugMessenger &) -> DebugMessenger & = delete;
private:
memory::NullOnMove<Instance *> m_instance {};
@ -30,4 +37,88 @@ private:
VkDebugUtilsMessengerEXT m_object;
};
class Buffer
{
public:
Buffer(Device *device, VkBufferCreateInfo info)
: m_device(device)
, m_object(m_device->create_buffer(info))
{
}
~Buffer()
{
if (m_device)
{
m_device->destroy_buffer(m_object);
}
}
Buffer(Buffer &&) = default;
Buffer(const Buffer &) = delete;
auto operator=(Buffer &&) -> Buffer & = default;
auto operator=(const Buffer &) -> Buffer & = delete;
[[nodiscard]] auto operator*() const -> VkBuffer
{
return m_object;
}
[[nodiscard]] operator VkBuffer() const
{
return m_object;
}
private:
memory::NullOnMove<Device *> m_device {};
VkBuffer m_object;
};
class Memory
{
public:
Memory(Device *device, VkBuffer buffer, VkMemoryAllocateInfo info)
: m_device(device)
, m_object(m_device->allocate_memory(info))
{
m_device->bind_memory(buffer, m_object);
}
~Memory()
{
if (m_device)
{
m_device->free_memory(m_object);
}
}
Memory(Memory &&) = default;
Memory(const Memory &) = delete;
auto operator=(Memory &&) -> Memory & = default;
auto operator=(const Memory &) -> Memory & = delete;
[[nodiscard]] auto operator*() const -> VkDeviceMemory
{
return m_object;
}
[[nodiscard]] operator VkDeviceMemory() const
{
return m_object;
}
private:
memory::NullOnMove<Device *> m_device {};
VkDeviceMemory m_object = VK_NULL_HANDLE;
};
} // namespace lt::renderer::vk::raii

View file

@ -1,6 +1,7 @@
#include <renderer/backend/vk/context/device.hpp>
#include <renderer/backend/vk/context/swapchain.hpp>
#include <renderer/backend/vk/renderer/pass.hpp>
#include <renderer/data/frame_constants.hpp>
namespace lt::renderer::vk {
@ -11,16 +12,85 @@ Pass::Pass(
const lt::assets::ShaderAsset &fragment_shader
)
: m_device(static_cast<Device *>(device))
, m_layout(m_device->create_pipeline_layout(
VkPipelineLayoutCreateInfo {
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.setLayoutCount = 0u,
.pSetLayouts = nullptr,
.pushConstantRangeCount = 0u,
.pPushConstantRanges = nullptr,
}
))
{
auto binding = VkDescriptorSetLayoutBinding {
.binding = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1'000,
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
};
const auto descriptor_binding_flags = VkDescriptorBindingFlagsEXT {
VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT_EXT
| VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT_EXT
| VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT_EXT
| VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT_EXT,
};
constexpr auto descriptor_count = uint32_t { 1'000 };
auto descriptor_binding_flags_info = VkDescriptorSetLayoutBindingFlagsCreateInfoEXT {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT,
.bindingCount = 1,
.pBindingFlags = &descriptor_binding_flags,
};
m_vertices_descriptor_set_layout = m_device->create_descriptor_set_layout(
{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.pNext = &descriptor_binding_flags_info,
.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT_EXT,
.bindingCount = 1u,
.pBindings = &binding,
}
);
auto pool_size = VkDescriptorPoolSize {
.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = descriptor_count,
};
m_descriptor_pool = m_device->create_desscriptor_pool(
{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.poolSizeCount = 1u,
.pPoolSizes = &pool_size,
}
);
auto descriptor_set_variable_descriptor_count_info
= VkDescriptorSetVariableDescriptorCountAllocateInfo {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO,
.descriptorSetCount = 1u,
.pDescriptorCounts = &descriptor_count,
};
m_vertices_descriptor_set = m_device->allocate_descriptor_set(
VkDescriptorSetAllocateInfo {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
.pNext = &descriptor_set_variable_descriptor_count_info,
.descriptorPool = m_descriptor_pool,
.descriptorSetCount = 1u,
.pSetLayouts = &m_vertices_descriptor_set_layout,
}
);
m_layout = m_device->create_pipeline_layout(
std::vector<VkDescriptorSetLayout> {
m_vertices_descriptor_set_layout,
},
std::vector<VkPushConstantRange> {
VkPushConstantRange {
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
.offset = 0u,
.size = sizeof(FrameConstants),
},
}
);
auto *vertex_module = create_module(
vertex_shader.unpack(lt::assets::ShaderAsset::BlobTag::code)
);
@ -112,17 +182,16 @@ Pass::Pass(
.blendConstants = { 0.0f, 0.0, 0.0, 0.0 },
};
auto attachment_description = VkAttachmentDescription {
.format = static_cast<Swapchain *>(swapchain)->get_format(),
.samples = VK_SAMPLE_COUNT_1_BIT,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
};
// auto attachment_description = VkAttachmentDescription {
// .format =,
// .samples = VK_SAMPLE_COUNT_1_BIT,
// .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
// .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
// .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
// .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
// .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
// .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
// };
auto color_attachment_ref = VkAttachmentReference {
.attachment = 0,
@ -144,21 +213,18 @@ Pass::Pass(
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
};
m_pass = m_device->create_pass(
VkRenderPassCreateInfo {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
.attachmentCount = 1u,
.pAttachments = &attachment_description,
.subpassCount = 1u,
.pSubpasses = &subpass_description,
.dependencyCount = 1u,
.pDependencies = &pass_dependency,
}
);
auto color_format = static_cast<Swapchain *>(swapchain)->get_format();
auto rendering_info = VkPipelineRenderingCreateInfoKHR {
.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
.colorAttachmentCount = 1u,
.pColorAttachmentFormats = &color_format,
};
m_pipeline = m_device->create_graphics_pipeline(
VkGraphicsPipelineCreateInfo {
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = &rendering_info,
.stageCount = static_cast<uint32_t>(shader_stages.size()),
.pStages = shader_stages.data(),
.pVertexInputState = &vertex_input,
@ -170,15 +236,14 @@ Pass::Pass(
.pColorBlendState = &color_blend,
.pDynamicState = &dynamic_state,
.layout = m_layout,
.renderPass = m_pass,
.renderPass = VK_NULL_HANDLE,
.subpass = 0u,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = -1,
}
);
m_framebuffers = static_cast<Swapchain *>(swapchain)->create_framebuffers_for_pass(m_pass);
// m_framebuffers = static_cast<Swapchain *>(swapchain)->create_framebuffers_for_pass(m_pass);
m_device->destroy_shader_module(vertex_module);
m_device->destroy_shader_module(fragment_module);
@ -192,9 +257,14 @@ Pass::~Pass()
}
m_device->wait_idle();
m_device->destroy_descriptor_set_layout(m_vertices_descriptor_set_layout);
m_device->free_descriptor_set(m_descriptor_pool, m_vertices_descriptor_set);
m_device->destroy_descriptor_pool(m_descriptor_pool);
m_device->destroy_framebuffers(m_framebuffers);
m_device->destroy_pipeline(m_pipeline);
m_device->destroy_pass(m_pass);
// m_device->destroy_pass(m_pass);
m_device->destroy_pipeline_layout(m_layout);
}
@ -207,7 +277,8 @@ void Pass::replace_swapchain(const ISwapchain &swapchain)
m_device->wait_idle();
m_device->destroy_framebuffers(m_framebuffers);
m_framebuffers = static_cast<const Swapchain &>(swapchain).create_framebuffers_for_pass(m_pass);
// m_framebuffers = static_cast<const Swapchain
// &>(swapchain).create_framebuffers_for_pass(m_pass);
}
auto Pass::create_module(lt::assets::Blob blob) -> VkShaderModule

Some files were not shown because too many files have changed in this diff Show more