Compare commits
45 commits
ci/build_m
...
main
Author | SHA1 | Date | |
---|---|---|---|
4a7a220af8 | |||
a43243ca3f | |||
5d30a56e22 | |||
51e044065a | |||
b0caeded2a | |||
585d37b31b | |||
4cd258bcb6 | |||
052ac6dd5b | |||
b005857c31 | |||
9389dfe7fb | |||
40503239df | |||
552602f0af | |||
76fc5dd572 | |||
cd571d4a9d | |||
813e8a3a3a | |||
d6aa5fc91d | |||
bd2f74b120 | |||
af4ce09838 | |||
f7591a23f4 | |||
51990599a7 | |||
459b3b961d | |||
d58f8994aa | |||
c57e5a56ac | |||
ea8986b764 | |||
e36991e6de | |||
60ad7cdc70 | |||
638a009047 | |||
a102db0699 | |||
2b96a85b62 | |||
d9229ad912 | |||
6a814bd177 | |||
2d019878a5 | |||
b0ad9ff964 | |||
22c62bf5f9 | |||
d83e269432 | |||
65f0d3bb73 | |||
0b94aaffa7 | |||
026f97ad0b | |||
8720fdcebf | |||
46505a6c24 | |||
754b6361ad | |||
bf485e354a | |||
688c88f255 | |||
3998c4127a | |||
798732632a |
129 changed files with 3482 additions and 2082 deletions
172
.drone.yml
172
.drone.yml
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: exec
|
type: exec
|
||||||
name: macrohard doors
|
name: amd64 — msvc
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- main
|
- main
|
||||||
|
@ -9,107 +9,62 @@ platform:
|
||||||
os: windows
|
os: windows
|
||||||
arch: amd64
|
arch: amd64
|
||||||
|
|
||||||
clone:
|
|
||||||
disable: true
|
|
||||||
steps:
|
steps:
|
||||||
- name: clone
|
- name: unit tests
|
||||||
environment:
|
shell: powershell
|
||||||
HOME: C:\Users\username\
|
|
||||||
- name: greeting
|
|
||||||
commands:
|
commands:
|
||||||
- echo 'Hello from Windows 10! :D'
|
- ./tools/ci/amd64/msvc/unit_tests.ps1
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: docker
|
type: docker
|
||||||
name: unit tests
|
name: amd64 — gcc
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- main
|
- main
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: unit tests
|
- name: unit tests
|
||||||
image: unit_tests:latest
|
image: amd64_gcc_unit_tests:latest
|
||||||
pull: if-not-exists
|
pull: if-not-exists
|
||||||
commands:
|
commands:
|
||||||
- |
|
- ./tools/ci/amd64/gcc/unit_tests.sh
|
||||||
set -e
|
|
||||||
|
|
||||||
conan build . \
|
|
||||||
-c tools.system.package_manager:mode=install \
|
|
||||||
-c tools.cmake.cmaketoolchain:generator=Ninja \
|
|
||||||
-s build_type=Release \
|
|
||||||
-o enable_static_analysis=False \
|
|
||||||
-o enable_tests=True \
|
|
||||||
-o use_mold=True \
|
|
||||||
--build=missing
|
|
||||||
|
|
||||||
for test in $(find ./build -type f -name '*_tests' -executable); do
|
|
||||||
echo "Running $test"
|
|
||||||
"$test"
|
|
||||||
done
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: valgrind
|
|
||||||
trigger:
|
|
||||||
branch:
|
|
||||||
- main
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: valgrind
|
- name: valgrind
|
||||||
image: valgrind:latest
|
image: amd64_gcc_valgrind:latest
|
||||||
pull: if-not-exists
|
pull: if-not-exists
|
||||||
commands:
|
commands:
|
||||||
- |
|
- ./tools/ci/amd64/gcc/valgrind.sh
|
||||||
set -e
|
|
||||||
|
|
||||||
conan build . \
|
|
||||||
-c tools.system.package_manager:mode=install \
|
|
||||||
-c tools.cmake.cmaketoolchain:generator=Ninja \
|
|
||||||
-s build_type=Release \
|
|
||||||
-o enable_static_analysis=False \
|
|
||||||
-o enable_tests=True \
|
|
||||||
-o use_mold=True \
|
|
||||||
--build=missing
|
|
||||||
|
|
||||||
find ./build -type f -name "*_tests" -executable | xargs -I {} bash -c 'valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose --error-exitcode=255 {}' || exit 1
|
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: docker
|
type: docker
|
||||||
name: leak sanitizer
|
name: amd64 — clang
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- 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: leak_sanitizer:latest
|
image: amd64_clang_lsan:latest
|
||||||
pull: if-not-exists
|
pull: if-not-exists
|
||||||
commands:
|
commands:
|
||||||
- |
|
- ./tools/ci/amd64/clang/lsan.sh
|
||||||
set -e
|
|
||||||
|
|
||||||
conan build . \
|
- name: memory sanitizer
|
||||||
-c tools.system.package_manager:mode=install \
|
image: amd64_clang_msan:latest
|
||||||
-c tools.cmake.cmaketoolchain:generator=Ninja \
|
pull: if-not-exists
|
||||||
-c tools.build:cxxflags='["-g", "-fno-omit-frame-pointer", "-nostdinc++", "-isystem", "/libcxx_lsan/include/c++/v1/", "-fsanitize=leak"]' \
|
commands:
|
||||||
-c tools.build:sharedlinkflags='["-L/libcxx_lsan/lib", "-Wl,-rpath,/libcxx_lsan/lib", "-lc++", "-lc++abi", "-fsanitize=leak"]' \
|
- ./tools/ci/amd64/clang/msan.sh
|
||||||
-c tools.build:exelinkflags='["-L/libcxx_lsan/lib", "-Wl,-rpath,/libcxx_lsan/lib", "-lc++", "-lc++abi", "-fsanitize=leak"]' \
|
|
||||||
-c tools.info.package_id:confs='["tools.build:cxxflags","tools.build:sharedlinkflags","tools.build:exelinkflags"]' \
|
|
||||||
-c tools.build:compiler_executables='{"c": "clang", "cpp": "clang++"}' \
|
|
||||||
-s build_type=Release \
|
|
||||||
-s compiler=clang \
|
|
||||||
-s compiler.version=20 \
|
|
||||||
-s compiler.libcxx=libc++ \
|
|
||||||
-o use_mold=True \
|
|
||||||
--build=missing
|
|
||||||
for test in $(find ./build -type f -name '*_tests' -executable); do
|
|
||||||
echo "Running $test"
|
|
||||||
"$test"
|
|
||||||
done
|
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
|
@ -120,49 +75,66 @@ trigger:
|
||||||
- main
|
- main
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: static_analysis
|
- name: clang tidy
|
||||||
image: static_analysis:latest
|
image: clang_tidy:latest
|
||||||
pull: if-not-exists
|
pull: if-not-exists
|
||||||
privileged: true
|
privileged: true
|
||||||
commands:
|
commands:
|
||||||
- |
|
- ./tools/ci/static_analysis/clang_tidy.sh
|
||||||
conan build . \
|
|
||||||
-c tools.system.package_manager:mode=install \
|
- name: clang format
|
||||||
-c tools.cmake.cmaketoolchain:generator=Ninja \
|
image: clang_format:latest
|
||||||
-s build_type=Release \
|
pull: if-not-exists
|
||||||
-o enable_static_analysis=True \
|
commands:
|
||||||
-o enable_tests=True \
|
- ./tools/ci/static_analysis/clang_format.sh
|
||||||
-o use_mold=True \
|
|
||||||
--build=missing
|
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: docker
|
type: docker
|
||||||
name: clang format
|
name: documentation — development
|
||||||
|
node:
|
||||||
|
environment: ryali
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- main
|
- main
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: clang format
|
- name: build and deploy
|
||||||
image: clang_format:latest
|
image: documentation:latest
|
||||||
pull: if-not-exists
|
pull: if-not-exists
|
||||||
commands:
|
commands:
|
||||||
- |
|
- pwd
|
||||||
set -e
|
- cd docs
|
||||||
clang-format --version
|
- mkdir generated
|
||||||
has_fomatting_issues=0
|
- touch generated/changelogs.rst
|
||||||
|
- touch generated/api.rst
|
||||||
|
- sphinx-build -M html . .
|
||||||
|
|
||||||
for file in $(find ./modules -name '*.?pp'); do
|
- rm -rf /light_docs_dev/*
|
||||||
echo "Checking format for $file"
|
- mv ./html/* /light_docs_dev/
|
||||||
if ! clang-format --dry-run --Werror "$file"; then
|
|
||||||
echo "❌ Formatting issue detected in $file"
|
|
||||||
has_fomatting_issues=1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$has_fomatting_issues" -eq 0 ]; then
|
---
|
||||||
echo "✅ All files are properly formatted! Well done! ^~^"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit ${has_fomatting_issues}
|
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,16 +1,43 @@
|
||||||
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(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/functions.cmake)
|
include(CheckCXXSourceCompiles)
|
||||||
include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/definitions.cmake)
|
include(${CMAKE_DIR}/functions.cmake)
|
||||||
include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/dependencies.cmake)
|
include(${CMAKE_DIR}/definitions.cmake)
|
||||||
|
include(${CMAKE_DIR}/dependencies.cmake)
|
||||||
|
|
||||||
add_option(ENABLE_STATIC_ANALYSIS "Performs static analysis via clang-tidy and fails build on failing checks")
|
add_option(ENABLE_UNIT_TESTS "Enables the building of the unit test modules")
|
||||||
|
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_TESTS "Enables the building of the test modules")
|
add_option(ENABLE_LLVM_COVERAGE "Enables the code coverage instrumentation for clang")
|
||||||
|
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,2 +1,6 @@
|
||||||
# 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,14 +11,18 @@ class LightRecipe(ConanFile):
|
||||||
generators = "CMakeDeps"
|
generators = "CMakeDeps"
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
"enable_tests": [True, False],
|
"enable_unit_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_tests": True,
|
"enable_unit_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,
|
||||||
|
@ -44,8 +48,10 @@ 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
Normal file
3
docs/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
_build/
|
||||||
|
generated/
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
Asset Management
|
Asset Management
|
||||||
===================================================================================================
|
===================================================================================================
|
||||||
Layout
|
|
||||||
|
On Disk (file) Layout
|
||||||
---------------------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
.. code-block:: md
|
||||||
|
|
||||||
{version} | 4 bytes, ie. uint32_t
|
{version} | 4 bytes, ie. uint32_t
|
||||||
{general metadata} | sizeof(AssetMetadata)
|
{general metadata} | sizeof(AssetMetadata)
|
||||||
{specialized metadata} | sizeof(XXXAssetMetadata), eg. TextureAssetMetadata
|
{specialized metadata} | sizeof(XXXAssetMetadata), eg. TextureAssetMetadata
|
||||||
|
@ -12,6 +16,9 @@ Layout
|
||||||
|
|
||||||
Sections
|
Sections
|
||||||
---------------------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
.. code-block:: md
|
||||||
|
|
||||||
version -> The version of the asset for forward compatibility
|
version -> The version of the asset for forward compatibility
|
||||||
general metadata -> Common asset metadata such as file-path, asset-type, creator, etc.
|
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.
|
specialized metadata -> Metadata specific to the asset, eg. texture dimensions for Textures.
|
||||||
|
@ -21,42 +28,45 @@ blob_0...n data -> The actual data, packed and compressed to be reacdy for
|
||||||
|
|
||||||
Loading
|
Loading
|
||||||
---------------------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------------------
|
||||||
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
|
Loading pre-baked asset files (like .png files) for baking:
|
||||||
|
|
||||||
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,4 +1,5 @@
|
||||||
Resource Management
|
.. architecture/resources
|
||||||
|
|
||||||
|
Resource Management
|
||||||
===================================================================================================
|
===================================================================================================
|
||||||
|
|
||||||
|
|
27
docs/conf.py
Normal file
27
docs/conf.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# 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']
|
68
docs/generate_changelog.py
Normal file
68
docs/generate_changelog.py
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
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')
|
10
docs/guidelines/conventions.rst
Normal file
10
docs/guidelines/conventions.rst
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
.. 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
|
||||||
|
--------------------
|
147
docs/guidelines/development.rst
Normal file
147
docs/guidelines/development.rst
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
.. 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>``
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
7
docs/guidelines/philosophy.rst
Normal file
7
docs/guidelines/philosophy.rst
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
.. guidelines/philosophy
|
||||||
|
|
||||||
|
Philosophy
|
||||||
|
===================================================================================================
|
||||||
|
|
||||||
|
| **A theory or attitude that acts as a guiding principle for behaviour.**
|
||||||
|
| --- Oxford Languages
|
|
@ -1,6 +1,32 @@
|
||||||
A bleeding-edge, cross-platform, cross-graphics-api, minimal-dependencies modern game-engine.
|
.. light documentation
|
||||||
|
|
||||||
|
.. 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
|
|
||||||
|
|
4
docs/light/features.rst
Normal file
4
docs/light/features.rst
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.. light/features
|
||||||
|
|
||||||
|
Features
|
||||||
|
===================================================================================================
|
4
docs/light/showcase.rst
Normal file
4
docs/light/showcase.rst
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.. light/demos
|
||||||
|
|
||||||
|
Showcase
|
||||||
|
===================================================================================================
|
35
docs/make.bat
Normal file
35
docs/make.bat
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
@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,22 +4,21 @@ 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(./window)
|
add_subdirectory(./surface)
|
||||||
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,21 +1,2 @@
|
||||||
add_library_module(app
|
add_library_module(app application.cpp)
|
||||||
application.cpp
|
target_link_libraries(app PRIVATE lt_debug)
|
||||||
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,203 +1,48 @@
|
||||||
#include <app/application.hpp>
|
#include <app/application.hpp>
|
||||||
#include <app/layer.hpp>
|
#include <app/system.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 {
|
namespace lt::app {
|
||||||
|
|
||||||
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()
|
||||||
{
|
{
|
||||||
m_window->set_visibility(true);
|
while (true)
|
||||||
|
|
||||||
while (!m_window->is_closed())
|
|
||||||
{
|
{
|
||||||
update_layers();
|
for (auto &system : m_systems)
|
||||||
render_layers();
|
|
||||||
render_user_interface();
|
|
||||||
poll_events();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::quit()
|
|
||||||
{
|
{
|
||||||
s_instance->m_window->close();
|
if (system->tick())
|
||||||
}
|
|
||||||
|
|
||||||
void Application::update_layers()
|
|
||||||
{
|
|
||||||
for (auto &it : *m_layer_stack)
|
|
||||||
{
|
|
||||||
// narrowing double -> float
|
|
||||||
it->on_update(static_cast<float>(m_timer.elapsed_time().count()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(Light): each layer should have their own "delta time"
|
|
||||||
m_timer.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::render_layers()
|
|
||||||
{
|
|
||||||
m_renderer->begin_frame();
|
|
||||||
|
|
||||||
for (auto &it : *m_layer_stack)
|
|
||||||
{
|
|
||||||
it->on_render();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_renderer->end_frame();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::render_user_interface()
|
|
||||||
{
|
|
||||||
m_user_interface->begin();
|
|
||||||
|
|
||||||
for (auto &it : *m_layer_stack)
|
|
||||||
{
|
|
||||||
it->on_user_interface_update();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_user_interface->end();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::poll_events()
|
|
||||||
{
|
|
||||||
m_window->poll_events();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::on_event(const Event &event)
|
|
||||||
{
|
|
||||||
// window
|
|
||||||
if (event.has_category(WindowEventCategory))
|
|
||||||
{
|
|
||||||
m_window->on_event(event);
|
|
||||||
|
|
||||||
if (event.get_event_type() == EventType::WindowResized)
|
|
||||||
{
|
|
||||||
m_renderer->on_window_resize(dynamic_cast<const WindowResizedEvent &>(event));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// input
|
|
||||||
if (event.has_category(InputEventCategory))
|
|
||||||
{
|
|
||||||
Input::instance().on_event(event);
|
|
||||||
|
|
||||||
if (!Input::instance().is_receiving_game_events())
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &it : std::ranges::reverse_view(*m_layer_stack))
|
for (auto &system : m_systems_to_be_registered)
|
||||||
{
|
{
|
||||||
if (it->on_event(event))
|
m_systems.emplace_back(system)->on_register();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &system : m_systems_to_be_unregistered)
|
||||||
|
{
|
||||||
|
m_systems.erase(
|
||||||
|
std::remove(m_systems.begin(), m_systems.end(), system),
|
||||||
|
m_systems.end()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_systems.empty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] auto Application::sanity_check() const -> bool
|
void Application::register_system(Ref<app::ISystem> system)
|
||||||
{
|
{
|
||||||
log_inf("Checking application sanity...");
|
m_systems.emplace_back(std::move(system));
|
||||||
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::log_debug_data() const
|
void Application::unregister_system(Ref<app::ISystem> system)
|
||||||
{
|
{
|
||||||
log_inf("Platform::");
|
m_systems_to_be_unregistered.emplace_back(std::move(system));
|
||||||
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
|
} // namespace lt::app
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
#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
|
|
|
@ -1,18 +0,0 @@
|
||||||
#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,18 +1,15 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <time/timer.hpp>
|
namespace lt::app {
|
||||||
|
|
||||||
namespace lt {
|
class ISystem;
|
||||||
|
|
||||||
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:
|
||||||
|
@ -24,54 +21,24 @@ public:
|
||||||
|
|
||||||
auto operator=(Application &&) -> Application & = delete;
|
auto operator=(Application &&) -> Application & = delete;
|
||||||
|
|
||||||
virtual ~Application();
|
virtual ~Application() = default;
|
||||||
|
|
||||||
[[nodiscard]] auto sanity_check() const -> bool;
|
|
||||||
|
|
||||||
void game_loop();
|
void game_loop();
|
||||||
|
|
||||||
[[nodiscard]] auto get_window() -> Window &
|
void register_system(Ref<app::ISystem> system);
|
||||||
{
|
|
||||||
return *m_window;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] auto get_layer_stack() -> LayerStack &
|
void unregister_system(Ref<app::ISystem> system);
|
||||||
{
|
|
||||||
return *m_layer_stack;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void quit();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Application();
|
Application() = default;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void update_layers();
|
std::vector<Ref<app::ISystem>> m_systems;
|
||||||
|
|
||||||
void render_layers();
|
std::vector<Ref<app::ISystem>> m_systems_to_be_unregistered;
|
||||||
|
|
||||||
void render_user_interface();
|
std::vector<Ref<app::ISystem>> m_systems_to_be_registered;
|
||||||
|
|
||||||
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
|
} // namespace lt::app
|
||||||
|
|
|
@ -2,21 +2,21 @@
|
||||||
|
|
||||||
#include <app/application.hpp>
|
#include <app/application.hpp>
|
||||||
|
|
||||||
int main(int argc, char *argv[]) // NOLINT
|
auto main(int argc, char *argv[]) -> int32_t
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
std::ignore = argc;
|
std::ignore = argc;
|
||||||
std::ignore = argv;
|
std::ignore = argv;
|
||||||
|
|
||||||
auto application = lt::Scope<lt::Application> {};
|
auto application = lt::Scope<lt::app::Application> {};
|
||||||
|
|
||||||
application = lt::create_application();
|
application = lt::app::create_application();
|
||||||
|
if (!application)
|
||||||
lt::ensure(application, "Failed to create application");
|
{
|
||||||
lt::ensure(application->sanity_check(), "Failed to verify the sanity of the application");
|
throw std::runtime_error { "Failed to create application\n" };
|
||||||
|
}
|
||||||
|
|
||||||
application->game_loop();
|
application->game_loop();
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
catch (const std::exception &exp)
|
catch (const std::exception &exp)
|
||||||
|
|
|
@ -1,116 +0,0 @@
|
||||||
#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
|
|
|
@ -1,50 +0,0 @@
|
||||||
#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
|
|
27
modules/app/public/system.hpp
Normal file
27
modules/app/public/system.hpp
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#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 renderer
|
PRIVATE logger
|
||||||
)
|
)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
struct FailedAssertion: std::exception
|
template<typename Expression_T, typename... Args_T>
|
||||||
|
struct ensure
|
||||||
{
|
{
|
||||||
FailedAssertion(const char *file, int line)
|
ensure(
|
||||||
|
Expression_T expression,
|
||||||
|
std::format_string<Args_T...> fmt,
|
||||||
|
Args_T &&...args,
|
||||||
|
const std::source_location &location = std::source_location::current()
|
||||||
|
)
|
||||||
{
|
{
|
||||||
log_crt("Assertion failed in: {} (line {})", file, line);
|
if (!static_cast<bool>(expression))
|
||||||
|
{
|
||||||
|
throw std::runtime_error { std::format(
|
||||||
|
"exception: {}\nlocation: {}:{}",
|
||||||
|
std::format(fmt, std::forward<Args_T>(args)...),
|
||||||
|
location.file_name(),
|
||||||
|
location.line()
|
||||||
|
) };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
template<typename Expression_T, typename... Args>
|
template<typename Expression_T, typename... Args_T>
|
||||||
constexpr void ensure(Expression_T &&expression, std::format_string<Args...> fmt, Args &&...args)
|
ensure(Expression_T, std::format_string<Args_T...>, Args_T &&...)
|
||||||
{
|
-> 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,5 +1,4 @@
|
||||||
add_library_module(ecs entity.cpp scene.cpp uuid.cpp serializer.cpp)
|
add_library_module(ecs entity.cpp scene.cpp uuid.cpp )
|
||||||
target_link_libraries(ecs
|
target_link_libraries(ecs
|
||||||
PUBLIC logger lt_debug EnTT::EnTT renderer input camera
|
PUBLIC logger lt_debug EnTT::EnTT input camera math
|
||||||
PRIVATE yaml-cpp::yaml-cpp asset_manager
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,73 +1,9 @@
|
||||||
#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);
|
||||||
|
@ -75,21 +11,6 @@ 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,6 +3,7 @@
|
||||||
#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 {
|
||||||
|
|
||||||
|
@ -12,11 +13,17 @@ class Framebuffer;
|
||||||
class Scene
|
class Scene
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void on_create();
|
template<typename... T>
|
||||||
|
auto group()
|
||||||
|
{
|
||||||
|
return m_registry.group(entt::get<T...>);
|
||||||
|
}
|
||||||
|
|
||||||
void on_update(float deltaTime);
|
template<typename T>
|
||||||
|
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,
|
||||||
|
@ -25,6 +32,12 @@ 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;
|
||||||
|
|
||||||
|
@ -41,4 +54,12 @@ 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 math imgui::imgui logger)
|
target_link_libraries(input PUBLIC surface math imgui::imgui logger)
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
#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 {
|
||||||
|
|
5
modules/input/private/system.cpp
Normal file
5
modules/input/private/system.cpp
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#include <input/system.hpp>
|
||||||
|
|
||||||
|
namespace lt::input {
|
||||||
|
|
||||||
|
} // namespace lt::input
|
|
@ -1,41 +0,0 @@
|
||||||
#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
|
|
|
@ -1,56 +0,0 @@
|
||||||
#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
|
|
|
@ -1,107 +0,0 @@
|
||||||
#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
|
|
|
@ -1,151 +0,0 @@
|
||||||
#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
|
|
|
@ -1,133 +0,0 @@
|
||||||
#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
|
|
64
modules/input/public/system.hpp
Normal file
64
modules/input/public/system.hpp
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
#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,7 +4,6 @@
|
||||||
|
|
||||||
namespace lt::math {
|
namespace lt::math {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* let...
|
* let...
|
||||||
* a = h / w ==> for aspect ratio adjustment
|
* a = h / w ==> for aspect ratio adjustment
|
||||||
|
|
|
@ -1,16 +1,11 @@
|
||||||
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
|
||||||
PUBLIC app
|
INTERFACE
|
||||||
PUBLIC opengl::opengl
|
app
|
||||||
PUBLIC ui
|
opengl::opengl
|
||||||
PUBLIC imgui
|
surface
|
||||||
PUBLIC input
|
|
||||||
)
|
)
|
||||||
|
|
||||||
add_test_module(libmirror
|
add_test_module(libmirror
|
||||||
|
|
|
@ -1,30 +1,77 @@
|
||||||
#include <app/application.hpp>
|
#include <app/application.hpp>
|
||||||
#include <app/entrypoint.hpp>
|
#include <app/entrypoint.hpp>
|
||||||
#include <app/layer_stack.hpp>
|
#include <app/system.hpp>
|
||||||
#include <math/vec2.hpp>
|
#include <math/vec2.hpp>
|
||||||
#include <mirror/layers/editor_layer.hpp>
|
#include <surface/system.hpp>
|
||||||
#include <window/window.hpp>
|
|
||||||
|
|
||||||
namespace lt {
|
namespace lt {
|
||||||
|
|
||||||
class Mirror: public Application
|
template<class... Ts>
|
||||||
|
struct overloads: Ts...
|
||||||
|
{
|
||||||
|
using Ts::operator()...;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Mirror: public app::Application
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Mirror()
|
Mirror()
|
||||||
{
|
{
|
||||||
get_window().set_properties(
|
m_editor_registry = create_ref<ecs::Registry>();
|
||||||
Window::Properties {
|
|
||||||
.title = "Mirror",
|
|
||||||
.size = math::uvec2(1280u, 720u),
|
|
||||||
.vsync = true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
get_layer_stack().emplace_layer<EditorLayer>("MirrorLayer");
|
setup_window_system();
|
||||||
|
register_systems();
|
||||||
|
|
||||||
|
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; },
|
||||||
};
|
};
|
||||||
|
|
||||||
auto create_application() -> Scope<Application>
|
return std::visit(visitor, event);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
{
|
{
|
||||||
return create_scope<Mirror>();
|
return create_scope<Mirror>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,14 +82,15 @@ void AssetBrowserPanel::on_user_interface_update()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Button
|
// Button
|
||||||
|
const auto path_str = path.string();
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
ImGui::PushID(path.c_str());
|
ImGui::PushID(path_str.c_str());
|
||||||
switch (asset_type)
|
switch (asset_type)
|
||||||
{
|
{
|
||||||
// Directory
|
// Directory
|
||||||
case AssetType::directory:
|
case AssetType::directory:
|
||||||
if (ImGui::ImageButton(
|
if (ImGui::ImageButton(
|
||||||
path.c_str(),
|
path_str.c_str(),
|
||||||
m_directory_texture->get_texture(),
|
m_directory_texture->get_texture(),
|
||||||
ImVec2(m_file_size, m_file_size)
|
ImVec2(m_file_size, m_file_size)
|
||||||
))
|
))
|
||||||
|
@ -101,7 +102,7 @@ void AssetBrowserPanel::on_user_interface_update()
|
||||||
// Scene
|
// Scene
|
||||||
case AssetType::scene:
|
case AssetType::scene:
|
||||||
if (ImGui::ImageButton(
|
if (ImGui::ImageButton(
|
||||||
path.c_str(),
|
path_str.c_str(),
|
||||||
m_scene_texture->get_texture(),
|
m_scene_texture->get_texture(),
|
||||||
ImVec2(m_file_size, m_file_size)
|
ImVec2(m_file_size, m_file_size)
|
||||||
))
|
))
|
||||||
|
@ -115,7 +116,7 @@ void AssetBrowserPanel::on_user_interface_update()
|
||||||
// Image
|
// Image
|
||||||
case AssetType::image:
|
case AssetType::image:
|
||||||
if (ImGui::ImageButton(
|
if (ImGui::ImageButton(
|
||||||
path.c_str(),
|
path_str.c_str(),
|
||||||
m_image_texture->get_texture(),
|
m_image_texture->get_texture(),
|
||||||
ImVec2(m_file_size, m_file_size)
|
ImVec2(m_file_size, m_file_size)
|
||||||
))
|
))
|
||||||
|
@ -126,7 +127,7 @@ void AssetBrowserPanel::on_user_interface_update()
|
||||||
// Text
|
// Text
|
||||||
case AssetType::text:
|
case AssetType::text:
|
||||||
if (ImGui::ImageButton(
|
if (ImGui::ImageButton(
|
||||||
path.c_str(),
|
path_str.c_str(),
|
||||||
m_text_texture->get_texture(),
|
m_text_texture->get_texture(),
|
||||||
ImVec2(m_file_size, m_file_size)
|
ImVec2(m_file_size, m_file_size)
|
||||||
))
|
))
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
add_library_module(renderer
|
add_library_module(renderer
|
||||||
|
system.cpp
|
||||||
blender.cpp
|
blender.cpp
|
||||||
buffers.cpp
|
buffers.cpp
|
||||||
framebuffer.cpp
|
framebuffer.cpp
|
||||||
|
@ -19,6 +20,7 @@ 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(
|
||||||
|
@ -34,4 +36,15 @@ 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,12 +4,13 @@
|
||||||
#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 {
|
||||||
|
|
||||||
|
@ -21,20 +22,26 @@ Renderer::Renderer(
|
||||||
CreateInfo create_info
|
CreateInfo create_info
|
||||||
)
|
)
|
||||||
: m_quad_renderer(
|
: m_quad_renderer(
|
||||||
|
create_scope<QuadRendererProgram>(
|
||||||
LT_MAX_QUAD_RENDERER_VERTICES,
|
LT_MAX_QUAD_RENDERER_VERTICES,
|
||||||
shared_context,
|
shared_context,
|
||||||
std::move(create_info.quad_renderer_shader)
|
std::move(create_info.quad_renderer_shader)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
, m_texture_renderer(
|
, m_texture_renderer(
|
||||||
|
create_scope<TextureRendererProgram>(
|
||||||
LT_MAX_TEXTURE_RENDERER_VERTICES,
|
LT_MAX_TEXTURE_RENDERER_VERTICES,
|
||||||
shared_context,
|
shared_context,
|
||||||
std::move(create_info.texture_renderer_shader)
|
std::move(create_info.texture_renderer_shader)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
, m_tinted_texture_renderer(
|
, m_tinted_texture_renderer(
|
||||||
|
create_scope<TintedTextureRendererProgram>(
|
||||||
LT_MAX_TINTED_TEXTURE_RENDERER_VERTICES,
|
LT_MAX_TINTED_TEXTURE_RENDERER_VERTICES,
|
||||||
shared_context,
|
shared_context,
|
||||||
std::move(create_info.tinted_texture_renderer_shader)
|
std::move(create_info.tinted_texture_renderer_shader)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
, m_view_projection_buffer(nullptr)
|
, m_view_projection_buffer(nullptr)
|
||||||
, m_render_command(nullptr)
|
, m_render_command(nullptr)
|
||||||
, m_blender(nullptr)
|
, m_blender(nullptr)
|
||||||
|
@ -55,6 +62,10 @@ 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,
|
||||||
|
@ -114,7 +125,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
|
||||||
|
@ -134,7 +145,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();
|
||||||
|
@ -147,7 +158,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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -168,7 +179,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();
|
||||||
|
@ -185,7 +196,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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -209,7 +220,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();
|
||||||
|
@ -256,66 +267,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
|
||||||
|
|
18
modules/renderer/private/system.cpp
Normal file
18
modules/renderer/private/system.cpp
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#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
|
50
modules/renderer/private/system.test.cpp
Normal file
50
modules/renderer/private/system.test.cpp
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
#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>(),
|
||||||
|
} };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
434
modules/renderer/private/vk/backend.cpp
Normal file
434
modules/renderer/private/vk/backend.cpp
Normal file
|
@ -0,0 +1,434 @@
|
||||||
|
#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
|
135
modules/renderer/private/vk/backend.hpp
Normal file
135
modules/renderer/private/vk/backend.hpp
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
#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
|
0
modules/renderer/private/vk/device.test.cpp
Normal file
0
modules/renderer/private/vk/device.test.cpp
Normal file
5
modules/renderer/private/vk/instance.cpp
Normal file
5
modules/renderer/private/vk/instance.cpp
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#include <renderer/vk/instance.hpp>
|
||||||
|
|
||||||
|
namespace lt::vk {
|
||||||
|
|
||||||
|
}
|
19
modules/renderer/private/vk/instance.hpp
Normal file
19
modules/renderer/private/vk/instance.hpp
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#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
|
8
modules/renderer/private/vk/instance.test.cpp
Normal file
8
modules/renderer/private/vk/instance.test.cpp
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#include <renderer/vk/instance.hpp>
|
||||||
|
#include <test/test.hpp>
|
||||||
|
|
||||||
|
lt::test::Suite raii = [] {
|
||||||
|
lt::test::Case { "raii" } = [] {
|
||||||
|
auto instance = lt::vk::Instance {};
|
||||||
|
};
|
||||||
|
};
|
29
modules/renderer/public/backend.hpp
Normal file
29
modules/renderer/public/backend.hpp
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#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
|
12
modules/renderer/public/components.hpp
Normal file
12
modules/renderer/public/components.hpp
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#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
|
9
modules/renderer/public/context.hpp
Normal file
9
modules/renderer/public/context.hpp
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace lt::renderer {
|
||||||
|
|
||||||
|
class Context
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace lt::renderer
|
|
@ -1,6 +1,5 @@
|
||||||
#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>
|
||||||
|
@ -8,11 +7,6 @@
|
||||||
#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)
|
||||||
|
@ -32,6 +26,10 @@ class Camera;
|
||||||
class WindowResizedEvent;
|
class WindowResizedEvent;
|
||||||
class Shader;
|
class Shader;
|
||||||
|
|
||||||
|
class TintedTextureRendererProgram;
|
||||||
|
class QuadRendererProgram;
|
||||||
|
class TextureRendererProgram;
|
||||||
|
|
||||||
class Renderer
|
class Renderer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -107,6 +105,8 @@ 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;
|
||||||
|
|
||||||
QuadRendererProgram m_quad_renderer;
|
Scope<QuadRendererProgram> m_quad_renderer;
|
||||||
|
|
||||||
TextureRendererProgram m_texture_renderer;
|
Scope<TextureRendererProgram> m_texture_renderer;
|
||||||
|
|
||||||
TintedTextureRendererProgram m_tinted_texture_renderer;
|
Scope<TintedTextureRendererProgram> m_tinted_texture_renderer;
|
||||||
|
|
||||||
Scope<ConstantBuffer> m_view_projection_buffer;
|
Scope<ConstantBuffer> m_view_projection_buffer;
|
||||||
|
|
||||||
|
|
60
modules/renderer/public/system.hpp
Normal file
60
modules/renderer/public/system.hpp
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
#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
|
16
modules/surface/CMakeLists.txt
Normal file
16
modules/surface/CMakeLists.txt
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
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
modules/surface/private/fuzz_seeds/a
Normal file
1
modules/surface/private/fuzz_seeds/a
Normal file
|
@ -0,0 +1 @@
|
||||||
|
1484824981238913982498139812098u24
|
292
modules/surface/private/linux/system.cpp
Normal file
292
modules/surface/private/linux/system.cpp
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
#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
|
103
modules/surface/private/system.fuzz.cpp
Normal file
103
modules/surface/private/system.fuzz.cpp
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
#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
|
198
modules/surface/private/system.test.cpp
Normal file
198
modules/surface/private/system.test.cpp
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
#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 = [] {
|
||||||
|
};
|
119
modules/surface/public/components.hpp
Normal file
119
modules/surface/public/components.hpp
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
#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
|
91
modules/surface/public/events/keyboard.hpp
Normal file
91
modules/surface/public/events/keyboard.hpp
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
#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
|
103
modules/surface/public/events/mouse.hpp
Normal file
103
modules/surface/public/events/mouse.hpp
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
#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
|
81
modules/surface/public/events/surface.hpp
Normal file
81
modules/surface/public/events/surface.hpp
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
#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
|
58
modules/surface/public/system.hpp
Normal file
58
modules/surface/public/system.hpp
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
#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,2 +1,4 @@
|
||||||
add_library_module(test test.cpp entrypoint.cpp)
|
add_library_module(test test.cpp entrypoint.cpp)
|
||||||
add_test_module(test test.tests.cpp)
|
add_library_module(fuzz_test test.cpp fuzz.cpp)
|
||||||
|
|
||||||
|
add_test_module(test test.test.cpp)
|
||||||
|
|
24
modules/test/private/fuzz.cpp
Normal file
24
modules/test/private/fuzz.cpp
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#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);
|
||||||
|
}
|
199
modules/test/private/test.test.cpp
Normal file
199
modules/test/private/test.test.cpp
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
#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" };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,36 +0,0 @@
|
||||||
#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,20 +7,78 @@
|
||||||
namespace lt::test {
|
namespace lt::test {
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
concept Printable = requires(std::ostream &os, T t) {
|
concept Printable = requires(std::ostream &stream, T value) {
|
||||||
{ os << t } -> std::same_as<std::ostream &>;
|
{ stream << value } -> 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(
|
||||||
|
@ -102,6 +160,27 @@ 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,
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue