Compare commits

..

23 commits

Author SHA1 Message Date
7d02876d12
refactor: disable clang-tidy checks for imgui backends
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-07-20 08:06:19 +03:30
a74e23051c
ci: fix
Some checks failed
continuous-integration/drone/pr Build is failing
2025-07-20 08:00:23 +03:30
9909f5c430
ci: refactors
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-07-20 07:58:41 +03:30
6ecb268196
fix: formatting issues
Some checks reported errors
continuous-integration/drone/pr Build encountered an error
2025-07-20 07:39:56 +03:30
144ca1aa4f
refactor: extract shell commands from .drone.yml to separate script files
Some checks reported errors
continuous-integration/drone/push Build encountered an error
continuous-integration/drone/pr Build encountered an error
2025-07-20 07:38:46 +03:30
3520ae36a6
ci: update .drone.yml
Some checks reported errors
continuous-integration/drone/push Build encountered an error
continuous-integration/drone/pr Build encountered an error
2025-07-20 07:30:59 +03:30
57031ee44e
ci: update .drone.yml
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-07-20 07:28:21 +03:30
90a1a06bbe
build: fix build on windows
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-07-20 07:10:08 +03:30
9662f97095
ci: wip
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-07-20 06:56:20 +03:30
8f12d76401
ci: wip
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-07-20 06:51:01 +03:30
cb5b97dddb
ci: wip
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-07-20 06:49:12 +03:30
253a8216a2
ci: wip
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-07-20 06:34:22 +03:30
bf2bf1e1a1
ci: wip
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-07-20 06:33:21 +03:30
f21e6450cd
ci: wip
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-07-20 06:32:24 +03:30
d1caf1df5b
ci: wip
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-07-20 06:22:51 +03:30
bcfbc5c1c1
ci: wip
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-07-20 06:22:02 +03:30
48bf9eb033
ci: wip
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-07-20 06:20:54 +03:30
1bdfa1fac0
ci: wip
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-07-20 06:16:06 +03:30
8df6e7967f
ci: wip
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-07-20 06:10:27 +03:30
034a6c7537
ci: wip
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-07-20 06:06:31 +03:30
b1bca72f6f
ci: check conan version
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-07-20 06:03:09 +03:30
306b65df94
ci: re-enable clone
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-07-20 06:01:36 +03:30
e59b3b8d3a
ci: add ls
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-07-20 05:59:54 +03:30
126 changed files with 1999 additions and 3366 deletions

View file

@ -13,7 +13,7 @@ steps:
- name: unit tests - name: unit tests
shell: powershell shell: powershell
commands: commands:
- ./tools/ci/amd64/msvc/unit_tests.ps1 - ./tools/ci/steps/amd64/msvc/unit-tests.ps1
--- ---
kind: pipeline kind: pipeline
@ -25,16 +25,16 @@ trigger:
steps: steps:
- name: unit tests - name: unit tests
image: amd64_gcc_unit_tests:latest image: unit_tests:latest
pull: if-not-exists pull: if-not-exists
commands: commands:
- ./tools/ci/amd64/gcc/unit_tests.sh - ./tools/ci/steps/amd64/gcc/unit-tests.sh
- name: valgrind - name: valgrind
image: amd64_gcc_valgrind:latest image: valgrind:latest
pull: if-not-exists pull: if-not-exists
commands: commands:
- ./tools/ci/amd64/gcc/valgrind.sh - ./tools/ci/steps/amd64/gcc/valgrind.sh
--- ---
kind: pipeline kind: pipeline
@ -45,26 +45,11 @@ trigger:
- main - main
steps: steps:
- name: code coverage
image: amd64_clang_coverage:latest
pull: if-not-exists
environment:
CODECOV_TOKEN:
from_secret: CODECOV_TOKEN
commands:
- ./tools/ci/amd64/clang/coverage.sh
- name: leak sanitizer - name: leak sanitizer
image: amd64_clang_lsan:latest image: leak_sanitizer:latest
pull: if-not-exists pull: if-not-exists
commands: commands:
- ./tools/ci/amd64/clang/lsan.sh - ./tools/ci/steps/amd64/clang/lsan.sh
- name: memory sanitizer
image: amd64_clang_msan:latest
pull: if-not-exists
commands:
- ./tools/ci/amd64/clang/msan.sh
--- ---
kind: pipeline kind: pipeline
@ -75,66 +60,24 @@ trigger:
- main - main
steps: steps:
- name: clang tidy - name: static_analysis
image: clang_tidy:latest image: static_analysis:latest
pull: if-not-exists pull: if-not-exists
privileged: true privileged: true
commands: commands:
- ./tools/ci/static_analysis/clang_tidy.sh - ./tools/ci/steps/static_analysis.sh
- name: clang format
image: clang_format:latest
pull: if-not-exists
commands:
- ./tools/ci/static_analysis/clang_format.sh
--- ---
kind: pipeline kind: pipeline
type: docker type: docker
name: documentation — development name: style
node:
environment: ryali
trigger: trigger:
branch: branch:
- main - main
steps: steps:
- name: build and deploy - name: clang format
image: documentation:latest image: clang_format:latest
pull: if-not-exists pull: if-not-exists
commands: commands:
- pwd - ./tools/ci/steps/style.sh
- 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:
- pwd
- 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,43 +1,16 @@
cmake_minimum_required(VERSION 3.14) cmake_minimum_required(VERSION 3.14)
project(Light) project(Light)
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)
set(CMAKE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake)
include(CheckCXXSourceCompiles) include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/functions.cmake)
include(${CMAKE_DIR}/functions.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/definitions.cmake)
include(${CMAKE_DIR}/definitions.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/dependencies.cmake)
include(${CMAKE_DIR}/dependencies.cmake)
add_option(ENABLE_UNIT_TESTS "Enables the building of the unit test modules") add_option(ENABLE_STATIC_ANALYSIS "Performs static analysis via clang-tidy and fails build on failing checks")
add_option(ENABLE_FUZZ_TESTS "Enables the building of the fuzz test modules")
add_option(ENABLE_STATIC_ANALYSIS "Makes clang-tidy checks mandatory for compilation")
if (ENABLE_STATIC_ANALYSIS) if (ENABLE_STATIC_ANALYSIS)
set(CMAKE_CXX_CLANG_TIDY "clang-tidy;--warnings-as-errors=*;--allow-no-checks") set(CMAKE_CXX_CLANG_TIDY "clang-tidy;--warnings-as-errors=*;--allow-no-checks")
endif () endif ()
add_option(ENABLE_LLVM_COVERAGE "Enables the code coverage instrumentation for clang") add_option(ENABLE_TESTS "Enables the building of the test modules")
if(ENABLE_LLVM_COVERAGE)
if (NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
message(FATAL_ERROR "ENABLE_LLVM_COVERAGE only supports the clang compiler")
endif ()
# Check for libc++
check_cxx_source_compiles("
#include <string>
#ifdef _LIBCPP_VERSION
int main() { return 0; }
#else
#error Not using libc++
#endif
" USING_LIBCXX)
if(NOT USING_LIBCXX)
message(FATAL_ERROR "ENABLE_LLVM_COVERAGE requires libc++, please compile with -stdlib=libc++")
endif()
add_compile_options(-fprofile-instr-generate -fcoverage-mapping)
add_link_options(-fprofile-instr-generate -fcoverage-mapping)
endif ()
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/modules) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/modules)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/glad) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/glad)

View file

@ -1,6 +1,2 @@
# Light # Light
See docs.light7734.com for a comprehensive project documentation See docs.light7734.com for a comprehensive project documentation
<!---FUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUCK
MEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!!!!!!!!-->

View file

@ -11,18 +11,14 @@ class LightRecipe(ConanFile):
generators = "CMakeDeps" generators = "CMakeDeps"
options = { options = {
"enable_unit_tests": [True, False], "enable_tests": [True, False],
"enable_fuzz_tests": [True, False],
"enable_llvm_coverage": [True, False],
"enable_static_analysis": [True, False], "enable_static_analysis": [True, False],
"use_mold": [True, False], "use_mold": [True, False],
"export_compile_commands": [True, False], "export_compile_commands": [True, False],
} }
default_options = { default_options = {
"enable_unit_tests": True, "enable_tests": True,
"enable_fuzz_tests": False,
"enable_llvm_coverage": False,
"enable_static_analysis": False, "enable_static_analysis": False,
"use_mold": False, "use_mold": False,
"export_compile_commands": True, "export_compile_commands": True,
@ -48,10 +44,8 @@ class LightRecipe(ConanFile):
tc.cache_variables["CMAKE_LINKER_TYPE"] = "MOLD" tc.cache_variables["CMAKE_LINKER_TYPE"] = "MOLD"
tc.cache_variables["CMAKE_EXPORT_COMPILE_COMMANDS"] = self.options.export_compile_commands tc.cache_variables["CMAKE_EXPORT_COMPILE_COMMANDS"] = self.options.export_compile_commands
tc.cache_variables["ENABLE_UNIT_TESTS"] = self.options.enable_unit_tests
tc.cache_variables["ENABLE_FUZZ_TESTS"] = self.options.enable_fuzz_tests
tc.cache_variables["ENABLE_LLVM_COVERAGE"] = self.options.enable_llvm_coverage
tc.cache_variables["ENABLE_STATIC_ANALYSIS"] = self.options.enable_static_analysis tc.cache_variables["ENABLE_STATIC_ANALYSIS"] = self.options.enable_static_analysis
tc.cache_variables["ENABLE_TESTS"] = self.options.enable_tests
repo = git.Repo(search_parent_directories=True) repo = git.Repo(search_parent_directories=True)
tc.cache_variables["GIT_HASH"] = repo.head.object.hexsha tc.cache_variables["GIT_HASH"] = repo.head.object.hexsha

3
docs/.gitignore vendored
View file

@ -1,3 +0,0 @@
_build/
generated/

View file

@ -1,72 +1,62 @@
Asset Management Asset Management
=================================================================================================== ===================================================================================================
Layout
On Disk (file) Layout
--------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------
{version} | 4 bytes, ie. uint32_t
.. code-block:: md {general metadata} | sizeof(AssetMetadata)
{specialized metadata} | sizeof(XXXAssetMetadata), eg. TextureAssetMetadata
{version} | 4 bytes, ie. uint32_t {n} | 4 bytes, ie. uint32_t
{general metadata} | sizeof(AssetMetadata) {blob_0...n metadata} | n * sizeof(BlobMetadata)
{specialized metadata} | sizeof(XXXAssetMetadata), eg. TextureAssetMetadata {blob_0...n data} | variable size based on actual data
{n} | 4 bytes, ie. uint32_t {end marker} | 8 byte, ie size_t for marking the END
{blob_0...n metadata} | n * sizeof(BlobMetadata)
{blob_0...n data} | variable size based on actual data
{end marker} | 8 byte, ie size_t for marking the END
Sections Sections
--------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------
version -> The version of the asset for forward compatibility
.. code-block:: md general metadata -> Common asset metadata such as file-path, asset-type, creator, etc.
specialized metadata -> Metadata specific to the asset, eg. texture dimensions for Textures.
version -> The version of the asset for forward compatibility n -> The number of blobs.
general metadata -> Common asset metadata such as file-path, asset-type, creator, etc. blob_0...n metadata -> Metadata specifying how the actual data is packed, required for unpacking.
specialized metadata -> Metadata specific to the asset, eg. texture dimensions for Textures. blob_0...n data -> The actual data, packed and compressed to be reacdy for direct engine consumption.
n -> The number of blobs.
blob_0...n metadata -> Metadata specifying how the actual data is packed, required for unpacking.
blob_0...n data -> The actual data, packed and compressed to be reacdy for direct engine consumption.
Loading Loading
--------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------
Loading pre-baked asset files (like .png files) for baking: Each `Loader` has ONE OR MORE supported input file types (detected via the file extension): eg. StbLoader -> Can read in .jpg, .png, .bmp, etc.... files
Each `Loader` has ONLY ONE supported output asset type:
Each **Loader** has ONE OR MORE supported input file types (detected via the file extension): eg. StbLoader -> Can read in .jpg, .png, .bmp, etc.... files
Each **Loader** has ONLY ONE supported output asset type:
eg. StbLoader -> outputs TextureAsset eg. StbLoader -> outputs TextureAsset
Multiple **Loader**\s MAY have as output the same asset type: Multiple `Loader`s MAY have as output the same asset type:
eg. StbLoader -> outputs TextureAsset eg. StbLoader -> outputs TextureAsset
eg. SomeOtherImgLoader -> outputs TextureAsset eg. SomeOtherImgLoader -> outputs TextureAsset
Multiple **Loader**\s SHOULD NOT have as input same extension types Multiple `Loader`s SHOULD NOT have as input same extension types
eg. .jpg, .png -> if supported, should only be supported by 1 **Loader** class eg. .jpg, .png -> if supported, should only be supported by 1 `Loader` class
Each **Loader** SHOULD read and turn the data from a file (eg. .png for textures) into something Each `Loader` SHOULD read and turn the data from a file (eg. .png for textures) into something
understandable by a **Packer** (not the engine itself). understandable by a `Packer` (not the engine itself).
A **Loader** SHOULD NOT be responsible for packing the parsed file data into asset data, A `Loader` SHOULD NOT be responsible for packing the parsed file data into asset data,
as that implies direct understanding of the layout required by the engine. as that implies direct understanding of the layout required by the engine.
And if that layout changes, ALL **Loader**s should change accordingly; And if that layout changes, ALL `Loader`s should change accordingly;
which makes a class that's responsible for reading files dependant on the engine's (potentially frequent) internal changes. which makes a class that's responsible for reading files dependant on the engine's (potentially frequent) internal changes.
The logic is to reduce many-to-one dependency into a one-to-one dependency by redirecting the packing process to **Packer** classes The logic is to reduce many-to-one dependency into a one-to-one dependency by redirecting the packing process to `Packer` classes
Packing Packing
--------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------
Each **Packer** is responsible for packing ONLY ONE asset type: Each `Packer` is responsible for packing ONLY ONE asset type:
eg. TexturePacker for packing texture assets from parsed image files. eg. TexturePacker for packing texture assets from parsed image files.
eg. ModelPacker for packing model assets from parsed model files. eg. ModelPacker for packing model assets from parsed model files.
Each **Packer** will output ONE OR MORE blobs of data, Each `Packer` will output ONE OR MORE blobs of data,
and for EACH blob of data, it'll write a BlobMetadata, AFTER the specialized metadata (eg. TextureAssetMetadata) and for EACH blob of data, it'll write a BlobMetadata, AFTER the specialized metadata (eg. TextureAssetMetadata)
A **Packer** will make use of the **Compressor** classes to compress the data, A `Packer` will make use of the `Compressor` classes to compress the data,
and lay it out in a way that is suitable for the engine's consumption. and lay it out in a way that is suitable for the engine's consumption.
Unpacking Unpacking
--------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------
A **Parser** is responsible for parsing ONLY ONE asset type: A `Parser` is responsible for parsing ONLY ONE asset type:
eg. TextureParser for parsing texture assets for direct engine consumption. eg. TextureParser for parsing texture assets for direct engine consumption.
eg. ModelParser for parsing model assets for direct engine consumption. eg. ModelParser for parsing model assets for direct engine consumption.

View file

@ -1,5 +1,4 @@
.. architecture/resources
Resource Management Resource Management
=================================================================================================== ===================================================================================================

View file

@ -1,27 +0,0 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = 'light'
copyright = '2025, light7734'
author = 'light7734'
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = []
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
html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']

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

@ -1,10 +0,0 @@
.. guidelines/conventions
Coding Conventions
===================================================================================================
Any line of code added to the engine, must abide by following conventions.
They may seem arbitrary, and sometimes they are. But to achieve **consistency**, which is not an arbitrary goal, is to
follow these guidelines.
AAA
--------------------

View file

@ -1,147 +0,0 @@
.. guidelines/development
Development
===================================================================================================
As a solo-project, I am not only the **developer**, but also the **manager**.
Therefore there is a need, if this project is to succeed, to have a development plan.
Such a plan should:
- Define a way to **distribute work** (across time, since there's only 1 developer).
- Define what is a **unit of work** (cycles).
- Provide a way to **track productivity**, which helps projecting the future and **detecting patterns** early on.
- Provide a **pipeline** for the work to go through and **minimize ambiguity**.
These are the **management** aspects of the project, which help the development goals to be more **pragmatic**
---by pulling my mind out of its **engineering dreamland**, and make it focus on the **broader picture**.
Cycle
---------------------------------------------------------------------------------------------------
A cycle is one **step** in development, one cycle = one ticket, and it consists of 4 stages:
1 - Make it known
- Write the commit message.
- This limits the **scope of changes** and gives you a very specific **goal** to work towards.
- If something outside of this scope really bothers you, fix and stash for a future cycle.
- Make a ticket if stash-fix is implausible ---**DO NOT** write **todo** comments.
- The message should follow the project's **commit message specifications**.
- Make a ticket.
- Version control (git) is a **development-tool**, not a **management-tool**.
- Provide a very brief description ---This may be used in the commit message's body.
2 - Make it work
- Write high-level tests that confirms the cycle's requirements are met.
- That is, specify requirements in a programming language instead of English.
- You're done when all the tests pass.
- Preferably write the tests first, but it's okay to start with the interface.
- Tests may not be necessary depending on the requirements and commit type.
- "Make it work" doesn't mean liberally producing shit code, you should:
- Follow project's **conventions**.
- Follow **best practices** and **proven swe principles**.
- Enable **warnings as errors**.
- Enable **static analysis**.
- Don't break any pre-existing-tests.
- Have the over-all picture in mind.
3 - Make it right
- Test driven refactoring
- Now you have a better picture of how things relate and work.
- Switch to a TDD-style development to do the refactoring while following swe best-practices and proven-principles.
4 - Make it fast
- This is an engine, at the end of the day, **performance** is king.
- Get a performance and/or memory profile and try to alleviate the bottlenecks.
- Avoid premature optimizations, be certain what you change has performance benefits.
Sprint
---------------------------------------------------------------------------------------------------
A sprint is the collection of all the finished cycles in one week.
It's meant to provide insight on development speed and help projecting the future.
Commit Message Specification
---------------------------------------------------------------------------------------------------
The project follows the `Conventional Commits Specification <https://www.conventionalcommits.org/en/v1.0.0-beta.4>`_.
.. code-block:: md
<type>[optional scope]: <description>
[optional body]
[optional footer]
With the following commit types:
- feat
- For adding a new feature.
- Causes a **minor** bump in version.
- fix
- For changes that fix one or more bug.
- Causes a **patch** bump in version.
- refactor
- For non feat/fix changes that improve the implementation and/or the interface.
- Causes a **patch** bump in version.
- perf
- For changes that (hopefully) improve the performance.
- Causes a **patch** bump in version.
- build
- For changes that affect the build system or external dependencies.
- Causes a **patch** bump in version.
- asset
- For changes to the files under the ``/data`` directory.
- Causes a **patch** bump in version.
- test
- For adding missing tests or correcting the existing tests.
- Does not affect the version.
- chore
- For releases, .gitignore changes, deleting unused files, etc.
- Does not affect the version.
- ci
- For changes to our CI configuration files and scripts, including files under ``/tools/ci``.
- Does not affect the version.
- docs
- For changes to the documentations.
- Does not affect the version.
Semantic Versioning
---------------------------------------------------------------------------------------------------
Coupled with conventional commit style messages, we can automajically version the project following
the **Semantic Versioning 2.0.0** specifications.
The full version identifier consits of a version core (major.minor.patch) + label + hexsha of the commit.
Using the following format:
.. code-block:: md
<major>.<minor>.<patch>-<label>+<short_hexsha>
eg.
0.8.1-kitten+ea898
0.5.0-kitten+01d85
1.5.0-akasha+7de53
kitten refers to all pre-release (1.0.0) versions
The shortened hexsha of a commit is obtained by:
``git rev-parse --short=5 <commit_hexsha>``

View file

@ -1,7 +0,0 @@
.. guidelines/philosophy
Philosophy
===================================================================================================
| **A theory or attitude that acts as a guiding principle for behaviour.**
| --- Oxford Languages

View file

@ -1,32 +1,6 @@
.. light documentation A bleeding-edge, cross-platform, cross-graphics-api, minimal-dependencies modern game-engine.
.. toctree::
:maxdepth: 2
:caption: Light Engine
light/showcase.rst
light/features.rst
.. toctree::
:maxdepth: 2
:caption: Software Architecture
architecture/assets.rst
architecture/resource.rst
.. toctree::
:maxdepth: 2
:caption: Development Guidelines
guidelines/philosophy.rst
guidelines/development.rst
guidelines/conventions.rst
.. toctree::
:maxdepth: 2
:caption: Generated Docs
generated/api.rst
generated/changelog.rst
Supported Platforms: Windows, Mac, Linux, FreeBSD
Supported GraphicsAPIs: DirectX12-Ultimate, Vulkan 1.4, Metal, OpenGL 4.6
Dependencies: stdlib, meshoptimizer

View file

@ -1,4 +0,0 @@
.. light/features
Features
===================================================================================================

View file

@ -1,4 +0,0 @@
.. light/demos
Showcase
===================================================================================================

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

@ -4,21 +4,22 @@ add_subdirectory(./time)
add_subdirectory(./logger) add_subdirectory(./logger)
add_subdirectory(./debug) add_subdirectory(./debug)
add_subdirectory(./math) add_subdirectory(./math)
#
add_subdirectory(./asset_baker) add_subdirectory(./asset_baker)
add_subdirectory(./asset_parser) add_subdirectory(./asset_parser)
# add_subdirectory(./asset_manager) add_subdirectory(./asset_manager)
#
add_subdirectory(./camera) add_subdirectory(./camera)
# add_subdirectory(./input) add_subdirectory(./input)
# add_subdirectory(./ui) add_subdirectory(./ui)
#
add_subdirectory(./surface) add_subdirectory(./window)
# add_subdirectory(./renderer) add_subdirectory(./renderer)
add_subdirectory(./ecs) add_subdirectory(./ecs)
#
add_subdirectory(./app) add_subdirectory(./app)
# apps # apps
add_subdirectory(./mirror) add_subdirectory(./mirror)
add_subdirectory(test) add_subdirectory(test)

View file

@ -1,2 +1,21 @@
add_library_module(app application.cpp) add_library_module(app
target_link_libraries(app PRIVATE lt_debug) application.cpp
layer.cpp
layer_stack.cpp
)
target_link_libraries(app
PUBLIC
renderer
logger
ui
asset_parser
asset_manager
lt_debug
ecs
window
glad
time
opengl::opengl
EnTT::EnTT
)

View file

@ -1,48 +1,203 @@
#include <app/application.hpp> #include <app/application.hpp>
#include <app/system.hpp> #include <app/layer.hpp>
#include <app/layer_stack.hpp>
#include <asset_manager/asset_manager.hpp>
#include <input/events/event.hpp>
#include <input/events/keyboard.hpp>
#include <input/events/window.hpp>
#include <input/input.hpp>
#include <lt_debug/assertions.hpp>
#include <ranges>
#include <renderer/blender.hpp>
#include <renderer/graphics_context.hpp>
#include <renderer/render_command.hpp>
#include <renderer/renderer.hpp>
#include <ui/ui.hpp>
#include <window/window.hpp>
namespace lt::app { namespace lt {
Application *Application::s_instance = nullptr;
Application::Application(): m_window(nullptr)
{
ensure(!s_instance, "Application constructed twice");
s_instance = this;
m_window = Window::create([this](auto &&PH1) { on_event(std::forward<decltype(PH1)>(PH1)); });
// create graphics context
m_graphics_context = GraphicsContext::create(
GraphicsAPI::OpenGL,
(GLFWwindow *)m_window->get_handle()
);
AssetManager::load_shader(
"LT_ENGINE_RESOURCES_TEXTURE_SHADER",
"data/assets/shaders/texture/vs.asset",
"data/assets/shaders/texture/ps.asset"
);
AssetManager::load_shader(
"LT_ENGINE_RESOURCES_TINTED_TEXTURE_SHADER",
"data/assets/shaders/tinted_texture/vs.asset",
"data/assets/shaders/tinted_texture/ps.asset"
);
AssetManager::load_shader(
"LT_ENGINE_RESOURCES_QUAD_SHADER",
"data/assets/shaders/quads/vs.asset",
"data/assets/shaders/quads/ps.asset"
);
m_renderer = Renderer::create(
(GLFWwindow *)m_window->get_handle(),
lt::GraphicsContext::get_shared_context(),
Renderer::CreateInfo {
.quad_renderer_shader = AssetManager::get_shader("LT_ENGINE_RESOURCES_QUAD_SHADER"),
.texture_renderer_shader = AssetManager::get_shader(
"LT_ENGINE_RESOURCES_TEXTURE_SHADER"
),
.tinted_texture_renderer_shader = AssetManager::get_shader(
"LT_ENGINE_RESOURCES_TINTED_"
"TEXTURE_SHADER"
),
}
);
ensure(m_graphics_context, "lWindow::lWindow: failed to create 'GraphicsContext'");
m_user_interface = UserInterface::create(
(GLFWwindow *)m_window->get_handle(),
lt::GraphicsContext::get_shared_context()
);
m_layer_stack = create_scope<LayerStack>();
}
Application::~Application()
{
/** This is required to make forward-declarations possible:
* https://stackoverflow.com/questions/34072862/why-is-error-invalid-application-of-sizeof-to-an-incomplete-type-using-uniqu
*/
}
void Application::game_loop() void Application::game_loop()
{ {
while (true) m_window->set_visibility(true);
while (!m_window->is_closed())
{ {
for (auto &system : m_systems) update_layers();
render_layers();
render_user_interface();
poll_events();
}
}
void Application::quit()
{
s_instance->m_window->close();
}
void Application::update_layers()
{
for (auto &it : *m_layer_stack)
{ {
if (system->tick()) // narrowing double -> float
it->on_update(static_cast<float>(m_timer.elapsed_time().count()));
}
// TODO(Light): each layer should have their own "delta time"
m_timer.reset();
}
void Application::render_layers()
{
m_renderer->begin_frame();
for (auto &it : *m_layer_stack)
{
it->on_render();
}
m_renderer->end_frame();
}
void Application::render_user_interface()
{
m_user_interface->begin();
for (auto &it : *m_layer_stack)
{
it->on_user_interface_update();
}
m_user_interface->end();
}
void Application::poll_events()
{
m_window->poll_events();
}
void Application::on_event(const Event &event)
{
// window
if (event.has_category(WindowEventCategory))
{
m_window->on_event(event);
if (event.get_event_type() == EventType::WindowResized)
{
m_renderer->on_window_resize(dynamic_cast<const WindowResizedEvent &>(event));
}
}
// input
if (event.has_category(InputEventCategory))
{
Input::instance().on_event(event);
if (!Input::instance().is_receiving_game_events())
{ {
return; return;
} }
} }
for (auto &system : m_systems_to_be_registered) for (auto &it : std::ranges::reverse_view(*m_layer_stack))
{ {
m_systems.emplace_back(system)->on_register(); if (it->on_event(event))
}
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; return;
} }
} }
} }
void Application::register_system(Ref<app::ISystem> system) [[nodiscard]] auto Application::sanity_check() const -> bool
{ {
m_systems.emplace_back(std::move(system)); log_inf("Checking application sanity...");
ensure(s_instance, "Application not constructed!?");
ensure(m_window, "Window is not initialized");
ensure(m_user_interface, "User interface is not initialized");
ensure(m_graphics_context, "Graphics context is not initialized");
ensure(m_renderer, "Renderer is not initialized");
ensure(m_layer_stack, "Layer_stack is not initialized");
ensure(!m_layer_stack->is_empty(), "Layer_stack is empty");
log_inf("Logging application state...");
this->log_debug_data();
m_graphics_context->log_debug_data();
m_user_interface->log_debug_data();
return true;
} }
void Application::unregister_system(Ref<app::ISystem> system) void Application::log_debug_data() const
{ {
m_systems_to_be_unregistered.emplace_back(std::move(system)); log_inf("Platform::");
log_inf(" Platform name: {}", constants::platform_name);
log_inf(" Platform identifier: {}", std::to_underlying(constants::platform));
log_inf(" CWD: {}", std::filesystem::current_path().generic_string());
} }
} // namespace lt::app } // namespace lt

View file

@ -0,0 +1,47 @@
#include <app/layer.hpp>
#include <input/events/char.hpp>
#include <input/events/event.hpp>
#include <input/events/keyboard.hpp>
#include <input/events/mouse.hpp>
#include <input/events/window.hpp>
namespace lt {
Layer::Layer(std::string name): m_layer_name(std::move(name))
{
}
auto Layer::on_event(const Event &event) -> bool
{
switch (event.get_event_type())
{
case EventType::MouseMoved: return on_mouse_moved(dynamic_cast<const MouseMovedEvent &>(event));
case EventType::ButtonPressed:
return on_button_pressed(dynamic_cast<const ButtonPressedEvent &>(event));
case EventType::ButtonReleased:
return on_button_released(dynamic_cast<const ButtonReleasedEvent &>(event));
case EventType::WheelScrolled:
return on_wheel_scrolled(dynamic_cast<const WheelScrolledEvent &>(event));
case EventType::KeyPressed: return on_key_pressed(dynamic_cast<const KeyPressedEvent &>(event));
case EventType::KeyRepeated: return on_key_repeat(dynamic_cast<const KeyRepeatEvent &>(event));
case EventType::KeyReleased:
return on_key_released(dynamic_cast<const KeyReleasedEvent &>(event));
case EventType::SetChar: return on_set_char(dynamic_cast<const SetCharEvent &>(event));
case EventType::WindowClosed:
return on_window_closed(dynamic_cast<const WindowClosedEvent &>(event));
case EventType::WindowResized:
return on_window_resized(dynamic_cast<const WindowResizedEvent &>(event));
case EventType::WindowMoved:
return on_window_moved(dynamic_cast<const WindowMovedEvent &>(event));
case EventType::WindowLostFocus:
return on_window_lost_focus(dynamic_cast<const WindowLostFocusEvent &>(event));
case EventType::WindowGainFocus:
return on_window_gain_focus(dynamic_cast<const WindowGainFocusEvent &>(event));
default: ensure(false, "Invalid event: {}", event.get_info_lt_log());
}
}
} // namespace lt

View file

@ -0,0 +1,18 @@
#include <app/layer.hpp>
#include <app/layer_stack.hpp>
namespace lt {
void LayerStack::attach_layer(Ref<Layer> layer)
{
log_trc("Attaching layer [{}]", layer->get_name());
m_layers.emplace_back(std::move(layer));
}
void LayerStack::detach_layer(const Ref<Layer> &layer)
{
log_trc("Detaching layer [{}]", layer->get_name());
m_layers.erase(std::find(m_layers.begin(), m_layers.end(), layer));
}
} // namespace lt

View file

@ -1,15 +1,18 @@
#pragma once #pragma once
namespace lt::app { #include <time/timer.hpp>
class ISystem; namespace lt {
class Renderer;
class Window;
class Event;
class GraphicsContext;
class UserInterface;
class LayerStack;
extern Scope<class Application> create_application(); extern 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 class Application
{ {
public: public:
@ -21,24 +24,54 @@ public:
auto operator=(Application &&) -> Application & = delete; auto operator=(Application &&) -> Application & = delete;
virtual ~Application() = default; virtual ~Application();
[[nodiscard]] auto sanity_check() const -> bool;
void game_loop(); void game_loop();
void register_system(Ref<app::ISystem> system); [[nodiscard]] auto get_window() -> Window &
{
return *m_window;
}
void unregister_system(Ref<app::ISystem> system); [[nodiscard]] auto get_layer_stack() -> LayerStack &
{
return *m_layer_stack;
}
static void quit();
protected: protected:
Application() = default; Application();
private: private:
std::vector<Ref<app::ISystem>> m_systems; void update_layers();
std::vector<Ref<app::ISystem>> m_systems_to_be_unregistered; void render_layers();
std::vector<Ref<app::ISystem>> m_systems_to_be_registered; void render_user_interface();
void poll_events();
void on_event(const Event &event);
void log_debug_data() const;
Timer m_timer;
Scope<Window> m_window;
Scope<UserInterface> m_user_interface;
Scope<GraphicsContext> m_graphics_context;
Scope<Renderer> m_renderer;
Scope<LayerStack> m_layer_stack;
static Application *s_instance;
}; };
} // namespace lt::app } // namespace lt

View file

@ -2,21 +2,21 @@
#include <app/application.hpp> #include <app/application.hpp>
auto main(int argc, char *argv[]) -> int32_t int main(int argc, char *argv[]) // NOLINT
try try
{ {
std::ignore = argc; std::ignore = argc;
std::ignore = argv; std::ignore = argv;
auto application = lt::Scope<lt::app::Application> {}; auto application = lt::Scope<lt::Application> {};
application = lt::app::create_application(); application = lt::create_application();
if (!application)
{ lt::ensure(application, "Failed to create application");
throw std::runtime_error { "Failed to create application\n" }; lt::ensure(application->sanity_check(), "Failed to verify the sanity of the application");
}
application->game_loop(); application->game_loop();
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
catch (const std::exception &exp) catch (const std::exception &exp)

View file

@ -0,0 +1,116 @@
#pragma once
namespace lt {
class Event;
class MouseMovedEvent;
class ButtonPressedEvent;
class ButtonReleasedEvent;
class WheelScrolledEvent;
class KeyPressedEvent;
class KeyRepeatEvent;
class KeyReleasedEvent;
class SetCharEvent;
class WindowClosedEvent;
class WindowResizedEvent;
class WindowMovedEvent;
class WindowLostFocusEvent;
class WindowGainFocusEvent;
class Layer
{
public:
Layer(std::string name);
virtual ~Layer() = default;
[[nodiscard]] auto get_name() const -> const std::string &
{
return m_layer_name;
}
virtual void on_update(float deltaTime)
{
}
virtual void on_user_interface_update()
{
}
virtual void on_render()
{
}
auto on_event(const Event &event) -> bool;
protected:
std::string m_layer_name;
virtual auto on_mouse_moved(const MouseMovedEvent & /*event*/) -> bool
{
return false;
}
virtual auto on_button_pressed(const ButtonPressedEvent & /*event*/) -> bool
{
return false;
}
virtual auto on_button_released(const ButtonReleasedEvent & /*event*/) -> bool
{
return false;
}
virtual auto on_wheel_scrolled(const WheelScrolledEvent & /*event*/) -> bool
{
return false;
}
virtual auto on_key_pressed(const KeyPressedEvent & /*event*/) -> bool
{
return false;
}
virtual auto on_key_repeat(const KeyRepeatEvent & /*event*/) -> bool
{
return false;
}
virtual auto on_key_released(const KeyReleasedEvent & /*event*/) -> bool
{
return false;
}
virtual auto on_set_char(const SetCharEvent & /*event*/) -> bool
{
return false;
}
virtual auto on_window_closed(const WindowClosedEvent & /*event*/) -> bool
{
return false;
}
virtual auto on_window_resized(const WindowResizedEvent & /*event*/) -> bool
{
return false;
}
virtual auto on_window_moved(const WindowMovedEvent & /*event*/) -> bool
{
return false;
}
virtual auto on_window_lost_focus(const WindowLostFocusEvent & /*event*/) -> bool
{
return false;
}
virtual auto on_window_gain_focus(const WindowGainFocusEvent & /*event*/) -> bool
{
return false;
}
};
} // namespace lt

View file

@ -0,0 +1,50 @@
#pragma once
namespace lt {
class Layer;
class Event;
class LayerStack
{
public:
template<typename Layer_T, typename... Args>
void emplace_layer(Args &&...args)
{
attach_layer(create_ref<Layer_T>(std::forward<Args>(args)...));
}
void attach_layer(Ref<Layer> layer);
void detach_layer(const Ref<Layer> &layer);
[[nodiscard]] auto is_empty() const -> bool
{
return m_layers.empty();
}
[[nodiscard]] auto begin() -> std::vector<Ref<Layer>>::iterator
{
return m_layers.begin();
}
[[nodiscard]] auto end() -> std::vector<Ref<Layer>>::iterator
{
return m_layers.end();
}
[[nodiscard]] auto rbegin() -> std::vector<Ref<Layer>>::reverse_iterator
{
return m_layers.rbegin();
}
[[nodiscard]] auto rend() -> std::vector<Ref<Layer>>::reverse_iterator
{
return m_layers.rend();
}
private:
std::vector<Ref<Layer>> m_layers;
};
} // namespace lt

View file

@ -1,27 +0,0 @@
#pragma once
namespace lt::app {
class ISystem
{
public:
ISystem() = default;
virtual ~ISystem() = default;
ISystem(ISystem &&) = default;
ISystem(const ISystem &) = delete;
auto operator=(ISystem &&) -> ISystem & = default;
auto operator=(const ISystem &) -> ISystem & = delete;
virtual void on_register() = 0;
virtual void on_unregister() = 0;
virtual auto tick() -> bool = 0;
};
} // namespace lt::app

View file

@ -5,5 +5,5 @@ add_library_module(asset_manager
target_link_libraries( target_link_libraries(
asset_manager asset_manager
PUBLIC asset_parser PUBLIC asset_parser
PRIVATE logger PRIVATE renderer
) )

View file

@ -1,36 +1,36 @@
#pragma once #pragma once
#include <format>
#include <logger/logger.hpp> #include <logger/logger.hpp>
#include <source_location>
namespace lt { namespace lt {
template<typename Expression_T, typename... Args_T> struct FailedAssertion: std::exception
struct ensure
{ {
ensure( FailedAssertion(const char *file, int line)
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)) log_crt("Assertion failed in: {} (line {})", file, line);
{
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> template<typename Expression_T, typename... Args>
ensure(Expression_T, std::format_string<Args_T...>, Args_T &&...) constexpr void ensure(Expression_T &&expression, std::format_string<Args...> fmt, Args &&...args)
-> ensure<Expression_T, Args_T...>; {
if (!static_cast<bool>(expression))
{
Logger::log(LogLvl::critical, fmt, std::forward<Args>(args)...);
throw ::lt::FailedAssertion(__FILE__, __LINE__);
}
}
template<typename Expression_T>
constexpr void ensure(Expression_T &&expression, const char *message)
{
if (!static_cast<bool>(expression))
{
Logger::log(LogLvl::critical, message);
throw ::lt::FailedAssertion(__FILE__, __LINE__);
}
}
} // namespace lt } // namespace lt

View file

@ -1,4 +1,5 @@
add_library_module(ecs entity.cpp scene.cpp uuid.cpp ) add_library_module(ecs entity.cpp scene.cpp uuid.cpp serializer.cpp)
target_link_libraries(ecs target_link_libraries(ecs
PUBLIC logger lt_debug EnTT::EnTT input camera math PUBLIC logger lt_debug EnTT::EnTT renderer input camera
PRIVATE yaml-cpp::yaml-cpp asset_manager
) )

View file

@ -1,9 +1,73 @@
#include <camera/component.hpp>
#include <ecs/components.hpp> #include <ecs/components.hpp>
#include <ecs/entity.hpp> #include <ecs/entity.hpp>
#include <ecs/scene.hpp> #include <ecs/scene.hpp>
#include <renderer/renderer.hpp>
namespace lt { namespace lt {
void Scene::on_create()
{
/* native scripts */
{
m_registry.view<NativeScriptComponent>().each([](NativeScriptComponent &nsc) {
if (nsc.instance == nullptr)
{
nsc.instance = nsc.CreateInstance();
nsc.instance->on_create();
}
});
}
}
void Scene::on_update(float deltaTime)
{
/* native scripts */
{
m_registry.view<NativeScriptComponent>().each([=](NativeScriptComponent &nsc) {
nsc.instance->on_update(deltaTime);
});
}
}
void Scene::on_render(const Ref<Framebuffer> &targetFrameBuffer /* = nullptr */)
{
auto *sceneCamera = (Camera *)nullptr;
auto *sceneCameraTransform = (TransformComponent *)nullptr;
/* scene camera */
{
m_registry.group(entt::get<TransformComponent, CameraComponent>)
.each([&](TransformComponent &transformComp, CameraComponent &cameraComp) {
if (cameraComp.isPrimary)
{
sceneCamera = &cameraComp.camera;
sceneCameraTransform = &transformComp;
}
});
}
/* draw quads */
{
if (sceneCamera)
{
Renderer::begin_scene(sceneCamera, *sceneCameraTransform, targetFrameBuffer);
m_registry.group(entt::get<TransformComponent, SpriteRendererComponent>)
.each([](TransformComponent &transformComp,
SpriteRendererComponent &spriteRendererComp) {
Renderer::draw_quad(
transformComp,
spriteRendererComp.tint,
spriteRendererComp.texture
);
});
Renderer::end_scene();
}
}
}
auto Scene::create_entity(const std::string &name, const TransformComponent &transform) -> Entity auto Scene::create_entity(const std::string &name, const TransformComponent &transform) -> Entity
{ {
return create_entity_with_uuid(name, UUID(), transform); return create_entity_with_uuid(name, UUID(), transform);
@ -11,6 +75,21 @@ auto Scene::create_entity(const std::string &name, const TransformComponent &tra
auto Scene::get_entity_by_tag(const std::string &tag) -> Entity auto Scene::get_entity_by_tag(const std::string &tag) -> Entity
{ {
// TagComponent tagComp(tag);
// entt::entity entity = entt::to_entity(m_registry, tagComp);
auto entity = Entity {};
m_registry.view<TagComponent>().each([&](TagComponent &tagComp) {
// if (tagComp.tag == tag)
// entity = entity(entt::to_entity(m_registry, tagComp), this);
});
if (entity.is_valid())
{
return entity;
}
ensure(false, "Scene::get_entity_by_tag: failed to find entity by tag: {}", tag);
return {}; return {};
} }

View file

@ -3,7 +3,6 @@
#include <ecs/components/transform.hpp> #include <ecs/components/transform.hpp>
#include <ecs/uuid.hpp> #include <ecs/uuid.hpp>
#include <entt/entt.hpp> #include <entt/entt.hpp>
#include <functional>
namespace lt { namespace lt {
@ -13,17 +12,11 @@ class Framebuffer;
class Scene class Scene
{ {
public: public:
template<typename... T> void on_create();
auto group()
{
return m_registry.group(entt::get<T...>);
}
template<typename T> void on_update(float deltaTime);
auto view()
{ void on_render(const Ref<Framebuffer> &targetFrameBuffer = nullptr);
return m_registry.view<T>();
}
auto create_entity( auto create_entity(
const std::string &name, const std::string &name,
@ -32,12 +25,6 @@ public:
auto get_entity_by_tag(const std::string &tag) -> Entity; auto get_entity_by_tag(const std::string &tag) -> Entity;
auto get_entt_registry() -> entt::registry &
{
return m_registry;
}
private: private:
friend class Entity; friend class Entity;
@ -54,12 +41,4 @@ private:
) -> Entity; ) -> Entity;
}; };
namespace ecs {
using Registry = Scene;
using Entity = ::lt::Entity;
} // namespace ecs
} // namespace lt } // namespace lt

View file

@ -1,2 +1,2 @@
add_library_module(input input.cpp) add_library_module(input input.cpp)
target_link_libraries(input PUBLIC surface math imgui::imgui logger) target_link_libraries(input PUBLIC math imgui::imgui logger)

View file

@ -1,5 +1,10 @@
#include <imgui.h> #include <imgui.h>
#include <input/events/char.hpp>
#include <input/events/event.hpp>
#include <input/events/keyboard.hpp>
#include <input/events/mouse.hpp>
#include <input/input.hpp> #include <input/input.hpp>
#include <input/key_codes.hpp>
#include <logger/logger.hpp> #include <logger/logger.hpp>
namespace lt { namespace lt {

View file

@ -1,5 +0,0 @@
#include <input/system.hpp>
namespace lt::input {
} // namespace lt::input

View file

@ -0,0 +1,41 @@
#pragma once
#include <input/events/event.hpp>
#include <sstream>
namespace lt {
class SetCharEvent: public Event
{
public:
SetCharEvent(unsigned int character): m_character(character)
{
}
[[nodiscard]] auto get_character() const -> int
{
return m_character;
}
[[nodiscard]] auto get_info_lt_log() const -> std::string override
{
std::stringstream ss;
ss << "CharSet: " << m_character;
return ss.str();
}
[[nodiscard]] auto get_event_type() const -> EventType override
{
return ::lt::EventType::SetChar;
}
[[nodiscard]] auto has_category(EventCategory category) const -> bool override
{
return static_cast<uint8_t>(InputEventCategory | KeyboardEventCategory) & category;
}
private:
const unsigned int m_character;
};
} // namespace lt

View file

@ -0,0 +1,56 @@
#pragma once
namespace lt {
enum class EventType : uint8_t
{
None = 0,
// input
MouseMoved,
WheelScrolled,
ButtonPressed,
ButtonReleased,
KeyPressed,
KeyRepeated,
KeyReleased,
SetChar,
// window
WindowMoved,
WindowResized,
WindowClosed,
WindowLostFocus,
WindowGainFocus,
};
enum EventCategory : uint8_t
{
None = 0,
WindowEventCategory = bit(0),
InputEventCategory = bit(1),
KeyboardEventCategory = bit(2),
MouseEventCategory = bit(3),
};
class Event
{
public:
Event() = default;
virtual ~Event() = default;
[[nodiscard]] virtual auto get_event_type() const -> EventType = 0;
[[nodiscard]] virtual auto get_info_lt_log() const -> std::string = 0;
[[nodiscard]] virtual auto has_category(EventCategory category) const -> bool = 0;
friend auto operator<<(std::ostream &os, const Event &e) -> std::ostream &
{
return os << e.get_info_lt_log();
}
};
} // namespace lt

View file

@ -0,0 +1,107 @@
#pragma once
#include <input/events/event.hpp>
#include <sstream>
namespace lt {
class KeyPressedEvent: public Event
{
public:
KeyPressedEvent(int key): m_key(key)
{
}
[[nodiscard]] auto get_key() const -> int
{
return m_key;
}
[[nodiscard]] auto get_info_lt_log() const -> std::string override
{
std::stringstream ss;
ss << "KeyPressed: " << m_key;
return ss.str();
}
[[nodiscard]] auto get_event_type() const -> EventType override
{
return ::lt::EventType::KeyPressed;
}
[[nodiscard]] auto has_category(EventCategory category) const -> bool override
{
return static_cast<uint8_t>(InputEventCategory | KeyboardEventCategory) & category;
}
private:
const int m_key;
};
class KeyRepeatEvent: public Event
{
public:
KeyRepeatEvent(int key): m_key(key)
{
}
[[nodiscard]] auto get_key() const -> int
{
return m_key;
}
[[nodiscard]] auto get_info_lt_log() const -> std::string override
{
std::stringstream ss;
ss << "KeyRepeated: " << m_key;
return ss.str();
}
[[nodiscard]] auto get_event_type() const -> EventType override
{
return ::lt::EventType::KeyRepeated;
}
[[nodiscard]] auto has_category(EventCategory category) const -> bool override
{
return static_cast<uint8_t>(InputEventCategory | KeyboardEventCategory) & category;
}
private:
const int m_key;
};
class KeyReleasedEvent: public Event
{
public:
KeyReleasedEvent(int key): m_key(key)
{
}
[[nodiscard]] auto get_key() const -> int
{
return m_key;
}
[[nodiscard]] auto get_info_lt_log() const -> std::string override
{
std::stringstream ss;
ss << "KeyReleased: " << m_key;
return ss.str();
}
[[nodiscard]] auto get_event_type() const -> EventType override
{
return ::lt::EventType::KeyReleased;
}
[[nodiscard]] auto has_category(EventCategory category) const -> bool override
{
return static_cast<uint8_t>(InputEventCategory | KeyboardEventCategory) & category;
}
private:
const int m_key;
};
} // namespace lt

View file

@ -0,0 +1,151 @@
#pragma once
#include <input/events/event.hpp>
#include <math/vec2.hpp>
#include <sstream>
namespace lt {
class MouseMovedEvent: public Event
{
public:
MouseMovedEvent(float x, float y): m_position(x, y)
{
}
[[nodiscard]] auto get_position() const -> const math::vec2 &
{
return m_position;
}
[[nodiscard]] auto get_x() const -> float
{
return m_position.x;
}
[[nodiscard]] auto get_y() const -> float
{
return m_position.y;
}
[[nodiscard]] auto get_info_lt_log() const -> std::string override
{
std::stringstream ss;
ss << "MouseMoved: " << m_position.x << ", " << m_position.y;
return ss.str();
}
[[nodiscard]] auto get_event_type() const -> EventType override
{
return ::lt::EventType::MouseMoved;
}
[[nodiscard]] auto has_category(EventCategory category) const -> bool override
{
return static_cast<uint8_t>(InputEventCategory | MouseEventCategory) & category;
}
private:
const math::vec2 m_position;
};
class WheelScrolledEvent: public Event
{
public:
WheelScrolledEvent(float offset): m_offset(offset)
{
}
[[nodiscard]] auto get_offset() const -> float
{
return m_offset;
}
[[nodiscard]] auto get_info_lt_log() const -> std::string override
{
std::stringstream ss;
ss << "WheelScrolled: " << m_offset;
return ss.str();
}
[[nodiscard]] auto get_event_type() const -> EventType override
{
return ::lt::EventType::WheelScrolled;
}
[[nodiscard]] auto has_category(EventCategory category) const -> bool override
{
return static_cast<uint8_t>(InputEventCategory | MouseEventCategory) & category;
}
private:
const float m_offset;
};
class ButtonPressedEvent: public Event
{
public:
ButtonPressedEvent(int button): m_button(button)
{
}
[[nodiscard]] auto get_button() const -> int
{
return m_button;
}
[[nodiscard]] auto get_info_lt_log() const -> std::string override
{
std::stringstream ss;
ss << "ButtonPressed: " << m_button;
return ss.str();
}
[[nodiscard]] auto get_event_type() const -> EventType override
{
return ::lt::EventType::ButtonPressed;
}
[[nodiscard]] auto has_category(EventCategory category) const -> bool override
{
return static_cast<uint8_t>(InputEventCategory | MouseEventCategory) & category;
}
private:
const int m_button;
};
class ButtonReleasedEvent: public Event
{
public:
ButtonReleasedEvent(int button): m_button(button)
{
}
[[nodiscard]] auto get_button() const -> int
{
return m_button;
}
[[nodiscard]] auto get_info_lt_log() const -> std::string override
{
std::stringstream ss;
ss << "ButtonReleased: " << m_button;
return ss.str();
}
[[nodiscard]] auto get_event_type() const -> EventType override
{
return ::lt::EventType::ButtonReleased;
}
[[nodiscard]] auto has_category(EventCategory category) const -> bool override
{
return static_cast<uint8_t>(InputEventCategory | MouseEventCategory) & category;
}
private:
const int m_button;
};
} // namespace lt

View file

@ -0,0 +1,133 @@
#pragma once
#include <input/events/event.hpp>
#include <math/vec2.hpp>
#include <sstream>
namespace lt {
class WindowClosedEvent: public Event
{
public:
[[nodiscard]] auto get_info_lt_log() const -> std::string override
{
return "WindowClosedEvent";
}
[[nodiscard]] auto get_event_type() const -> EventType override
{
return ::lt::EventType::WindowClosed;
}
[[nodiscard]] auto has_category(EventCategory category) const -> bool override
{
return static_cast<uint8_t>(WindowEventCategory) & category;
}
};
class WindowMovedEvent: public Event
{
public:
WindowMovedEvent(int x, int y): m_position(x, y)
{
}
[[nodiscard]] auto get_position() const -> const math::ivec2 &
{
return m_position;
}
[[nodiscard]] auto get_info_lt_log() const -> std::string override
{
std::stringstream ss;
ss << "WindwoMoved: " << m_position.x << ", " << m_position.y;
return ss.str();
;
}
[[nodiscard]] auto get_event_type() const -> EventType override
{
return ::lt::EventType::WindowMoved;
}
[[nodiscard]] auto has_category(EventCategory category) const -> bool override
{
return static_cast<uint8_t>(WindowEventCategory) & category;
}
private:
const math::ivec2 m_position;
};
class WindowResizedEvent: public Event
{
public:
WindowResizedEvent(unsigned int width, unsigned int height): m_size(width, height)
{
}
[[nodiscard]] auto get_size() const -> const math::uvec2 &
{
return m_size;
}
[[nodiscard]] auto get_info_lt_log() const -> std::string override
{
std::stringstream ss;
ss << "WindowResized: " << m_size.x << ", " << m_size.y;
return ss.str();
}
[[nodiscard]] auto get_event_type() const -> EventType override
{
return ::lt::EventType::WindowResized;
}
[[nodiscard]] auto has_category(EventCategory category) const -> bool override
{
return static_cast<uint8_t>(WindowEventCategory) & category;
}
private:
const math::uvec2 m_size;
};
class WindowLostFocusEvent: public Event
{
public:
[[nodiscard]] auto get_info_lt_log() const -> std::string override
{
return "WindowLostFocus";
}
[[nodiscard]] auto get_event_type() const -> EventType override
{
return ::lt::EventType::WindowLostFocus;
}
[[nodiscard]] auto has_category(EventCategory category) const -> bool override
{
return static_cast<uint8_t>(WindowEventCategory) & category;
}
};
class WindowGainFocusEvent: public Event
{
public:
[[nodiscard]] auto get_info_lt_log() const -> std::string override
{
return "WindowGainFocus";
}
[[nodiscard]] auto get_event_type() const -> EventType override
{
return ::lt::EventType::WindowGainFocus;
}
[[nodiscard]] auto has_category(EventCategory category) const -> bool override
{
return static_cast<uint8_t>(WindowEventCategory) & category;
}
};
} // namespace lt

View file

@ -1,64 +0,0 @@
#pragma once
#include <surface/system.hpp>
namespace lt::input {
template<class... Ts>
struct overloads: Ts...
{
using Ts::operator()...;
};
/**
*
* @note If this system is attached, it will always consume the input events f rom surface.
* Therefore if you want any input detection mechanism, callbacks should be setup with this
* system and not directly with surface.
*/
class System
{
public:
System(lt::surface::System &surface_system)
{
surface_system.add_event_listener([this](auto &&event) {
return handle_event(std::forward<decltype(event)>(event));
});
};
private:
auto handle_event(const lt::surface::System::Event &event) -> bool
{
const auto visitor = overloads {
[this](const lt::surface::KeyPressedEvent &event) {
m_keys[event.get_key()] = true;
return true;
},
[](const lt::surface::KeyRepeatEvent &) { return false; },
[](const lt::surface::KeyReleasedEvent &) { return false; },
[](const lt::surface::KeySetCharEvent &) { return false; },
[](const lt::surface::MouseMovedEvent &) { return false; },
[](const lt::surface::WheelScrolledEvent &) { return false; },
[](const lt::surface::ButtonPressedEvent &) { return false; },
[](const lt::surface::ButtonReleasedEvent &) { return false; },
[](const auto &) { return false; },
};
return std::visit(visitor, event);
}
void setup_callbacks(GLFWwindow *handle);
std::array<bool, 512> m_keys {};
};
} // namespace lt::input

View file

@ -4,6 +4,7 @@
namespace lt::math { namespace lt::math {
/** /**
* let... * let...
* a = h / w ==> for aspect ratio adjustment * a = h / w ==> for aspect ratio adjustment

View file

@ -1,11 +1,16 @@
add_library_module(libmirror add_library_module(libmirror
layers/editor_layer.cpp
panels/asset_browser.cpp
panels/properties.cpp
panels/scene_hierarchy.cpp
) )
target_link_libraries( target_link_libraries(
libmirror libmirror
INTERFACE PUBLIC app
app PUBLIC opengl::opengl
opengl::opengl PUBLIC ui
surface PUBLIC imgui
PUBLIC input
) )
add_test_module(libmirror add_test_module(libmirror

View file

@ -1,77 +1,30 @@
#include <app/application.hpp> #include <app/application.hpp>
#include <app/entrypoint.hpp> #include <app/entrypoint.hpp>
#include <app/system.hpp> #include <app/layer_stack.hpp>
#include <math/vec2.hpp> #include <math/vec2.hpp>
#include <surface/system.hpp> #include <mirror/layers/editor_layer.hpp>
#include <window/window.hpp>
namespace lt { namespace lt {
template<class... Ts> class Mirror: public Application
struct overloads: Ts...
{
using Ts::operator()...;
};
class Mirror: public app::Application
{ {
public: public:
Mirror() Mirror()
{ {
m_editor_registry = create_ref<ecs::Registry>(); get_window().set_properties(
Window::Properties {
setup_window_system(); .title = "Mirror",
register_systems(); .size = math::uvec2(1280u, 720u),
.vsync = true,
m_window_system->add_event_listener(
m_window,
[&](const surface::SurfaceComponent::Event &event) {
const auto visitor = overloads {
[&](const lt::surface::KeyPressedEvent &event) {
std::cout << "key pressed: " << event.to_string() << std::endl;
if (event.get_key() == 81)
{
unregister_system(m_window_system);
log_inf("Quitting...");
}
return true;
},
[](const auto &) { return false; },
};
return std::visit(visitor, event);
} }
); );
get_layer_stack().emplace_layer<EditorLayer>("MirrorLayer");
} }
void setup_window_system()
{
using lt::surface::SurfaceComponent;
m_window_system = create_ref<lt::surface::System>(m_editor_registry);
m_window = m_editor_registry->create_entity("Editor Window");
m_window.add_component<SurfaceComponent>(SurfaceComponent::CreateInfo {
.title = "Editor Window",
.resolution = { 800u, 600u },
.vsync = true,
.visible = true,
});
}
void register_systems()
{
register_system(m_window_system);
}
private:
Ref<ecs::Registry> m_editor_registry;
Ref<lt::surface::System> m_window_system;
lt::ecs::Entity m_window;
}; };
auto app::create_application() -> Scope<app::Application> auto create_application() -> Scope<Application>
{ {
return create_scope<Mirror>(); return create_scope<Mirror>();
} }

View file

@ -1,5 +1,4 @@
add_library_module(renderer add_library_module(renderer
system.cpp
blender.cpp blender.cpp
buffers.cpp buffers.cpp
framebuffer.cpp framebuffer.cpp
@ -20,7 +19,6 @@ add_library_module(renderer
gl/shader.cpp gl/shader.cpp
gl/texture.cpp gl/texture.cpp
gl/vertex_layout.cpp gl/vertex_layout.cpp
vk/instance.cpp
) )
target_link_libraries( target_link_libraries(
@ -36,15 +34,4 @@ target_link_libraries(
PUBLIC yaml-cpp::yaml-cpp PUBLIC yaml-cpp::yaml-cpp
PUBLIC EnTT::EnTT PUBLIC EnTT::EnTT
PRIVATE lt_debug PRIVATE lt_debug
PRIVATE window
PUBLIC vulkan
)
add_test_module(renderer
system.test.cpp
)
target_link_libraries(
renderer_tests
PRIVATE lt_debug
PRIVATE window
) )

View file

@ -4,13 +4,12 @@
#include <renderer/blender.hpp> #include <renderer/blender.hpp>
#include <renderer/buffers.hpp> #include <renderer/buffers.hpp>
#include <renderer/framebuffer.hpp> #include <renderer/framebuffer.hpp>
#include <renderer/programs/quad.hpp>
#include <renderer/programs/texture.hpp>
#include <renderer/programs/tinted_texture.hpp>
#include <renderer/render_command.hpp> #include <renderer/render_command.hpp>
#include <renderer/renderer.hpp> #include <renderer/renderer.hpp>
#include <renderer/shader.hpp> #include <renderer/shader.hpp>
#include <renderer/texture.hpp> #include <renderer/texture.hpp>
#include <span>
#include <utility>
namespace lt { namespace lt {
@ -22,26 +21,20 @@ Renderer::Renderer(
CreateInfo create_info CreateInfo create_info
) )
: m_quad_renderer( : m_quad_renderer(
create_scope<QuadRendererProgram>(
LT_MAX_QUAD_RENDERER_VERTICES, LT_MAX_QUAD_RENDERER_VERTICES,
shared_context, shared_context,
std::move(create_info.quad_renderer_shader) std::move(create_info.quad_renderer_shader)
) )
)
, m_texture_renderer( , m_texture_renderer(
create_scope<TextureRendererProgram>(
LT_MAX_TEXTURE_RENDERER_VERTICES, LT_MAX_TEXTURE_RENDERER_VERTICES,
shared_context, shared_context,
std::move(create_info.texture_renderer_shader) std::move(create_info.texture_renderer_shader)
) )
)
, m_tinted_texture_renderer( , m_tinted_texture_renderer(
create_scope<TintedTextureRendererProgram>(
LT_MAX_TINTED_TEXTURE_RENDERER_VERTICES, LT_MAX_TINTED_TEXTURE_RENDERER_VERTICES,
shared_context, shared_context,
std::move(create_info.tinted_texture_renderer_shader) std::move(create_info.tinted_texture_renderer_shader)
) )
)
, m_view_projection_buffer(nullptr) , m_view_projection_buffer(nullptr)
, m_render_command(nullptr) , m_render_command(nullptr)
, m_blender(nullptr) , m_blender(nullptr)
@ -62,10 +55,6 @@ Renderer::Renderer(
m_blender->enable(BlendFactor::SRC_ALPHA, BlendFactor::INVERSE_SRC_ALPHA); m_blender->enable(BlendFactor::SRC_ALPHA, BlendFactor::INVERSE_SRC_ALPHA);
} }
Renderer::~Renderer() // NOLINT
{
}
auto Renderer::create( auto Renderer::create(
GLFWwindow *windowHandle, GLFWwindow *windowHandle,
Ref<SharedContext> sharedContext, Ref<SharedContext> sharedContext,
@ -125,7 +114,7 @@ void Renderer::draw_quad_impl(
//==================== DRAW_QUAD_TINT ====================// //==================== DRAW_QUAD_TINT ====================//
void Renderer::draw_quad_impl(const math::mat4 &transform, const math::vec4 &tint) void Renderer::draw_quad_impl(const math::mat4 &transform, const math::vec4 &tint)
{ {
auto map = std::span<QuadRendererProgram::QuadVertexData> { m_quad_renderer->get_map_current(), auto map = std::span<QuadRendererProgram::QuadVertexData> { m_quad_renderer.get_map_current(),
4 }; 4 };
// top left // top left
@ -145,7 +134,7 @@ void Renderer::draw_quad_impl(const math::mat4 &transform, const math::vec4 &tin
map[3].tint = tint; map[3].tint = tint;
// advance // advance
if (!m_quad_renderer->advance()) if (!m_quad_renderer.advance())
{ {
log_wrn("Exceeded LT_MAX_QUAD_RENDERER_VERTICES: {}", LT_MAX_QUAD_RENDERER_VERTICES); log_wrn("Exceeded LT_MAX_QUAD_RENDERER_VERTICES: {}", LT_MAX_QUAD_RENDERER_VERTICES);
flush_scene(); flush_scene();
@ -158,7 +147,7 @@ void Renderer::draw_quad_impl(const math::mat4 &transform, const Ref<Texture> &t
texture->bind(); texture->bind();
auto map = std::span<TextureRendererProgram::TextureVertexData> { auto map = std::span<TextureRendererProgram::TextureVertexData> {
m_texture_renderer->get_map_current(), m_texture_renderer.get_map_current(),
4 4
}; };
@ -179,7 +168,7 @@ void Renderer::draw_quad_impl(const math::mat4 &transform, const Ref<Texture> &t
map[3].texcoord = { 0.0f, 1.0f }; map[3].texcoord = { 0.0f, 1.0f };
// advance // advance
if (!m_texture_renderer->advance()) if (!m_texture_renderer.advance())
{ {
log_wrn("Exceeded LT_MAX_TEXTURE_RENDERER_VERTICES: {}", LT_MAX_TEXTURE_RENDERER_VERTICES); log_wrn("Exceeded LT_MAX_TEXTURE_RENDERER_VERTICES: {}", LT_MAX_TEXTURE_RENDERER_VERTICES);
flush_scene(); flush_scene();
@ -196,7 +185,7 @@ void Renderer::draw_quad_impl(
texture->bind(); texture->bind();
auto map = std::span<TintedTextureRendererProgram::TintedTextureVertexData> { auto map = std::span<TintedTextureRendererProgram::TintedTextureVertexData> {
m_tinted_texture_renderer->get_map_current(), m_tinted_texture_renderer.get_map_current(),
4 4
}; };
@ -220,7 +209,7 @@ void Renderer::draw_quad_impl(
map[3].tint = tint; map[3].tint = tint;
map[3].texcoord = { 0.0f, 1.0f }; map[3].texcoord = { 0.0f, 1.0f };
if (!m_tinted_texture_renderer->advance()) if (!m_tinted_texture_renderer.advance())
{ {
log_wrn("Exceeded LT_MAX_TEXTURE_RENDERER_VERTICES: {}", LT_MAX_TEXTURE_RENDERER_VERTICES); log_wrn("Exceeded LT_MAX_TEXTURE_RENDERER_VERTICES: {}", LT_MAX_TEXTURE_RENDERER_VERTICES);
flush_scene(); flush_scene();
@ -267,66 +256,66 @@ void Renderer::begin_scene_impl(
m_view_projection_buffer->un_map(); m_view_projection_buffer->un_map();
// map renderers // map renderers
m_quad_renderer->map(); m_quad_renderer.map();
m_texture_renderer->map(); m_texture_renderer.map();
m_tinted_texture_renderer->map(); m_tinted_texture_renderer.map();
} }
void Renderer::flush_scene() void Renderer::flush_scene()
{ {
/* tinted texture renderer */ /* tinted texture renderer */
m_tinted_texture_renderer->un_map(); m_tinted_texture_renderer.un_map();
if (m_tinted_texture_renderer->get_quad_count()) if (m_tinted_texture_renderer.get_quad_count())
{ {
m_tinted_texture_renderer->bind(); m_tinted_texture_renderer.bind();
m_render_command->draw_indexed(m_tinted_texture_renderer->get_quad_count() * 6u); m_render_command->draw_indexed(m_tinted_texture_renderer.get_quad_count() * 6u);
} }
/* quad renderer */ /* quad renderer */
m_quad_renderer->un_map(); m_quad_renderer.un_map();
if (m_quad_renderer->get_quad_count()) if (m_quad_renderer.get_quad_count())
{ {
m_quad_renderer->bind(); m_quad_renderer.bind();
m_render_command->draw_indexed(m_quad_renderer->get_quad_count() * 6u); m_render_command->draw_indexed(m_quad_renderer.get_quad_count() * 6u);
} }
/* texture renderer */ /* texture renderer */
m_texture_renderer->un_map(); m_texture_renderer.un_map();
if (m_texture_renderer->get_quad_count()) if (m_texture_renderer.get_quad_count())
{ {
m_texture_renderer->bind(); m_texture_renderer.bind();
m_render_command->draw_indexed(m_texture_renderer->get_quad_count() * 6u); m_render_command->draw_indexed(m_texture_renderer.get_quad_count() * 6u);
} }
m_quad_renderer->map(); m_quad_renderer.map();
m_texture_renderer->map(); m_texture_renderer.map();
m_tinted_texture_renderer->map(); m_tinted_texture_renderer.map();
} }
void Renderer::end_scene_impl() void Renderer::end_scene_impl()
{ {
/* tinted texture renderer */ /* tinted texture renderer */
m_tinted_texture_renderer->un_map(); m_tinted_texture_renderer.un_map();
if (m_tinted_texture_renderer->get_quad_count()) if (m_tinted_texture_renderer.get_quad_count())
{ {
m_tinted_texture_renderer->bind(); m_tinted_texture_renderer.bind();
m_render_command->draw_indexed(m_tinted_texture_renderer->get_quad_count() * 6u); m_render_command->draw_indexed(m_tinted_texture_renderer.get_quad_count() * 6u);
} }
/* quad renderer */ /* quad renderer */
m_quad_renderer->un_map(); m_quad_renderer.un_map();
if (m_quad_renderer->get_quad_count()) if (m_quad_renderer.get_quad_count())
{ {
m_quad_renderer->bind(); m_quad_renderer.bind();
m_render_command->draw_indexed(m_quad_renderer->get_quad_count() * 6u); m_render_command->draw_indexed(m_quad_renderer.get_quad_count() * 6u);
} }
/* texture renderer */ /* texture renderer */
m_texture_renderer->un_map(); m_texture_renderer.un_map();
if (m_texture_renderer->get_quad_count()) if (m_texture_renderer.get_quad_count())
{ {
m_texture_renderer->bind(); m_texture_renderer.bind();
m_render_command->draw_indexed(m_texture_renderer->get_quad_count() * 6u); m_render_command->draw_indexed(m_texture_renderer.get_quad_count() * 6u);
} }
// reset frame buffer // reset frame buffer

View file

@ -1,18 +0,0 @@
#include <lt_debug/assertions.hpp>
#include <renderer/system.hpp>
namespace lt::renderer {
System::System(InitRequirements requirements): m_registry(std::move(requirements.registry))
{
ensure(m_registry, "null registry");
ensure(requirements.glfw_window_handle, "null glfw handle");
}
System::~System() = default;
void System::tick(TickRequirements requirements)
{
}
} // namespace lt::renderer

View file

@ -1,50 +0,0 @@
#include <ranges>
#include <renderer/system.hpp>
#include <test/test.hpp>
#include <window/window.hpp>
using namespace lt;
using lt::test::Case;
using lt::test::Suite;
Suite raii = [] {
using lt::test::expect_true;
using lt::test::expect_throw;
using renderer::System;
auto *window = static_cast<GLFWwindow *>(lt::Window::create([](auto &&PH1) {})->get_handle());
Case { "happy" } = [=] {
std::ignore = System { {
.glfw_window_handle = window,
.registry = create_ref<ecs::Registry>(),
} };
};
Case { "unhappy" } = [=] {
expect_throw([=] {
std::ignore = System { {
.glfw_window_handle = window,
.registry = {},
} };
});
expect_throw([=] {
std::ignore = System { {
.glfw_window_handle = {},
.registry = create_ref<ecs::Registry>(),
} };
});
};
Case { "plenty" } = [=] {
for (auto idx : std::views::iota(0, 100'001))
{
std::ignore = System { {
.glfw_window_handle = window,
.registry = create_ref<ecs::Registry>(),
} };
}
};
};

View file

@ -1,434 +0,0 @@
#include <renderer/vk/backend.hpp>
#if defined(_WIN32)
#elif defined(__unix__)
#include <dlfcn.h>
namespace {
void *library; // NOLINT
}
#endif
namespace lt::renderer::vk {
// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
// global functions
PFN_vkGetInstanceProcAddr vk_get_instance_proc_address;
PFN_vkCreateInstance vk_create_instance;
PFN_vkEnumerateInstanceExtensionProperties vk_enumerate_instance_extension_properties;
PFN_vkEnumerateInstanceLayerProperties vk_enumerate_instance_layer_properties;
// instance functions
PFN_vkDestroyInstance vk_destroy_instance;
PFN_vkEnumeratePhysicalDevices vk_enumerate_physical_devices;
PFN_vkGetPhysicalDeviceProperties vk_get_physical_device_properties;
PFN_vkGetPhysicalDeviceQueueFamilyProperties vk_get_physical_device_queue_family_properties;
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_vkEnumerateDeviceExtensionProperties vk_enumerate_device_extension_properties;
// extension instance functions
PFN_vkCmdBeginDebugUtilsLabelEXT vk_cmd_begin_debug_label;
PFN_vkCmdEndDebugUtilsLabelEXT vk_cmd_end_debug_label;
PFN_vkCmdInsertDebugUtilsLabelEXT vk_cmd_insert_debug_label;
PFN_vkCreateDebugUtilsMessengerEXT vk_create_debug_messenger;
PFN_vkDestroyDebugUtilsMessengerEXT vk_destroy_debug_messenger;
PFN_vkQueueBeginDebugUtilsLabelEXT vk_queue_begin_debug_label;
PFN_vkQueueEndDebugUtilsLabelEXT vk_queue_end_debug_label;
PFN_vkQueueInsertDebugUtilsLabelEXT vk_queue_insert_debug_label;
PFN_vkSetDebugUtilsObjectNameEXT vk_set_debug_object_name;
PFN_vkSetDebugUtilsObjectTagEXT vk_set_debug_object_tag;
PFN_vkSubmitDebugUtilsMessageEXT vk_submit_debug_message;
// device functions
PFN_vkGetDeviceQueue vk_get_device_queue;
PFN_vkCreateCommandPool vk_create_command_pool;
PFN_vkDestroyCommandPool vk_destroy_command_pool;
PFN_vkAllocateCommandBuffers vk_allocate_command_buffers;
PFN_vkFreeCommandBuffers vk_free_command_buffers;
PFN_vkBeginCommandBuffer vk_begin_command_buffer;
PFN_vkEndCommandBuffer vk_end_command_buffer;
PFN_vkCmdPipelineBarrier vk_cmd_pipeline_barrier;
PFN_vkQueueSubmit vk_queue_submit;
PFN_vkQueueWaitIdle vk_queue_wait_idle;
PFN_vkDeviceWaitIdle vk_device_wait_idle;
PFN_vkCreateFence vk_create_fence;
PFN_vkDestroyFence vk_destroy_fence;
PFN_vkWaitForFences vk_wait_for_fences;
PFN_vkResetFences vk_reset_fences;
PFN_vkCreateSemaphore vk_create_semaphore;
PFN_vkDestroySemaphore vk_destroy_semaphore;
PFN_vkCreateSwapchainKHR vk_create_swapchain_khr;
PFN_vkDestroySwapchainKHR vk_destroy_swapchain_khr;
PFN_vkGetSwapchainImagesKHR vk_get_swapchain_images_khr;
PFN_vkAcquireNextImageKHR vk_acquire_next_image_khr;
PFN_vkQueuePresentKHR vk_queue_present_khr;
PFN_vkCreateImageView vk_create_image_view;
PFN_vkDestroyImageView vk_destroy_image_view;
PFN_vkCreateRenderPass vk_create_render_pass;
PFN_vkDestroyRenderPass vk_destroy_render_pass;
PFN_vkCreateFramebuffer vk_create_frame_buffer;
PFN_vkDestroyFramebuffer vk_destroy_frame_buffer;
PFN_vkCreateShaderModule vk_create_shader_module;
PFN_vkDestroyShaderModule vk_destroy_shader_module;
PFN_vkCreatePipelineLayout vk_create_pipeline_layout;
PFN_vkDestroyPipelineLayout vk_destroy_pipeline_layout;
PFN_vkCreateGraphicsPipelines vk_create_graphics_pipelines;
PFN_vkDestroyPipeline vk_destroy_pipeline;
PFN_vkCmdBeginRenderPass vk_cmd_begin_render_pass;
PFN_vkCmdEndRenderPass vk_cmd_end_render_pass;
PFN_vkCmdBindPipeline vk_cmd_bind_pipeline;
PFN_vkCmdDraw vk_cmd_draw;
PFN_vkCmdSetViewport vk_cmd_set_viewport;
PFN_vkCmdSetScissor vk_cmd_set_scissors;
// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
Backend::Backend()
{
load_library();
load_global_functions();
initialize_instance();
load_instance_functions();
initialize_debug_messenger();
initialize_physical_device();
initialize_logical_device();
load_device_functions();
initialize_queue();
}
Backend::~Backend()
{
vk_destroy_device(m_device, nullptr);
vk_destroy_debug_messenger(m_instance, m_debug_messenger, nullptr);
vk_destroy_instance(m_instance, nullptr);
}
auto parse_message_type(VkDebugUtilsMessageTypeFlagsEXT message_types) -> const char *
{
if (message_types == VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT)
{
return "GENERAL";
}
if (message_types
== (VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
| VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT))
{
return "VALIDATION | PERFORMANCE";
}
if (message_types == VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT)
{
return "VALIDATION";
}
return "PERFORMANCE";
}
auto parse_message_severity(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity) -> LogLvl
{
switch (message_severity)
{
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: return LogLvl::trace;
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: return LogLvl::info;
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: return LogLvl::warn;
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: return LogLvl::error;
default: ensure(false, "Invalid message severity: {}", static_cast<int>(message_severity));
}
return {};
}
auto validation_layers_callback(
VkDebugUtilsMessageSeverityFlagBitsEXT const message_severity,
VkDebugUtilsMessageTypeFlagsEXT const message_types,
VkDebugUtilsMessengerCallbackDataEXT const *const callback_data,
void *const vulkan_user_data
) -> VkBool32
{
std::ignore = vulkan_user_data;
const auto &type = parse_message_type(message_types);
const auto level = parse_message_severity(message_severity);
Logger::log(level, ":: <{}> :: {}", type, callback_data->pMessage);
return static_cast<VkBool32>(VK_FALSE);
}
void Backend::initialize_instance()
{
auto app_info = VkApplicationInfo {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pApplicationName = "Hallo Hallo Hallo :3",
.applicationVersion = VK_MAKE_VERSION(1, 4, 0),
.pEngineName = "light",
.engineVersion = VK_MAKE_VERSION(1, 4, 0),
.apiVersion = VK_API_VERSION_1_4,
};
auto extensions = std::vector<const char *> {
VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
VK_KHR_SURFACE_EXTENSION_NAME,
"VK_KHR_xlib_surface",
};
auto layers = std::vector<const char *> {
"VK_LAYER_KHRONOS_validation",
};
auto instance_info = VkInstanceCreateInfo {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pApplicationInfo = &app_info,
.enabledLayerCount = static_cast<uint32_t>(layers.size()),
.ppEnabledLayerNames = layers.data(),
.enabledExtensionCount = static_cast<uint32_t>(extensions.size()),
.ppEnabledExtensionNames = extensions.data(),
};
{
auto count = 0u;
vk_enumerate_instance_extension_properties(nullptr, &count, nullptr);
auto extensions = std::vector<VkExtensionProperties>(count);
vk_enumerate_instance_extension_properties(nullptr, &count, extensions.data());
log_inf("Available vulkan instance extensions:");
for (auto &ext : extensions)
{
log_inf("\t{} @ {}", ext.extensionName, ext.specVersion);
}
}
vk_create_instance(&instance_info, nullptr, &m_instance);
ensure(m_instance, "Failed to create vulkan instance");
}
void Backend::initialize_debug_messenger()
{
const auto info = VkDebugUtilsMessengerCreateInfoEXT {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT
| VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT
| VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
| VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
| VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
| VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
.pfnUserCallback = &validation_layers_callback,
};
ensure(
!vk_create_debug_messenger(m_instance, &info, nullptr, &m_debug_messenger),
"Failed to create vulkan debug utils messenger"
);
}
void Backend::initialize_physical_device()
{
auto count = 0u;
vk_enumerate_physical_devices(m_instance, &count, nullptr);
ensure(count != 0u, "Failed to find any physical devices with Vulkan support");
auto devices = std::vector<VkPhysicalDevice>(count);
vk_enumerate_physical_devices(m_instance, &count, devices.data());
for (auto &device : devices)
{
auto properties = VkPhysicalDeviceProperties {};
auto features = VkPhysicalDeviceFeatures {};
vk_get_physical_device_properties(device, &properties);
vk_get_physical_device_features(device, &features);
if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
&& features.geometryShader)
{
m_physical_device = device;
}
}
ensure(m_physical_device, "Failed to find any suitable Vulkan physical device");
}
void Backend::initialize_logical_device()
{
const float priorities = .0f;
auto queue_info = VkDeviceQueueCreateInfo {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = find_suitable_queue_family(),
.queueCount = 1u,
.pQueuePriorities = &priorities,
};
auto physical_device_features = VkPhysicalDeviceFeatures {};
auto extensions = std::vector<const char *> {
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
};
auto device_info = VkDeviceCreateInfo {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &queue_info,
.enabledExtensionCount = static_cast<uint32_t>(extensions.size()),
.ppEnabledExtensionNames = extensions.data(),
.pEnabledFeatures = &physical_device_features,
};
ensure(
!vk_create_device(m_physical_device, &device_info, nullptr, &m_device),
"Failed to create logical vulkan device"
);
}
void Backend::initialize_queue()
{
vk_get_device_queue(m_device, find_suitable_queue_family(), 0, &m_queue);
}
void Backend::load_library()
{
library = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
ensure(library, "Failed to dlopen libvulkan.so");
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
vk_get_instance_proc_address = reinterpret_cast<PFN_vkGetInstanceProcAddr>(
dlsym(library, "vkGetInstanceProcAddr")
);
ensure(vk_get_instance_proc_address, "Failed to load vulkan function: vkGetInstanceProcAddr");
}
void Backend::load_global_functions()
{
constexpr auto load_fn = []<typename T>(T &pfn, const char *fn_name) {
// 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);
};
load_fn(vk_create_instance, "vkCreateInstance");
load_fn(vk_enumerate_instance_extension_properties, "vkEnumerateInstanceExtensionProperties");
load_fn(vk_enumerate_instance_layer_properties, "vkEnumerateInstanceLayerProperties");
}
void Backend::load_instance_functions()
{
const auto load_fn = [&]<typename T>(T &pfn, const char *fn_name) {
// 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);
};
load_fn(vk_destroy_instance, "vkDestroyInstance");
load_fn(vk_enumerate_physical_devices, "vkEnumeratePhysicalDevices");
load_fn(vk_get_physical_device_properties, "vkGetPhysicalDeviceProperties");
load_fn(
vk_get_physical_device_queue_family_properties,
"vkGetPhysicalDeviceQueueFamilyProperties"
);
load_fn(vk_create_device, "vkCreateDevice");
load_fn(vk_get_device_proc_address, "vkGetDeviceProcAddr");
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_cmd_begin_debug_label, "vkCmdBeginDebugUtilsLabelEXT");
load_fn(vk_cmd_end_debug_label, "vkCmdEndDebugUtilsLabelEXT");
load_fn(vk_cmd_insert_debug_label, "vkCmdInsertDebugUtilsLabelEXT");
load_fn(vk_create_debug_messenger, "vkCreateDebugUtilsMessengerEXT");
load_fn(vk_destroy_debug_messenger, "vkDestroyDebugUtilsMessengerEXT");
load_fn(vk_queue_begin_debug_label, "vkQueueBeginDebugUtilsLabelEXT");
load_fn(vk_queue_end_debug_label, "vkQueueEndDebugUtilsLabelEXT");
load_fn(vk_queue_insert_debug_label, "vkQueueInsertDebugUtilsLabelEXT");
load_fn(vk_set_debug_object_name, "vkSetDebugUtilsObjectNameEXT");
load_fn(vk_set_debug_object_tag, "vkSetDebugUtilsObjectTagEXT");
load_fn(vk_submit_debug_message, "vkSubmitDebugUtilsMessageEXT");
}
void Backend::load_device_functions()
{
const auto load_fn = [&]<typename T>(T &pfn, const char *fn_name) {
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
pfn = reinterpret_cast<T>(vk_get_device_proc_address(m_device, fn_name));
ensure(pfn, "Failed to load vulkan device function: {}", fn_name);
log_trc("Loaded device function: {}", fn_name);
};
load_fn(vk_get_device_queue, "vkGetDeviceQueue");
load_fn(vk_create_command_pool, "vkCreateCommandPool");
load_fn(vk_destroy_command_pool, "vkDestroyCommandPool");
load_fn(vk_allocate_command_buffers, "vkAllocateCommandBuffers");
load_fn(vk_free_command_buffers, "vkFreeCommandBuffers");
load_fn(vk_begin_command_buffer, "vkBeginCommandBuffer");
load_fn(vk_end_command_buffer, "vkEndCommandBuffer");
load_fn(vk_cmd_pipeline_barrier, "vkCmdPipelineBarrier");
load_fn(vk_queue_submit, "vkQueueSubmit");
load_fn(vk_queue_wait_idle, "vkQueueWaitIdle");
load_fn(vk_device_wait_idle, "vkDeviceWaitIdle");
load_fn(vk_create_fence, "vkCreateFence");
load_fn(vk_destroy_fence, "vkDestroyFence");
load_fn(vk_wait_for_fences, "vkWaitForFences");
load_fn(vk_reset_fences, "vkResetFences");
load_fn(vk_create_semaphore, "vkCreateSemaphore");
load_fn(vk_destroy_semaphore, "vkDestroySemaphore");
load_fn(vk_create_swapchain_khr, "vkCreateSwapchainKHR");
load_fn(vk_destroy_swapchain_khr, "vkDestroySwapchainKHR");
load_fn(vk_get_swapchain_images_khr, "vkGetSwapchainImagesKHR");
load_fn(vk_acquire_next_image_khr, "vkAcquireNextImageKHR");
load_fn(vk_queue_present_khr, "vkQueuePresentKHR");
load_fn(vk_create_image_view, "vkCreateImageView");
load_fn(vk_destroy_image_view, "vkDestroyImageView");
load_fn(vk_create_render_pass, "vkCreateRenderPass");
load_fn(vk_destroy_render_pass, "vkDestroyRenderPass");
load_fn(vk_create_frame_buffer, "vkCreateFramebuffer");
load_fn(vk_destroy_frame_buffer, "vkDestroyFramebuffer");
load_fn(vk_create_shader_module, "vkCreateShaderModule");
load_fn(vk_destroy_shader_module, "vkDestroyShaderModule");
load_fn(vk_create_pipeline_layout, "vkCreatePipelineLayout");
load_fn(vk_destroy_pipeline_layout, "vkDestroyPipelineLayout");
load_fn(vk_create_graphics_pipelines, "vkCreateGraphicsPipelines");
load_fn(vk_destroy_pipeline, "vkDestroyPipeline");
load_fn(vk_cmd_begin_render_pass, "vkCmdBeginRenderPass");
load_fn(vk_cmd_end_render_pass, "vkCmdEndRenderPass");
load_fn(vk_cmd_bind_pipeline, "vkCmdBindPipeline");
load_fn(vk_cmd_draw, "vkCmdDraw");
load_fn(vk_cmd_set_viewport, "vkCmdSetViewport");
load_fn(vk_cmd_set_scissors, "vkCmdSetScissor");
}
[[nodiscard]] auto Backend::find_suitable_queue_family() const -> uint32_t
{
auto count = 0u;
vk_get_physical_device_queue_family_properties(m_physical_device, &count, nullptr);
ensure(count != 0u, "Failed to find any physical devices with Vulkan support");
auto families = std::vector<VkQueueFamilyProperties>(count);
vk_get_physical_device_queue_family_properties(m_physical_device, &count, families.data());
const auto required_flags = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT;
for (auto idx = 0u; auto &family : families)
{
if ((family.queueFlags & required_flags) == required_flags)
{
return idx;
}
}
ensure(false, "Failed to find a suitable Vulkan queue family");
return 0;
}
} // namespace lt::renderer::vk

View file

@ -1,135 +0,0 @@
#pragma once
#define VK_NO_PROTOTYPES
#include <renderer/backend.hpp>
#include <vulkan/vulkan.h>
namespace lt::renderer::vk {
// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
// global functions
extern PFN_vkGetInstanceProcAddr vk_get_instance_proc_address;
extern PFN_vkCreateInstance vk_create_instance;
extern PFN_vkEnumerateInstanceExtensionProperties vk_enumerate_instance_extension_properties;
extern PFN_vkEnumerateInstanceLayerProperties vk_enumerate_instance_layer_properties;
// instance functions
extern PFN_vkDestroyInstance vk_destroy_instance;
extern PFN_vkEnumeratePhysicalDevices vk_enumerate_physical_devices;
extern PFN_vkGetPhysicalDeviceProperties vk_get_physical_device_properties;
extern PFN_vkGetPhysicalDeviceQueueFamilyProperties vk_get_physical_device_queue_family_properties;
extern PFN_vkCreateDevice vk_create_device;
extern PFN_vkGetDeviceProcAddr vk_get_device_proc_address;
extern PFN_vkDestroyDevice vk_destroy_device;
extern PFN_vkGetPhysicalDeviceFeatures vk_get_physical_device_features;
extern PFN_vkEnumerateDeviceExtensionProperties vk_enumerate_device_extension_properties;
// extension instance functions
extern PFN_vkCmdBeginDebugUtilsLabelEXT vk_cmd_begin_debug_label;
extern PFN_vkCmdEndDebugUtilsLabelEXT vk_cmd_end_debug_label;
extern PFN_vkCmdInsertDebugUtilsLabelEXT vk_cmd_insert_debug_label;
extern PFN_vkCreateDebugUtilsMessengerEXT vk_create_debug_messenger;
extern PFN_vkDestroyDebugUtilsMessengerEXT vk_destroy_debug_messenger;
extern PFN_vkQueueBeginDebugUtilsLabelEXT vk_queue_begin_debug_label;
extern PFN_vkQueueEndDebugUtilsLabelEXT vk_queue_end_debug_label;
extern PFN_vkQueueInsertDebugUtilsLabelEXT vk_queue_insert_debug_label;
extern PFN_vkSetDebugUtilsObjectNameEXT vk_set_debug_object_name;
extern PFN_vkSetDebugUtilsObjectTagEXT vk_set_debug_object_tag;
extern PFN_vkSubmitDebugUtilsMessageEXT vk_submit_debug_message;
// device functions
extern PFN_vkGetDeviceQueue vk_get_device_queue;
extern PFN_vkCreateCommandPool vk_create_command_pool;
extern PFN_vkDestroyCommandPool vk_destroy_command_pool;
extern PFN_vkAllocateCommandBuffers vk_allocate_command_buffers;
extern PFN_vkFreeCommandBuffers vk_free_command_buffers;
extern PFN_vkBeginCommandBuffer vk_begin_command_buffer;
extern PFN_vkEndCommandBuffer vk_end_command_buffer;
extern PFN_vkCmdPipelineBarrier vk_cmd_pipeline_barrier;
extern PFN_vkQueueSubmit vk_queue_submit;
extern PFN_vkQueueWaitIdle vk_queue_wait_idle;
extern PFN_vkDeviceWaitIdle vk_device_wait_idle;
extern PFN_vkCreateFence vk_create_fence;
extern PFN_vkDestroyFence vk_destroy_fence;
extern PFN_vkWaitForFences vk_wait_for_fences;
extern PFN_vkResetFences vk_reset_fences;
extern PFN_vkCreateSemaphore vk_create_semaphore;
extern PFN_vkDestroySemaphore vk_destroy_semaphore;
extern PFN_vkCreateSwapchainKHR vk_create_swapchain_khr;
extern PFN_vkDestroySwapchainKHR vk_destroy_swapchain_khr;
extern PFN_vkGetSwapchainImagesKHR vk_get_swapchain_images_khr;
extern PFN_vkAcquireNextImageKHR vk_acquire_next_image_khr;
extern PFN_vkQueuePresentKHR vk_queue_present_khr;
extern PFN_vkCreateImageView vk_create_image_view;
extern PFN_vkDestroyImageView vk_destroy_image_view;
extern PFN_vkCreateRenderPass vk_create_render_pass;
extern PFN_vkDestroyRenderPass vk_destroy_render_pass;
extern PFN_vkCreateFramebuffer vk_create_frame_buffer;
extern PFN_vkDestroyFramebuffer vk_destroy_frame_buffer;
extern PFN_vkCreateShaderModule vk_create_shader_module;
extern PFN_vkDestroyShaderModule vk_destroy_shader_module;
extern PFN_vkCreatePipelineLayout vk_create_pipeline_layout;
extern PFN_vkDestroyPipelineLayout vk_destroy_pipeline_layout;
extern PFN_vkCreateGraphicsPipelines vk_create_graphics_pipelines;
extern PFN_vkDestroyPipeline vk_destroy_pipeline;
extern PFN_vkCmdBeginRenderPass vk_cmd_begin_render_pass;
extern PFN_vkCmdEndRenderPass vk_cmd_end_render_pass;
extern PFN_vkCmdBindPipeline vk_cmd_bind_pipeline;
extern PFN_vkCmdDraw vk_cmd_draw;
extern PFN_vkCmdSetViewport vk_cmd_set_viewport;
extern PFN_vkCmdSetScissor vk_cmd_set_scissors;
// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
class Backend: public ::lt::renderer::Backend
{
public:
Backend();
Backend(Backend &&) = default;
auto operator=(Backend &&) -> Backend & = default;
Backend(const Backend &) = delete;
auto operator=(const Backend &) -> Backend & = delete;
~Backend() override;
[[nodiscard]] constexpr auto get_api() const -> API override
{
return API::vulkan;
}
private:
void initialize_instance();
void initialize_debug_messenger();
void initialize_physical_device();
void initialize_logical_device();
void initialize_queue();
void load_library();
void load_global_functions();
void load_instance_functions();
void load_device_functions();
[[nodiscard]] auto find_suitable_queue_family() const -> uint32_t;
VkInstance m_instance {};
VkPhysicalDevice m_physical_device {};
VkDevice m_device {};
VkQueue m_queue {};
VkDebugUtilsMessengerEXT m_debug_messenger {};
};
} // namespace lt::renderer::vk

View file

@ -1,5 +0,0 @@
#include <renderer/vk/instance.hpp>
namespace lt::vk {
}

View file

@ -1,19 +0,0 @@
#pragma once
#include <vulkan/vulkan.h>
#include <vulkan/vulkan.hpp>
namespace lt::vk {
class Instance
{
public:
Instance()
{
}
private:
::vk::Instance m_instace;
};
} // namespace lt::vk

View file

@ -1,8 +0,0 @@
#include <renderer/vk/instance.hpp>
#include <test/test.hpp>
lt::test::Suite raii = [] {
lt::test::Case { "raii" } = [] {
auto instance = lt::vk::Instance {};
};
};

View file

@ -1,29 +0,0 @@
#pragma once
namespace lt::renderer {
class Backend
{
public:
enum class API : uint8_t
{
vulkan,
directx,
};
Backend() = default;
Backend(Backend &&) = default;
auto operator=(Backend &&) -> Backend & = default;
Backend(const Backend &) = delete;
auto operator=(const Backend &) -> Backend & = delete;
virtual ~Backend() = default;
[[nodiscard]] virtual auto get_api() const -> API = 0;
};
} // namespace lt::renderer

View file

@ -1,12 +0,0 @@
#pragma once
namespace lt::renderer {
/** Requires a Transform Component
* @todo(Light): Figure out how to enforce a component requirement list for a component
*/
struct RendererComponent
{
};
} // namespace lt::renderer

View file

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

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
//
#include <math/mat4.hpp> #include <math/mat4.hpp>
#include <math/vec3.hpp> #include <math/vec3.hpp>
#include <math/vec4.hpp> #include <math/vec4.hpp>
@ -7,6 +8,11 @@
#include <renderer/buffers.hpp> #include <renderer/buffers.hpp>
#include <renderer/render_command.hpp> #include <renderer/render_command.hpp>
#include <renderer/renderer.hpp> #include <renderer/renderer.hpp>
///
#include <renderer/programs/quad.hpp>
#include <renderer/programs/texture.hpp>
#include <renderer/programs/tinted_texture.hpp>
#include <utility> #include <utility>
#define LT_MAX_QUAD_RENDERER_VERTICES (1028u * 4u) #define LT_MAX_QUAD_RENDERER_VERTICES (1028u * 4u)
@ -26,10 +32,6 @@ class Camera;
class WindowResizedEvent; class WindowResizedEvent;
class Shader; class Shader;
class TintedTextureRendererProgram;
class QuadRendererProgram;
class TextureRendererProgram;
class Renderer class Renderer
{ {
public: public:
@ -105,8 +107,6 @@ public:
s_context->end_scene_impl(); s_context->end_scene_impl();
} }
~Renderer();
void on_window_resize(const WindowResizedEvent &event); void on_window_resize(const WindowResizedEvent &event);
void begin_frame(); void begin_frame();
@ -116,11 +116,11 @@ public:
private: private:
static Renderer *s_context; static Renderer *s_context;
Scope<QuadRendererProgram> m_quad_renderer; QuadRendererProgram m_quad_renderer;
Scope<TextureRendererProgram> m_texture_renderer; TextureRendererProgram m_texture_renderer;
Scope<TintedTextureRendererProgram> m_tinted_texture_renderer; TintedTextureRendererProgram m_tinted_texture_renderer;
Scope<ConstantBuffer> m_view_projection_buffer; Scope<ConstantBuffer> m_view_projection_buffer;

View file

@ -1,60 +0,0 @@
#pragma once
#include <base/base.hpp>
#include <ecs/scene.hpp>
struct GLFWwindow;
namespace lt::renderer {
/** The system for putting gore on your display
*
* Exclusively operates on components:
* - RendererComponent
* - PostEffectsComponent
* - UserInterfaceComponent
*
* Requires read acces on components:
* - TransformComponent
*/
class System
{
public:
/** The configurations of this system. */
struct Properties
{
};
/** The requirements for this system to initialize. */
struct InitRequirements
{
GLFWwindow *glfw_window_handle;
Ref<ecs::Registry> registry;
};
/** The requirements for this system to tick. */
struct TickRequirements
{
double delta_time;
};
[[nodiscard]] System(InitRequirements requirements);
System(System &&) = default;
System(const System &) = delete;
auto operator=(System &&) -> System & = default;
auto operator=(const System &) -> System & = delete;
~System();
void tick(TickRequirements requirements);
private:
Ref<ecs::Registry> m_registry;
};
} // namespace lt::renderer

View file

@ -1,16 +0,0 @@
if (NOT WIN32)
add_library_module(surface linux/system.cpp)
else()
endif()
target_link_libraries(surface PUBLIC
ecs
app
PRIVATE
glfw
logger
lt_debug
)
add_test_module(surface system.test.cpp)
add_fuzz_module(surface system.fuzz.cpp)

View file

@ -1 +0,0 @@
1484824981238913982498139812098u24

View file

@ -1,292 +0,0 @@
#define GLFW_EXPOSE_NATIVE_X11
#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>
#include <surface/system.hpp>
namespace lt::surface {
void glfw_error_callbac(int32_t code, const char *description)
{
log_err("GLFW ERROR: {} -> {}", code, description);
}
void handle_event(GLFWwindow *window, const SurfaceComponent::Event &event)
{
auto &callbacks = *static_cast<std::vector<SurfaceComponent::EventCallback> *>(
glfwGetWindowUserPointer(window)
);
for (auto &callback : callbacks)
{
if (callback(event))
{
return;
}
}
}
void bind_glfw_events(GLFWwindow *handle)
{
glfwSetWindowPosCallback(handle, [](GLFWwindow *window, int xpos, int ypos) {
handle_event(window, MovedEvent { xpos, ypos });
});
glfwSetWindowSizeCallback(handle, [](GLFWwindow *window, int width, int height) {
handle_event(
window,
ResizedEvent { static_cast<uint32_t>(width), static_cast<uint32_t>(height) }
);
});
glfwSetWindowCloseCallback(handle, [](GLFWwindow *window) {
handle_event(window, ClosedEvent {});
});
glfwSetWindowFocusCallback(handle, [](GLFWwindow *window, int focus) {
if (focus == GLFW_TRUE)
{
handle_event(window, GainFocusEvent {});
}
else
{
handle_event(window, LostFocusEvent {});
}
});
glfwSetCursorPosCallback(handle, [](GLFWwindow *window, double xpos, double ypos) {
handle_event(
window,
MouseMovedEvent { static_cast<float>(xpos), static_cast<float>(ypos) }
);
});
glfwSetMouseButtonCallback(
handle,
[](GLFWwindow *window, int button, int action, int /*mods*/) {
if (action == GLFW_PRESS)
{
handle_event(window, ButtonPressedEvent { button });
}
else if (action == GLFW_RELEASE)
{
handle_event(window, ButtonReleasedEvent { button });
}
}
);
glfwSetScrollCallback(handle, [](GLFWwindow *window, double /*xoffset*/, double yoffset) {
handle_event(window, WheelScrolledEvent { static_cast<float>(yoffset) });
});
glfwSetKeyCallback(
handle,
[](GLFWwindow *window, int key, int /*scancode*/, int action, int /*mods*/) {
if (action == GLFW_PRESS)
{
handle_event(window, KeyPressedEvent { key });
}
else if (action == GLFW_RELEASE)
{
handle_event(window, KeyReleasedEvent { key });
}
}
);
glfwSetCharCallback(handle, [](GLFWwindow *window, unsigned int character) {
handle_event(window, KeySetCharEvent { character });
});
}
void init_glfw() {};
System::System(Ref<ecs::Registry> registry): m_registry(std::move(registry))
{
glfwSetErrorCallback(&glfw_error_callbac);
ensure(glfwInit(), "Failed to initialize 'glfw'");
ensure(m_registry, "Failed to initialize surface system: null registry");
ensure(
m_registry->view<SurfaceComponent>().size() == 0,
"Failed to initialize surface system: registry has surface component(s)"
);
m_registry->get_entt_registry()
.on_construct<SurfaceComponent>()
.connect<&System::on_surface_construct>(this);
m_registry->get_entt_registry()
.on_update<SurfaceComponent>()
.connect<&System::on_surface_update>(this);
m_registry->get_entt_registry()
.on_destroy<SurfaceComponent>()
.connect<&System::on_surface_destroy>(this);
}
System::~System()
{
m_registry->get_entt_registry()
.on_construct<SurfaceComponent>()
.disconnect<&System::on_surface_construct>(this);
m_registry->get_entt_registry()
.on_update<SurfaceComponent>()
.connect<&System::on_surface_update>(this);
m_registry->get_entt_registry()
.on_destroy<SurfaceComponent>()
.disconnect<&System::on_surface_destroy>(this);
m_registry->view<SurfaceComponent>().each([&](const entt::entity entity, SurfaceComponent &) {
m_registry->get_entt_registry().remove<SurfaceComponent>(entity);
});
glfwTerminate();
}
void System::on_surface_construct(entt::registry &registry, entt::entity entity)
{
try
{
auto &surface = registry.get<SurfaceComponent>(entity);
ensure_component_sanity(surface);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
surface.m_glfw_handle = glfwCreateWindow(
static_cast<int>(surface.get_resolution().x),
static_cast<int>(surface.get_resolution().y),
surface.get_title().begin(),
nullptr,
nullptr
);
ensure(surface.m_glfw_handle, "Failed to create 'GLFWwindow'");
glfwSetWindowUserPointer(surface.m_glfw_handle, &surface.m_event_callbacks);
surface.m_native_handle = glfwGetX11Window(surface.m_glfw_handle);
bind_glfw_events(surface.m_glfw_handle);
}
catch (...)
{
registry.remove<SurfaceComponent>(entity);
throw;
}
}
void System::on_surface_update(entt::registry &registry, entt::entity entity)
{
auto &surface = registry.get<SurfaceComponent>(entity);
glfwSetWindowUserPointer(surface.m_glfw_handle, &surface.m_event_callbacks);
}
void System::on_surface_destroy(entt::registry &registry, entt::entity entity)
{
auto &surface = registry.get<SurfaceComponent>(entity);
if (surface.m_glfw_handle)
{
glfwDestroyWindow(surface.m_glfw_handle);
}
}
void System::set_title(ecs::Entity entity, std::string_view new_title)
{
auto &surface = entity.get_component<SurfaceComponent>();
surface.m_title = new_title;
glfwSetWindowTitle(surface.m_glfw_handle, surface.m_title.c_str());
}
auto System::tick() -> bool
{
m_registry->view<SurfaceComponent>().each([](SurfaceComponent &surface) {
glfwSwapBuffers(surface.m_glfw_handle);
});
glfwPollEvents();
return false;
}
void System::set_size(ecs::Entity surface_entity, const math::uvec2 &new_size)
{
auto &surface = surface_entity.get_component<SurfaceComponent>();
surface.m_resolution = new_size;
glfwSetWindowSize(
surface.m_glfw_handle,
static_cast<int>(new_size.x),
static_cast<int>(new_size.y)
);
}
void System::set_v_sync(ecs::Entity surface_entity, bool vsync)
{
auto &surface = surface_entity.get_component<SurfaceComponent>();
surface.m_vsync = vsync;
glfwSwapInterval(vsync);
}
void System::set_visibility(ecs::Entity surface_entity, bool visible)
{
auto &surface = surface_entity.get_component<SurfaceComponent>();
surface.m_visible = visible;
if (visible)
{
glfwShowWindow(surface.m_glfw_handle);
}
else
{
glfwHideWindow(surface.m_glfw_handle);
}
}
void System::add_event_listener(
ecs::Entity surface_entity,
SurfaceComponent::EventCallback callback
)
{
auto &surface = surface_entity.get_component<SurfaceComponent>();
surface.m_event_callbacks.emplace_back(std::move(callback));
}
void System::ensure_component_sanity(const SurfaceComponent &component)
{
auto [width, height] = component.get_resolution();
ensure(width != 0u, "Received bad values for surface component: width({}) == 0", width);
ensure(height != 0u, "Received bad values for surface component: height({}) == 0", height);
ensure(
width < SurfaceComponent::max_dimension,
"Received bad values for surface component: width({}) > max_dimension({})",
width,
SurfaceComponent::max_dimension
);
ensure(
height < SurfaceComponent::max_dimension,
"Received bad values for surface component: height({}) > max_dimension({})",
height,
SurfaceComponent::max_dimension
);
ensure(
component.get_title().size() < SurfaceComponent::max_title_length,
"Received bad values for surface component: title.size({}) > max_title_length({})",
component.get_title().size(),
SurfaceComponent::max_title_length
);
}
} // namespace lt::surface
namespace lt {
} // namespace lt

View file

@ -1,103 +0,0 @@
#include <ecs/scene.hpp>
#include <surface/system.hpp>
#include <test/fuzz.hpp>
#include <test/test.hpp>
namespace lt::surface {
enum class Action : uint8_t
{
create_entity,
create_surface_component,
destroy_surface_component,
tick,
};
void create_surface_component(test::FuzzDataProvider &provider, ecs::Registry &registry)
{
const auto length = std::min(provider.consume<uint32_t>().value_or(16), 255u);
const auto title = provider.consume_string(length).value_or("");
const auto resolution = math::uvec2 {
provider.consume<uint32_t>().value_or(32u),
provider.consume<uint32_t>().value_or(64u),
};
const auto visible = provider.consume<bool>().value_or(false);
const auto vsync = provider.consume<bool>().value_or(false);
try
{
registry.create_entity("").add_component<surface::SurfaceComponent>(
surface::SurfaceComponent::CreateInfo {
.title = std::move(title),
.resolution = resolution,
.vsync = vsync,
.visible = visible,
}
);
}
catch (const std::exception &exp)
{
std::ignore = exp;
}
}
void remove_surface_component(ecs::Registry &registry)
{
const auto view = registry.get_entt_registry().view<SurfaceComponent>();
if (!view->empty())
{
registry.get_entt_registry().remove<SurfaceComponent>(*view.begin());
}
}
void check_invariants()
{
}
test::FuzzHarness harness = [](const uint8_t *data, size_t size) {
auto provider = test::FuzzDataProvider { data, size };
auto registry = create_ref<ecs::Registry>();
auto system = surface::System { registry };
while (auto action = provider.consume<uint8_t>())
{
switch (static_cast<Action>(action.value()))
{
case Action::create_entity:
{
const auto length = std::min(provider.consume<uint32_t>().value_or(16), 255u);
const auto tag = provider.consume_string(length).value_or("");
registry->create_entity(tag);
break;
}
case Action::create_surface_component:
{
create_surface_component(provider, *registry);
break;
}
case Action::destroy_surface_component:
{
remove_surface_component(*registry);
break;
}
case Action::tick:
{
system.tick();
break;
}
}
check_invariants();
}
return 0;
};
} // namespace lt::surface

View file

@ -1,198 +0,0 @@
#include <surface/system.hpp>
#include <test/test.hpp>
using namespace lt;
using std::ignore;
using surface::SurfaceComponent;
using surface::System;
using test::Case;
using test::expect_eq;
using test::expect_ne;
using test::expect_not_nullptr;
using test::expect_throw;
using test::Suite;
constexpr auto title = "TestWindow";
constexpr auto width = 800u;
constexpr auto height = 600u;
constexpr auto vsync = true;
constexpr auto visible = false;
class Fixture
{
public:
[[nodiscard]] auto registry() -> Ref<ecs::Registry>
{
return m_registry;
}
auto add_surface_component(
SurfaceComponent::CreateInfo info = SurfaceComponent::CreateInfo {
.title = title,
.resolution = { width, height },
.vsync = vsync,
.visible = visible,
}
) -> SurfaceComponent &
{
auto entity = m_registry->create_entity("");
return entity.add_component<SurfaceComponent>(info);
}
void check_values(const SurfaceComponent &component)
{
expect_ne(std::get<SurfaceComponent::X11NativeHandle>(component.get_native_handle()), 0);
expect_eq(component.get_resolution().x, width);
expect_eq(component.get_resolution().y, height);
expect_eq(component.get_title(), title);
expect_eq(component.is_vsync(), vsync);
expect_eq(component.is_visible(), visible);
}
private:
Ref<ecs::Registry> m_registry = create_ref<ecs::Registry>();
};
Suite raii = [] {
Case { "happy path won't throw" } = [] {
auto fixture = Fixture {};
ignore = System { fixture.registry() };
};
Case { "many won't freeze/throw" } = [] {
auto fixture = Fixture {};
/* range is small since glfw init/terminate is slow. */
for (auto idx : std::views::iota(0, 100))
{
ignore = System { fixture.registry() };
}
};
Case { "unhappy path throws" } = [] {
expect_throw([] { ignore = System { {} }; });
auto fixture = Fixture {};
fixture.add_surface_component();
expect_throw([&] { ignore = System { { fixture.registry() } }; });
};
Case { "post construct has correct state" } = [] {
auto fixture = Fixture {};
auto system = System { fixture.registry() };
expect_eq(fixture.registry()->view<SurfaceComponent>()->size(), 0);
};
Case { "post destruct has correct state" } = [] {
auto fixture = Fixture {};
auto system = create_scope<System>(fixture.registry());
fixture.add_surface_component();
expect_eq(fixture.registry()->view<SurfaceComponent>()->size(), 1);
system.reset();
expect_eq(fixture.registry()->view<SurfaceComponent>()->size(), 0);
};
};
Suite system_events = [] {
Case { "on_register won't throw" } = [] {
auto fixture = Fixture {};
auto system = System { fixture.registry() };
system.on_register();
expect_eq(fixture.registry()->view<SurfaceComponent>().size(), 0);
};
Case { "on_unregister won't throw" } = [] {
auto fixture = Fixture {};
auto system = System { fixture.registry() };
system.on_register();
system.on_unregister();
expect_eq(fixture.registry()->view<SurfaceComponent>().size(), 0);
};
};
Suite registry_events = [] {
Case { "on_construct<SurfaceComponent> initializes component" } = [] {
auto fixture = Fixture {};
auto system = System { fixture.registry() };
const auto &component = fixture.add_surface_component();
expect_eq(fixture.registry()->view<SurfaceComponent>().size(), 1);
fixture.check_values(component);
};
Case { "unhappy on_construct<SurfaceComponent> throws" } = [] {
auto fixture = Fixture {};
auto system = System { fixture.registry() };
expect_throw([&] { fixture.add_surface_component({ .resolution = { width, 0 } }); });
expect_throw([&] { fixture.add_surface_component({ .resolution = { 0, height } }); });
expect_throw([&] {
fixture.add_surface_component(
{ .title = "", .resolution = { SurfaceComponent::max_dimension + 1, height } }
);
});
expect_throw([&] {
fixture.add_surface_component(
{ .title = "", .resolution = { width, SurfaceComponent::max_dimension + 1 } }
);
});
auto big_str = std::string {};
big_str.resize(SurfaceComponent::max_title_length + 1);
expect_throw([&] {
fixture.add_surface_component({ .title = big_str, .resolution = { width, height } });
});
};
Case { "unhappy on_construct<SurfaceComponent> removes component" } = [] {
auto fixture = Fixture {};
auto system = System { fixture.registry() };
expect_throw([&] { fixture.add_surface_component({ .resolution = { width, 0 } }); });
expect_eq(fixture.registry()->view<SurfaceComponent>().size(), 0);
};
Case { "on_destrroy<SurfaceComponent> cleans up component" } = [] {
auto fixture = Fixture {};
auto system = create_scope<System>(fixture.registry());
const auto &component = fixture.add_surface_component();
expect_eq(fixture.registry()->view<SurfaceComponent>().size(), 1);
fixture.check_values(component);
system.reset();
expect_eq(fixture.registry()->view<SurfaceComponent>().size(), 0);
};
};
Suite tick = [] {
Case { "ticking on empty registry won't throw" } = [] {
auto fixture = Fixture {};
System { fixture.registry() }.tick();
};
Case { "ticking on non-empty registry won't throw" } = [] {
auto fixture = Fixture {};
auto system = System { fixture.registry() };
fixture.add_surface_component();
system.tick();
};
Case { "ticking on chaotic registry won't throw" } = [] {
};
};
Suite property_setters = [] {
};
Suite listeners = [] {
};

View file

@ -1,119 +0,0 @@
#pragma once
#include <math/vec2.hpp>
#include <surface/events/keyboard.hpp>
#include <surface/events/mouse.hpp>
#include <surface/events/surface.hpp>
#include <variant>
struct GLFWwindow;
namespace lt::surface {
/** Represents a platform's surface (eg. a Window).
*
* @note Read-only component, should only be modified through a system.
*/
class SurfaceComponent
{
public:
friend class System;
using Event = std::variant<
// surface events
ClosedEvent,
MovedEvent,
ResizedEvent,
LostFocusEvent,
GainFocusEvent,
// keyboard events
KeyPressedEvent,
KeyRepeatEvent,
KeyReleasedEvent,
KeySetCharEvent,
// mouse events
MouseMovedEvent,
WheelScrolledEvent,
ButtonPressedEvent,
ButtonReleasedEvent>;
using EventCallback = std::function<bool(const Event &)>;
using WindowsNativeHandle = void *;
using X11NativeHandle = unsigned long;
using NativeHandle = std::variant<WindowsNativeHandle, X11NativeHandle>;
static constexpr auto max_dimension = 4096;
static constexpr auto max_title_length = 256;
struct CreateInfo
{
std::string_view title;
math::uvec2 resolution;
bool vsync;
bool visible;
};
SurfaceComponent(const CreateInfo &info)
: m_title(info.title)
, m_resolution(info.resolution)
, m_vsync(info.vsync)
, m_visible(info.visible)
{
}
[[nodiscard]] auto get_title() const -> std::string_view
{
return m_title;
}
[[nodiscard]] auto get_resolution() const -> const math::uvec2 &
{
return m_resolution;
}
[[nodiscard]] auto is_vsync() const -> bool
{
return m_vsync;
}
[[nodiscard]] auto is_visible() const -> bool
{
return m_visible;
}
[[nodiscard]] auto get_native_handle() const -> NativeHandle
{
return m_native_handle;
}
private:
[[nodiscard]] auto get_glfw_handle() const -> GLFWwindow *
{
return m_glfw_handle;
}
std::string m_title;
math::uvec2 m_resolution;
bool m_vsync;
bool m_visible;
NativeHandle m_native_handle;
GLFWwindow *m_glfw_handle {};
std::vector<EventCallback> m_event_callbacks;
};
} // namespace lt::surface

View file

@ -1,91 +0,0 @@
#pragma once
#include <format>
namespace lt::surface {
class KeyPressedEvent
{
public:
KeyPressedEvent(int32_t key): m_key(key)
{
}
[[nodiscard]] auto get_key() const -> int32_t
{
return m_key;
}
[[nodiscard]] auto to_string() const -> std::string
{
return std::format("KeyPressed: {}", m_key);
}
private:
int32_t m_key;
};
class KeyRepeatEvent
{
public:
KeyRepeatEvent(int key): m_key(key)
{
}
[[nodiscard]] auto get_key() const -> int32_t
{
return m_key;
}
[[nodiscard]] auto to_string() const -> std::string
{
return std::format("KeyRepeated: {}", m_key);
}
private:
int32_t m_key;
};
class KeyReleasedEvent
{
public:
KeyReleasedEvent(int key): m_key(key)
{
}
[[nodiscard]] auto get_key() const -> int32_t
{
return m_key;
}
[[nodiscard]] auto to_string() const -> std::string
{
return std::format("KeyReleased: {}", m_key);
}
private:
int32_t m_key;
};
class KeySetCharEvent
{
public:
KeySetCharEvent(uint32_t character): m_character(character)
{
}
[[nodiscard]] auto get_character() const -> uint32_t
{
return m_character;
}
[[nodiscard]] auto to_string() const -> std::string
{
return std::format("KeyCharSet: {}", m_character);
}
private:
uint32_t m_character;
};
} // namespace lt::surface

View file

@ -1,103 +0,0 @@
#pragma once
#include <math/vec2.hpp>
namespace lt::surface {
class MouseMovedEvent
{
public:
MouseMovedEvent(float x, float y): m_position(x, y)
{
}
[[nodiscard]] auto get_position() const -> const math::vec2 &
{
return m_position;
}
[[nodiscard]] auto get_x() const -> float
{
return m_position.x;
}
[[nodiscard]] auto get_y() const -> float
{
return m_position.y;
}
[[nodiscard]] auto to_string() const -> std::string
{
return std::format("MouseMoved: {}, {}", m_position.x, m_position.y);
}
private:
math::vec2 m_position;
};
class WheelScrolledEvent
{
public:
WheelScrolledEvent(float offset): m_offset(offset)
{
}
[[nodiscard]] auto get_offset() const -> float
{
return m_offset;
}
[[nodiscard]] auto to_string() const -> std::string
{
std::stringstream ss;
ss << "WheelScrolled: " << m_offset;
return ss.str();
}
private:
float m_offset;
};
class ButtonPressedEvent
{
public:
ButtonPressedEvent(int button): m_button(button)
{
}
[[nodiscard]] auto get_button() const -> int
{
return m_button;
}
[[nodiscard]] auto to_string() const -> std::string
{
return std::format("ButtonPressed: {}", m_button);
}
private:
int m_button;
};
class ButtonReleasedEvent
{
public:
ButtonReleasedEvent(int button): m_button(button)
{
}
[[nodiscard]] auto get_button() const -> int
{
return m_button;
}
[[nodiscard]] auto to_string() const -> std::string
{
return std::format("ButtonReleased: {}", m_button);
}
private:
int m_button;
};
} // namespace lt::surface

View file

@ -1,81 +0,0 @@
#pragma once
#include <math/vec2.hpp>
#include <sstream>
namespace lt::surface {
class ClosedEvent
{
public:
[[nodiscard]] auto to_string() const -> std::string_view
{
return "SurfaceClosedEvent";
}
};
class MovedEvent
{
public:
MovedEvent(int x, int y): m_position(x, y)
{
}
[[nodiscard]] auto get_position() const -> const math::ivec2 &
{
return m_position;
}
[[nodiscard]] auto to_string() const -> std::string
{
auto stream = std::stringstream {};
stream << "WindwoMoved: " << m_position.x << ", " << m_position.y;
return stream.str();
}
private:
math::ivec2 m_position;
};
class ResizedEvent
{
public:
ResizedEvent(unsigned int width, unsigned int height): m_size(width, height)
{
}
[[nodiscard]] auto get_size() const -> const math::uvec2 &
{
return m_size;
}
[[nodiscard]] auto to_string() const -> std::string
{
auto stream = std::stringstream {};
stream << "SurfaceResized: " << m_size.x << ", " << m_size.y;
return stream.str();
}
private:
math::uvec2 m_size;
};
class LostFocusEvent
{
public:
[[nodiscard]] auto to_string() const -> std::string_view
{
return "SurfaceLostFocus";
}
};
class GainFocusEvent
{
public:
[[nodiscard]] auto to_string() const -> std::string_view
{
return "SurfaceGainFocus";
}
};
} // namespace lt::surface

View file

@ -1,58 +0,0 @@
#pragma once
#include <app/system.hpp>
#include <ecs/entity.hpp>
#include <ecs/scene.hpp>
#include <surface/components.hpp>
namespace lt::surface {
class System: public app::ISystem
{
public:
[[nodiscard]] System(Ref<ecs::Registry> registry);
~System() override;
System(System &&) = default;
System(const System &) = delete;
auto operator=(System &&) -> System & = default;
auto operator=(const System &) -> System & = delete;
void on_register() override
{
}
void on_unregister() override
{
}
auto tick() -> bool override;
static void set_title(ecs::Entity surface_entity, std::string_view new_title);
void set_size(ecs::Entity surface_entity, const math::uvec2 &new_size);
void set_v_sync(ecs::Entity surface_entity, bool vsync);
void set_visibility(ecs::Entity surface_entity, bool visible);
void add_event_listener(ecs::Entity surface_entity, SurfaceComponent::EventCallback callback);
private:
void on_surface_construct(entt::registry &registry, entt::entity entity);
void on_surface_update(entt::registry &registry, entt::entity entity);
void on_surface_destroy(entt::registry &registry, entt::entity entity);
void ensure_component_sanity(const SurfaceComponent &component);
Ref<ecs::Registry> m_registry;
};
} // namespace lt::surface

View file

@ -1,4 +1,2 @@
add_library_module(test test.cpp entrypoint.cpp) add_library_module(test test.cpp entrypoint.cpp)
add_library_module(fuzz_test test.cpp fuzz.cpp) add_test_module(test test.tests.cpp)
add_test_module(test test.test.cpp)

View file

@ -1,24 +0,0 @@
#include <test/test.hpp>
namespace lt::test {
auto process_fuzz_input(const uint8_t *data, size_t size) -> int32_t
try
{
return details::Registry::process_fuzz_input(data, size);
}
catch (const std::exception &exp)
{
std::cout << "Fuzz input resulted in uncaught exception:\n";
std::cout << "\texception.what: " << exp.what() << '\n';
std::cout << "\tinput size: " << size << '\n';
return EXIT_FAILURE;
}
}; // namespace lt::test
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
return lt::test::process_fuzz_input(data, size);
}

View file

@ -1,199 +0,0 @@
#include <test/test.hpp>
using lt::test::Case;
using lt::test::Suite;
Suite expects = []() {
using lt::test::expect_unreachable;
using lt::test::expect_true;
using lt::test::expect_false;
using lt::test::expect_eq;
using lt::test::expect_ne;
using lt::test::expect_le;
using lt::test::expect_throw;
Case { "" } = [] {
};
Case { "expect_unreachable" } = [] {
auto unhappy = false;
// clang-format off
try { expect_unreachable(); }
catch (const std::exception &exp) { unhappy = true; }
// clang-format on
if (!unhappy)
{
throw std::runtime_error { "expect_unreachable" };
}
};
Case { "expect_true - happy" } = [] {
auto oongaboonga = int {};
auto *oongaboonga_ptr_here = &oongaboonga;
expect_true(oongaboonga_ptr_here);
expect_true(true);
expect_true(1); // NOLINT
};
Case { "expect_true - unhappy" } = [] {
auto unhappy_counter = 0u;
auto *where_oongaboonga_ptr = (int *)nullptr;
// clang-format off
try { expect_true(where_oongaboonga_ptr); }
catch (const std::exception& exp) { ++unhappy_counter; }
try { expect_true(!true); }
catch (const std::exception& exp) { ++unhappy_counter; }
try { expect_true(false); }
catch (const std::exception& exp) { ++unhappy_counter; }
try { expect_true(0); } // NOLINT
catch (const std::exception& exp) { ++unhappy_counter; }
// clang-format on
};
Case { "expect_false - happy" } = [] {
auto *oongaboonga_is_slacking = (int *)nullptr;
expect_false(oongaboonga_is_slacking);
expect_false(false);
expect_false(0); // NOLINT
};
Case { "expect_false - unhappy" } = [] {
auto oongaboonga = int {};
auto *oonga_oonga_can_rest_now = (int *)nullptr;
auto unhappy_counter = 0u;
// clang-format off
try { expect_false(oonga_oonga_can_rest_now); }
catch (const std::exception& exp) { ++unhappy_counter; }
try { expect_false(true); }
catch (const std::exception& exp) { ++unhappy_counter; }
try { expect_false(!false); }
catch (const std::exception& exp) { ++unhappy_counter; }
try { expect_false(1); } // NOLINT
catch (const std::exception& exp) { ++unhappy_counter; }
// clang-format on
};
Case { "expect_true - unhappy" } = [] {
auto unhappy_counter = 0u;
auto *where_oongaboonga_ptr = (int *)nullptr;
// clang-format off
try { expect_true(where_oongaboonga_ptr); }
catch (const std::exception& exp) { ++unhappy_counter; }
try { expect_true(!true); }
catch (const std::exception& exp) { ++unhappy_counter; }
try { expect_true(false); }
catch (const std::exception& exp) { ++unhappy_counter; }
try { expect_true(0); } // NOLINT
catch (const std::exception& exp) { ++unhappy_counter; }
// clang-format on
};
Case { "expect_eq - happy" } = [] {
expect_eq(5, 5);
expect_eq(20.0, 20.0);
expect_eq(true, 1);
};
Case { "expect_eq - unhappy" } = [] {
auto unhappy = false;
// clang-format off
try { expect_eq(true, false); }
catch (const std::exception &exp) { unhappy = true; }
// clang-format on
if (!unhappy)
{
throw std::runtime_error { "expect_eq unhappy" };
}
};
Case { "expect_ne - happy " } = [] {
expect_ne(5, 5.0000001);
expect_ne(20.0, 69.0);
expect_ne(true, 0);
};
Case { "expect_ne - unhappy" } = [] {
auto unhappy_counter = 0u;
// clang-format off
try { expect_ne(5, 5); }
catch (const std::exception &exp) { ++unhappy_counter; }
try { expect_ne(20.0, 20.0); }
catch (const std::exception &exp) { ++unhappy_counter; }
try { expect_ne(true, 1); }
catch (const std::exception &exp) { ++unhappy_counter; }
// clang-format on
if (unhappy_counter != 3)
{
throw std::runtime_error { "expect_ne unhappy" };
}
};
Case { "expect_throw - happy" } = [] {
expect_throw([] { throw std::runtime_error { "nonsense" }; });
};
Case { "expect_throw - unhappy" } = [] {
auto unhappy = false;
// clang-format off
try { expect_throw([] {}); }
catch (const std::exception &exp) { unhappy = true; }
// clang-format on
if (!unhappy)
{
throw std::runtime_error { "expect_throw - unhappy" };
}
};
Case { "expect_le - happy" } = [] {
expect_le(69, 420);
expect_le(19.694206942069420, 20.0);
expect_le(false, 1);
};
Case { "expect_le - unhappy" } = [] {
auto unhappy_counter = 0u;
// clang-format off
try { expect_le(20020619 + 23, 20020619 ); }
catch (const std::exception &exp) { ++unhappy_counter; }
try { expect_le(420, 69); }
catch (const std::exception &exp) { ++unhappy_counter; }
try { expect_le(20.0, 19.694206942069420); }
catch (const std::exception &exp) { ++unhappy_counter; }
try { expect_le(1, false); }
catch (const std::exception &exp) { ++unhappy_counter; }
// clang-format on
if (unhappy_counter != 4)
{
throw std::runtime_error { "expect_le - unhappy" };
}
};
};

View file

@ -0,0 +1,36 @@
#include <test/test.hpp>
lt::test::Suite meta = []() {
using lt::test::expect_eq;
using lt::test::expect_true;
lt::test::Case { "test_1" } = [] {
expect_eq(5, 5);
};
lt::test::Case { "test_2" } = [] {
expect_eq(20.0, 20.0);
};
lt::test::Case { "test_3" } = [] {
auto exception_thrown = false;
try
{
expect_eq(true, false);
}
catch (const std::exception &exp)
{
exception_thrown = true;
}
expect_true(exception_thrown);
};
lt::test::Case { "test_4" } = [] {
expect_eq(true, 1);
};
lt::test::Case { "test_5" } = [] {
};
};

View file

@ -7,78 +7,20 @@
namespace lt::test { namespace lt::test {
template<typename T> template<typename T>
concept Printable = requires(std::ostream &stream, T value) { concept Printable = requires(std::ostream &os, T t) {
{ stream << value } -> std::same_as<std::ostream &>; { os << t } -> std::same_as<std::ostream &>;
} || requires(std::ostream &stream, T value) {
{ stream << std::to_underlying<T>(value) } -> std::same_as<std::ostream &>;
}; };
template<typename T> template<typename T>
concept Testable = Printable<T> && std::equality_comparable<T>; concept Testable = Printable<T> && std::equality_comparable<T>;
constexpr void expect_unreachable(
std::source_location source_location = std::source_location::current()
)
{
throw std::runtime_error {
std::format(
"Failed unreachable expectation:\n"
"\tlocation: {}:{}",
source_location.file_name(),
source_location.line()
),
};
};
constexpr void expect_throw(
std::invocable auto invocable,
std::source_location source_location = std::source_location::current()
)
{
try
{
invocable();
}
catch (const std::exception &exp)
{
return;
}
throw std::runtime_error {
std::format(
"Failed throwing expectation:\n"
"\tlocation: {}:{}",
source_location.file_name(),
source_location.line()
),
};
}
constexpr void expect_eq( constexpr void expect_eq(
Testable auto lhs, Testable auto lhs,
Testable auto rhs, Testable auto rhs,
std::source_location source_location = std::source_location::current() std::source_location source_location = std::source_location::current()
) )
{ {
if constexpr (std::is_enum_v<decltype(lhs)>)
{
if (lhs != rhs) if (lhs != rhs)
{
throw std::runtime_error {
std::format(
"Failed equality expectation:\n"
"\tactual: {}\n"
"\texpected: {}\n"
"\tlocation: {}:{}",
std::to_underlying<decltype(lhs)>(lhs),
std::to_underlying<decltype(rhs)>(rhs),
source_location.file_name(),
source_location.line()
),
};
}
}
else if (lhs != rhs)
{ {
throw std::runtime_error { throw std::runtime_error {
std::format( std::format(
@ -160,27 +102,6 @@ constexpr void expect_false(
} }
} }
constexpr void expect_not_nullptr(
auto *pointer,
std::source_location source_location = std::source_location::current()
)
{
if (pointer == nullptr)
{
throw std::runtime_error {
std::format(
"Failed true expectation:\n"
"\tactual: nullptr\n"
"\texpected: not nullptr\n"
"\tlocation: {}:{}",
source_location.file_name(),
source_location.line()
),
};
}
}
constexpr void expect_le( constexpr void expect_le(
Testable auto lhs, Testable auto lhs,
Testable auto rhs, Testable auto rhs,

View file

@ -1,62 +0,0 @@
#include <cstring>
#include <test/test.hpp>
namespace lt::test {
class FuzzDataProvider
{
public:
FuzzDataProvider(const uint8_t *data, size_t size): m_data(data, size)
{
}
template<typename T>
requires(
std::is_trivially_constructible_v<T> //
&& std::is_trivially_copy_constructible_v<T> //
&& std::is_trivially_copy_assignable_v<T>
)
auto consume() -> std::optional<T>
{
if (m_data.size() < sizeof(T))
{
return std::nullopt;
}
T value;
std::memcpy(&value, m_data.data(), sizeof(T));
m_data = m_data.subspan(sizeof(T));
return value;
}
auto consume_string(size_t size) -> std::optional<std::string>
{
if (m_data.size() < size)
{
return std::nullopt;
}
// NOLINTNEXTLINE
auto value = std::string { (const char *)m_data.data(), size };
m_data = m_data.subspan(size);
return value;
}
auto consume_remaining_as_string() -> std::string
{
if (m_data.empty())
{
return std::string {};
}
return { m_data.begin(), m_data.end() };
};
private:
std::span<const uint8_t> m_data;
};
} // namespace lt::test

View file

@ -5,31 +5,40 @@
namespace lt::test { namespace lt::test {
namespace concepts {
template<typename T>
concept printable = requires(std::ostream &os, T t) {
{ os << t } -> std::same_as<std::ostream &>;
};
template<
class T,
auto expr =
[] {
}>
concept test = requires(T test) {
{ test.name } -> printable;
{ test = expr } -> std::same_as<void>;
};
} // namespace concepts
namespace details { namespace details {
class Registry class Registry
{ {
public: public:
using FuzzFunction = int32_t (*)(const uint8_t *, size_t); using Suite = void (*)();
using SuiteFunction = void (*)();
static void register_suite(SuiteFunction suite) static void register_suite(Suite suite)
{ {
instance().m_suites.emplace_back(suite); instance().m_suites.emplace_back(suite);
} }
static void register_fuzz_harness(FuzzFunction suite)
{
if (instance().m_fuzz_harness)
{
throw std::logic_error {
"Attempting to register fuzz harness while one is already registered",
};
}
instance().m_fuzz_harness = suite;
}
static auto run_all() -> int32_t static auto run_all() -> int32_t
{ {
for (auto &test : instance().m_suites) for (auto &test : instance().m_suites)
@ -37,26 +46,14 @@ public:
test(); test();
} }
std::cout << "_________________________[TEST RESULTS]_________________________\n";
std::cout << "Ran " << instance().m_failed_count + instance().m_pasesed_count << " tests:\n" std::cout << "Ran " << instance().m_failed_count + instance().m_pasesed_count << " tests:\n"
<< "\tpassed: " << instance().m_pasesed_count << '\n' << "\tpassed: " << instance().m_pasesed_count << '\n'
<< "\tfailed: " << instance().m_failed_count << '\n'; << "\tfailed: " << instance().m_failed_count << '\n';
std::cout << "________________________________________________________________\n\n\n";
return instance().m_failed_count; return instance().m_failed_count;
} }
static auto process_fuzz_input(const uint8_t *data, size_t size) -> int32_t
{
if (!instance().m_fuzz_harness)
{
throw std::logic_error {
"Attempting to process fuzz input with no active harness",
};
}
return instance().m_fuzz_harness(data, size);
}
static void increment_passed_count() static void increment_passed_count()
{ {
++instance().m_pasesed_count; ++instance().m_pasesed_count;
@ -68,10 +65,7 @@ public:
} }
private: private:
Registry() Registry() = default;
{
std::cout << "________________________________________________________________\n";
}
[[nodiscard]] static auto instance() -> Registry & [[nodiscard]] static auto instance() -> Registry &
{ {
@ -79,9 +73,7 @@ private:
return registry; return registry;
} }
std::vector<SuiteFunction> m_suites; std::vector<void (*)()> m_suites;
FuzzFunction m_fuzz_harness {};
int32_t m_pasesed_count {}; int32_t m_pasesed_count {};
int32_t m_failed_count {}; int32_t m_failed_count {};
@ -94,8 +86,7 @@ struct Case
{ {
auto operator=(std::invocable auto test) -> void // NOLINT auto operator=(std::invocable auto test) -> void // NOLINT
{ {
std::cout << "[Running-----------] --> "; std::cout << "Running... " << name;
std::cout << name << '\n';
try try
{ {
@ -103,14 +94,14 @@ struct Case
} }
catch (const std::exception &exp) catch (const std::exception &exp)
{ {
std::cout << exp.what() << "\n"; std::cout << " --> FAIL !" << '\n';
std::cout << "[-----------FAIL !!]" << "\n\n"; std::cout << exp.what() << "\n\n";
details::Registry::increment_failed_count(); details::Registry::increment_failed_count();
return; // TODO(Light): Should we run the remaining tests after a failure? return; // TODO(Light): Should we run the remaining tests after a failure?
} }
details::Registry::increment_passed_count(); details::Registry::increment_passed_count();
std::cout << "[--------SUCCESS :D]" << "\n\n"; std::cout << " --> SUCCESS :D" << "\n";
} }
std::string_view name; std::string_view name;
@ -127,18 +118,6 @@ struct TestSuite
} }
}; };
struct TestFuzzHarness
{
template<class TestFuzzHarness>
constexpr TestFuzzHarness(TestFuzzHarness suite)
{
#ifndef LIGHT_SKIP_FUZZ_TESTS
details::Registry::register_fuzz_harness(+suite);
#endif
}
};
using Suite = const TestSuite; using Suite = const TestSuite;
using FuzzHarness = const TestFuzzHarness;
} // namespace lt::test } // namespace lt::test

View file

@ -1,3 +1,3 @@
add_library_module(time timer.cpp) add_library_module(time timer.cpp)
add_test_module(time timer.test.cpp) add_test_module(time timer.tests.cpp)

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