Compare commits
23 commits
main
...
ci/build_m
Author | SHA1 | Date | |
---|---|---|---|
7d02876d12 | |||
a74e23051c | |||
9909f5c430 | |||
6ecb268196 | |||
144ca1aa4f | |||
3520ae36a6 | |||
57031ee44e | |||
90a1a06bbe | |||
9662f97095 | |||
8f12d76401 | |||
cb5b97dddb | |||
253a8216a2 | |||
bf2bf1e1a1 | |||
f21e6450cd | |||
d1caf1df5b | |||
bcfbc5c1c1 | |||
48bf9eb033 | |||
1bdfa1fac0 | |||
8df6e7967f | |||
034a6c7537 | |||
b1bca72f6f | |||
306b65df94 | |||
e59b3b8d3a |
126 changed files with 1999 additions and 3366 deletions
87
.drone.yml
87
.drone.yml
|
@ -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/
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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!!!!!!!!-->
|
|
||||||
|
|
12
conanfile.py
12
conanfile.py
|
@ -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
3
docs/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
||||||
_build/
|
|
||||||
generated/
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
.. architecture/resources
|
|
||||||
|
|
||||||
Resource Management
|
Resource Management
|
||||||
|
|
||||||
===================================================================================================
|
===================================================================================================
|
||||||
|
|
||||||
|
|
27
docs/conf.py
27
docs/conf.py
|
@ -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']
|
|
|
@ -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')
|
|
|
@ -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
|
|
||||||
--------------------
|
|
|
@ -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>``
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
.. guidelines/philosophy
|
|
||||||
|
|
||||||
Philosophy
|
|
||||||
===================================================================================================
|
|
||||||
|
|
||||||
| **A theory or attitude that acts as a guiding principle for behaviour.**
|
|
||||||
| --- Oxford Languages
|
|
|
@ -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
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
.. light/features
|
|
||||||
|
|
||||||
Features
|
|
||||||
===================================================================================================
|
|
|
@ -1,4 +0,0 @@
|
||||||
.. light/demos
|
|
||||||
|
|
||||||
Showcase
|
|
||||||
===================================================================================================
|
|
|
@ -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
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
|
|
@ -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();
|
||||||
if (system->tick())
|
render_user_interface();
|
||||||
{
|
poll_events();
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (auto &system : m_systems_to_be_registered)
|
void Application::quit()
|
||||||
{
|
{
|
||||||
m_systems.emplace_back(system)->on_register();
|
s_instance->m_window->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &system : m_systems_to_be_unregistered)
|
void Application::update_layers()
|
||||||
{
|
{
|
||||||
m_systems.erase(
|
for (auto &it : *m_layer_stack)
|
||||||
std::remove(m_systems.begin(), m_systems.end(), system),
|
{
|
||||||
m_systems.end()
|
// narrowing double -> float
|
||||||
);
|
it->on_update(static_cast<float>(m_timer.elapsed_time().count()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_systems.empty())
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &it : std::ranges::reverse_view(*m_layer_stack))
|
||||||
|
{
|
||||||
|
if (it->on_event(event))
|
||||||
{
|
{
|
||||||
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
|
||||||
|
|
47
modules/app/private/layer.cpp
Normal file
47
modules/app/private/layer.cpp
Normal 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
|
18
modules/app/private/layer_stack.cpp
Normal file
18
modules/app/private/layer_stack.cpp
Normal 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
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
116
modules/app/public/layer.hpp
Normal file
116
modules/app/public/layer.hpp
Normal 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
|
50
modules/app/public/layer_stack.hpp
Normal file
50
modules/app/public/layer_stack.hpp
Normal 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
|
|
@ -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
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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 {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
#include <input/system.hpp>
|
|
||||||
|
|
||||||
namespace lt::input {
|
|
||||||
|
|
||||||
} // namespace lt::input
|
|
41
modules/input/public/events/char.hpp
Normal file
41
modules/input/public/events/char.hpp
Normal 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
|
56
modules/input/public/events/event.hpp
Normal file
56
modules/input/public/events/event.hpp
Normal 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
|
107
modules/input/public/events/keyboard.hpp
Normal file
107
modules/input/public/events/keyboard.hpp
Normal 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
|
151
modules/input/public/events/mouse.hpp
Normal file
151
modules/input/public/events/mouse.hpp
Normal 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
|
133
modules/input/public/events/window.hpp
Normal file
133
modules/input/public/events/window.hpp
Normal 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
|
|
@ -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
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -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,25 +21,19 @@ 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)
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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
|
|
|
@ -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>(),
|
|
||||||
} };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -1,5 +0,0 @@
|
||||||
#include <renderer/vk/instance.hpp>
|
|
||||||
|
|
||||||
namespace lt::vk {
|
|
||||||
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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 {};
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -1,9 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
namespace lt::renderer {
|
|
||||||
|
|
||||||
class Context
|
|
||||||
{
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace lt::renderer
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
|
@ -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)
|
|
|
@ -1 +0,0 @@
|
||||||
1484824981238913982498139812098u24
|
|
|
@ -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 ®istry, 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 ®istry, 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 ®istry, 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
|
|
|
@ -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 ®istry)
|
|
||||||
{
|
|
||||||
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 ®istry)
|
|
||||||
{
|
|
||||||
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
|
|
|
@ -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 = [] {
|
|
||||||
};
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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 ®istry, entt::entity entity);
|
|
||||||
|
|
||||||
void on_surface_update(entt::registry ®istry, entt::entity entity);
|
|
||||||
|
|
||||||
void on_surface_destroy(entt::registry ®istry, entt::entity entity);
|
|
||||||
|
|
||||||
void ensure_component_sanity(const SurfaceComponent &component);
|
|
||||||
|
|
||||||
Ref<ecs::Registry> m_registry;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace lt::surface
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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" };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
36
modules/test/private/test.tests.cpp
Normal file
36
modules/test/private/test.tests.cpp
Normal 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" } = [] {
|
||||||
|
};
|
||||||
|
};
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
||||||
|
|
|
@ -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
Loading…
Add table
Reference in a new issue