From 8659041bbc19f22130b11e93b5dc1fede39cb35c Mon Sep 17 00:00:00 2001 From: TheLavaBlock <36247472+TheLavaBlock@users.noreply.github.com> Date: Wed, 18 Sep 2019 13:36:28 +0200 Subject: [PATCH] v0.4.2 --- .appveyor.yml | 20 + .gitignore | 1 + .gitmodules | 52 ++ .travis.yml | 42 ++ CMakeLists.txt | 254 +++++++++ LICENSE | 21 + README.md | 345 +++++++++++- ext/Vulkan-Headers | 1 + ext/VulkanMemoryAllocator | 1 + ext/argh | 1 + ext/assimp | 1 + ext/better-enums | 1 + ext/bitmap | 1 + ext/glfw | 1 + ext/gli | 1 + ext/glm | 1 + ext/json | 1 + ext/physfs | 1 + ext/selene | 1 + ext/spdlog | 1 + ext/stb | 1 + ext/tinyfiledialogs | 1 + ext/tinyobjloader | 1 + ext/volk | 1 + liblava/base.hpp | 11 + liblava/base/base.cpp | 71 +++ liblava/base/base.hpp | 76 +++ liblava/base/device.cpp | 212 ++++++++ liblava/base/device.hpp | 129 +++++ liblava/base/instance.cpp | 304 +++++++++++ liblava/base/instance.hpp | 65 +++ liblava/base/memory.cpp | 143 +++++ liblava/base/memory.hpp | 64 +++ liblava/base/physical_device.cpp | 109 ++++ liblava/base/physical_device.hpp | 49 ++ liblava/core.hpp | 12 + liblava/core/data.hpp | 166 ++++++ liblava/core/id.hpp | 200 +++++++ liblava/core/math.hpp | 86 +++ liblava/core/time.hpp | 117 +++++ liblava/core/types.hpp | 122 +++++ liblava/core/version.hpp | 42 ++ liblava/def.hpp | 20 + liblava/frame.hpp | 13 + liblava/frame/frame.cpp | 307 +++++++++++ liblava/frame/frame.hpp | 119 +++++ liblava/frame/input.cpp | 97 ++++ liblava/frame/input.hpp | 159 ++++++ liblava/frame/render_target.cpp | 93 ++++ liblava/frame/render_target.hpp | 86 +++ liblava/frame/render_thread.hpp | 70 +++ liblava/frame/renderer.cpp | 164 ++++++ liblava/frame/renderer.hpp | 51 ++ liblava/frame/swapchain.cpp | 237 +++++++++ liblava/frame/swapchain.hpp | 71 +++ liblava/frame/window.cpp | 256 +++++++++ liblava/frame/window.hpp | 147 ++++++ liblava/fwd.hpp | 74 +++ liblava/lava.hpp | 11 + liblava/resource.hpp | 11 + liblava/resource/buffer.cpp | 127 +++++ liblava/resource/buffer.hpp | 54 ++ liblava/resource/format.cpp | 460 ++++++++++++++++ liblava/resource/format.hpp | 48 ++ liblava/resource/image.cpp | 113 ++++ liblava/resource/image.hpp | 72 +++ liblava/resource/mesh.cpp | 381 ++++++++++++++ liblava/resource/mesh.hpp | 112 ++++ liblava/resource/texture.cpp | 491 ++++++++++++++++++ liblava/resource/texture.hpp | 103 ++++ liblava/utils.hpp | 12 + liblava/utils/file.cpp | 437 ++++++++++++++++ liblava/utils/file.hpp | 185 +++++++ liblava/utils/log.hpp | 80 +++ liblava/utils/random.hpp | 59 +++ liblava/utils/telegram.hpp | 100 ++++ liblava/utils/thread.hpp | 97 ++++ liblava/utils/utility.hpp | 53 ++ .../texture}/lava_block_logo_200.png | Bin .../texture}/lava_block_logo_50.png | Bin tests/driver.cpp | 67 +++ tests/driver.hpp | 81 +++ tests/tests.cpp | 220 ++++++++ 83 files changed, 8063 insertions(+), 5 deletions(-) create mode 100644 .appveyor.yml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .travis.yml create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 160000 ext/Vulkan-Headers create mode 160000 ext/VulkanMemoryAllocator create mode 160000 ext/argh create mode 160000 ext/assimp create mode 160000 ext/better-enums create mode 160000 ext/bitmap create mode 160000 ext/glfw create mode 160000 ext/gli create mode 160000 ext/glm create mode 160000 ext/json create mode 160000 ext/physfs create mode 160000 ext/selene create mode 160000 ext/spdlog create mode 160000 ext/stb create mode 160000 ext/tinyfiledialogs create mode 160000 ext/tinyobjloader create mode 160000 ext/volk create mode 100644 liblava/base.hpp create mode 100644 liblava/base/base.cpp create mode 100644 liblava/base/base.hpp create mode 100644 liblava/base/device.cpp create mode 100644 liblava/base/device.hpp create mode 100644 liblava/base/instance.cpp create mode 100644 liblava/base/instance.hpp create mode 100644 liblava/base/memory.cpp create mode 100644 liblava/base/memory.hpp create mode 100644 liblava/base/physical_device.cpp create mode 100644 liblava/base/physical_device.hpp create mode 100644 liblava/core.hpp create mode 100644 liblava/core/data.hpp create mode 100644 liblava/core/id.hpp create mode 100644 liblava/core/math.hpp create mode 100644 liblava/core/time.hpp create mode 100644 liblava/core/types.hpp create mode 100644 liblava/core/version.hpp create mode 100644 liblava/def.hpp create mode 100644 liblava/frame.hpp create mode 100644 liblava/frame/frame.cpp create mode 100644 liblava/frame/frame.hpp create mode 100644 liblava/frame/input.cpp create mode 100644 liblava/frame/input.hpp create mode 100644 liblava/frame/render_target.cpp create mode 100644 liblava/frame/render_target.hpp create mode 100644 liblava/frame/render_thread.hpp create mode 100644 liblava/frame/renderer.cpp create mode 100644 liblava/frame/renderer.hpp create mode 100644 liblava/frame/swapchain.cpp create mode 100644 liblava/frame/swapchain.hpp create mode 100644 liblava/frame/window.cpp create mode 100644 liblava/frame/window.hpp create mode 100644 liblava/fwd.hpp create mode 100644 liblava/lava.hpp create mode 100644 liblava/resource.hpp create mode 100644 liblava/resource/buffer.cpp create mode 100644 liblava/resource/buffer.hpp create mode 100644 liblava/resource/format.cpp create mode 100644 liblava/resource/format.hpp create mode 100644 liblava/resource/image.cpp create mode 100644 liblava/resource/image.hpp create mode 100644 liblava/resource/mesh.cpp create mode 100644 liblava/resource/mesh.hpp create mode 100644 liblava/resource/texture.cpp create mode 100644 liblava/resource/texture.hpp create mode 100644 liblava/utils.hpp create mode 100644 liblava/utils/file.cpp create mode 100644 liblava/utils/file.hpp create mode 100644 liblava/utils/log.hpp create mode 100644 liblava/utils/random.hpp create mode 100644 liblava/utils/telegram.hpp create mode 100644 liblava/utils/thread.hpp create mode 100644 liblava/utils/utility.hpp rename {doc/img => res/texture}/lava_block_logo_200.png (100%) rename {doc/img => res/texture}/lava_block_logo_50.png (100%) create mode 100644 tests/driver.cpp create mode 100644 tests/driver.hpp create mode 100644 tests/tests.cpp diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 00000000..2c2fa5be --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,20 @@ +version: '{build}' +branches: + only: + - master +skip_tags: true +image: Visual Studio 2019 +platform: x64 +configuration: Release +environment: + CFLAGS: /WX +matrix: + fast_finish: true +install: + - git submodule update --init +before_build: + - cmake -G "Visual Studio 16 2019" -A x64 . +build: + project: $(APPVEYOR_BUILD_FOLDER)\$(APPVEYOR_PROJECT_NAME).sln + parallel: true + verbosity: minimal \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..a4164267 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,52 @@ +[submodule "ext/argh"] + path = ext/argh + url = https://github.com/adishavit/argh.git +[submodule "ext/assimp"] + path = ext/assimp + url = https://github.com/assimp/assimp.git +[submodule "ext/better-enums"] + path = ext/better-enums + url = https://github.com/aantron/better-enums.git +[submodule "ext/bitmap"] + path = ext/bitmap + url = https://github.com/ArashPartow/bitmap.git +[submodule "ext/glfw"] + path = ext/glfw + url = https://github.com/glfw/glfw.git +[submodule "ext/gli"] + path = ext/gli + url = https://github.com/g-truc/gli.git +[submodule "ext/glm"] + path = ext/glm + url = https://github.com/g-truc/glm.git +[submodule "ext/json"] + path = ext/json + url = https://github.com/nlohmann/json.git +[submodule "ext/physfs"] + path = ext/physfs + url = https://github.com/criptych/physfs.git +[submodule "ext/selene"] + path = ext/selene + url = https://github.com/kmhofmann/selene.git + ignore = dirty +[submodule "ext/spdlog"] + path = ext/spdlog + url = https://github.com/gabime/spdlog.git +[submodule "ext/stb"] + path = ext/stb + url = https://github.com/nothings/stb.git +[submodule "ext/tinyfiledialogs"] + path = ext/tinyfiledialogs + url = https://github.com/native-toolkit/tinyfiledialogs.git +[submodule "ext/tinyobjloader"] + path = ext/tinyobjloader + url = https://github.com/syoyo/tinyobjloader.git +[submodule "ext/volk"] + path = ext/volk + url = https://github.com/zeux/volk.git +[submodule "ext/Vulkan-Headers"] + path = ext/Vulkan-Headers + url = https://github.com/KhronosGroup/Vulkan-Headers.git +[submodule "ext/VulkanMemoryAllocator"] + path = ext/VulkanMemoryAllocator + url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..79bc7a49 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,42 @@ +language: cpp +compiler: gcc +sudo: false +dist: trusty + +os: + - linux + +git: + submodules: false + +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-8 + +before_install: + - sudo apt-get update + - sudo apt-get install -y libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev + - git submodule update --init + +install: + - DEPS_DIR="${TRAVIS_BUILD_DIR}/deps" + - mkdir ${DEPS_DIR} && cd ${DEPS_DIR} + - travis_retry wget --no-check-certificate https://github.com/Kitware/CMake/releases/download/v3.15.3/cmake-3.15.3-Linux-x86_64.tar.gz + - tar -xvf cmake-3.15.3-Linux-x86_64.tar.gz > /dev/null + - mv cmake-3.15.3-Linux-x86_64 cmake-install + - PATH=${DEPS_DIR}/cmake-install:${DEPS_DIR}/cmake-install/bin:$PATH + - cd ${TRAVIS_BUILD_DIR} + +script: + - echo ${PATH} + - cmake --version + - export CC=gcc-8 + - export CXX=g++-8 + - echo ${CXX} + - ${CXX} --version + - ${CXX} -v + - mkdir -p build && cd build + - cmake -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=Release .. && make -j4 \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..3759eabf --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,254 @@ +# file : CMakeLists.txt +# copyright : Copyright (c) 2018-present, Lava Block OÜ +# license : MIT; see accompanying LICENSE file + +cmake_minimum_required(VERSION 3.12) + +project(liblava VERSION 0.4.2 LANGUAGES C CXX) + +message("") +message("========================================================================") +message(" copyright (c) 2018-present, Lava Block OÜ MIT licensed ") +message("========================================================================") +message(" ") +message(" _| _| _| _| ") +message(" _| _|_|_| _| _|_|_| _| _| _|_|_| ") +message(" _| _| _| _| _| _| _| _| _| _| _| ") +message(" _| _| _| _| _| _| _| _| _| _| _| ") +message(" _| _| _|_|_| _| _|_|_| _| _|_|_| ") +message(" ") +message("========================================================================") +message(" 2019 preview 2 v0.4.2 ") +message("========================================================================") +message(" https://git.io/liblava lava-block.com ") +message("========================================================================") + +if(CMAKE_COMPILER_IS_GNUCXX) + set_property(GLOBAL PROPERTY ALLOW_DUPLICATE_CUSTOM_TARGETS ON) +endif() + +if(NOT DEFINED CMAKE_SUPPRESS_DEVELOPER_WARNINGS) + set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS 1 CACHE INTERNAL "No dev warnings") +endif() + +set(LIBLAVA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/liblava) +set(LIBLAVA_EXT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext) +set(LIBLAVA_TESTS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tests) + +# add_subdirectory(ext) + +message(">> liblava/core") + +if(CMAKE_COMPILER_IS_GNUCXX) + find_package (Threads) +endif() + +add_library(lava.core INTERFACE) + +target_include_directories(lava.core INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR} + ${LIBLAVA_EXT_DIR}/better-enums + ${LIBLAVA_EXT_DIR}/glm + ) + +target_sources(lava.core INTERFACE + ${LIBLAVA_DIR}/core/data.hpp + ${LIBLAVA_DIR}/core/id.hpp + ${LIBLAVA_DIR}/core/math.hpp + ${LIBLAVA_DIR}/core/time.hpp + ${LIBLAVA_DIR}/core/types.hpp + ${LIBLAVA_DIR}/core/version.hpp + ) + +target_compile_features(lava.core INTERFACE + cxx_std_20 + ) + +if(CMAKE_COMPILER_IS_GNUCXX) + target_link_libraries(lava.core INTERFACE + stdc++fs + ${CMAKE_THREAD_LIBS_INIT} + ) +endif() + +message(">> liblava/utils") + +add_library(lava.utils STATIC + ${LIBLAVA_DIR}/utils/file.cpp + ${LIBLAVA_DIR}/utils/file.hpp + ${LIBLAVA_DIR}/utils/log.hpp + ${LIBLAVA_DIR}/utils/random.hpp + ${LIBLAVA_DIR}/utils/telegram.hpp + ${LIBLAVA_DIR}/utils/thread.hpp + ${LIBLAVA_DIR}/utils/utility.hpp + ${LIBLAVA_EXT_DIR}/tinyfiledialogs/tinyfiledialogs.c + ) + +if(WIN32) + set_source_files_properties(${LIBLAVA_EXT_DIR}/tinyfiledialogs/tinyfiledialogs.c PROPERTIES COMPILE_FLAGS " /W0 ") +endif() + +target_include_directories(lava.utils PUBLIC + ${LIBLAVA_EXT_DIR}/spdlog/include + ${LIBLAVA_EXT_DIR}/physfs/src + ${LIBLAVA_EXT_DIR}/tinyfiledialogs + ${LIBLAVA_EXT_DIR}/json/single_include + ) + +message(">>> ext/physfs") + +set(PHYSFS_ARCHIVE_ZIP ON CACHE BOOL "" FORCE) +set(PHYSFS_ARCHIVE_7Z OFF CACHE BOOL "" FORCE) +set(PHYSFS_ARCHIVE_GRP OFF CACHE BOOL "" FORCE) +set(PHYSFS_ARCHIVE_WAD OFF CACHE BOOL "" FORCE) +set(PHYSFS_ARCHIVE_HOG OFF CACHE BOOL "" FORCE) +set(PHYSFS_ARCHIVE_MVL OFF CACHE BOOL "" FORCE) +set(PHYSFS_ARCHIVE_QPAK OFF CACHE BOOL "" FORCE) +set(PHYSFS_ARCHIVE_SLB OFF CACHE BOOL "" FORCE) +set(PHYSFS_ARCHIVE_ISO9660 OFF CACHE BOOL "" FORCE) +set(PHYSFS_ARCHIVE_VDF OFF CACHE BOOL "" FORCE) + +set(PHYSFS_BUILD_SHARED OFF CACHE BOOL "" FORCE) +set(PHYSFS_BUILD_TEST OFF CACHE BOOL "" FORCE) +add_subdirectory(${LIBLAVA_EXT_DIR}/physfs physfs EXCLUDE_FROM_ALL) + +message("<<< ext/physfs") + +target_link_libraries(lava.utils + lava.core + physfs-static + ) + +message(">> liblava/base") + +add_library(lava.base STATIC + ${LIBLAVA_DIR}/base/base.cpp + ${LIBLAVA_DIR}/base/base.hpp + ${LIBLAVA_DIR}/base/device.cpp + ${LIBLAVA_DIR}/base/device.hpp + ${LIBLAVA_DIR}/base/instance.cpp + ${LIBLAVA_DIR}/base/instance.hpp + ${LIBLAVA_DIR}/base/memory.cpp + ${LIBLAVA_DIR}/base/memory.hpp + ${LIBLAVA_DIR}/base/physical_device.cpp + ${LIBLAVA_DIR}/base/physical_device.hpp + ${LIBLAVA_EXT_DIR}/volk/volk.c + ) + +target_include_directories(lava.base PUBLIC + ${LIBLAVA_EXT_DIR}/Vulkan-Headers/include + ${LIBLAVA_EXT_DIR}/VulkanMemoryAllocator/src + ${LIBLAVA_EXT_DIR}/volk + ) + +target_link_libraries(lava.base + lava.utils + ${CMAKE_DL_LIBS} + ) + +message(">> liblava/resource") + +option(LIBLAVA_ASSIMP "build assimp library" ON) + +add_library(lava.resource STATIC + ${LIBLAVA_DIR}/resource/buffer.cpp + ${LIBLAVA_DIR}/resource/buffer.hpp + ${LIBLAVA_DIR}/resource/format.cpp + ${LIBLAVA_DIR}/resource/format.hpp + ${LIBLAVA_DIR}/resource/image.cpp + ${LIBLAVA_DIR}/resource/image.hpp + ${LIBLAVA_DIR}/resource/mesh.cpp + ${LIBLAVA_DIR}/resource/mesh.hpp + ${LIBLAVA_DIR}/resource/texture.cpp + ${LIBLAVA_DIR}/resource/texture.hpp + ) + +if(LIBLAVA_ASSIMP) + set(LIBLAVA_ASSIMP_INC ${LIBLAVA_EXT_DIR}/assimp/include) + set(LIBLAVA_ASSIMP_LIB assimp) + target_compile_definitions(lava.resource PRIVATE LIBLAVA_ASSIMP=1) +else() + set(LIBLAVA_ASSIMP_INC "") + set(LIBLAVA_ASSIMP_LIB "") +endif() + +target_include_directories(lava.resource PUBLIC + ${LIBLAVA_EXT_DIR}/stb + ${LIBLAVA_EXT_DIR}/gli + ${LIBLAVA_EXT_DIR}/tinyobjloader + ${LIBLAVA_EXT_DIR}/bitmap + ${LIBLAVA_EXT_DIR}/selene + ${LIBLAVA_ASSIMP_INC} + ) + +message(">>> ext/assimp") + +if(LIBLAVA_ASSIMP) + set(BUILD_SHARED_LIBS OFF) + set(ASSIMP_BUILD_TESTS OFF) + set(INJECT_DEBUG_POSTFIX OFF) + set(ASSIMP_BUILD_ASSIMP_TOOLS OFF) + add_subdirectory(${LIBLAVA_EXT_DIR}/assimp assimp EXCLUDE_FROM_ALL) +endif() + +message("<<< ext/assimp") +message(">>> ext/selene") + +add_subdirectory(${LIBLAVA_EXT_DIR}/selene selene EXCLUDE_FROM_ALL) + +message("<<< ext/glfw") + +target_link_libraries(lava.resource + lava.base + ${LIBLAVA_ASSIMP_LIB} + ) + +message(">> liblava/frame") + +add_library(lava.frame STATIC + ${LIBLAVA_DIR}/frame/frame.cpp + ${LIBLAVA_DIR}/frame/frame.hpp + ${LIBLAVA_DIR}/frame/input.cpp + ${LIBLAVA_DIR}/frame/input.hpp + ${LIBLAVA_DIR}/frame/render_target.cpp + ${LIBLAVA_DIR}/frame/render_target.hpp + ${LIBLAVA_DIR}/frame/render_thread.hpp + ${LIBLAVA_DIR}/frame/renderer.cpp + ${LIBLAVA_DIR}/frame/renderer.hpp + ${LIBLAVA_DIR}/frame/swapchain.cpp + ${LIBLAVA_DIR}/frame/swapchain.hpp + ${LIBLAVA_DIR}/frame/window.cpp + ${LIBLAVA_DIR}/frame/window.hpp + ) + +target_include_directories(lava.frame PUBLIC + ${LIBLAVA_EXT_DIR}/glfw/include + ${LIBLAVA_EXT_DIR}/argh + ) + +message(">>> ext/glfw") + +set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE) +set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE) +set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) +add_subdirectory(${LIBLAVA_EXT_DIR}/glfw glfw EXCLUDE_FROM_ALL) + +message("<<< ext/glfw") + +target_link_libraries(lava.frame + lava.resource + glfw + ${GLFW_LIBRARIES} + ) + +message(">> tests/driver") + +add_executable(lava + ${LIBLAVA_TESTS_DIR}/driver.cpp + ${LIBLAVA_TESTS_DIR}/driver.hpp + ${LIBLAVA_TESTS_DIR}/tests.cpp + ) + +target_link_libraries(lava lava.frame) + +message("========================================================================") \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..ea0fd86f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018-present, Lava Block OÜ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 2b96abca..58ceec08 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,340 @@ -<p align="center"> - <a href="https://lava-block.com"> - <img src="https://raw.githubusercontent.com/liblava/liblava/master/doc/img/lava_block_logo_200.png"> - </a> -</p> +<img align="left" src="https://raw.githubusercontent.com/liblava/liblava/master/res/texture/lava_block_logo_200.png"> + +**liblava is a modern and easy-to-use library for the <a href="https://www.khronos.org/vulkan/">Vulkan API</a>** + +liblava is a lean framework that provides essentials for low-level graphics and is specially well suited for prototyping, tooling and education. + + + C++20 standard + + Modular (5 modules) + + Cross Platform (Windows | Linux) + + [](https://github.com/liblava/liblava) [](https://travis-ci.com/liblava/liblava) [](https://ci.appveyor.com/project/TheLavaBlock/liblava) [](LICENSE) [](https://twitter.com/thelavablock) + +## features + +* written in modern C++ +* latest Vulkan API support +* run loop abstraction +* window and input handling +* render target and renderer +* load texture and mesh +* file system and logging +* test driver +* and much more... + +WIP latest **<a href="https://github.com/liblava/liblava/releases">preview 2 / v0.4.2</a>** (Sep 18, 2019) + +## hello frame + +Let's write **Hello World** in Vulkan: *a simple program that just renders a colored window* + +Well, just ? - Vulkan is a low-level, verbose graphics API and such a program can take several hundred lines of code. + +All we need is a window, a device and a renderer. + +The good news is that **liblava** will set up all for you. + +```c++ +#include <liblava/lava.h> + +using namespace lava; +``` + +Here are a few examples to get to know `lava`. + +#### 1. frame init + +```c++ +int main(int argc, char* argv[]) { + + frame frame( {argc, argv} ); + + return frame.ready() ? 0 : error::not_ready; +} +``` + +This is how to initialize `lava::frame` with command line arguments. + +#### 2. run loop + +```c++ +frame frame(argh); +if (!frame.ready()) + return error::not_ready; + +auto count = 0; + +frame.add_run([&]() { + + sleep(1.0); + count++; + + log()->debug("{} - running {} sec", count, frame.get_running_time()); + + if (count == 3) + frame.shut_down(); + + return true; +}); + +return frame.run() ? 0 : error::aborted; +``` + +The last line performs a loop with the run we added before. If *count* reaches 3 it will exit. + +#### 3. window input + +Here is another example that shows how to create a window and handle input. This is straightforward. + +```c++ +frame frame(argh); +if (!frame.ready()) + return error::not_ready; + +window window; +if (!window.create()) + return error::create_failed; + +input input; +window.assign(&input); + +input.key_listeners.add([&](key_event::ref event) { + + if (event.key == GLFW_KEY_ESCAPE && event.action == GLFW_PRESS) + frame.shut_down(); +}); + +frame.add_run([&]() { + + handle_events(input); + + if (window.has_close_request()) + frame.shut_down(); + + return true; +}); + +return frame.run() ? 0 : error::aborted; +``` + +With this knowledge in hand let's write **hello frame**... + +#### 4. clear color + +```c++ +frame frame(argh); +if (!frame.ready()) + return error::not_ready; + +window window; +if (!window.create()) + return error::create_failed; + +input input; +window.assign(&input); + +input.key_listeners.add([&](key_event::ref event) { + + if (event.key == GLFW_KEY_ESCAPE && event.action == GLFW_PRESS) + frame.shut_down(); +}); + +auto device = frame.create_device(); +if (!device) + return error::create_failed; + +auto render_target = create_target(&window, device); +if (!render_target) + return error::create_failed; + +renderer simple_renderer; +if (!simple_renderer.create(render_target->get_swapchain())) + return error::create_failed; + +auto frame_count = render_target->get_frame_count(); + +VkCommandPool cmd_pool; +VkCommandBuffers cmd_bufs(frame_count); + +auto build_cmd_bufs = [&]() { + + VkCommandPoolCreateInfo create_info + { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .queueFamilyIndex = device->get_graphics_queue().family_index, + }; + if (!check(device->call().vkCreateCommandPool(device->get(), &create_info, memory::alloc(), &cmd_pool))) + return false; + + VkCommandBufferAllocateInfo alloc_info + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = cmd_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = frame_count, + }; + if (!check(device->call().vkAllocateCommandBuffers(device->get(), &alloc_info, cmd_bufs.data()))) + return false; + + VkCommandBufferBeginInfo begin_info + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, + }; + + VkClearColorValue clear_color = { random(0.f, 1.f), random(0.f, 1.f), random(0.f, 1.f), 0.f }; + + VkImageSubresourceRange image_range + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1, + }; + + for (auto i = 0u; i < frame_count; i++) { + + auto cmd_buf = cmd_bufs[i]; + auto target_image = render_target->get_backbuffer_image(i); + + if (!check(device->call().vkBeginCommandBuffer(cmd_buf, &begin_info))) + return false; + + insert_image_memory_barrier(device, cmd_buf, target_image, + VK_ACCESS_MEMORY_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + image_range); + + device->call().vkCmdClearColorImage(cmd_buf, target_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + &clear_color, 1, &image_range); + + insert_image_memory_barrier(device, cmd_buf, target_image, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + image_range); + + if (!check(device->call().vkEndCommandBuffer(cmd_buf))) + return false; + } + + return true; +}; + +auto clean_cmd_bufs = [&]() { + + for (auto& cmd_buf : cmd_bufs) + device->call().vkFreeCommandBuffers(device->get(), cmd_pool, 1, &cmd_buf); + + device->call().vkDestroyCommandPool(device->get(), cmd_pool, memory::alloc()); +}; + +build_cmd_bufs(); + +render_target->on_swapchain_start = build_cmd_bufs; +render_target->on_swapchain_stop = clean_cmd_bufs; + +frame.add_run([&]() { + + handle_events(input); + + if (window.has_close_request()) + return frame.shut_down(); + + if (window.has_resize_request()) + return window.handle_resize(); + + if (window.iconified()) { + + frame.set_wait_for_events(true); + return true; + + } else { + + if (frame.waiting_for_events()) + frame.set_wait_for_events(false); + } + + auto frame_index = simple_renderer.begin_frame(); + if (!frame_index) + return true; + + return simple_renderer.end_frame({ cmd_bufs[*frame_index] }); +}); + +frame.add_run_end([&]() { + + clean_cmd_bufs(); + simple_renderer.destroy(); + render_target->destroy(); +}); + +return frame.run() ? 0 : error::aborted; +``` + +Check the [Awesome Vulkan ecosystem](http://www.vinjn.com/awesome-vulkan/) to learn more about Vulkan (Tutorials/Samples/Books). + +## tests + +Run the driver to test [the above examples](https://github.com/liblava/liblava/blob/master/tests/tests.cpp). + +List all tests: + +``` +$ lava -t +``` + +Run test 2 for example: + +``` +$ lava 2 +``` + +## requirements + +* **C++20** compatible compiler +* CMake **3.12+** +* [Vulkan SDK](https://vulkan.lunarg.com) + +## build + +``` +$ git clone https://github.com/liblava/liblava.git +$ cd liblava + +$ git submodule update --init --recursive + +$ mkdir build +$ cd build + +$ cmake .. +$ make +``` + +## third-party / license + +* [argh](https://github.com/adishavit/argh) / 3-clause BSD +* [assimp](https://github.com/assimp/assimp) / modified 3-clause BSD +* [better-enums](https://github.com/aantron/better-enums) / 2-clause BSD +* [bitmap](https://github.com/ArashPartow/bitmap) / MIT +* [glfw](https://github.com/glfw/glfw) / zlib +* [gli](https://github.com/g-truc/gli) / MIT +* [glm](https://github.com/g-truc/glm) / MIT +* [json](https://github.com/nlohmann/json) / MIT +* [physfs](https://github.com/criptych/physfs) / zlib +* [selene](https://github.com/kmhofmann/selene) / MIT +* [spdlog](https://github.com/gabime/spdlog) / MIT +* [stb](https://github.com/nothings/stb) / MIT +* [tinyfiledialogs](https://github.com/native-toolkit/tinyfiledialogs) / zlib +* [tinyobjloader](https://github.com/syoyo/tinyobjloader) / MIT +* [volk](https://github.com/zeux/volk) / MIT +* [Vulkan-Headers](https://github.com/KhronosGroup/Vulkan-Headers) / Apache 2.0 +* [VulkanMemoryAllocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator) / MIT + +## license + +liblava is licensed under [MIT License](LICENSE.md) which allows you to use the software for any purpose you might like, including commercial and for-profit use. However, this library includes several third-party Open-Source libraries, which are licensed under their own respective Open-Source licenses. These licenses allow static linking with closed source software. All copies of liblava must include a copy of the MIT License terms and the copyright notice. + +Copyright (c) 2018-present, <a href="https://lava-block.com">Lava Block OÜ</a> + +<img src="https://raw.githubusercontent.com/liblava/liblava/master/res/texture/lava_block_logo_50.png"> \ No newline at end of file diff --git a/ext/Vulkan-Headers b/ext/Vulkan-Headers new file mode 160000 index 00000000..5b44df19 --- /dev/null +++ b/ext/Vulkan-Headers @@ -0,0 +1 @@ +Subproject commit 5b44df19e040fca0048ab30c553a8c2d2cb9623e diff --git a/ext/VulkanMemoryAllocator b/ext/VulkanMemoryAllocator new file mode 160000 index 00000000..909f36b7 --- /dev/null +++ b/ext/VulkanMemoryAllocator @@ -0,0 +1 @@ +Subproject commit 909f36b714c9239ee0b112a321220213a474ba53 diff --git a/ext/argh b/ext/argh new file mode 160000 index 00000000..d4f5b4a2 --- /dev/null +++ b/ext/argh @@ -0,0 +1 @@ +Subproject commit d4f5b4a2bf6fcf9c6a4f83c512fce9bbcf584b42 diff --git a/ext/assimp b/ext/assimp new file mode 160000 index 00000000..b6edcb35 --- /dev/null +++ b/ext/assimp @@ -0,0 +1 @@ +Subproject commit b6edcb35a8253699a18a5e97f0fc8169fcda175a diff --git a/ext/better-enums b/ext/better-enums new file mode 160000 index 00000000..4b76e778 --- /dev/null +++ b/ext/better-enums @@ -0,0 +1 @@ +Subproject commit 4b76e7783bacf5220e0f29d19845a72d19ce0d6b diff --git a/ext/bitmap b/ext/bitmap new file mode 160000 index 00000000..624fb94c --- /dev/null +++ b/ext/bitmap @@ -0,0 +1 @@ +Subproject commit 624fb94c7e7986d1dda731fa15ee8fce4d7a13c4 diff --git a/ext/glfw b/ext/glfw new file mode 160000 index 00000000..7105ff2d --- /dev/null +++ b/ext/glfw @@ -0,0 +1 @@ +Subproject commit 7105ff2dfd004a46bd732c1d0c9f461bae6d51b3 diff --git a/ext/gli b/ext/gli new file mode 160000 index 00000000..559cbe1e --- /dev/null +++ b/ext/gli @@ -0,0 +1 @@ +Subproject commit 559cbe1ec38878e182507d331e0780fbae5baf15 diff --git a/ext/glm b/ext/glm new file mode 160000 index 00000000..7c07544b --- /dev/null +++ b/ext/glm @@ -0,0 +1 @@ +Subproject commit 7c07544b34c2f8655e4134239137d32aa2ccd5c8 diff --git a/ext/json b/ext/json new file mode 160000 index 00000000..ea60d40f --- /dev/null +++ b/ext/json @@ -0,0 +1 @@ +Subproject commit ea60d40f4a60a47d3be9560d8f7bc37c163fe47b diff --git a/ext/physfs b/ext/physfs new file mode 160000 index 00000000..b574ac6e --- /dev/null +++ b/ext/physfs @@ -0,0 +1 @@ +Subproject commit b574ac6ec7f58aa29cd7bb1c785c962b5d155b5f diff --git a/ext/selene b/ext/selene new file mode 160000 index 00000000..27c90148 --- /dev/null +++ b/ext/selene @@ -0,0 +1 @@ +Subproject commit 27c90148b48c04f32b788abe724166b14234eda1 diff --git a/ext/spdlog b/ext/spdlog new file mode 160000 index 00000000..a51b4856 --- /dev/null +++ b/ext/spdlog @@ -0,0 +1 @@ +Subproject commit a51b4856377a71f81b6d74b9af459305c4c644f8 diff --git a/ext/stb b/ext/stb new file mode 160000 index 00000000..052dce11 --- /dev/null +++ b/ext/stb @@ -0,0 +1 @@ +Subproject commit 052dce117ed989848a950308bd99eef55525dfb1 diff --git a/ext/tinyfiledialogs b/ext/tinyfiledialogs new file mode 160000 index 00000000..b5e548ad --- /dev/null +++ b/ext/tinyfiledialogs @@ -0,0 +1 @@ +Subproject commit b5e548ad5eeb1bab4a34a6010057d06d85c26734 diff --git a/ext/tinyobjloader b/ext/tinyobjloader new file mode 160000 index 00000000..d47e8545 --- /dev/null +++ b/ext/tinyobjloader @@ -0,0 +1 @@ +Subproject commit d47e8545eb2b43dc34b542d9a27e6ca5970ebe33 diff --git a/ext/volk b/ext/volk new file mode 160000 index 00000000..d6c2bde9 --- /dev/null +++ b/ext/volk @@ -0,0 +1 @@ +Subproject commit d6c2bde94f70506240eac22763cf3adf930cedf5 diff --git a/liblava/base.hpp b/liblava/base.hpp new file mode 100644 index 00000000..6c327147 --- /dev/null +++ b/liblava/base.hpp @@ -0,0 +1,11 @@ +// file : liblava/base.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/base/base.hpp> +#include <liblava/base/device.hpp> +#include <liblava/base/instance.hpp> +#include <liblava/base/memory.hpp> +#include <liblava/base/physical_device.hpp> diff --git a/liblava/base/base.cpp b/liblava/base/base.cpp new file mode 100644 index 00000000..57df8644 --- /dev/null +++ b/liblava/base/base.cpp @@ -0,0 +1,71 @@ +// file : liblava/base/base.cpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#include <liblava/base/base.hpp> + +bool lava::check(VkResult result) { + + if (result == VK_SUCCESS) + return true; + + if (result > 0) { + + log()->critical("VkResult = {}", to_string(result)); + return false; + } + + log()->error("VkResult = {}", to_string(result)); + return false; +} + +lava::string lava::to_string(VkResult result) { + +#define RETURN_STR(result_code) case result_code: return string(#result_code); + + switch (result) { + + RETURN_STR(VK_SUCCESS) + RETURN_STR(VK_NOT_READY) + RETURN_STR(VK_TIMEOUT) + RETURN_STR(VK_EVENT_SET) + RETURN_STR(VK_EVENT_RESET) + RETURN_STR(VK_INCOMPLETE) + RETURN_STR(VK_ERROR_OUT_OF_HOST_MEMORY) + RETURN_STR(VK_ERROR_OUT_OF_DEVICE_MEMORY) + RETURN_STR(VK_ERROR_INITIALIZATION_FAILED) + RETURN_STR(VK_ERROR_DEVICE_LOST) + RETURN_STR(VK_ERROR_MEMORY_MAP_FAILED) + RETURN_STR(VK_ERROR_LAYER_NOT_PRESENT) + RETURN_STR(VK_ERROR_EXTENSION_NOT_PRESENT) + RETURN_STR(VK_ERROR_FEATURE_NOT_PRESENT) + RETURN_STR(VK_ERROR_INCOMPATIBLE_DRIVER) + RETURN_STR(VK_ERROR_TOO_MANY_OBJECTS) + RETURN_STR(VK_ERROR_FORMAT_NOT_SUPPORTED) + RETURN_STR(VK_ERROR_FRAGMENTED_POOL) + RETURN_STR(VK_ERROR_OUT_OF_POOL_MEMORY) + RETURN_STR(VK_ERROR_INVALID_EXTERNAL_HANDLE) + RETURN_STR(VK_ERROR_SURFACE_LOST_KHR) + RETURN_STR(VK_ERROR_NATIVE_WINDOW_IN_USE_KHR) + RETURN_STR(VK_SUBOPTIMAL_KHR) + RETURN_STR(VK_ERROR_OUT_OF_DATE_KHR) + RETURN_STR(VK_ERROR_INCOMPATIBLE_DISPLAY_KHR) + RETURN_STR(VK_ERROR_VALIDATION_FAILED_EXT) + RETURN_STR(VK_ERROR_INVALID_SHADER_NV) + RETURN_STR(VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT) + RETURN_STR(VK_ERROR_FRAGMENTATION_EXT) + RETURN_STR(VK_ERROR_NOT_PERMITTED_EXT) + RETURN_STR(VK_ERROR_INVALID_DEVICE_ADDRESS_EXT) + RETURN_STR(VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT) + + default: + return fmt::format("[invalid VkResult {}]", result); + } + +#undef RETURN_STR +} + +lava::string lava::version_to_string(ui32 version) { + + return fmt::format("{}.{}.{}", VK_VERSION_MAJOR(version), VK_VERSION_MINOR(version), VK_VERSION_PATCH(version)); +} diff --git a/liblava/base/base.hpp b/liblava/base/base.hpp new file mode 100644 index 00000000..9743cae9 --- /dev/null +++ b/liblava/base/base.hpp @@ -0,0 +1,76 @@ +// file : liblava/base/base.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/utils.hpp> + +#define VK_NO_PROTOTYPES +#include <vulkan/vulkan.h> + +#include <volk.h> + +namespace lava { + +using VkFormats = std::vector<VkFormat>; + +using VkImages = std::vector<VkImage>; +using VkImagesRef = VkImages const&; + +using VkImageViews = std::vector<VkImageView>; +using VkImageViewsRef = VkImageViews const&; + +using VkFramebuffers = std::vector<VkFramebuffer>; + +using VkCommandPools = std::vector<VkCommandPool>; +using VkCommandBuffers = std::vector<VkCommandBuffer>; + +using VkFences = std::vector<VkFence>; +using VkSemaphores = std::vector<VkSemaphore>; + +using VkPresentModeKHRs = std::vector<VkPresentModeKHR>; + +using VkDescriptorSets = std::vector<VkDescriptorSet>; +using VkDescriptorSetLayouts = std::vector<VkDescriptorSetLayout>; +using VkDescriptorSetLayoutBindings = std::vector<VkDescriptorSetLayoutBinding>; + +using VkPushConstantRanges = std::vector<VkPushConstantRange>; + +using VkAttachmentReferences = std::vector<VkAttachmentReference>; + +using VkClearValues = std::vector<VkClearValue>; + +using VkPipelineShaderStageCreateInfos = std::vector<VkPipelineShaderStageCreateInfo>; + +using VkVertexInputBindingDescriptions = std::vector<VkVertexInputBindingDescription>; +using VkVertexInputAttributeDescriptions = std::vector<VkVertexInputAttributeDescription>; + +using VkPipelineColorBlendAttachmentStates = std::vector<VkPipelineColorBlendAttachmentState>; +using VkDynamicStates = std::vector<VkDynamicState>; + +using VkQueueFamilyPropertiesList = std::vector<VkQueueFamilyProperties>; +using VkExtensionPropertiesList = std::vector<VkExtensionProperties>; + +using VkLayerPropertiesList = std::vector<VkLayerProperties>; +using VkExtensionPropertiesList = std::vector<VkExtensionProperties>; + +using VkPhysicalDevices = std::vector<VkPhysicalDevice>; + +bool check(VkResult result); +inline bool failed(VkResult result) { return !check(result); } + +string to_string(VkResult result); +string version_to_string(ui32 version); + +// limits + +static constexpr ui32 const Vk_Limit_DescriptorSets = 4; +static constexpr ui32 const Vk_Limit_Bindings = 16; +static constexpr ui32 const Vk_Limit_Attachments = 8; +static constexpr ui32 const Vk_Limit_VertexAttribs = 16; +static constexpr ui32 const Vk_Limit_VertexBuffers = 4; +static constexpr ui32 const Vk_Limit_PushConstant_Size = 128; +static constexpr ui32 const Vk_Limit_UBO_Size = 16 * 1024; + +} // lava diff --git a/liblava/base/device.cpp b/liblava/base/device.cpp new file mode 100644 index 00000000..f194382e --- /dev/null +++ b/liblava/base/device.cpp @@ -0,0 +1,212 @@ +// file : liblava/base/device.cpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#include <liblava/base/device.hpp> +#include <liblava/base/instance.hpp> +#include <liblava/base/physical_device.hpp> + +namespace lava { + +device::~device() { + + destroy(); +} + +bool device::create(create_param const& param) { + + _physical_device = param._physical_device; + if (!_physical_device) + return false; + + std::vector<VkDeviceQueueCreateInfo> queue_create_info_list(param.queue_info_list.size()); + + for (size_t i = 0, e = param.queue_info_list.size(); i != e; ++i) { + + queue_create_info_list[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + + ui32 index = 0; + if (!_physical_device->get_queue_family(index, param.queue_info_list[i].flags)) { + + log()->error("device::create physical_device::get_queue_family failed"); + return false; + } + + queue_create_info_list[i].queueFamilyIndex = index; + queue_create_info_list[i].queueCount = param.queue_info_list[i].count(); + queue_create_info_list[i].pQueuePriorities = param.queue_info_list[i].priorities.data(); + } + + VkDeviceCreateInfo create_info + { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .queueCreateInfoCount = to_ui32(queue_create_info_list.size()), + .pQueueCreateInfos = queue_create_info_list.data(), + .enabledLayerCount = 0, + .ppEnabledLayerNames = nullptr, + .enabledExtensionCount = to_ui32(param.extensions.size()), + .ppEnabledExtensionNames = param.extensions.data(), + .pEnabledFeatures = &_physical_device->get_features(), + }; + + if (failed(vkCreateDevice(_physical_device->get(), &create_info, memory::alloc(), &vk_device))) { + + log()->error("device::create vkCreateDevice failed"); + return false; + } + + volkLoadDeviceTable(&table, vk_device); + + graphics_queues.clear(); + compute_queues.clear(); + transfer_queues.clear(); + + index_map queue_family_map; + + for (size_t i = 0, ei = queue_create_info_list.size(); i != ei; ++i) { + + if (!queue_family_map.count(queue_create_info_list[i].queueFamilyIndex)) + queue_family_map.emplace(queue_create_info_list[i].queueFamilyIndex, 0); + + for (size_t j = 0, ej = queue_create_info_list[i].queueCount; j != ej; ++j) { + + auto counter = queue_family_map[queue_create_info_list[i].queueFamilyIndex]; + queue_family_map[queue_create_info_list[i].queueFamilyIndex]++; + + VkQueue queue = nullptr; + call().vkGetDeviceQueue(vk_device, queue_create_info_list[i].queueFamilyIndex, counter, &queue); + + if (param.queue_info_list[i].flags & VK_QUEUE_GRAPHICS_BIT) + graphics_queues.push_back({ queue, queue_create_info_list[i].queueFamilyIndex }); + if (param.queue_info_list[i].flags & VK_QUEUE_COMPUTE_BIT) + compute_queues.push_back({ queue, queue_create_info_list[i].queueFamilyIndex }); + if (param.queue_info_list[i].flags & VK_QUEUE_TRANSFER_BIT) + transfer_queues.push_back({ queue, queue_create_info_list[i].queueFamilyIndex }); + } + } + + return create_descriptor_pool(); +} + +void device::destroy() { + + if (!vk_device) + return; + + graphics_queues.clear(); + compute_queues.clear(); + transfer_queues.clear(); + + call().vkDestroyDescriptorPool(vk_device, descriptor_pool, memory::alloc()); + descriptor_pool = nullptr; + + call().vkDestroyDevice(vk_device, memory::alloc()); + vk_device = nullptr; + + table = {}; +} + +bool device::create_descriptor_pool() { + + auto const count = 11u; + auto const size = 1000u; + + VkDescriptorPoolSize const pool_size[count] = + { + { VK_DESCRIPTOR_TYPE_SAMPLER, size }, + { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, size }, + { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, size }, + { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, size }, + { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, size }, + { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, size }, + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, size }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, size }, + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, size }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, size }, + { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, size }, + }; + + VkDescriptorPoolCreateInfo pool_info + { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, + .maxSets = size * count, + .poolSizeCount = count, + .pPoolSizes = pool_size, + }; + + return check(call().vkCreateDescriptorPool(vk_device, &pool_info, memory::alloc(), &descriptor_pool)); +} + +bool device::is_surface_supported(VkSurfaceKHR surface) const { + + return _physical_device->is_surface_supported(get_graphics_queue().family_index, surface); +} + +VkPhysicalDeviceFeatures const& device::get_features() const { return _physical_device->get_features(); } + +VkPhysicalDeviceProperties const& device::get_properties() const { return _physical_device->get_properties(); } + +device::ptr device_manager::create() { + + auto& physical_device = instance::get_first_physical_device(); + + if (!physical_device.is_swapchain_supported()) + return nullptr; + + auto device = create(physical_device.create_default_device_param()); + if (!device) + return nullptr; + + auto allocator = allocator::make(physical_device.get(), device->get()); + if (!allocator) + return nullptr; + + device->set_allocator(allocator); + + return device; +} + +device::ptr device_manager::create(device::create_param const& param) { + + auto result = std::make_shared<device>(); + if (!result->create(param)) + return nullptr; + + list.push_back(result); + return result; +} + +void device_manager::wait_idle() { + + for (auto& device : list) + device->wait_for_idle(); +} + +void device_manager::clear() { + + for (auto& device : list) + device->destroy(); + + list.clear(); +} + +} // lava + +bool lava::load_and_create_shader_module(device* device, data const& data, VkShaderModule& out) { + + VkShaderModuleCreateInfo shader_module_create_info + { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = data.size, + .pCode = reinterpret_cast<ui32*>(data.ptr), + }; + + VkShaderModule new_module; + auto result = device->call().vkCreateShaderModule(device->get(), &shader_module_create_info, memory::alloc(), &new_module); + if (failed(result)) + return false; + + out = new_module; + return true; +} diff --git a/liblava/base/device.hpp b/liblava/base/device.hpp new file mode 100644 index 00000000..3331ca24 --- /dev/null +++ b/liblava/base/device.hpp @@ -0,0 +1,129 @@ +// file : liblava/base/device.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/base/base.hpp> +#include <liblava/base/memory.hpp> + +namespace lava { + +// fwd +struct physical_device; + +struct device : no_copy_no_move { + + using ptr = std::shared_ptr<device>; + using list = std::vector<device::ptr>; + + struct create_param { + + const physical_device* _physical_device = nullptr; + + struct queue_info { + + using list = std::vector<queue_info>; + + VkQueueFlags flags = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT; + + using priority_list = std::vector<float>; + priority_list priorities; + + ui32 count() const { return to_ui32(priorities.size()); } + + explicit queue_info(ui32 count = 1) { + + for (auto i = 0u; i < count; ++i) + priorities.push_back(1.f); + } + }; + + queue_info::list queue_info_list; + + names extensions; + + void set_default_queues() { + + extensions.push_back("VK_KHR_swapchain"); + queue_info_list.resize(1); + } + }; + + ~device(); + + bool create(create_param const& param); + void destroy(); + + struct queue { + + using list = std::vector<queue>; + using ref = queue const&; + + VkQueue vk_queue = nullptr; + ui32 family_index = 0; + }; + + queue::ref get_graphics_queue(ui32 index = 0) const { return get_graphics_queues().at(index); } + queue::ref get_compute_queue(ui32 index = 0) const { return get_compute_queues().at(index); } + queue::ref get_transfer_queue(ui32 index = 0) const { return get_transfer_queues().at(index); } + + queue::list const& get_graphics_queues() const { return graphics_queues; } + queue::list const& get_compute_queues() const { return compute_queues; } + queue::list const& get_transfer_queues() const { return transfer_queues; } + + VkDevice get() const { return vk_device; } + VolkDeviceTable const& call() const { return table; } + + bool wait_for_idle() const { return check(call().vkDeviceWaitIdle(vk_device)); } + + VkDescriptorPool get_descriptor_pool() const { return descriptor_pool; } + + physical_device const* get_physical_device() const { return _physical_device; } + + VkPhysicalDeviceFeatures const& get_features() const; + VkPhysicalDeviceProperties const& get_properties() const; + + bool is_surface_supported(VkSurfaceKHR surface) const; + + void set_allocator(allocator::ptr value) { _allocator = value; } + allocator::ptr get_allocator() { return _allocator; } + + VmaAllocator alloc() const { return _allocator != nullptr ? _allocator->get() : nullptr; } + +private: + bool create_descriptor_pool(); + + VkDevice vk_device = nullptr; + physical_device const* _physical_device = nullptr; + + VolkDeviceTable table = {}; + + VkDescriptorPool descriptor_pool = nullptr; + + queue::list graphics_queues; + queue::list compute_queues; + queue::list transfer_queues; + + allocator::ptr _allocator; +}; + +struct device_manager { + + device::ptr create(); + + device::ptr create(device::create_param const& param); + + device::list const& get_all() const { return list; } + + void wait_idle(); + + void clear(); + +private: + device::list list; +}; + +bool load_and_create_shader_module(device* device, data const& data, VkShaderModule& out); + +} // lava diff --git a/liblava/base/instance.cpp b/liblava/base/instance.cpp new file mode 100644 index 00000000..42f08ef7 --- /dev/null +++ b/liblava/base/instance.cpp @@ -0,0 +1,304 @@ +// file : liblava/base/instance.cpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#include <liblava/base/instance.hpp> +#include <liblava/base/memory.hpp> + +#define VK_LAYER_LUNARG_STANDARD_VALIDATION_NAME "VK_LAYER_LUNARG_standard_validation" +#define VK_LAYER_LUNARG_ASSISTENT_LAYER_NAME "VK_LAYER_LUNARG_assistant_layer" +#define VK_LAYER_RENDERDOC_CAPTURE_NAME "VK_LAYER_RENDERDOC_Capture" + +namespace lava { + +struct instance::impl { + + explicit impl(instance* instance_) : _instance(instance_) {} + + instance* _instance = nullptr; + instance::debug debug; + + void print_info() const; + + bool create_validation_report(); + void destroy_validation_report(); + + VkDebugUtilsMessengerEXT debug_messanger = nullptr; +}; + +instance::instance() : _impl(std::make_unique<impl>(this)) {} + +instance::~instance() { + + if (vk_instance) + destroy(); +} + +bool instance::create(create_param& param, debug& debug, name appName) { + + _impl->debug = debug; + + if (_impl->debug.validation) { + + if (!exists(param.layer_to_enable, VK_LAYER_LUNARG_STANDARD_VALIDATION_NAME)) + param.layer_to_enable.push_back(VK_LAYER_LUNARG_STANDARD_VALIDATION_NAME); + } + + if (_impl->debug.render_doc) { + + if (!exists(param.layer_to_enable, VK_LAYER_RENDERDOC_CAPTURE_NAME)) + param.layer_to_enable.push_back(VK_LAYER_RENDERDOC_CAPTURE_NAME); + } + + if (_impl->debug.assistent) { + + if (!exists(param.layer_to_enable, VK_LAYER_LUNARG_ASSISTENT_LAYER_NAME)) + param.layer_to_enable.push_back(VK_LAYER_LUNARG_ASSISTENT_LAYER_NAME); + } + + if (_impl->debug.utils) { + + if (!exists(param.extension_to_enable, VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) + param.extension_to_enable.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + if (!param.check()) { + + log()->error("instance create param failed"); + return false; + } + + VkApplicationInfo application_info + { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = appName ? appName : _lava_, + .applicationVersion = VK_MAKE_VERSION(0, 1, 0), + .pEngineName = _lava_, + }; + + application_info.engineVersion = VK_MAKE_VERSION(_internal_version.major, _internal_version.minor, _internal_version.patch); + application_info.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo instanceCreateInfo + { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &application_info, + .enabledLayerCount = to_ui32(param.layer_to_enable.size()), + .ppEnabledLayerNames = param.layer_to_enable.data(), + .enabledExtensionCount = to_ui32(param.extension_to_enable.size()), + .ppEnabledExtensionNames = param.extension_to_enable.data(), + }; + + auto result = vkCreateInstance(&instanceCreateInfo, memory::alloc(), &vk_instance); + if (failed(result)) + return false; + + volkLoadInstance(vk_instance); + + if (!enumerate_physical_devices()) + return false; + + if (_impl->debug.info) + _impl->print_info(); + + if (_impl->debug.utils) + if (!_impl->create_validation_report()) + return false; + + return true; +} + +void instance::destroy() { + + if (!vk_instance) + return + + physical_devices.clear(); + + if (_impl->debug.utils) + _impl->destroy_validation_report(); + + vkDestroyInstance(vk_instance, memory::alloc()); + vk_instance = nullptr; +} + +bool instance::create_param::check() const { + + auto layer_properties = enumerate_layer_properties(); + for (auto const& layer_name : layer_to_enable) { + + auto itr = std::find_if(layer_properties.begin(), layer_properties.end(), + [&](VkLayerProperties const& extProp) { + return strcmp(layer_name, extProp.layerName) == 0; + }); + + if (itr == layer_properties.end()) + return false; + } + + auto extensions_properties = enumerate_extension_properties(); + for (auto const& ext_name : extension_to_enable) { + + auto itr = std::find_if(extensions_properties.begin(), extensions_properties.end(), + [&](VkExtensionProperties const& extProp) { + return strcmp(ext_name, extProp.extensionName) == 0; + }); + + if (itr == extensions_properties.end()) + return false; + } + + return true; +} + +static VKAPI_ATTR VkBool32 VKAPI_CALL validation_callback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, + VkDebugUtilsMessageTypeFlagsEXT message_type, const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data) { + + if (message_severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { + + log()->error("validation report: {}", callback_data->pMessage); + assert(!"check validation error"); + + } else if (message_severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) + log()->warn("validation report: {}", callback_data->pMessage); + else if (message_severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) + log()->info("validation report: {}", callback_data->pMessage); + else if (message_severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) + log()->trace("validation report: {}", callback_data->pMessage); + + return VK_FALSE; +} + +bool instance::impl::create_validation_report() { + + VkDebugUtilsMessengerCreateInfoEXT create_info + { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .messageSeverity = 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_callback, + }; + + if (debug.verbose) + create_info.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT; + + return check(vkCreateDebugUtilsMessengerEXT(_instance->vk_instance, &create_info, memory::alloc(), &debug_messanger)); +} + +void instance::impl::destroy_validation_report() { + + if (!debug_messanger) + return; + + vkDestroyDebugUtilsMessengerEXT(_instance->vk_instance, debug_messanger, memory::alloc()); + + debug_messanger = nullptr; +} + +void instance::impl::print_info() const { + + auto global_extensions = instance::enumerate_extension_properties(); + log()->info("found {} global extensions", global_extensions.size()); + for (auto const& extension : global_extensions) { + + log()->info("- {:38} - {:3}", extension.extensionName, VK_VERSION_PATCH(extension.specVersion)); + } + + auto layer_properties = instance::enumerate_layer_properties(); + log()->info("found {} instance layers:", layer_properties.size()); + for (auto const& layer : layer_properties) { + + log()->info("- {:40} - {:8} - {:2} - {}", layer.layerName, version_to_string(layer.specVersion), layer.implementationVersion, layer.description); + + auto extensions = instance::enumerate_extension_properties(layer.layerName); + log()->info("-- Found {} extensions", extensions.size()); + + for (auto const& extension : extensions) { + + log()->info("--- {:38} - {:6}", extension.extensionName, version_to_string(extension.specVersion)); + } + } + + log()->info("Found {} physical devices", _instance->physical_devices.size()); + + auto device_index = 0u; + for (auto& physical_device : _instance->physical_devices) { + + auto& properties = physical_device.get_properties(); + + log()->info("- {}. {} - {}", device_index++, properties.deviceName, physical_device.get_device_type_string()); + log()->info("-- apiVersion: {} - driverVersion: {}", version_to_string(properties.apiVersion), version_to_string(properties.driverVersion)); + log()->info("-- vendorID: {} - deviceID: {}", properties.vendorID, properties.deviceID); + } +} + +VkLayerPropertiesList instance::enumerate_layer_properties() { + + auto layer_count = 0u; + auto result = vkEnumerateInstanceLayerProperties(&layer_count, nullptr); + if (failed(result)) + return {}; + + VkLayerPropertiesList list(layer_count); + result = vkEnumerateInstanceLayerProperties(&layer_count, list.data()); + if (failed(result)) + return {}; + + return list; +} + +VkExtensionPropertiesList instance::enumerate_extension_properties(name layer_name) { + + auto property_count = 0u; + auto result = vkEnumerateInstanceExtensionProperties(layer_name, &property_count, nullptr); + if (failed(result)) + return {}; + + VkExtensionPropertiesList list(property_count); + result = vkEnumerateInstanceExtensionProperties(layer_name, &property_count, list.data()); + if (failed(result)) + return {}; + + return list; +} + +bool instance::enumerate_physical_devices() { + + physical_devices.clear(); + + auto count = 0u; + auto result = vkEnumeratePhysicalDevices(vk_instance, &count, nullptr); + if (failed(result)) + return false; + + VkPhysicalDevices devices(count); + result = vkEnumeratePhysicalDevices(vk_instance, &count, devices.data()); + if (failed(result)) + return false; + + for (auto& device : devices) { + + physical_device physical_device; + physical_device.initialize(device); + physical_devices.push_back(std::move(physical_device)); + } + + return true; +} + +internal_version instance::get_version() { + + ui32 instance_version = VK_API_VERSION_1_0; + + auto enumerate_instance_version = (PFN_vkEnumerateInstanceVersion)vkGetInstanceProcAddr(nullptr, "vkEnumerateInstanceVersion"); + if (enumerate_instance_version) + enumerate_instance_version(&instance_version); + + internal_version version; + version.major = VK_VERSION_MAJOR(instance_version); + version.minor = VK_VERSION_MINOR(instance_version); + version.patch = VK_VERSION_PATCH(VK_HEADER_VERSION); + return version; +} + +} // lava diff --git a/liblava/base/instance.hpp b/liblava/base/instance.hpp new file mode 100644 index 00000000..d39afe5c --- /dev/null +++ b/liblava/base/instance.hpp @@ -0,0 +1,65 @@ +// file : liblava/base/instance.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/base/physical_device.hpp> + +namespace lava { + +struct instance : no_copy_no_move { + + struct create_param { + + names layer_to_enable; + names extension_to_enable; + + bool check() const; + }; + + struct debug { + + bool validation = false; + bool assistent = false; + bool render_doc = false; + bool verbose = false; + bool utils = false; + bool info = false; + }; + + static instance& singleton() { + + static instance instance; + return instance; + } + + bool create(create_param& param, debug& debug, name appName = nullptr); + void destroy(); + + static VkLayerPropertiesList enumerate_layer_properties(); + static VkExtensionPropertiesList enumerate_extension_properties(name layer_name = nullptr); + + physical_device::list const& get_physical_devices() const { return physical_devices; } + + static physical_device const& get_first_physical_device() { return singleton().physical_devices.front(); } + + static VkInstance get() { return singleton().vk_instance; } + + static internal_version get_version(); + +private: + explicit instance(); + ~instance(); + + bool enumerate_physical_devices(); + + VkInstance vk_instance = nullptr; + + physical_device::list physical_devices; + + struct impl; + std::unique_ptr<impl> _impl; +}; + +} // lava diff --git a/liblava/base/memory.cpp b/liblava/base/memory.cpp new file mode 100644 index 00000000..46ce3c54 --- /dev/null +++ b/liblava/base/memory.cpp @@ -0,0 +1,143 @@ +// file : liblava/base/memory.cpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#include <liblava/base/memory.hpp> + +#ifdef _WIN32 +#pragma warning(push, 4) +#pragma warning(disable : 4127) // conditional expression is constant +#pragma warning(disable : 4100) // unreferenced formal parameter +#pragma warning(disable : 4189) // local variable is initialized but not referenced +#pragma warning(disable : 4324) // structure was padded due to alignment specifier +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wtype-limits" +#endif + +#define VMA_STATIC_VULKAN_FUNCTIONS 0 +#define VMA_IMPLEMENTATION +#include <vk_mem_alloc.h> + +#ifdef _WIN32 +#pragma warning(pop) +#else +#pragma GCC diagnostic pop +#endif + +#define LAVA_CUSTOM_CPU_ALLOCATION_CALLBACK_USER_DATA (void*)(intptr_t) 20180208 + +namespace lava { + +static void* custom_cpu_allocation(void* user_data, size_t size, size_t alignment, VkSystemAllocationScope allocation_scope) { + + assert(user_data == LAVA_CUSTOM_CPU_ALLOCATION_CALLBACK_USER_DATA); + return alloc_data(size, alignment); +} + +static void* custom_cpu_reallocation(void* user_data, void* original, size_t size, size_t alignment, VkSystemAllocationScope allocation_scope) { + + assert(user_data == LAVA_CUSTOM_CPU_ALLOCATION_CALLBACK_USER_DATA); + return realloc_data(original, size, alignment); +} + +static void custom_cpu_free(void* user_Data, void* memory) { + + assert(user_Data == LAVA_CUSTOM_CPU_ALLOCATION_CALLBACK_USER_DATA); + free_data(memory); +} + +memory::memory() { + + if (!use_custom_cpu_callbacks) + return; + + vk_callbacks.pUserData = LAVA_CUSTOM_CPU_ALLOCATION_CALLBACK_USER_DATA; + vk_callbacks.pfnAllocation = &custom_cpu_allocation; + vk_callbacks.pfnReallocation = &custom_cpu_reallocation; + vk_callbacks.pfnFree = &custom_cpu_free; +} + +type memory::find_type_with_properties(VkPhysicalDeviceMemoryProperties properties, ui32 type_bits, VkMemoryPropertyFlags required_properties) { + + auto bits = type_bits; + auto len = std::min(properties.memoryTypeCount, 32u); + + for (auto i = 0u; i < len; ++i) { + + if ((bits & 1) == 1) + if ((properties.memoryTypes[i].propertyFlags & required_properties) == required_properties) + return (int)i; + + bits >>= 1; + } + + return no_type; +} + +type memory::find_type(VkPhysicalDevice gpu, VkMemoryPropertyFlags properties, ui32 type_bits) { + + VkPhysicalDeviceMemoryProperties prop{}; + vkGetPhysicalDeviceMemoryProperties(gpu, &prop); + + for (auto i = 0u; i < prop.memoryTypeCount; ++i) + if ((prop.memoryTypes[i].propertyFlags & properties) == properties && type_bits & (1 << i)) + return i; + + return no_type; +} + +allocator::allocator(VkPhysicalDevice physical_device, VkDevice device) { + + VmaVulkanFunctions vulkan_function + { + .vkGetPhysicalDeviceProperties = vkGetPhysicalDeviceProperties, + .vkGetPhysicalDeviceMemoryProperties = vkGetPhysicalDeviceMemoryProperties, + .vkAllocateMemory = vkAllocateMemory, + .vkFreeMemory = vkFreeMemory, + .vkMapMemory = vkMapMemory, + .vkUnmapMemory = vkUnmapMemory, + .vkFlushMappedMemoryRanges = vkFlushMappedMemoryRanges, + .vkInvalidateMappedMemoryRanges = vkInvalidateMappedMemoryRanges, + .vkBindBufferMemory = vkBindBufferMemory, + .vkBindImageMemory = vkBindImageMemory, + .vkGetBufferMemoryRequirements = vkGetBufferMemoryRequirements, + .vkGetImageMemoryRequirements = vkGetImageMemoryRequirements, + .vkCreateBuffer = vkCreateBuffer, + .vkDestroyBuffer = vkDestroyBuffer, + .vkCreateImage = vkCreateImage, + .vkDestroyImage = vkDestroyImage, + .vkCmdCopyBuffer = vkCmdCopyBuffer, +#if VMA_DEDICATED_ALLOCATION + .vkGetBufferMemoryRequirements2KHR = vkGetBufferMemoryRequirements2KHR, + .vkGetImageMemoryRequirements2KHR = vkGetImageMemoryRequirements2KHR, +#endif +#if VMA_BIND_MEMORY2 + .vkBindBufferMemory2KHR = vkBindBufferMemory2KHR, + .vkBindImageMemory2KHR = vkBindImageMemory2KHR, +#endif + }; + + VmaAllocatorCreateInfo allocator_info + { + .physicalDevice = physical_device, + .device = device, + .pAllocationCallbacks = memory::alloc(), + .pVulkanFunctions = &vulkan_function, + }; + + check(vmaCreateAllocator(&allocator_info, &vma_allocator)); +} + +allocator::~allocator() { + + if (!vma_allocator) + return; + + vmaDestroyAllocator(vma_allocator); + vma_allocator = nullptr; +} + +} // lava diff --git a/liblava/base/memory.hpp b/liblava/base/memory.hpp new file mode 100644 index 00000000..993b312b --- /dev/null +++ b/liblava/base/memory.hpp @@ -0,0 +1,64 @@ +// file : liblava/base/memory.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/base/base.hpp> + +#include <vk_mem_alloc.h> + +namespace lava { + +struct allocator { + + explicit allocator(VkPhysicalDevice physical_device, VkDevice device); + ~allocator(); + + using ptr = std::shared_ptr<allocator>; + + static ptr make(VkPhysicalDevice physical_device, VkDevice device) { + + return std::make_shared<allocator>(physical_device, device); + } + + bool valid() const { return vma_allocator != nullptr; } + + VmaAllocator get() const { return vma_allocator; } + +private: + VmaAllocator vma_allocator = nullptr; +}; + +struct memory : no_copy_no_move { + + static memory& get() { + + static memory memory; + return memory; + } + + static VkAllocationCallbacks* alloc() { + + if (get().use_custom_cpu_callbacks) + return &get().vk_callbacks; + + return nullptr; + } + + static type find_type_with_properties(VkPhysicalDeviceMemoryProperties properties, ui32 type_bits, + VkMemoryPropertyFlags required_properties); + + static type find_type(VkPhysicalDevice gpu, VkMemoryPropertyFlags properties, ui32 type_bits); + + void set_callbacks(VkAllocationCallbacks const& callbacks) { vk_callbacks = callbacks; } + void set_use_custom_cpu_callbacks(bool value) { use_custom_cpu_callbacks = value; } + +private: + memory(); + + bool use_custom_cpu_callbacks = true; + VkAllocationCallbacks vk_callbacks = {}; +}; + +} // lava diff --git a/liblava/base/physical_device.cpp b/liblava/base/physical_device.cpp new file mode 100644 index 00000000..cf07a002 --- /dev/null +++ b/liblava/base/physical_device.cpp @@ -0,0 +1,109 @@ +// file : liblava/base/physical_device.cpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#include <liblava/base/physical_device.hpp> + +namespace lava { + +void physical_device::initialize(VkPhysicalDevice vk_device_) { + + vk_device = vk_device_; + + vkGetPhysicalDeviceProperties(vk_device, &properties); + vkGetPhysicalDeviceFeatures(vk_device, &features); + vkGetPhysicalDeviceMemoryProperties(vk_device, &memory_properties); + + auto queue_family_count = 0u; + vkGetPhysicalDeviceQueueFamilyProperties(vk_device, &queue_family_count, nullptr); + if (queue_family_count > 0) { + + queue_family_properties.resize(queue_family_count); + vkGetPhysicalDeviceQueueFamilyProperties(vk_device, &queue_family_count, queue_family_properties.data()); + } + + auto extension_count = 0u; + vkEnumerateDeviceExtensionProperties(vk_device, nullptr, &extension_count, nullptr); + if (extension_count > 0) { + + extension_properties.resize(extension_count); + vkEnumerateDeviceExtensionProperties(vk_device, nullptr, &extension_count, extension_properties.data()); + } +} + +bool physical_device::is_supported(string_ref extension) const { + + for (auto& extension_property : extension_properties) { + + if (string(extension_property.extensionName) == extension) + return true; + } + + return false; +} + +bool physical_device::get_queue_family(ui32& index, VkQueueFlags flags) const { + + for (size_t i = 0, e = queue_family_properties.size(); i != e; ++i) { + + if (queue_family_properties[i].queueFlags & flags) { + + index = (ui32)i; + return true; + } + } + + return false; +} + +device::create_param physical_device::create_default_device_param() const { + + device::create_param create_param; + create_param._physical_device = this; + create_param.set_default_queues(); + + return create_param; +} + +string physical_device::get_device_type_string() const { + + string result; + switch (properties.deviceType) { + + case VK_PHYSICAL_DEVICE_TYPE_OTHER: + result = "OTHER"; + break; + case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: + result = "INTEGRATED_GPU"; + break; + case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: + result = "DISCRETE_GPU"; + break; + case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: + result = "VIRTUAL_GPU"; + break; + case VK_PHYSICAL_DEVICE_TYPE_CPU: + result = "CPU"; + break; + default: + result = "UNKNOWN"; + break; + } + return result; +} + +bool physical_device::is_swapchain_supported() const { + + return is_supported(VK_KHR_SWAPCHAIN_EXTENSION_NAME); +} + +bool physical_device::is_surface_supported(ui32 queue_family_index, VkSurfaceKHR surface) const { + + VkBool32 res = VK_FALSE; + if (failed(vkGetPhysicalDeviceSurfaceSupportKHR(vk_device, queue_family_index, surface, &res))) + return false; + + return res == VK_TRUE; +} + +} // lava diff --git a/liblava/base/physical_device.hpp b/liblava/base/physical_device.hpp new file mode 100644 index 00000000..719ef2db --- /dev/null +++ b/liblava/base/physical_device.hpp @@ -0,0 +1,49 @@ +// file : liblava/base/physical_device.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/base/device.hpp> + +namespace lava { + +struct physical_device { + + using list = std::vector<physical_device>; + + physical_device() = default; + + void initialize(VkPhysicalDevice vk_device); + + bool is_supported(string_ref extension) const; + bool get_queue_family(ui32& index, VkQueueFlags flags) const; + + device::create_param create_default_device_param() const; + + VkPhysicalDeviceProperties const& get_properties() const { return properties; } + VkPhysicalDeviceFeatures const& get_features() const { return features; } + VkPhysicalDeviceMemoryProperties const& get_memory_properties() const { return memory_properties; } + + VkQueueFamilyPropertiesList const& getQueueFamilyProperties() const { return queue_family_properties; } + VkExtensionPropertiesList const& getExtensionProperties() const { return extension_properties; } + + VkPhysicalDevice get() const { return vk_device; } + + string get_device_type_string() const; + + bool is_swapchain_supported() const; + bool is_surface_supported(ui32 queue_family_index, VkSurfaceKHR surface) const; + +private: + VkPhysicalDevice vk_device = nullptr; + + VkPhysicalDeviceProperties properties = {}; + VkPhysicalDeviceFeatures features = {}; + VkPhysicalDeviceMemoryProperties memory_properties = {}; + + VkQueueFamilyPropertiesList queue_family_properties; + VkExtensionPropertiesList extension_properties; +}; + +} // lava diff --git a/liblava/core.hpp b/liblava/core.hpp new file mode 100644 index 00000000..f1bd43fc --- /dev/null +++ b/liblava/core.hpp @@ -0,0 +1,12 @@ +// file : liblava/core.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/core/data.hpp> +#include <liblava/core/id.hpp> +#include <liblava/core/math.hpp> +#include <liblava/core/time.hpp> +#include <liblava/core/types.hpp> +#include <liblava/core/version.hpp> diff --git a/liblava/core/data.hpp b/liblava/core/data.hpp new file mode 100644 index 00000000..b3beca3f --- /dev/null +++ b/liblava/core/data.hpp @@ -0,0 +1,166 @@ +// file : liblava/core/data.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/core/types.hpp> + +#include <string.h> + +namespace lava { + +using data_ptr = char*; +using data_cptr = char const*; + +struct data_provider { + + using alloc_func = std::function<data_ptr(size_t, size_t)>; + alloc_func alloc; + + using free_func = std::function<void()>; + free_func free; + + using realloc_func = std::function<data_ptr(data_ptr, size_t, size_t)>; + realloc_func realloc; +}; + +template <typename T> +inline data_ptr as_ptr(T* value) { return (data_ptr)value; } + +struct data { + + data_ptr ptr = nullptr; + + size_t size = 0; + size_t alignment = 0; +}; + +template <typename T> +inline T align_up(T val, T align) { return (val + align - 1) / align * align; } + +inline size_t align(size_t size, size_t min = 0) { + + if (min == 0) + return align_up(size, sizeof(void*)); + + return align_up((size + min - 1) & ~(min - 1), sizeof(void*)); +} + +template <typename T> +inline size_t align(size_t min = 0) { return align(sizeof(T), min); } + +inline void* alloc_data(size_t size, size_t alignment = 8) { +#if _WIN32 + return _aligned_malloc(size, alignment); +#else + return aligned_alloc(alignment, size); +#endif +} + +inline void free_data(void* data) { +#if _WIN32 + _aligned_free(data); +#else + free(data); +#endif +} + +inline void* realloc_data(void* data, size_t size, size_t alignment) { +#if _WIN32 + return _aligned_realloc(data, size, alignment); +#else + return realloc(data, align(size, alignment)); +#endif +} + +inline size_t next_pow_2(size_t x) { + + x--; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x++; + return x; +} + +struct scope_data : data { + + explicit scope_data(size_t length = 0, bool alloc = true) { + + if (length) + set(length, alloc); + } + + explicit scope_data(i64 length, bool alloc = true) : scope_data(to_size_t(length), alloc) {} + explicit scope_data(data const& data) { + + ptr = data.ptr; + size = data.size; + alignment = data.alignment; + } + + ~scope_data() { + + free(); + } + + void set(size_t length, bool alloc = true) { + + size = length; + alignment = align<data_ptr>(); + + if (alloc) + allocate(); + } + + bool allocate() { + + ptr = as_ptr(alloc_data(size, alignment)); + return ptr != nullptr; + } + + void free() { + + if (!ptr) + return; + + free_data(ptr); + ptr = nullptr; + } +}; + +#ifndef __GNUC__ +#define strndup(p, n) _strdup(p) +#endif + +inline char* human_readable(size_t const sz) { + + static ui32 const buffer_size = 32; + + char const prefixes[] = "KMGTPEZY"; + char buf[buffer_size]; + i32 which = -1; + + auto result = to_r64(sz); + while (result > 1024 && which < 7) { + + result /= 1024; + ++which; + } + + char unit[] = "\0i"; + if (which >= 0) + unit[0] = prefixes[which]; + + snprintf(buf, buffer_size, "%.2f %sB", result, unit); + return strndup(buf, buffer_size); +} + +#ifndef __GNUC__ +#undef strndup +#endif + +} // lava diff --git a/liblava/core/id.hpp b/liblava/core/id.hpp new file mode 100644 index 00000000..e4584b9f --- /dev/null +++ b/liblava/core/id.hpp @@ -0,0 +1,200 @@ +// file : liblava/core/id.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/core/types.hpp> + +#include <memory> +#include <atomic> +#include <deque> +#include <mutex> +#include <set> + +namespace lava { + +struct id { + + using ref = id const&; + using set = std::set<id>; + using set_ref = set const&; + using list = std::vector<id>; + using map = std::map<id, id>; + using index_map = std::map<id, index>; + using string_map = std::map<id, string>; + + type value = undef; + ui32 version = 0; + + bool is_valid() const { return value != undef; } + string to_string(bool show_version = false) const { + + char result[32]; + if (show_version) + snprintf(result, sizeof(result), "%u.%u", value, version); + else + snprintf(result, sizeof(result), "%u", value); + return string(result); + } + + void invalidate() { *this = {}; } + + bool operator==(ref rhs) const { return (value == rhs.value) && (version == rhs.version); } + bool operator!=(ref rhs) const { return !(*this == rhs); } + bool operator<(ref rhs) const { return std::tie(value, version) < std::tie(rhs.value, rhs.version); } + + bool check(id::map& map) { + + if (!is_valid()) + return false; + + if (!map.count(*this)) + return false; + + *this = map.at(*this); + return true; + } +}; + +using string_id_map = std::map<string, id>; +constexpr id const undef_id = id(); + +struct ids { + + static ids& global() { + + static ids instance; + return instance; + } + + static id next() { return ids::global().get_next(); } + static void free(id::ref id) { ids::global().reuse(id); } + + id get_next() { + + if (!reuse_ids) + return { ++next_id }; + + return get_next_locked(); + } + + void reuse(id::ref id) { + + if (reuse_ids) + reuse_locked(id); + } + + void set_reuse(bool state) { reuse_ids = state; } + bool is_reusing() const { return reuse_ids; } + + type get_max() const { return next_id; } + void set_max(type max) { + + if (max > next_id) + next_id = max; + } + +private: + id get_next_locked() { + + std::unique_lock<std::mutex> lock(queue_mutex); + if (free_ids.empty()) + return { ++next_id }; + + auto next_id = free_ids.front(); + free_ids.pop_front(); + return { next_id.value, next_id.version + 1 }; + } + + void reuse_locked(id::ref id) { + + std::unique_lock<std::mutex> lock(queue_mutex); + free_ids.push_back(id); + } + + std::atomic<type> next_id = { undef }; + std::mutex queue_mutex; + + bool reuse_ids = true; + std::deque<id> free_ids; +}; + +template <typename T> +inline id add_to_id_map(T const& object, std::map<id, T>& map) { + + auto next = ids::next(); + map.emplace(next, std::move(object)); + return next; +} + +template <typename T> +inline bool remove_from_id_map(id::ref object, std::map<id, T>& map) { + + if (!map.count(object)) + return false; + + map.erase(object); + ids::free(object); + + return true; +} + +template <typename T> +struct listeners { + + id add(typename T::func const& listener) { + + return add_to_id_map(listener, list); + } + + void remove(id& id) { + + if (remove_from_id_map(id, list)) + id.invalidate(); + } + + typename T::listeners const& get_list() const { return list; } + +private: + typename T::listeners list = {}; +}; + +template <typename T, typename Meta> +struct registry { + + using ptr = std::shared_ptr<T>; + using map = std::map<id, ptr>; + + using meta_map = std::map<id, Meta>; + + id create(Meta info = {}) { + + auto object = std::make_shared<T>(); + add(object, info); + + return object->get_id(); + } + + void add(ptr object, Meta info = {}) { + + objects.emplace(object->get_id(), object); + meta.emplace(object->get_id(), info); + } + + bool has(id::ref object) const { return objects.count(object); } + + ptr get(id::ref object) const { return objects.at(object).get(); } + Meta get_meta(id::ref object) const { return meta.at(object).get(); } + + map const& get_all() const { return objects; } + meta_map const& get_all_meta() const { return meta; } + + void remove(id::ref object) { objects.erase(object); } + +private: + map objects; + meta_map meta; +}; + +} // lava diff --git a/liblava/core/math.hpp b/liblava/core/math.hpp new file mode 100644 index 00000000..587c8a1e --- /dev/null +++ b/liblava/core/math.hpp @@ -0,0 +1,86 @@ +// file : liblava/core/math.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/core/types.hpp> + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include <glm/glm.hpp> +#include <glm/gtc/matrix_transform.hpp> +#include <glm/gtc/type_ptr.hpp> + +namespace lava { + +using v2 = glm::vec2; +using v3 = glm::vec3; +using v4 = glm::vec4; + +using uv2 = glm::uvec2; + +using mat3 = glm::mat3; +using mat4 = glm::mat4; + +using iv2 = glm::ivec2; +using iv3 = glm::ivec3; + +struct rect { + + rect() = default; + + rect(i32 left, i32 top, ui32 width, ui32 height) : left_top({ left, top }) { + + set_size({ width, height }); + } + + rect(iv2 const& left_top, ui32 width, ui32 height) : left_top(left_top) { + + set_size({ width, height }); + } + + rect(iv2 const& left_top, uv2 const& size) : left_top(left_top) { + + set_size(size); + } + + iv2 const& get_origin() const { return left_top; } + iv2 const& get_end_point() const { return right_bottom; } + + uv2 get_size() const { + + assert(left_top.x <= right_bottom.x); + assert(left_top.y <= right_bottom.y); + return { right_bottom.x - left_top.x, right_bottom.y - left_top.y }; + } + + void set_size(uv2 const& size) { + + right_bottom.x = left_top.x + size.x; + right_bottom.y = left_top.y + size.y; + } + + void move(iv2 const& offset) { + + left_top += offset; + right_bottom += offset; + } + + bool contains(iv2 point) const { + + return (left_top.x < point.x) && (left_top.y < point.y) && + (right_bottom.x > point.x) && (right_bottom.y > point.y); + } + +private: + iv2 left_top = iv2(); + iv2 right_bottom = iv2(); +}; + +template <typename T> +inline T ceil_div(T x, T y) { return (x + y - 1) / y; } + +v3 const default_color = v3{ 0.8118f, 0.0627f, 0.1255f }; // #CF1020 : 207, 16, 32 + +} // lava diff --git a/liblava/core/time.hpp b/liblava/core/time.hpp new file mode 100644 index 00000000..eaaef8df --- /dev/null +++ b/liblava/core/time.hpp @@ -0,0 +1,117 @@ +// file : liblava/core/time.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/core/types.hpp> + +#include <chrono> +#include <iomanip> +#include <sstream> + +namespace lava { + +inline time ms_in_seconds(ui32 ms) { return ms / 1000.f; } + +inline ui32 seconds_in_ms(time sec) { return to_ui32(sec * 1000.f); } + +struct time_info { + + ui32 hours = 0; + ui32 minutes = 0; + ui32 seconds = 0; + ui32 ms = 0; +}; + +inline time to_time(ui32 hours = 0, ui32 minutes = 0, ui32 seconds = 0, ui32 ms = 0) { + + return hours * 3600 + minutes * 60 + seconds + ms / 1000.; +} + +inline time to_time(time_info info) { + + return to_time(info.hours, info.minutes, info.seconds, info.ms); +} + +inline time_info to_time(time time) { + + time_info info; + info.hours = to_ui32(time / 3600); + auto temp = time - info.hours * 3600; + info.minutes = to_ui32(temp / 60); + temp = temp - info.minutes * 60; + info.seconds = to_ui32(temp); + temp = temp - info.seconds; + info.ms = to_ui32(temp * 1000); + return info; +} + +using time_point = std::chrono::high_resolution_clock::time_point; +using duration = std::chrono::high_resolution_clock::duration; + +inline float to_float_seconds(duration d) { + + return std::chrono::duration_cast<std::chrono::duration<float>>(d).count(); +} + +struct timer { + + timer() : time_point(clock::now()) {} + + void reset() { time_point = clock::now(); } + + time elapsed() const { + + return std::chrono::duration_cast<second>(clock::now() - time_point).count(); + } + +private: + using clock = std::chrono::high_resolution_clock; + using second = std::chrono::duration<time, std::ratio<1>>; + + std::chrono::time_point<clock> time_point; +}; + +struct run_time { + + time seconds = 0.; + time clock = 0.016; + + time system = 0.; + time delta = 0.; + + bool use_fix_delta = false; + time fix_delta = 0.02; + + r32 speed = 1.f; + bool paused = false; + + time get_speed_delta() const { return delta * speed; } +}; + +#pragma warning(push) +#pragma warning(disable : 4996) //_CRT_SECURE_NO_WARNINGS + +template <typename CLOCK_TYPE = std::chrono::system_clock> +inline string time_stamp(const typename CLOCK_TYPE::time_point& time_point, string_ref format = "%Y-%m-%d %H-%M-%S") { + + auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(time_point.time_since_epoch()) % 1000; + + const std::time_t t = CLOCK_TYPE::to_time_t(time_point); + const std::tm tm = *std::localtime(std::addressof(t)); + + std::ostringstream stm; + stm << std::put_time(std::addressof(tm), format.c_str()) << '.' << std::setfill('0') << std::setw(3) << ms.count(); + return stm.str(); +} + +inline string get_current_time_and_date() { + + auto now = std::chrono::system_clock::now(); + return time_stamp(now); +} + +#pragma warning(pop) + +} // lava diff --git a/liblava/core/types.hpp b/liblava/core/types.hpp new file mode 100644 index 00000000..76cd14dd --- /dev/null +++ b/liblava/core/types.hpp @@ -0,0 +1,122 @@ +// file : liblava/core/types.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/def.hpp> + +#include <cassert> +#include <cstdint> +#include <string> +#include <string_view> +#include <vector> +#include <map> + +#include <functional> + +#include <enum.h> + +namespace lava { + +using int8 = std::int8_t; +using i8 = int8; + +using uint8 = std::uint8_t; +using ui8 = uint8; + +using int16 = std::int16_t; +using i16 = int16; + +using uint16 = std::uint16_t; +using ui16 = uint16; + +using int32 = std::int32_t; +using i32 = int32; + +using uint32 = std::uint32_t; +using ui32 = uint32; + +using int64 = std::int64_t; +using i64 = int64; + +using uint64 = std::uint64_t; +using ui64 = uint64; + +using char8 = std::int_fast8_t; +using c8 = char8; + +using uchar8 = std::uint_fast8_t; +using uc8 = uchar8; + +using char16 = int16; +using c16 = char16; + +using uchar16 = uint16; +using uc16 = uchar16; + +using char32 = int32; +using c32 = char32; + +using uchar32 = uint32; +using uc32 = uchar32; + +using size_t = std::size_t; +using uchar = unsigned char; +using r32 = float; +using r64 = double; + +using time = r64; +using real = r64; + +using type = ui32; +constexpr type const no_type = 0xffffffff; +constexpr type const undef = 0; + +using index = type; +constexpr index const no_index = no_type; +using index_list = std::vector<index>; +using index_map = std::map<index, index>; + +using string = std::string; +using string_ref = string const&; +using string_list = std::vector<string>; +using string_view = std::string_view; + +using name = char const*; +using names = std::vector<name>; +using names_ref = names const&; + +constexpr name _lava_ = "lava"; +constexpr name _liblava_ = "liblava"; + +template <typename T> +inline r32 to_r32(T value) { return static_cast<r32>(value); } + +template <typename T> +inline r64 to_r64(T value) { return static_cast<r64>(value); } + +template <typename T> +inline i32 to_i32(T value) { return static_cast<i32>(value); } + +template <typename T> +inline i64 to_i64(T value) { return static_cast<i64>(value); } + +template <typename T> +inline ui32 to_ui32(T value) { return static_cast<ui32>(value); } + +template <typename T> +inline ui64 to_ui64(T value) { return static_cast<ui64>(value); } + +template <typename T> +inline size_t to_size_t(T value) { return static_cast<size_t>(value); } + +struct no_copy_no_move { + + no_copy_no_move() = default; + no_copy_no_move(no_copy_no_move const&) = delete; + + void operator=(no_copy_no_move const&) = delete; +}; + +} // lava diff --git a/liblava/core/version.hpp b/liblava/core/version.hpp new file mode 100644 index 00000000..626b3d34 --- /dev/null +++ b/liblava/core/version.hpp @@ -0,0 +1,42 @@ +// file : liblava/core/version.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/core/types.hpp> + +namespace lava { + +struct internal_version { + + i32 major = LIBLAVA_VERSION_MAJOR; + i32 minor = LIBLAVA_VERSION_MINOR; + i32 patch = LIBLAVA_VERSION_PATCH; +}; + +constexpr internal_version const _internal_version = {}; + +enum class version_stage { + + preview, + alpha, + beta, + rc, + release +}; + +struct version { + + i32 year = 2019; + i32 release = 0; + version_stage stage = version_stage::preview; + i32 rev = 2; +}; + +constexpr version const _version = {}; + +constexpr name _build_date = LIBLAVA_BUILD_DATE; +constexpr name _build_time = LIBLAVA_BUILD_TIME; + +} // lava diff --git a/liblava/def.hpp b/liblava/def.hpp new file mode 100644 index 00000000..06045de3 --- /dev/null +++ b/liblava/def.hpp @@ -0,0 +1,20 @@ +// file : liblava/def.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#if defined(NDEBUG) +#define LIBLAVA_DEBUG 0 +#endif + +#ifndef LIBLAVA_DEBUG +#define LIBLAVA_DEBUG 1 +#endif + +#define LIBLAVA_BUILD_DATE __DATE__ +#define LIBLAVA_BUILD_TIME __TIME__ + +#define LIBLAVA_VERSION_MAJOR 0 +#define LIBLAVA_VERSION_MINOR 4 +#define LIBLAVA_VERSION_PATCH 2 diff --git a/liblava/frame.hpp b/liblava/frame.hpp new file mode 100644 index 00000000..a1828e0c --- /dev/null +++ b/liblava/frame.hpp @@ -0,0 +1,13 @@ +// file : liblava/frame.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/frame/frame.hpp> +#include <liblava/frame/input.hpp> +#include <liblava/frame/render_target.hpp> +#include <liblava/frame/render_thread.hpp> +#include <liblava/frame/renderer.hpp> +#include <liblava/frame/swapchain.hpp> +#include <liblava/frame/window.hpp> diff --git a/liblava/frame/frame.cpp b/liblava/frame/frame.cpp new file mode 100644 index 00000000..2306778d --- /dev/null +++ b/liblava/frame/frame.cpp @@ -0,0 +1,307 @@ +// file : liblava/frame/frame.cpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#include <liblava/frame/frame.hpp> +#include <liblava/base/memory.hpp> + +#if !LIBLAVA_DEBUG && _WIN32 +#include <windows.h> +#include <iostream> +#endif + +void hide_console(lava::name program) { + +#if !LIBLAVA_DEBUG && _WIN32 + std::cout << "starting " << program; + + const auto dot_count = 3; + for (auto i = 0u; i < dot_count; ++i) { + + lava::sleep(1.0 / dot_count); + std::cout << "."; + } + + FreeConsole(); +#endif +} + +void log_command_line(argh::parser& cmd_line) { + + if (!cmd_line.pos_args().empty()) { + + for (auto& pos_arg : cmd_line.pos_args()) + lava::log()->info("cmd line (pos) {}", pos_arg.c_str()); + } + + if (!cmd_line.flags().empty()) { + + for (auto& flag : cmd_line.flags()) + lava::log()->info("cmd line (flag) {}", flag.c_str()); + } + + if (!cmd_line.params().empty()) { + + for (auto& param : cmd_line.params()) + lava::log()->info("cmd line (para) {} = {}", param.first.c_str(), param.second.c_str()); + } +} + +lava::time lava::now() { return glfwGetTime(); } + +namespace lava { + +static bool _initialized = false; + +frame::frame(argh::parser cmd_line) { + + frame_config config; + config.cmd_line = cmd_line; + + setup(config); +} + +frame::frame(frame_config config) { + + setup(config); +} + +frame::~frame() { teardown(); } + +bool frame::ready() const { return _initialized; } + +bool frame::setup(frame_config config) { + + if (_initialized) + return false; + + cmd_line = config.cmd_line; + +#if LIBLAVA_DEBUG + config.log.debug = true; + config.debug.validation = true; + config.debug.utils = true; +#endif + + hide_console(config.app); + + if (cmd_line[{ "-d", "--debug" }]) + config.debug.validation = true; + + if (cmd_line[{ "-a", "--assist" }]) + config.debug.assistent = true; + + if (cmd_line[{ "-r", "--renderdoc" }]) + config.debug.render_doc = true; + + if (cmd_line[{ "-v", "--verbose" }]) + config.debug.verbose = true; + + if (cmd_line[{ "-u", "--utils" }]) + config.debug.utils = true; + + if (cmd_line[{ "-i", "--info" }]) + config.debug.info = true; + + if (auto log_level = -1; cmd_line({ "-l", "--log" }) >> log_level) + config.log.level = log_level; + + setup_log(config.log); + + log()->info(">>> {} / {} - {} {}", to_string(_version).c_str(), to_string(_internal_version).c_str(), _build_date, _build_time); + + log_command_line(cmd_line); + + if (config.log.level >= 0) + log()->info("log {}", spdlog::level::to_str((spdlog::level::level_enum)config.log.level)); + + glfwSetErrorCallback([](i32 error, name description) { + + log()->error("glfw {} - {}", error, description); + }); + + log()->debug("glfw {}", glfwGetVersionString()); + + if (glfwInit() != GLFW_TRUE) { + + log()->error("glfw init failed"); + return false; + } + + if (glfwVulkanSupported() != GLFW_TRUE) { + + log()->error("glfw vulkan not supported"); + return false; + } + + glfwDefaultWindowHints(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + auto result = volkInitialize(); + if (failed(result)) { + + log()->error("volk init failed"); + return false; + } + + log()->info("vulkan {}", to_string(instance::get_version()).c_str()); + + instance::create_param param; + + auto glfw_extensions_count = 0u; + auto glfw_extensions = glfwGetRequiredInstanceExtensions(&glfw_extensions_count); + for (auto i = 0u; i < glfw_extensions_count; ++i) + param.extension_to_enable.push_back(glfw_extensions[i]); + + if (!instance::singleton().create(param, config.debug, config.app)) { + + log()->error("instance create failed"); + return false; + } + + log()->debug("physfs {}", to_string(file_system::get_version()).c_str()); + + if (!file_system::get().initialize(cmd_line[0].c_str(), config.org, config.app, config.ext)) { + + log()->error("file system init failed"); + return false; + } + + file_system::get().mount_res(); + + if (config.data_folder) + file_system::get().create_data_folder(); + + _initialized = true; + + log()->info("---"); + + return true; +} + +void frame::teardown() { + + if (!_initialized) + return; + + manager.clear(); + + instance::singleton().destroy(); + + glfwTerminate(); + + log()->info("<<<"); + + log()->flush(); + spdlog::drop_all(); + + file_system::get().terminate(); + + _initialized = false; +} + +bool frame::run() { + + if (running) + return false; + + running = true; + start_time = now(); + + while (running) { + + if (!run_step()) + return false; + } + + manager.wait_idle(); + + trigger_run_end(); + + running = false; + start_time = 0.0; + + return true; +} + +bool frame::run_step() { + + handle_events(wait_for_events); + + for (auto& func : run_map) + if (!func.second()) + return false; + + return true; +} + +bool frame::shut_down() { + + if (!running) + return false; + + running = false; + + return true; +} + +id frame::add_run(run_func_ref func) { + + auto id = ids::next(); + run_map.emplace(id, func); + + return id; +} + +bool frame::remove_run(id::ref id) { + + if (!run_map.count(id)) + return false; + + run_map.erase(id); + + return true; +} + +id frame::add_run_end(run_end_func_ref func) { + + auto id = ids::next(); + run_end_map.emplace(id, func); + + return id; +} + +bool frame::remove_run_end(id::ref id) { + + if (!run_end_map.count(id)) + return false; + + run_end_map.erase(id); + + return true; +} + +void frame::trigger_run_end() { + + for (auto& func : run_end_map) + func.second(); +} + +} // lava + +VkSurfaceKHR lava::create_surface(GLFWwindow* window) { + + VkSurfaceKHR surface = nullptr; + if (failed(glfwCreateWindowSurface(instance::get(), window, memory::alloc(), &surface))) + return nullptr; + + return surface; +} + +void lava::handle_events(bool wait_for_events) { + + if (wait_for_events) + glfwWaitEvents(); + else + glfwPollEvents(); +} diff --git a/liblava/frame/frame.hpp b/liblava/frame/frame.hpp new file mode 100644 index 00000000..1d82b8e8 --- /dev/null +++ b/liblava/frame/frame.hpp @@ -0,0 +1,119 @@ +// file : liblava/frame/frame.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/base/instance.hpp> +#include <liblava/base/device.hpp> + +#define GLFW_INCLUDE_NONE +#define GLFW_INCLUDE_VULKAN +#include <GLFW/glfw3.h> + +#include <argh.h> + +namespace lava { + +struct frame_config { + + argh::parser cmd_line; + + name org = _liblava_; + name app = _lava_; + name ext = _zip_; + + log_config log; + instance::debug debug; + + bool data_folder = false; +}; + +enum error { + + not_ready = -1, + create_failed = -2, + aborted = -3, +}; + +time now(); + +struct frame : no_copy_no_move { + + using ptr = std::shared_ptr<frame>; + + explicit frame(argh::parser cmd_line); + explicit frame(frame_config config); + ~frame(); + + static ptr make(argh::parser cmd_line) { return std::make_shared<frame>(cmd_line); } + static ptr make(frame_config config) { return std::make_shared<frame>(config); } + + bool ready() const; + + bool run(); + bool run_step(); + + bool shut_down(); + + device* create_device() { + + auto device = manager.create(); + if (!device) + return nullptr; + + return device.get(); + } + + device_manager manager; + + using run_func = std::function<bool()>; + using run_func_ref = run_func const&; + + id add_run(run_func_ref func); + bool remove_run(id::ref id); + + time get_running_time() const { + + if (start_time == 0.0) + return 0.0; + + return now() - start_time; + } + + using run_end_func = std::function<void()>; + using run_end_func_ref = run_end_func const&; + + id add_run_end(run_end_func_ref func); + bool remove_run_end(id::ref id); + + void trigger_run_end(); + + argh::parser& get_cmd_line() { return cmd_line; } + + bool waiting_for_events() const { return wait_for_events; } + void set_wait_for_events(bool value = true) { wait_for_events = value; } + +private: + + bool setup(frame_config config = {}); + void teardown(); + + argh::parser cmd_line; + + bool running = false; + bool wait_for_events = false; + time start_time = 0.0; + + using run_func_map = std::map<id, run_func>; + run_func_map run_map; + + using run_end_func_map = std::map<id, run_end_func>; + run_end_func_map run_end_map; +}; + +VkSurfaceKHR create_surface(GLFWwindow* window); + +void handle_events(bool wait_for_events = false); + +} // lava diff --git a/liblava/frame/input.cpp b/liblava/frame/input.cpp new file mode 100644 index 00000000..6ee8c7ee --- /dev/null +++ b/liblava/frame/input.cpp @@ -0,0 +1,97 @@ +// file : liblava/frame/input.cpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#include <liblava/frame/input.hpp> + +namespace lava { + +void input::clear_all_events() { + + clear_key_events(); + clear_scroll_events(); + clear_mouse_button_events(); + clear_mouse_move_events(); + clear_mouse_active_events(); +} + +} // lava + +void lava::handle_key_events(input& input) { + + for (auto& event : input.get_key_events()) { + + for (auto& listener : input.key_listeners.get_list()) + listener.second(event); + + for (auto& callback : input.get_callbacks()) + callback->on_key_event(event); + } + + input.clear_key_events(); +} + +void lava::handle_scroll_events(input& input) { + + for (auto& event : input.get_scroll_events()) { + + for (auto& listener : input.scroll_listeners.get_list()) + listener.second(event); + + for (auto& callback : input.get_callbacks()) + callback->on_scroll_event(event); + } + + input.clear_scroll_events(); +} + +void lava::handle_mouse_button_events(input& input) { + + for (auto& event : input.get_mouse_button_events()) { + + for (auto& listener : input.mouse_button_listeners.get_list()) + listener.second(event); + + for (auto& callback : input.get_callbacks()) + callback->on_mouse_button_event(event); + } + + input.clear_mouse_button_events(); +} + +void lava::handle_mouse_move_events(input& input) { + + for (auto& event : input.get_mouse_move_events()) { + + for (auto& listener : input.mouse_move_listeners.get_list()) + listener.second(event); + + for (auto& callback : input.get_callbacks()) + callback->on_mouse_move_event(event); + } + + input.clear_mouse_move_events(); +} + +void lava::handle_mouse_active_events(input& input) { + + for (auto& event : input.get_mouse_active_events()) { + + for (auto& listener : input.mouse_active_listeners.get_list()) + listener.second(event); + + for (auto& callback : input.get_callbacks()) + callback->on_mouse_active_event(event); + } + + input.clear_mouse_active_events(); +} + +void lava::handle_events(input& input) { + + handle_key_events(input); + handle_scroll_events(input); + handle_mouse_button_events(input); + handle_mouse_move_events(input); + handle_mouse_active_events(input); +} diff --git a/liblava/frame/input.hpp b/liblava/frame/input.hpp new file mode 100644 index 00000000..35fb5fd8 --- /dev/null +++ b/liblava/frame/input.hpp @@ -0,0 +1,159 @@ +// file : liblava/frame/input.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/utils/utility.hpp> +#include <liblava/core/id.hpp> + +namespace lava { + +struct key_event { + + using ref = key_event const&; + using func = std::function<void(ref)>; + using listeners = std::map<id, func>; + using list = std::vector<key_event>; + + id sender; + + i32 key = 0; + i32 scancode = 0; + i32 action = 0; + i32 mods = 0; +}; + +struct scroll_offset { + + r64 x = 0.0; + r64 y = 0.0; +}; + +struct scroll_event { + + using ref = scroll_event const&; + using func = std::function<void(ref)>; + using listeners = std::map<id, func>; + using list = std::vector<scroll_event>; + + id sender; + + scroll_offset offset; +}; + +struct mouse_button_event { + + using ref = mouse_button_event const&; + using func = std::function<void(ref)>; + using listeners = std::map<id, func>; + using list = std::vector<mouse_button_event>; + + id sender; + + i32 button = 0; + i32 action = 0; + i32 modes = 0; +}; + +struct mouse_position { + + r64 x = 0.0; + r64 y = 0.0; +}; + +struct mouse_move_event { + + using ref = mouse_move_event const&; + using func = std::function<void(ref)>; + using listeners = std::map<id, func>; + using list = std::vector<mouse_move_event>; + + id sender; + + mouse_position position; +}; + +struct mouse_active_event { + + using ref = mouse_active_event const&; + using func = std::function<void(ref)>; + using listeners = std::map<id, func>; + using list = std::vector<mouse_active_event>; + + id sender; + + bool active = false; +}; + +struct input_callback { + + using list = std::vector<input_callback*>; + + key_event::func on_key_event; + scroll_event::func on_scroll_event; + mouse_button_event::func on_mouse_button_event; + mouse_move_event::func on_mouse_move_event; + mouse_active_event::func on_mouse_active_event; +}; + +struct input { + + void add_key_event(key_event::ref event) { key_events.push_back(event); } + void add_scroll_event(scroll_event::ref event) { scroll_events.push_back(event); } + void add_mouse_button_event(mouse_button_event::ref event) { mouse_button_events.push_back(event); } + void add_mouse_move_event(mouse_move_event::ref event) { mouse_move_events.push_back(event); } + void add_mouse_active_event(mouse_active_event::ref event) { mouse_active_events.push_back(event); } + + listeners<key_event> key_listeners; + listeners<scroll_event> scroll_listeners; + listeners<mouse_button_event> mouse_button_listeners; + listeners<mouse_move_event> mouse_move_listeners; + listeners<mouse_active_event> mouse_active_listeners; + + void add_callback(input_callback* callback) { callbacks.push_back(callback); } + void remove_callback(input_callback* callback) { remove(callbacks, callback); } + input_callback::list const& get_callbacks() const { return callbacks; } + + key_event::list const& get_key_events() const { return key_events; } + scroll_event::list const& get_scroll_events() const { return scroll_events; } + mouse_button_event::list const& get_mouse_button_events() const { return mouse_button_events; } + mouse_move_event::list const& get_mouse_move_events() const { return mouse_move_events; } + mouse_active_event::list const& get_mouse_active_events() const { return mouse_active_events; } + + void clear_key_events() { key_events.clear(); } + void clear_scroll_events() { scroll_events.clear(); } + void clear_mouse_button_events() { mouse_button_events.clear(); } + void clear_mouse_move_events() { mouse_move_events.clear(); } + void clear_mouse_active_events() { mouse_active_events.clear(); } + + void clear_all_events(); + + mouse_position get_mouse_position() const { return _mouse_position; } + void set_mouse_position(mouse_position const& position) { _mouse_position = position; } + +private: + mouse_position _mouse_position; + + key_event::list key_events; + scroll_event::list scroll_events; + mouse_button_event::list mouse_button_events; + mouse_move_event::list mouse_move_events; + mouse_active_event::list mouse_active_events; + + input_callback::list callbacks; +}; + +void handle_key_events(input& input); + +void handle_scroll_events(input& input); + +void handle_mouse_button_events(input& input); + +void handle_mouse_move_events(input& input); + +void handle_mouse_active_events(input& input); + +void handle_events(input& input); + +} // lava diff --git a/liblava/frame/render_target.cpp b/liblava/frame/render_target.cpp new file mode 100644 index 00000000..7eb2ee5a --- /dev/null +++ b/liblava/frame/render_target.cpp @@ -0,0 +1,93 @@ +// file : liblava/frame/render_target.cpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#include <liblava/frame/render_target.hpp> +#include <liblava/frame/window.hpp> +#include <liblava/frame/frame.hpp> + +namespace lava { + +bool render_target::create(device* device, VkSurfaceKHR surface, uv2 size) { + + if (!_swapchain.create(device, surface, size)) + return false; + + set_clear_color(); + + _swapchain_callback.on_created = [&]() { + + if (on_create_attachments) { + + auto target_attachments = on_create_attachments(); + + for (auto& callback : target_callbacks) + if (!callback->on_created(target_attachments, get_size())) + return false; + } + + if (on_swapchain_start) + if (!on_swapchain_start()) + return false; + + return true; + }; + + _swapchain_callback.on_destroyed = [&]() { + + if (on_swapchain_stop) + on_swapchain_stop(); + + for (auto& callback : target_callbacks) + callback->on_destroyed(); + + if (on_destroy_attachments) + on_destroy_attachments(); + }; + + _swapchain.add_callback(&_swapchain_callback); + + return true; +} + +void render_target::destroy() { + + target_callbacks.clear(); + + _swapchain.remove_callback(&_swapchain_callback); + _swapchain.destroy(); +} + +void render_target::set_clear_color(v3 value) { + + clear_color.float32[0] = value.r; + clear_color.float32[1] = value.g; + clear_color.float32[2] = value.b; + clear_color.float32[3] = 1.f; +} + +} // lava + +lava::render_target::ptr lava::create_target(window* window, device* device) { + + auto surface = create_surface(window->get()); + if (!surface) + return nullptr; + + if (!device->is_surface_supported(surface)) + return nullptr; + + auto width = 0u; + auto height = 0u; + window->get_framebuffer_size(width, height); + + auto target = std::make_shared<render_target>(); + if (!target->create(device, surface, { width, height })) + return nullptr; + + auto target_ptr = target.get(); + + window->on_resize = [&, target_ptr](ui32 new_width, ui32 new_height) { return target_ptr->resize({ new_width, new_height }); }; + + return target; +} diff --git a/liblava/frame/render_target.hpp b/liblava/frame/render_target.hpp new file mode 100644 index 00000000..c98458ec --- /dev/null +++ b/liblava/frame/render_target.hpp @@ -0,0 +1,86 @@ +// file : liblava/frame/render_target.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/frame/swapchain.hpp> +#include <liblava/fwd.hpp> + +namespace lava { + +struct render_target { + + using ptr = std::shared_ptr<render_target>; + + struct callback { + + using list = std::vector<callback*>; + + using created_func = std::function<bool(VkImageViews const&, uv2)>; + created_func on_created; + + using destroyed_func = std::function<void()>; + destroyed_func on_destroyed; + }; + + bool create(device* device, VkSurfaceKHR surface, uv2 size); + void destroy(); + + void set_clear_color(v3 value = default_color); + VkClearColorValue get_clear_color() const { return clear_color; } + + uv2 get_size() const { return _swapchain.get_size(); } + bool resize(uv2 new_size) { return _swapchain.resize(new_size); } + + ui32 get_frame_count() const { return _swapchain.get_backbuffer_count(); } + + bool must_reload() const { return _swapchain.must_reload(); } + void reload() { _swapchain.resize(_swapchain.get_size()); } + + device* get_device() { return _swapchain.get_device(); } + swapchain* get_swapchain() { return &_swapchain; } + + VkFormat get_format() const { return _swapchain.get_format(); } + + image::list const& get_backbuffers() const { return _swapchain.get_backbuffers(); } + inline image::ptr get_backbuffer(index index) { + + auto& backbuffers = get_backbuffers(); + if (index >= backbuffers.size()) + return nullptr; + + return backbuffers.at(index); + } + + inline VkImage get_backbuffer_image(index index) { + + auto result = get_backbuffer(index); + return result ? result->get() : nullptr; + } + + void add_target_callback(callback* callback) { target_callbacks.push_back(callback); } + + using swapchain__start_func = std::function<bool()>; + swapchain__start_func on_swapchain_start; + + using swapchain_stop_func = std::function<void()>; + swapchain_stop_func on_swapchain_stop; + + using create_attachments_func = std::function<VkImageViews()>; + create_attachments_func on_create_attachments; + + using destroy_attachments_func = std::function<void()>; + destroy_attachments_func on_destroy_attachments; + +private: + swapchain _swapchain; + swapchain::callback _swapchain_callback; + VkClearColorValue clear_color = {}; + + callback::list target_callbacks; +}; + +render_target::ptr create_target(window* window, device* device); + +} // lava diff --git a/liblava/frame/render_thread.hpp b/liblava/frame/render_thread.hpp new file mode 100644 index 00000000..5fb3fe73 --- /dev/null +++ b/liblava/frame/render_thread.hpp @@ -0,0 +1,70 @@ +// file : liblava/frame/render_thread.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/frame/renderer.hpp> + +#include <thread> + +namespace lava { + +struct render_thread { + + ~render_thread() { destroy(); } + + bool create(swapchain* swapchain) { + + _renderer.on_destroy = [&]() { stop(); }; + + return _renderer.create(swapchain); + } + + void destroy() { _renderer.destroy(); } + + void start() { thread = std::thread(&render_thread::render, this); } + + void stop() { + + if (!running) + return; + + running = false; + thread.join(); + } + + using render_func = std::function<VkCommandBuffers(ui32)>; + render_func on_render; + + renderer* get_renderer() { return &_renderer; } + bool is_running() const { return running; } + +private: + void render() { + + running = true; + + while (running) { + + if (!_renderer.active) + continue; + + if (!on_render) + continue; + + auto frame_index = _renderer.begin_frame(); + if (!frame_index) + continue; + + _renderer.end_frame(on_render(*frame_index)); + } + } + + std::thread thread; + bool running = false; + + renderer _renderer; +}; + +} // lava diff --git a/liblava/frame/renderer.cpp b/liblava/frame/renderer.cpp new file mode 100644 index 00000000..2eb3c72e --- /dev/null +++ b/liblava/frame/renderer.cpp @@ -0,0 +1,164 @@ +// file : liblava/frame/renderer.cpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#include <liblava/frame/renderer.hpp> + +#include <array> + +namespace lava { + +bool renderer::create(swapchain* target_) { + + target = target_; + dev = target->get_device(); + + queue = dev->get_graphics_queue(); + queued_frames = target->get_backbuffer_count(); + + fences.resize(queued_frames); + image_acquired_semaphores.resize(queued_frames); + render_complete_semaphores.resize(queued_frames); + + for (auto i = 0u; i < queued_frames; ++i) { + + { + VkFenceCreateInfo create_info + { + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + .flags = VK_FENCE_CREATE_SIGNALED_BIT, + }; + + if (failed(dev->call().vkCreateFence(dev->get(), &create_info, memory::alloc(), &fences[i]))) + return false; + } + + { + VkSemaphoreCreateInfo create_info + { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + }; + + if (failed(dev->call().vkCreateSemaphore(dev->get(), &create_info, memory::alloc(), &image_acquired_semaphores[i]))) + return false; + + if (failed(dev->call().vkCreateSemaphore(dev->get(), &create_info, memory::alloc(), &render_complete_semaphores[i]))) + return false; + } + } + + return true; +} + +void renderer::destroy() { + + if (on_destroy) + on_destroy(); + + for (auto i = 0u; i < queued_frames; ++i) { + + dev->call().vkDestroyFence(dev->get(), fences[i], memory::alloc()); + dev->call().vkDestroySemaphore(dev->get(), image_acquired_semaphores[i], memory::alloc()); + dev->call().vkDestroySemaphore(dev->get(), render_complete_semaphores[i], memory::alloc()); + } + + fences.clear(); + image_acquired_semaphores.clear(); + render_complete_semaphores.clear(); + + queued_frames = 0; +} + +std::optional<index> renderer::begin_frame() { + + if (!active) + return {}; + + std::array<VkFence, 1> const wait_fences = { fences[current_sync] }; + + for (;;) { + + auto result = dev->call().vkWaitForFences(dev->get(), to_ui32(wait_fences.size()), wait_fences.data(), VK_TRUE, 100); + if (result == VK_SUCCESS) + break; + + if (result == VK_TIMEOUT) + continue; + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + + target->request_reload(); + return {}; + } + + if (failed(result)) + return {}; + } + + auto current_semaphore = image_acquired_semaphores[current_sync]; + + auto result = dev->call().vkAcquireNextImageKHR(dev->get(), target->get(), UINT64_MAX, current_semaphore, nullptr, &frame_index); + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + + target->request_reload(); + return {}; + } + + if (failed(result)) + return {}; + + if (!check(dev->call().vkResetFences(dev->get(), to_ui32(wait_fences.size()), wait_fences.data()))) + return {}; + + return get_current_frame(); +} + +bool renderer::end_frame(VkCommandBuffers const& cmd_buffers) { + + assert(!cmd_buffers.empty()); + + std::array<VkSemaphore, 1> const wait_semaphores = { image_acquired_semaphores[current_sync] }; + std::array<VkSemaphore, 1> const sync_present_semaphores = { render_complete_semaphores[current_sync] }; + + VkPipelineStageFlags const wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + + VkSubmitInfo submit_info + { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .waitSemaphoreCount = to_ui32(wait_semaphores.size()), + .pWaitSemaphores = wait_semaphores.data(), + .pWaitDstStageMask = &wait_stage, + .commandBufferCount = to_ui32(cmd_buffers.size()), + .pCommandBuffers = cmd_buffers.data(), + .signalSemaphoreCount = to_ui32(sync_present_semaphores.size()), + .pSignalSemaphores = sync_present_semaphores.data(), + }; + + std::array<VkSubmitInfo, 1> const submit_infos = { submit_info }; + VkFence current_fence = fences[current_sync]; + + if (failed(dev->call().vkQueueSubmit(queue.vk_queue, to_ui32(submit_infos.size()), submit_infos.data(), current_fence))) + return false; + + std::array<VkSwapchainKHR, 1> const swapchains = { target->get() }; + std::array<ui32, 1> const indices = { frame_index }; + + VkPresentInfoKHR present_info + { + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .waitSemaphoreCount = to_ui32(sync_present_semaphores.size()), + .pWaitSemaphores = sync_present_semaphores.data(), + .swapchainCount = to_ui32(swapchains.size()), + .pSwapchains = swapchains.data(), + .pImageIndices = indices.data(), + }; + + if (failed(dev->call().vkQueuePresentKHR(queue.vk_queue, &present_info))) + return false; + + current_sync = (current_sync + 1) % queued_frames; + + return true; +} + +} // lava diff --git a/liblava/frame/renderer.hpp b/liblava/frame/renderer.hpp new file mode 100644 index 00000000..69d21bbc --- /dev/null +++ b/liblava/frame/renderer.hpp @@ -0,0 +1,51 @@ +// file : liblava/frame/renderer.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/frame/swapchain.hpp> + +#include <optional> + +namespace lava { + +struct renderer { + + bool create(swapchain* target); + void destroy(); + + std::optional<index> begin_frame(); + bool end_frame(VkCommandBuffers const& cmd_buffers); + + bool frame(VkCommandBuffers const& cmd_buffers) { + + if (!begin_frame()) + return false; + + return end_frame(cmd_buffers); + } + + index get_current_frame() const { return frame_index; } + + using destroy_func = std::function<void()>; + destroy_func on_destroy; + + bool active = true; + +private: + device* dev = nullptr; + device::queue queue; + + swapchain* target = nullptr; + + index frame_index = 0; + ui32 queued_frames = 2; + + ui32 current_sync = 0; + VkFences fences = {}; + VkSemaphores image_acquired_semaphores = {}; + VkSemaphores render_complete_semaphores = {}; +}; + +} // lava diff --git a/liblava/frame/swapchain.cpp b/liblava/frame/swapchain.cpp new file mode 100644 index 00000000..5916034a --- /dev/null +++ b/liblava/frame/swapchain.cpp @@ -0,0 +1,237 @@ +// file : liblava/frame/swapchain.cpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#include <liblava/frame/swapchain.hpp> +#include <liblava/base/instance.hpp> + +namespace lava { + +bool swapchain::create(device* device_, VkSurfaceKHR surface_, uv2 size_) { + + dev = device_; + surface = surface_; + size = size_; + + set_surface_format(); + + return create_internal(); +} + +void swapchain::destroy() { + + dev->wait_for_idle(); + + destroy_backbuffer_views(); + destroy_internal(); + + vkDestroySurfaceKHR(instance::get(), surface, memory::alloc()); + surface = nullptr; +} + +bool swapchain::resize(uv2 new_size) { + + dev->wait_for_idle(); + + if (!backbuffers.empty()) { + + for (auto& callback : callbacks) + callback->on_destroyed(); + + destroy_backbuffer_views(); + } + + size = new_size; + if (size.x == 0 || size.y == 0) + return true; + + auto result = create_internal(); + assert(result); + if (!result) + return false; + + for (auto& callback : reverse(callbacks)) { + + result = callback->on_created(); + assert(result); + if (!result) + return false; + } + + reload_request = false; + return true; +} + +void swapchain::set_surface_format() { + + auto count = 0u; + check(vkGetPhysicalDeviceSurfaceFormatsKHR(dev->get_physical_device()->get(), surface, &count, nullptr)); + + std::vector<VkSurfaceFormatKHR> formats(count); + check(vkGetPhysicalDeviceSurfaceFormatsKHR(dev->get_physical_device()->get(), surface, &count, formats.data())); + + if (count == 1) { + + if (formats[0].format == VK_FORMAT_UNDEFINED) { + + format.format = VK_FORMAT_B8G8R8A8_UNORM; + format.colorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; + + } else { + + format = formats[0]; + } + + } else { + + VkFormat requestSurfaceImageFormat[] = { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM }; + + VkColorSpaceKHR requestSurfaceColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; + + auto requestedFound = false; + for (auto& i : requestSurfaceImageFormat) { + + if (requestedFound) + break; + + for (ui32 j = 0; j < count; j++) { + + if (formats[j].format == i && + formats[j].colorSpace == requestSurfaceColorSpace) { + + format = formats[j]; + requestedFound = true; + } + } + } + + if (!requestedFound) + format = formats[0]; + } +} + +VkPresentModeKHR swapchain::choose_present_mode(VkPresentModeKHRs const& present_modes) const { + + for (auto const& present_mode : present_modes) + if (present_mode == VK_PRESENT_MODE_MAILBOX_KHR) + return present_mode; + + return VK_PRESENT_MODE_FIFO_KHR; +} + +bool swapchain::create_internal() { + + auto present_mode_count = 0u; + if (vkGetPhysicalDeviceSurfacePresentModesKHR(dev->get_physical_device()->get(), surface, &present_mode_count, nullptr) != VK_SUCCESS || present_mode_count == 0) { + + log()->error("swapchain::create_internal vkGetPhysicalDeviceSurfacePresentModesKHR(1) failed"); + return false; + } + + VkPresentModeKHRs present_modes(present_mode_count); + if (vkGetPhysicalDeviceSurfacePresentModesKHR(dev->get_physical_device()->get(), surface, &present_mode_count, present_modes.data()) != VK_SUCCESS) { + + log()->error("swapchain::create_internal vkGetPhysicalDeviceSurfacePresentModesKHR(2) failed"); + return false; + } + + VkPresentModeKHR present_mode = choose_present_mode(present_modes); + + VkSwapchainKHR old_swapchain = vk_swapchain; + + VkSwapchainCreateInfoKHR info + { + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .surface = surface, + .minImageCount = 0, + .imageFormat = format.format, + .imageColorSpace = format.colorSpace, + .imageExtent = {}, + .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, + .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR, + .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + .presentMode = present_mode, + .clipped = VK_TRUE, + .oldSwapchain = old_swapchain, + }; + + VkSurfaceCapabilitiesKHR cap{}; + check(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(dev->get_physical_device()->get(), surface, &cap)); + + if (cap.maxImageCount > 0) + info.minImageCount = (cap.minImageCount + 2 < cap.maxImageCount) ? (cap.minImageCount + 2) : cap.maxImageCount; + else + info.minImageCount = cap.minImageCount + 2; + + if (cap.currentExtent.width == 0xffffffff) { + + info.imageExtent.width = size.x; + info.imageExtent.height = size.y; + + } else { + + size.x = cap.currentExtent.width; + size.y = cap.currentExtent.height; + info.imageExtent.width = size.x; + info.imageExtent.height = size.y; + } + + info.preTransform = cap.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR : cap.currentTransform; + + check(dev->call().vkCreateSwapchainKHR(dev->get(), &info, memory::alloc(), &vk_swapchain)); + + auto backbuffer_count = 0u; + check(dev->call().vkGetSwapchainImagesKHR(dev->get(), vk_swapchain, &backbuffer_count, nullptr)); + + VkImages images(backbuffer_count); + check(dev->call().vkGetSwapchainImagesKHR(dev->get(), vk_swapchain, &backbuffer_count, images.data())); + + for (auto& image : images) { + + auto backbuffer = image::make(format.format, image); + if (!backbuffer) { + + log()->error("swapchain::create_internal backbuffer make failed"); + return false; + } + + if (!backbuffer->create(dev, size)) { + + log()->error("swapchain::create_internal backBuffer create failed"); + return false; + } + + backbuffers.push_back(backbuffer); + } + + if (old_swapchain) + dev->call().vkDestroySwapchainKHR(dev->get(), old_swapchain, memory::alloc()); + + return true; +} + +void swapchain::destroy_internal() { + + if (!vk_swapchain) + return; + + dev->call().vkDestroySwapchainKHR(dev->get(), vk_swapchain, memory::alloc()); + vk_swapchain = nullptr; +} + +void swapchain::destroy_backbuffer_views() { + + for (auto& backBuffer : backbuffers) + backBuffer->destroy_view(); + + backbuffers.clear(); +} + +void swapchain::add_callback(callback* cb) { callbacks.push_back(cb); } + +void swapchain::remove_callback(callback* cb) { remove(callbacks, cb); } + +} // lava diff --git a/liblava/frame/swapchain.hpp b/liblava/frame/swapchain.hpp new file mode 100644 index 00000000..3e669083 --- /dev/null +++ b/liblava/frame/swapchain.hpp @@ -0,0 +1,71 @@ +// file : liblava/frame/swapchain.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/resource/image.hpp> + +namespace lava { + +struct swapchain { + + bool create(device* device, VkSurfaceKHR surface, uv2 size); + + void destroy(); + + bool resize(uv2 new_size); + + void request_reload() { reload_request = true; } + bool must_reload() const { return reload_request; } + + device* get_device() { return dev; } + + uv2 get_size() const { return size; } + VkFormat get_format() const { return format.format; } + + VkSwapchainKHR get() const { return vk_swapchain; } + + ui32 get_backbuffer_count() const { return to_ui32(backbuffers.size()); } + image::list const& get_backbuffers() const { return backbuffers; } + + struct callback { + + using list = std::vector<callback*>; + + using created_func = std::function<bool()>; + created_func on_created; + + using destroyed_func = std::function<void()>; + destroyed_func on_destroyed; + }; + + void add_callback(callback* cb); + void remove_callback(callback* cb); + +private: + void set_surface_format(); + + VkPresentModeKHR choose_present_mode(VkPresentModeKHRs const& present_modes) const; + + bool create_internal(); + + void destroy_internal(); + void destroy_backbuffer_views(); + + device* dev = nullptr; + + VkSurfaceKHR surface = nullptr; + VkSurfaceFormatKHR format = {}; + + VkSwapchainKHR vk_swapchain = nullptr; + + image::list backbuffers; + + uv2 size; + bool reload_request = false; + + callback::list callbacks; +}; + +} // lava diff --git a/liblava/frame/window.cpp b/liblava/frame/window.cpp new file mode 100644 index 00000000..97b8a29c --- /dev/null +++ b/liblava/frame/window.cpp @@ -0,0 +1,256 @@ +// file : liblava/frame/window.cpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#include <liblava/frame/window.hpp> +#include <liblava/frame/frame.hpp> + +namespace lava { + +bool window::create(name save_name_, state* state) { + + save_name = save_name_; + + auto primary = glfwGetPrimaryMonitor(); + auto mode = glfwGetVideoMode(primary); + + string default_title = title; + if (debug_title) + default_title = fmt::format("%s [%s]", title.c_str(), save_name.c_str()); + + if (state) { + + windowed = !state->fullscreen; + + if (state->fullscreen) { + + handle = glfwCreateWindow(mode->width, mode->height, default_title.c_str(), primary, nullptr); + if (!handle) { + + log()->error("Window::create glfwCreateWindow(1) failed"); + return false; + } + + } else { + + handle = glfwCreateWindow(state->width, state->height, default_title.c_str(), nullptr, nullptr); + if (!handle) { + + log()->error("window::create glfwCreateWindow(2) failed"); + return false; + } + + glfwSetWindowPos(handle, state->x, state->y); + } + + set_floating(state->floating); + set_resizable(state->resizable); + set_decorated(state->decorated); + + if (state->maximized) + maximize(); + + } else { + + if (!windowed) { + + handle = glfwCreateWindow(mode->width, mode->height, default_title.c_str(), primary, nullptr); + if (!handle) { + + log()->error("window::create glfwCreateWindow(3) failed"); + return false; + } + + } else { + + handle = glfwCreateWindow(mode->width / 2, mode->height / 2, default_title.c_str(), nullptr, nullptr); + if (!handle) { + + log()->error("window::create glfwCreateWindow(4) failed"); + return false; + } + + glfwSetWindowPos(handle, mode->width / 4, mode->height / 4); + } + } + + switch_mode_request = false; + handle_message(); + + return true; +} + +void window::destroy() { + + _input = nullptr; + + glfwDestroyWindow(handle); + handle = nullptr; +} + +window::state window::get_state() const { + + window::state state; + + get_position(state.x, state.y); + get_size(state.width, state.height); + + state.fullscreen = fullscreen(); + state.floating = floating(); + state.resizable = resizable(); + state.decorated = decorated(); + state.maximized = maximized(); + + return state; +} + +void window::set_title(name text) { + + title = text; + + if (!handle) + return; + + if (debug_title) + glfwSetWindowTitle(handle, fmt::format("%s [%s]", title.c_str(), save_name.c_str()).c_str()); + else + glfwSetWindowTitle(handle, title.c_str()); +} + +bool window::switch_mode() { + + destroy(); + + return create(save_name.c_str()); +} + +void window::handle_message() { + + glfwSetWindowUserPointer(handle, this); + + glfwSetFramebufferSizeCallback(handle, [](GLFWwindow* handle, i32 width, i32 height) { + + auto window = get_window(handle); + if (!window) + return; + + window->width = to_ui32(width); + window->height = to_ui32(height); + window->resize_request = true; + }); + + glfwSetKeyCallback(handle, [](GLFWwindow* handle, i32 key, i32 scancode, i32 action, i32 mods) { + + auto window = get_window(handle); + if (!window) + return; + + window->_input->add_key_event({ window->get_id(), key, scancode, action, mods }); + }); + + glfwSetScrollCallback(handle, [](GLFWwindow* handle, r64 x_offset, r64 y_offset) { + + auto window = get_window(handle); + if (!window) + return; + + if (window->_input) + window->_input->add_scroll_event({ window->get_id(), x_offset, y_offset }); + }); + + glfwSetMouseButtonCallback(handle, [](GLFWwindow* handle, i32 button, i32 action, i32 mods) { + + auto window = get_window(handle); + if (!window) + return; + + if (window->_input) + window->_input->add_mouse_button_event({ window->get_id(), button, action, mods }); + }); + + glfwSetCursorPosCallback(handle, [](GLFWwindow* handle, r64 x_position, r64 y_position) { + + auto window = get_window(handle); + if (!window) + return; + + if (window->_input) + window->_input->add_mouse_move_event({ window->get_id(), { x_position, y_position } }); + }); + + glfwSetCursorEnterCallback(handle, [](GLFWwindow* handle, i32 entered) { + + auto window = get_window(handle); + if (!window) + return; + + if (window->_input) + window->_input->add_mouse_active_event({ window->get_id(), entered > 0 }); + }); +} + +template <int attr> +static int is_attribute_set(GLFWwindow* handle) { return glfwGetWindowAttrib(handle, attr); } + +template <int attr> +static bool is_bool_attribute_set(GLFWwindow* handle) { return is_attribute_set<attr>(handle) == 1; } + +void window::set_position(i32 x, i32 y) { glfwSetWindowPos(handle, x, y); } + +void window::get_position(i32& x, i32& y) const { glfwGetWindowPos(handle, &x, &y); } + +void window::set_size(ui32 w, ui32 h) { glfwSetWindowSize(handle, w, h); } + +void window::get_size(ui32& w, ui32& h) const { glfwGetWindowSize(handle, (i32*)&w, (i32*)&h); } + +void window::get_framebuffer_size(ui32& w, ui32& h) const { glfwGetFramebufferSize(handle, (i32*)&w, (i32*)&h); } + +void window::set_mouse_position(r64 x, r64 y) { glfwSetCursorPos(handle, x, y); } + +void window::get_mouse_position(r64& x, r64& y) const { glfwGetCursorPos(handle, &x, &y); } + +void window::hide_mouse_cursor() { glfwSetInputMode(handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); } + +void window::show_mouse_cursor() { glfwSetInputMode(handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } + +float window::get_aspect_ratio() const { return height != 0 ? to_r32(width) / to_r32(height) : 0.f; } + +void window::show() { glfwShowWindow(handle); } + +void window::hide() { glfwHideWindow(handle); } + +bool window::visible() const { return is_bool_attribute_set<GLFW_VISIBLE>(handle); } + +void window::iconify() { glfwIconifyWindow(handle); } + +bool window::iconified() const { return is_bool_attribute_set<GLFW_ICONIFIED>(handle); } + +void window::restore() { glfwRestoreWindow(handle); } + +void window::maximize() { glfwMaximizeWindow(handle); } + +bool window::maximized() const { return is_bool_attribute_set<GLFW_MAXIMIZED>(handle); } + +void window::focus() { glfwFocusWindow(handle); } + +bool window::focused() const { return is_bool_attribute_set<GLFW_FOCUSED>(handle); } + +bool window::hovered() const { return is_bool_attribute_set<GLFW_HOVERED>(handle); } + +bool window::resizable() const { return is_bool_attribute_set<GLFW_RESIZABLE>(handle); } + +void window::set_resizable(bool value) { glfwSetWindowAttrib(handle, GLFW_RESIZABLE, value); } + +bool window::decorated() const { return is_bool_attribute_set<GLFW_DECORATED>(handle); } + +void window::set_decorated(bool value) { glfwSetWindowAttrib(handle, GLFW_DECORATED, value); } + +bool window::floating() const { return is_bool_attribute_set<GLFW_FLOATING>(handle); } + +void window::set_floating(bool value) { glfwSetWindowAttrib(handle, GLFW_FLOATING, value); } + +window* window::get_window(GLFWwindow* handle) { return static_cast<window*>(glfwGetWindowUserPointer(handle)); } + +bool window::has_close_request() const { return glfwWindowShouldClose(handle) == 1; } + +} // lava diff --git a/liblava/frame/window.hpp b/liblava/frame/window.hpp new file mode 100644 index 00000000..c245ef97 --- /dev/null +++ b/liblava/frame/window.hpp @@ -0,0 +1,147 @@ +// file : liblava/frame/window.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/frame/input.hpp> + +// fwd +struct GLFWwindow; + +namespace lava { + +struct window { + + struct state { + + i32 x = 0; + i32 y = 0; + ui32 width = 0; + ui32 height = 0; + bool fullscreen = false; + bool floating = false; + bool resizable = true; + bool decorated = true; + bool maximized = false; + }; + + using ptr = std::shared_ptr<window>; + using event = std::function<void(ptr)>; + using map = std::map<id, ptr>; + + window() = default; + explicit window(id::ref id) : _id(id) {} + explicit window(name title) : title(title) {} + + bool create(name save_name = "0", state* state = nullptr); + void destroy(); + + id::ref get_id() const { return _id; } + state get_state() const; + + void set_title(name text); + name get_title() const { return title.c_str(); } + + name get_save_name() const { return save_name.c_str(); } + + void set_position(i32 x, i32 y); + void get_position(i32& x, i32& y) const; + void set_size(ui32 width, ui32 height); + void get_size(ui32& width, ui32& height) const; + + void get_framebuffer_size(ui32& width, ui32& height) const; + + void set_mouse_position(r64 x, r64 y); + void get_mouse_position(r64& x, r64& y) const; + + void hide_mouse_cursor(); + void show_mouse_cursor(); + + float get_aspect_ratio() const; + + void show(); + void hide(); + bool visible() const; + + void iconify(); + bool iconified() const; + void restore(); + + void maximize(); + bool maximized() const; + + void focus(); + bool focused() const; + + void set_fullscreen(bool active) { + + if (windowed == active) + switch_mode_request = true; + + windowed = !active; + } + bool fullscreen() const { return !windowed; } + + bool hovered() const; + + bool resizable() const; + void set_resizable(bool value); + + bool decorated() const; + void set_decorated(bool value); + + bool floating() const; + void set_floating(bool value); + + static window* get_window(GLFWwindow* handle); + + bool has_close_request() const; + bool has_switch_mode_request() const { return switch_mode_request; } + + bool switch_mode(); + + GLFWwindow* get() const { return handle; } + + bool has_resize_request() const { return resize_request; } + bool handle_resize() { + + if (on_resize) + if (!on_resize(width, height)) + return false; + + resize_request = false; + return true; + } + + using resize_func = std::function<bool(ui32, ui32)>; + resize_func on_resize; + + void assign(input* callback) { _input = callback; } + + void set_debug_title(bool value = true) { debug_title = value; } + bool has_debug_title() const { return debug_title; } + + void update_title() { set_title(title.c_str()); } + +private: + void handle_message(); + + id _id; + + GLFWwindow* handle = nullptr; + input* _input = nullptr; + + string title = _lava_; + string save_name; + + bool windowed = true; + bool switch_mode_request = false; + bool debug_title = false; + + bool resize_request = false; + ui32 width = 0; + ui32 height = 0; +}; + +} // lava diff --git a/liblava/fwd.hpp b/liblava/fwd.hpp new file mode 100644 index 00000000..b8dcc522 --- /dev/null +++ b/liblava/fwd.hpp @@ -0,0 +1,74 @@ +// file : liblava/fwd.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +namespace lava { + + // liblava/core.h + struct data_provider; + struct data; + struct scope_data; + struct id; + struct ids; + struct rect; + struct time_info; + struct timer; + struct run_time; + struct internal_version; + struct version; + + // liblava/utils.h + struct file_guard; + struct file_system; + struct file; + struct file_data; + struct file_dialog; + struct log_config; + struct config_file_callback; + struct config_file; + struct irandom; + struct random_generator; + struct random; + struct pseudo_random_generator; + struct telegram; + struct dispatcher; + struct thread_pool; + + // liblava/base.h + struct device_manager; + struct device; + struct instance; + struct allocator; + struct memory; + struct physical_device; + + // liblava/resource.h + struct buffer; + struct image; + struct vertex; + struct mesh_data; + struct mesh; + struct texture_file_format; + struct texture; + + // liblava/frame.h + struct frame_config; + struct frame; + struct key_event; + struct scroll_offset; + struct scroll_event; + struct mouse_button_event; + struct mouse_position; + struct mouse_move_event; + struct mouse_active_event; + struct input_callback; + struct input; + struct render_target; + struct render_thread; + struct renderer; + struct swapchain; + struct window; + +} // lava diff --git a/liblava/lava.hpp b/liblava/lava.hpp new file mode 100644 index 00000000..29ceb989 --- /dev/null +++ b/liblava/lava.hpp @@ -0,0 +1,11 @@ +// file : liblava/lava.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/core.hpp> +#include <liblava/utils.hpp> +#include <liblava/base.hpp> +#include <liblava/resource.hpp> +#include <liblava/frame.hpp> diff --git a/liblava/resource.hpp b/liblava/resource.hpp new file mode 100644 index 00000000..d027cf8a --- /dev/null +++ b/liblava/resource.hpp @@ -0,0 +1,11 @@ +// file : liblava/resource.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/resource/buffer.hpp> +#include <liblava/resource/format.hpp> +#include <liblava/resource/image.hpp> +#include <liblava/resource/mesh.hpp> +#include <liblava/resource/texture.hpp> diff --git a/liblava/resource/buffer.cpp b/liblava/resource/buffer.cpp new file mode 100644 index 00000000..37e4fb00 --- /dev/null +++ b/liblava/resource/buffer.cpp @@ -0,0 +1,127 @@ +// file : liblava/resource/buffer.cpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#include <liblava/resource/buffer.hpp> + +namespace lava { + +VkPipelineStageFlags buffer::usage_to_possible_stages(VkBufferUsageFlags usage) { + + VkPipelineStageFlags flags = 0; + + if (usage & (VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT)) + flags |= VK_PIPELINE_STAGE_TRANSFER_BIT; + if (usage & (VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT)) + flags |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT; + if (usage & VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT) + flags |= VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT; + if (usage & (VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT)) + flags |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + if (usage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) + flags |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + + return flags; +} + +VkAccessFlags buffer::usage_to_possible_access(VkBufferUsageFlags usage) { + + VkAccessFlags flags = 0; + + if (usage & (VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT)) + flags |= VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + if (usage & VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) + flags |= VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; + if (usage & VK_BUFFER_USAGE_INDEX_BUFFER_BIT) + flags |= VK_ACCESS_INDEX_READ_BIT; + if (usage & VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT) + flags |= VK_ACCESS_INDIRECT_COMMAND_READ_BIT; + if (usage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) + flags |= VK_ACCESS_UNIFORM_READ_BIT; + if (usage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) + flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; + + return flags; +} + +buffer::buffer() : _id(ids::next()) {} + +buffer::~buffer() { + + ids::free(_id); + + destroy(); +} + +bool buffer::create(device* device, void const* data, size_t size, VkBufferUsageFlags usage, bool mapped, VmaMemoryUsage memoryUsage) { + + dev = device; + + VkBufferCreateInfo buffer_info + { + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .size = size, + .usage = usage, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + }; + + VmaAllocationCreateFlags const alloc_flags = mapped ? VMA_ALLOCATION_CREATE_MAPPED_BIT : 0; + + VmaAllocationCreateInfo alloc_info + { + .flags = alloc_flags, + .usage = memoryUsage, + }; + + if (failed(vmaCreateBuffer(dev->alloc(), &buffer_info, &alloc_info, &vk_buffer, &allocation, &allocation_info))) { + + log()->error("buffer::create vmaCreateBuffer failed"); + return false; + } + + if (!mapped) { + + data_ptr map = nullptr; + if (failed(vmaMapMemory(dev->alloc(), allocation, (void**)(&map)))) { + + log()->error("buffer::create vmaMapMemory failed"); + return false; + } + + memcpy(map, data, size); + + vmaUnmapMemory(dev->alloc(), allocation); + + } else if (data) { + + memcpy(allocation_info.pMappedData, data, size); + + flush(); + } + + descriptor.buffer = vk_buffer; + descriptor.offset = 0; + descriptor.range = size; + + return true; +} + +void buffer::destroy() { + + if (!vk_buffer) + return; + + vmaDestroyBuffer(dev->alloc(), vk_buffer, allocation); + vk_buffer = nullptr; + allocation = nullptr; + + dev = nullptr; +} + +void buffer::flush(VkDeviceSize offset, VkDeviceSize size) { + + vmaFlushAllocation(dev->alloc(), allocation, offset, size); +} + +} // lava diff --git a/liblava/resource/buffer.hpp b/liblava/resource/buffer.hpp new file mode 100644 index 00000000..9a10924d --- /dev/null +++ b/liblava/resource/buffer.hpp @@ -0,0 +1,54 @@ +// file : liblava/resource/buffer.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/base/device.hpp> +#include <liblava/base/memory.hpp> + +namespace lava { + +struct buffer { + + using ptr = std::shared_ptr<buffer>; + using list = std::vector<ptr>; + + static VkPipelineStageFlags usage_to_possible_stages(VkBufferUsageFlags usage); + static VkAccessFlags usage_to_possible_access(VkBufferUsageFlags usage); + + explicit buffer(); + ~buffer(); + + static ptr make() { return std::make_shared<buffer>(); } + + bool create(device* device, void const* data, size_t size, VkBufferUsageFlags usage, bool mapped = false, + VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_GPU_ONLY); + void destroy(); + + id::ref get_id() const { return _id; } + device* get_device() { return dev; } + + bool is_valid() const { return vk_buffer != nullptr; } + + VkBuffer get() const { return vk_buffer; } + VkDescriptorBufferInfo const& get_descriptor() const { return descriptor; } + + size_t get_size() const { return allocation_info.size; } + void* get_mapped_data() const { return allocation_info.pMappedData; } + VkDeviceMemory get_device_memory() const { return allocation_info.deviceMemory; } + + void flush(VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE); + +private: + id _id; + device* dev = nullptr; + + VkBuffer vk_buffer = nullptr; + VmaAllocation allocation = nullptr; + + VmaAllocationInfo allocation_info = {}; + VkDescriptorBufferInfo descriptor = {}; +}; + +} // lava diff --git a/liblava/resource/format.cpp b/liblava/resource/format.cpp new file mode 100644 index 00000000..ba67e45a --- /dev/null +++ b/liblava/resource/format.cpp @@ -0,0 +1,460 @@ +// file : liblava/resource/format.cpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#include <liblava/resource/format.hpp> +#include <liblava/base/memory.hpp> + +bool lava::format_is_depth(VkFormat format) { + + switch (format) { + + case VK_FORMAT_D16_UNORM: + case VK_FORMAT_D16_UNORM_S8_UINT: + case VK_FORMAT_D24_UNORM_S8_UINT: + case VK_FORMAT_D32_SFLOAT: + case VK_FORMAT_X8_D24_UNORM_PACK32: + case VK_FORMAT_D32_SFLOAT_S8_UINT: + return true; + + default: + return false; + } +} + +bool lava::format_is_stencil(VkFormat format) { return format == VK_FORMAT_S8_UINT; } + +bool lava::format_is_depth_stencil(VkFormat format) { return format_is_depth(format) || format_is_stencil(format); } + +VkImageAspectFlags lava::format_to_aspect_mask(VkFormat format) { + + switch (format) { + + case VK_FORMAT_UNDEFINED: + return 0; + + case VK_FORMAT_S8_UINT: + return VK_IMAGE_ASPECT_STENCIL_BIT; + + case VK_FORMAT_D16_UNORM_S8_UINT: + case VK_FORMAT_D24_UNORM_S8_UINT: + case VK_FORMAT_D32_SFLOAT_S8_UINT: + return VK_IMAGE_ASPECT_STENCIL_BIT | VK_IMAGE_ASPECT_DEPTH_BIT; + + case VK_FORMAT_D16_UNORM: + case VK_FORMAT_D32_SFLOAT: + case VK_FORMAT_X8_D24_UNORM_PACK32: + return VK_IMAGE_ASPECT_DEPTH_BIT; + + default: + return VK_IMAGE_ASPECT_COLOR_BIT; + } +} + +void lava::format_block_dim(VkFormat format, ui32& width, ui32& height) { + +#define fmt(x, w, h) \ + case VK_FORMAT_##x: \ + width = w; \ + height = h; \ + break + + switch (format) { + + fmt(ETC2_R8G8B8A8_UNORM_BLOCK, 4, 4); + fmt(ETC2_R8G8B8A8_SRGB_BLOCK, 4, 4); + fmt(ETC2_R8G8B8A1_UNORM_BLOCK, 4, 4); + fmt(ETC2_R8G8B8A1_SRGB_BLOCK, 4, 4); + fmt(ETC2_R8G8B8_UNORM_BLOCK, 4, 4); + fmt(ETC2_R8G8B8_SRGB_BLOCK, 4, 4); + fmt(EAC_R11_UNORM_BLOCK, 4, 4); + fmt(EAC_R11_SNORM_BLOCK, 4, 4); + fmt(EAC_R11G11_UNORM_BLOCK, 4, 4); + fmt(EAC_R11G11_SNORM_BLOCK, 4, 4); + + fmt(BC1_RGB_UNORM_BLOCK, 4, 4); + fmt(BC1_RGB_SRGB_BLOCK, 4, 4); + fmt(BC1_RGBA_UNORM_BLOCK, 4, 4); + fmt(BC1_RGBA_SRGB_BLOCK, 4, 4); + fmt(BC2_UNORM_BLOCK, 4, 4); + fmt(BC2_SRGB_BLOCK, 4, 4); + fmt(BC3_UNORM_BLOCK, 4, 4); + fmt(BC3_SRGB_BLOCK, 4, 4); + + fmt(ASTC_4x4_SRGB_BLOCK, 4, 4); + fmt(ASTC_5x4_SRGB_BLOCK, 5, 4); + fmt(ASTC_5x5_SRGB_BLOCK, 5, 5); + fmt(ASTC_6x5_SRGB_BLOCK, 6, 5); + fmt(ASTC_6x6_SRGB_BLOCK, 6, 6); + fmt(ASTC_8x5_SRGB_BLOCK, 8, 5); + fmt(ASTC_8x6_SRGB_BLOCK, 8, 6); + fmt(ASTC_8x8_SRGB_BLOCK, 8, 8); + fmt(ASTC_10x5_SRGB_BLOCK, 10, 5); + fmt(ASTC_10x6_SRGB_BLOCK, 10, 6); + fmt(ASTC_10x8_SRGB_BLOCK, 10, 8); + fmt(ASTC_10x10_SRGB_BLOCK, 10, 10); + fmt(ASTC_12x10_SRGB_BLOCK, 12, 10); + fmt(ASTC_12x12_SRGB_BLOCK, 12, 12); + fmt(ASTC_4x4_UNORM_BLOCK, 4, 4); + fmt(ASTC_5x4_UNORM_BLOCK, 5, 4); + fmt(ASTC_5x5_UNORM_BLOCK, 5, 5); + fmt(ASTC_6x5_UNORM_BLOCK, 6, 5); + fmt(ASTC_6x6_UNORM_BLOCK, 6, 6); + fmt(ASTC_8x5_UNORM_BLOCK, 8, 5); + fmt(ASTC_8x6_UNORM_BLOCK, 8, 6); + fmt(ASTC_8x8_UNORM_BLOCK, 8, 8); + fmt(ASTC_10x5_UNORM_BLOCK, 10, 5); + fmt(ASTC_10x6_UNORM_BLOCK, 10, 6); + fmt(ASTC_10x8_UNORM_BLOCK, 10, 8); + fmt(ASTC_10x10_UNORM_BLOCK, 10, 10); + fmt(ASTC_12x10_UNORM_BLOCK, 12, 10); + fmt(ASTC_12x12_UNORM_BLOCK, 12, 12); + + default: + width = 1; + height = 1; + break; + } + +#undef fmt +} + +void lava::format_align_dim(VkFormat format, ui32& width, ui32& height) { + + ui32 align_width, align_height; + format_block_dim(format, align_width, align_height); + width = ((width + align_width - 1) / align_width) * align_width; + height = ((height + align_height - 1) / align_height) * align_height; +} + +void lava::format_num_blocks(VkFormat format, ui32& width, ui32& height) { + + ui32 align_width, align_height; + format_block_dim(format, align_width, align_height); + width = (width + align_width - 1) / align_width; + height = (height + align_height - 1) / align_height; +} + +lava::ui32 lava::format_block_size(VkFormat format) { + +#define fmt(x, bpp) \ + case VK_FORMAT_##x: \ + return bpp + + switch (format) { + + fmt(R4G4_UNORM_PACK8, 1); + fmt(R4G4B4A4_UNORM_PACK16, 2); + fmt(B4G4R4A4_UNORM_PACK16, 2); + fmt(R5G6B5_UNORM_PACK16, 2); + fmt(B5G6R5_UNORM_PACK16, 2); + fmt(R5G5B5A1_UNORM_PACK16, 2); + fmt(B5G5R5A1_UNORM_PACK16, 2); + fmt(A1R5G5B5_UNORM_PACK16, 2); + fmt(R8_UNORM, 1); + fmt(R8_SNORM, 1); + fmt(R8_USCALED, 1); + fmt(R8_SSCALED, 1); + fmt(R8_UINT, 1); + fmt(R8_SINT, 1); + fmt(R8_SRGB, 1); + fmt(R8G8_UNORM, 2); + fmt(R8G8_SNORM, 2); + fmt(R8G8_USCALED, 2); + fmt(R8G8_SSCALED, 2); + fmt(R8G8_UINT, 2); + fmt(R8G8_SINT, 2); + fmt(R8G8_SRGB, 2); + fmt(R8G8B8_UNORM, 3); + fmt(R8G8B8_SNORM, 3); + fmt(R8G8B8_USCALED, 3); + fmt(R8G8B8_SSCALED, 3); + fmt(R8G8B8_UINT, 3); + fmt(R8G8B8_SINT, 3); + fmt(R8G8B8_SRGB, 3); + fmt(R8G8B8A8_UNORM, 4); + fmt(R8G8B8A8_SNORM, 4); + fmt(R8G8B8A8_USCALED, 4); + fmt(R8G8B8A8_SSCALED, 4); + fmt(R8G8B8A8_UINT, 4); + fmt(R8G8B8A8_SINT, 4); + fmt(R8G8B8A8_SRGB, 4); + fmt(B8G8R8A8_UNORM, 4); + fmt(B8G8R8A8_SNORM, 4); + fmt(B8G8R8A8_USCALED, 4); + fmt(B8G8R8A8_SSCALED, 4); + fmt(B8G8R8A8_UINT, 4); + fmt(B8G8R8A8_SINT, 4); + fmt(B8G8R8A8_SRGB, 4); + fmt(A8B8G8R8_UNORM_PACK32, 4); + fmt(A8B8G8R8_SNORM_PACK32, 4); + fmt(A8B8G8R8_USCALED_PACK32, 4); + fmt(A8B8G8R8_SSCALED_PACK32, 4); + fmt(A8B8G8R8_UINT_PACK32, 4); + fmt(A8B8G8R8_SINT_PACK32, 4); + fmt(A8B8G8R8_SRGB_PACK32, 4); + fmt(A2B10G10R10_UNORM_PACK32, 4); + fmt(A2B10G10R10_SNORM_PACK32, 4); + fmt(A2B10G10R10_USCALED_PACK32, 4); + fmt(A2B10G10R10_SSCALED_PACK32, 4); + fmt(A2B10G10R10_UINT_PACK32, 4); + fmt(A2B10G10R10_SINT_PACK32, 4); + fmt(A2R10G10B10_UNORM_PACK32, 4); + fmt(A2R10G10B10_SNORM_PACK32, 4); + fmt(A2R10G10B10_USCALED_PACK32, 4); + fmt(A2R10G10B10_SSCALED_PACK32, 4); + fmt(A2R10G10B10_UINT_PACK32, 4); + fmt(A2R10G10B10_SINT_PACK32, 4); + fmt(R16_UNORM, 2); + fmt(R16_SNORM, 2); + fmt(R16_USCALED, 2); + fmt(R16_SSCALED, 2); + fmt(R16_UINT, 2); + fmt(R16_SINT, 2); + fmt(R16_SFLOAT, 2); + fmt(R16G16_UNORM, 4); + fmt(R16G16_SNORM, 4); + fmt(R16G16_USCALED, 4); + fmt(R16G16_SSCALED, 4); + fmt(R16G16_UINT, 4); + fmt(R16G16_SINT, 4); + fmt(R16G16_SFLOAT, 4); + fmt(R16G16B16_UNORM, 6); + fmt(R16G16B16_SNORM, 6); + fmt(R16G16B16_USCALED, 6); + fmt(R16G16B16_SSCALED, 6); + fmt(R16G16B16_UINT, 6); + fmt(R16G16B16_SINT, 6); + fmt(R16G16B16_SFLOAT, 6); + fmt(R16G16B16A16_UNORM, 8); + fmt(R16G16B16A16_SNORM, 8); + fmt(R16G16B16A16_USCALED, 8); + fmt(R16G16B16A16_SSCALED, 8); + fmt(R16G16B16A16_UINT, 8); + fmt(R16G16B16A16_SINT, 8); + fmt(R16G16B16A16_SFLOAT, 8); + fmt(R32_UINT, 4); + fmt(R32_SINT, 4); + fmt(R32_SFLOAT, 4); + fmt(R32G32_UINT, 8); + fmt(R32G32_SINT, 8); + fmt(R32G32_SFLOAT, 8); + fmt(R32G32B32_UINT, 12); + fmt(R32G32B32_SINT, 12); + fmt(R32G32B32_SFLOAT, 12); + fmt(R32G32B32A32_UINT, 16); + fmt(R32G32B32A32_SINT, 16); + fmt(R32G32B32A32_SFLOAT, 16); + fmt(R64_UINT, 8); + fmt(R64_SINT, 8); + fmt(R64_SFLOAT, 8); + fmt(R64G64_UINT, 16); + fmt(R64G64_SINT, 16); + fmt(R64G64_SFLOAT, 16); + fmt(R64G64B64_UINT, 24); + fmt(R64G64B64_SINT, 24); + fmt(R64G64B64_SFLOAT, 24); + fmt(R64G64B64A64_UINT, 32); + fmt(R64G64B64A64_SINT, 32); + fmt(R64G64B64A64_SFLOAT, 32); + fmt(B10G11R11_UFLOAT_PACK32, 4); + fmt(E5B9G9R9_UFLOAT_PACK32, 4); + fmt(D16_UNORM, 2); + fmt(X8_D24_UNORM_PACK32, 4); + fmt(D32_SFLOAT, 4); + fmt(S8_UINT, 1); + fmt(D16_UNORM_S8_UINT, 3); // doesn't make sense. + fmt(D24_UNORM_S8_UINT, 4); + fmt(D32_SFLOAT_S8_UINT, 5); // doesn't make sense. + + // ETC2 + fmt(ETC2_R8G8B8A8_UNORM_BLOCK, 16); + fmt(ETC2_R8G8B8A8_SRGB_BLOCK, 16); + fmt(ETC2_R8G8B8A1_UNORM_BLOCK, 8); + fmt(ETC2_R8G8B8A1_SRGB_BLOCK, 8); + fmt(ETC2_R8G8B8_UNORM_BLOCK, 8); + fmt(ETC2_R8G8B8_SRGB_BLOCK, 8); + fmt(EAC_R11_UNORM_BLOCK, 8); + fmt(EAC_R11_SNORM_BLOCK, 8); + fmt(EAC_R11G11_UNORM_BLOCK, 16); + fmt(EAC_R11G11_SNORM_BLOCK, 16); + + // BC + fmt(BC1_RGB_UNORM_BLOCK, 8); + fmt(BC1_RGB_SRGB_BLOCK, 8); + fmt(BC1_RGBA_UNORM_BLOCK, 8); + fmt(BC1_RGBA_SRGB_BLOCK, 8); + fmt(BC2_UNORM_BLOCK, 16); + fmt(BC2_SRGB_BLOCK, 16); + fmt(BC3_UNORM_BLOCK, 16); + fmt(BC3_SRGB_BLOCK, 16); + + // ASTC + fmt(ASTC_4x4_SRGB_BLOCK, 16); + fmt(ASTC_5x4_SRGB_BLOCK, 16); + fmt(ASTC_5x5_SRGB_BLOCK, 16); + fmt(ASTC_6x5_SRGB_BLOCK, 16); + fmt(ASTC_6x6_SRGB_BLOCK, 16); + fmt(ASTC_8x5_SRGB_BLOCK, 16); + fmt(ASTC_8x6_SRGB_BLOCK, 16); + fmt(ASTC_8x8_SRGB_BLOCK, 16); + fmt(ASTC_10x5_SRGB_BLOCK, 16); + fmt(ASTC_10x6_SRGB_BLOCK, 16); + fmt(ASTC_10x8_SRGB_BLOCK, 16); + fmt(ASTC_10x10_SRGB_BLOCK, 16); + fmt(ASTC_12x10_SRGB_BLOCK, 16); + fmt(ASTC_12x12_SRGB_BLOCK, 16); + fmt(ASTC_4x4_UNORM_BLOCK, 16); + fmt(ASTC_5x4_UNORM_BLOCK, 16); + fmt(ASTC_5x5_UNORM_BLOCK, 16); + fmt(ASTC_6x5_UNORM_BLOCK, 16); + fmt(ASTC_6x6_UNORM_BLOCK, 16); + fmt(ASTC_8x5_UNORM_BLOCK, 16); + fmt(ASTC_8x6_UNORM_BLOCK, 16); + fmt(ASTC_8x8_UNORM_BLOCK, 16); + fmt(ASTC_10x5_UNORM_BLOCK, 16); + fmt(ASTC_10x6_UNORM_BLOCK, 16); + fmt(ASTC_10x8_UNORM_BLOCK, 16); + fmt(ASTC_10x10_UNORM_BLOCK, 16); + fmt(ASTC_12x10_UNORM_BLOCK, 16); + fmt(ASTC_12x12_UNORM_BLOCK, 16); + + default: + assert(0 && "Unknown format."); + return 0; + } +#undef fmt +} + +bool lava::get_supported_depth_format(VkPhysicalDevice physical_device, VkFormat* depth_format) { + + VkFormats depth_formats = { VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D32_SFLOAT, VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D16_UNORM_S8_UINT, VK_FORMAT_D16_UNORM }; + + for (auto& format : depth_formats) { + + VkFormatProperties format_props; + vkGetPhysicalDeviceFormatProperties(physical_device, format, &format_props); + + if (format_props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) { + + *depth_format = format; + return true; + } + } + + return false; +} + +VkImageMemoryBarrier lava::new_image_memory_barrier(VkImage image, VkImageLayout old_layout, VkImageLayout new_layout) { + + return { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = 0, + .dstAccessMask = 0, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange = {}, + }; +} + +void lava::set_image_layout(VkCommandBuffer cmd_buffer, VkImage image, VkImageLayout old_image_layout, VkImageLayout new_image_layout, + VkImageSubresourceRange subresource_range, VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask) { + + auto image_memory_barrier = new_image_memory_barrier(image, old_image_layout, new_image_layout); + image_memory_barrier.subresourceRange = subresource_range; + + switch (old_image_layout) { + + case VK_IMAGE_LAYOUT_UNDEFINED: + image_memory_barrier.srcAccessMask = 0; + break; + + case VK_IMAGE_LAYOUT_PREINITIALIZED: + image_memory_barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; + break; + + case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: + image_memory_barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + break; + + case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: + image_memory_barrier.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + break; + + case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: + image_memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + break; + + case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: + image_memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + break; + + case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: + image_memory_barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; + break; + default: + break; + } + + switch (new_image_layout) { + + case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: + image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + break; + + case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: + image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + break; + + case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: + image_memory_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + break; + + case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: + image_memory_barrier.dstAccessMask = image_memory_barrier.dstAccessMask | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + break; + + case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: + if (image_memory_barrier.srcAccessMask == 0) + image_memory_barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + + image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + break; + default: + break; + } + + vkCmdPipelineBarrier(cmd_buffer, src_stage_mask, dst_stage_mask, 0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier); +} + +void lava::set_image_layout(VkCommandBuffer cmd_buffer, VkImage image, VkImageAspectFlags aspect_mask, VkImageLayout old_image_layout, + VkImageLayout new_image_layout, VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask) { + + VkImageSubresourceRange subresource_range + { + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }; + + set_image_layout(cmd_buffer, image, old_image_layout, new_image_layout, subresource_range, src_stage_mask, dst_stage_mask); +} + +void lava::insert_image_memory_barrier(lava::device* device, VkCommandBuffer cmd_buffer, VkImage image, VkAccessFlags src_access_mask, VkAccessFlags dst_access_mask, + VkImageLayout old_image_layout, VkImageLayout new_image_layout, + VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask, VkImageSubresourceRange subresource_range) { + + auto image_memory_barrier = new_image_memory_barrier(image, old_image_layout, new_image_layout); + + image_memory_barrier.srcAccessMask = src_access_mask; + image_memory_barrier.dstAccessMask = dst_access_mask; + image_memory_barrier.subresourceRange = subresource_range; + + device->call().vkCmdPipelineBarrier(cmd_buffer, src_stage_mask, dst_stage_mask, 0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier); +} diff --git a/liblava/resource/format.hpp b/liblava/resource/format.hpp new file mode 100644 index 00000000..63b4a449 --- /dev/null +++ b/liblava/resource/format.hpp @@ -0,0 +1,48 @@ +// file : liblava/resource/format.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/base/device.hpp> +#include <liblava/core/data.hpp> + +namespace lava { + +bool format_is_depth(VkFormat format); + +bool format_is_stencil(VkFormat format); + +bool format_is_depth_stencil(VkFormat format); + +VkImageAspectFlags format_to_aspect_mask(VkFormat format); + +void format_block_dim(VkFormat format, ui32& width, ui32& height); + +void format_align_dim(VkFormat format, ui32& width, ui32& height); + +void format_num_blocks(VkFormat format, ui32& width, ui32& height); + +ui32 format_block_size(VkFormat format); + +bool get_supported_depth_format(VkPhysicalDevice physical_device, VkFormat* depth_format); + +VkImageMemoryBarrier new_image_memory_barrier(VkImage image, VkImageLayout old_layout, VkImageLayout new_layout); + +void set_image_layout(VkCommandBuffer cmd_buffer, VkImage image, VkImageLayout old_image_layout, + VkImageLayout new_image_layout, VkImageSubresourceRange subresource_range, + VkPipelineStageFlags src_stage_mask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VkPipelineStageFlags dst_stage_mask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); + +void set_image_layout(VkCommandBuffer cmd_buffer, VkImage image, VkImageAspectFlags aspect_mask, + VkImageLayout old_image_layout, VkImageLayout new_image_layout, + VkPipelineStageFlags src_stage_mask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VkPipelineStageFlags dst_stage_mask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); + +void insert_image_memory_barrier(lava::device* device, VkCommandBuffer cmd_buffer, VkImage image, VkAccessFlags src_access_mask, + VkAccessFlags dst_access_mask, VkImageLayout old_image_layout, + VkImageLayout new_image_layout, VkPipelineStageFlags src_stage_mask, + VkPipelineStageFlags dst_stage_mask, + VkImageSubresourceRange subresource_range); + +} // lava diff --git a/liblava/resource/image.cpp b/liblava/resource/image.cpp new file mode 100644 index 00000000..1ce8d5ba --- /dev/null +++ b/liblava/resource/image.cpp @@ -0,0 +1,113 @@ +// file : liblava/resource/image.cpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#include <liblava/resource/image.hpp> + +namespace lava { + +image::image(VkFormat format, VkImage vk_image) : _id(ids::next()), vk_image(vk_image) { + + info = + { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .imageType = VK_IMAGE_TYPE_2D, + .format = format, + .extent = { 0, 0, 1 }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }; + + subresource_range = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }; + + view_info = + { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .image = vk_image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = format, + .components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }, + .subresourceRange = subresource_range, + }; +} + +image::~image() { ids::free(_id); } + +image::ptr image::make(VkFormat format, VkImage vk_image) { return std::make_shared<image>(format, vk_image); } + +image::ptr image::make(VkFormat format, device* device, uv2 size, VkImage vk_image) { + + auto result = make(format, vk_image); + + if (!result->create(device, size)) + return nullptr; + + return result; +} + +bool image::create(device* device, uv2 size, VmaMemoryUsage memory_usage, bool mip_levels_generation) { + + dev = device; + + info.extent = { size.x, size.y, 1 }; + + if (!vk_image) { + + VmaAllocationCreateInfo allocInfo + { + .usage = memory_usage, + }; + + if (failed(vmaCreateImage(dev->alloc(), &info, &allocInfo, &vk_image, &allocation, nullptr))) { + + log()->error("image::create vmaCreateImage failed"); + return false; + } + } + + view_info.image = vk_image; + view_info.subresourceRange = subresource_range; + + return check(device->call().vkCreateImageView(device->get(), &view_info, memory::alloc(), &view)); +} + +void image::destroy(bool only_view) { + + if (view) { + + dev->call().vkDestroyImageView(dev->get(), view, memory::alloc()); + view = nullptr; + } + + if (only_view) + return; + + if (vk_image) { + + vmaDestroyImage(dev->alloc(), vk_image, allocation); + vk_image = nullptr; + allocation = nullptr; + } + + dev = nullptr; +} + +} // lava diff --git a/liblava/resource/image.hpp b/liblava/resource/image.hpp new file mode 100644 index 00000000..e98125d4 --- /dev/null +++ b/liblava/resource/image.hpp @@ -0,0 +1,72 @@ +// file : liblava/resource/image.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/base/device.hpp> +#include <liblava/base/memory.hpp> +#include <liblava/core/id.hpp> +#include <liblava/core/math.hpp> + +namespace lava { + +struct image { + + using ptr = std::shared_ptr<image>; + using map = std::map<id, ptr>; + using list = std::vector<ptr>; + + static ptr make(VkFormat format, VkImage vk_image = nullptr); + static ptr make(VkFormat format, device* device, uv2 size, VkImage vk_image = nullptr); + + explicit image(VkFormat format, VkImage vk_image = nullptr); + ~image(); + + bool create(device* device, uv2 size, VmaMemoryUsage memory_usage = VMA_MEMORY_USAGE_GPU_ONLY, bool mip_levels_generation = false); + void destroy(bool only_view = false); + void destroy_view() { destroy(true); } + + id::ref get_id() const { return _id; } + device* get_device() { return dev; } + + VkFormat get_format() const { return info.format; } + uv2 get_size() const { return { info.extent.width, info.extent.height }; } + ui32 get_depth() const { return info.extent.depth; } + + VkImage get() const { return vk_image; } + VkImageView get_view() const { return view; } + + VkImageCreateInfo const& get_info() const { return info; } + VkImageViewCreateInfo const& get_view_info() const { return view_info; } + VkImageSubresourceRange const& get_subresource_range() const { return subresource_range; } + + void set_flags(VkImageCreateFlagBits flags) { info.flags = flags; } + void set_tiling(VkImageTiling tiling) { info.tiling = tiling; } + void set_usage(VkImageUsageFlags usage) { info.usage = usage; } + void set_layout(VkImageLayout initial) { info.initialLayout = initial; } + + void set_aspectMask(VkImageAspectFlags aspectMask) { subresource_range.aspectMask = aspectMask; } + + void set_level_count(ui32 levels) { subresource_range.levelCount = levels; info.mipLevels = levels; } + void set_layer_count(ui32 layers) { subresource_range.layerCount = layers; info.arrayLayers = layers; } + + void set_component(VkComponentMapping mapping = {}) { view_info.components = mapping; } + void set_view_type(VkImageViewType type) { view_info.viewType = type; } + +private: + id _id; + device* dev = nullptr; + + VkImage vk_image = nullptr; + VkImageCreateInfo info; + + VmaAllocation allocation = nullptr; + + VkImageView view = nullptr; + + VkImageViewCreateInfo view_info; + VkImageSubresourceRange subresource_range; +}; + +} // lava diff --git a/liblava/resource/mesh.cpp b/liblava/resource/mesh.cpp new file mode 100644 index 00000000..32cb0155 --- /dev/null +++ b/liblava/resource/mesh.cpp @@ -0,0 +1,381 @@ +// file : liblava/resource/mesh.cpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#include <liblava/resource/mesh.hpp> + +#ifndef LIBLAVA_ASSIMP +#define LIBLAVA_ASSIMP 0 +#endif + +#if LIBLAVA_ASSIMP +#include <assimp/Importer.hpp> +#include <assimp/cimport.h> +#include <assimp/postprocess.h> +#include <assimp/scene.h> +#endif + +#ifndef LIBLAVA_TINYOBJLOADER +#define LIBLAVA_TINYOBJLOADER 1 +#endif + +#if LIBLAVA_TINYOBJLOADER + +#ifdef _WIN32 +#pragma warning(push, 4) +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + +#define TINYOBJLOADER_IMPLEMENTATION +#include <tiny_obj_loader.h> + +#ifdef _WIN32 +#pragma warning(pop) +#else +#pragma GCC diagnostic pop +#endif + +#endif + +namespace lava { + +mesh::mesh() : _id(ids::next()) {} + +mesh::~mesh() { + + ids::free(_id); + + destroy(); +} + +void mesh::add_data(mesh_data const& value) { + + auto index_base = to_ui32(data.vertices.size()); + + data.vertices.insert(data.vertices.end(), value.vertices.begin(),value.vertices.end()); + + for (auto& index : value.indices) + data.indices.push_back(index_base + index); +} + +bool mesh::create(device* device, bool mapped_, VmaMemoryUsage memory_usage_) { + + dev = device; + mapped = mapped_; + memory_usage = memory_usage_; + + if (!data.vertices.empty()) { + + vertex_buffer = buffer::make(); + + if (!vertex_buffer->create(device, data.vertices.data(), sizeof(vertex) * data.vertices.size(), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, mapped, memory_usage)) { + + log()->error("mesh::create vertexBuffer create failed"); + return false; + } + } + + if (!data.indices.empty()) { + + index_buffer = buffer::make(); + + if (!index_buffer->create(device, data.indices.data(), sizeof(ui32) * data.indices.size(), VK_BUFFER_USAGE_INDEX_BUFFER_BIT, mapped, memory_usage)) { + + log()->error("mesh::create indexBuffer create failed"); + return false; + } + } + + return true; +} + +void mesh::destroy() { + + vertex_buffer = nullptr; + index_buffer = nullptr; + + dev = nullptr; +} + +bool mesh::reload() { + + auto device = dev; + destroy(); + + return create(device, mapped, memory_usage); +} + +void mesh::bind(VkCommandBuffer cmd_buffer) const { + + if (vertex_buffer && vertex_buffer->is_valid()) { + + std::array<VkDeviceSize, 1> const buffer_offsets = { 0 }; + std::array<VkBuffer, 1> const buffers = { vertex_buffer->get() }; + + vkCmdBindVertexBuffers(cmd_buffer, 0, to_ui32(buffers.size()), buffers.data(), buffer_offsets.data()); + } + + if (index_buffer && index_buffer->is_valid()) + vkCmdBindIndexBuffer(cmd_buffer, index_buffer->get(), 0, VK_INDEX_TYPE_UINT32); +} + +void mesh::draw(VkCommandBuffer cmd_buffer) const { + + if (!data.indices.empty()) + vkCmdDrawIndexed(cmd_buffer, to_ui32(data.indices.size()), 1, 0, 0, 0); + else + vkCmdDraw(cmd_buffer, to_ui32(data.vertices.size()), 1, 0, 0); +} + +} // lava + +lava::mesh::ptr lava::load_mesh(device* device, name filename) { + +#if LIBLAVA_TINYOBJLOADER + if (has_extension(filename, "OBJ")) { + + tinyobj::attrib_t attrib; + std::vector<tinyobj::shape_t> shapes; + std::vector<tinyobj::material_t> materials; + std::string err; + std::string warn; + + string target_file = filename; + + file_guard temp_file_remover; + { + file file(filename); + if (file.is_open() && file.get_type()._value == file_type::fs) { + + string temp_file; + temp_file = file_system::get_pref_dir(); + temp_file += get_filename_from(target_file, true); + + scope_data temp_data(file.get_size()); + if (!temp_data.ptr) + return nullptr; + + if (is_file_error(file.read(temp_data.ptr))) + return nullptr; + + if (!write_file(temp_file.c_str(), temp_data.ptr, temp_data.size)) + return nullptr; + + target_file = temp_file; + + temp_file_remover.filename = target_file; + } + } + + if (tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, target_file.c_str())) { + + auto mesh = mesh::make(); + + for (auto const& shape : shapes) { + + for (auto const& index : shape.mesh.indices) { + + vertex vertex; + + vertex.position = v3(attrib.vertices[3 * index.vertex_index], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2]); + + vertex.color = v3(1.f, 1.f, 1.f); + + if (!attrib.texcoords.empty()) + vertex.uv = v2(attrib.texcoords[2 * index.texcoord_index], 1.f - attrib.texcoords[2 * index.texcoord_index + 1]); + + vertex.normal = attrib.normals.empty() ? v3(0.f) : v3(attrib.normals[3 * index.normal_index], + attrib.normals[3 * index.normal_index + 1], + attrib.normals[3 * index.normal_index + 2]); + + mesh->get_vertices().push_back(vertex); + mesh->get_indices().push_back(mesh->get_indices_count()); + } + } + + if (mesh->empty()) + return nullptr; + + if (!mesh->create(device)) + return nullptr; + + return mesh; + } + } +#endif + +#if LIBLAVA_ASSIMP + Assimp::Importer importer; + + file file(filename); + scope_data temp_data(file.get_size(), false); + + if (file.is_open()) { + + if (!temp_data.allocate()) + return nullptr; + + if (is_file_error(file.read(temp_data.ptr))) + return nullptr; + } + + static ui32 const assimp_flags = aiProcess_FlipWindingOrder | aiProcess_Triangulate | aiProcess_PreTransformVertices | aiProcess_GenNormals; + + aiScene const* scene = nullptr; + if (file.is_open()) + scene = importer.ReadFileFromMemory(temp_data.ptr, temp_data.size, assimp_flags); + else + scene = importer.ReadFile(filename, assimp_flags); + + if (!scene) + return nullptr; + + auto mesh = mesh::make(); + + for (auto m = 0u; m < scene->mNumMeshes; ++m) { + + for (auto v = 0u; v < scene->mMeshes[m]->mNumVertices; ++v) { + + vertex vertex; + + vertex.position = glm::make_vec3(&scene->mMeshes[m]->mVertices[v].x); + vertex.color = (scene->mMeshes[m]->HasVertexColors(0)) ? glm::make_vec3(&scene->mMeshes[m]->mColors[0][v].r) : v3(1.f); + vertex.uv = (scene->mMeshes[m]->HasTextureCoords(0)) ? glm::make_vec2(&scene->mMeshes[m]->mTextureCoords[0][v].x) : v2(0.f); + vertex.normal = scene->mMeshes[m]->mNormals ? glm::make_vec3(&scene->mMeshes[m]->mNormals[v].x) : v3(0.f); + + vertex.position.y *= -1.0f; + + mesh->get_vertices().push_back(vertex); + } + + auto index_base = mesh->get_indices_count(); + for (auto f = 0u; f < scene->mMeshes[m]->mNumFaces; ++f) { + + for (auto i = 0u; i < 3; ++i) { + + mesh->get_indices().push_back(scene->mMeshes[m]->mFaces[f].mIndices[i] + index_base); + } + } + } + + if (mesh->empty()) + return nullptr; + + if (!mesh->create(device)) + return nullptr; + + return mesh; +#else + return nullptr; +#endif +} + +lava::mesh::ptr lava::load_mesh(device* device, mesh_type type) { + + switch (type._value) { + + case mesh_type::cube: { + + auto cube = mesh::make(); + cube->get_vertices() = { + + // front + { { 1.f, 1.f, 1.f }, { 1.f, 1.f, 1.f }, { 1.f, 1.f }, { 0.f, 0.f, 1.f } }, + { { -1.f, 1.f, 1.f }, { 1.f, 1.f, 1.f }, { 0.f, 1.f }, { 0.f, 0.f, 1.f } }, + { { -1.f, -1.f, 1.f }, { 1.f, 1.f, 1.f }, { 0.f, 0.f }, { 0.f, 0.f, 1.f } }, + { { 1.f, -1.f, 1.f }, { 1.f, 1.f, 1.f }, { 1.f, 0.f }, { 0.f, 0.f, 1.f } }, + + // back + { { 1.f, 1.f, -1.f }, { 1.f, 1.f, 1.f }, { 0.f, 1.f }, { 0.f, 0.f, -1.f } }, + { { -1.f, 1.f, -1.f }, { 1.f, 1.f, 1.f }, { 1.f, 1.f }, { 0.f, 0.f, -1.f } }, + { { -1.f, -1.f, -1.f }, { 1.f, 1.f, 1.f }, { 1.f, 0.f }, { 0.f, 0.f, -1.f } }, + { { 1.f, -1.f, -1.f }, { 1.f, 1.f, 1.f }, { 0.f, 0.f }, { 0.f, 0.f, -1.f } }, + + // left + { { -1.f, 1.f, 1.f }, { 1.f, 1.f, 1.f }, { 1.f, 1.f }, { -1.f, 0.f, 0.f } }, + { { -1.f, 1.f, -1.f }, { 1.f, 1.f, 1.f }, { 0.f, 1.f }, { -1.f, 0.f, 0.f } }, + { { -1.f, -1.f, -1.f }, { 1.f, 1.f, 1.f }, { 0.f, 0.f }, { -1.f, 0.f, 0.f } }, + { { -1.f, -1.f, 1.f }, { 1.f, 1.f, 1.f }, { 1.f, 0.f }, { -1.f, 0.f, 0.f } }, + + // right + { { 1.f, 1.f, 1.f }, { 1.f, 1.f, 1.f }, { 0.f, 1.f }, { 1.f, 0.f, 0.f } }, + { { 1.f, -1.f, 1.f }, { 1.f, 1.f, 1.f }, { 0.f, 0.f }, { 1.f, 0.f, 0.f } }, + { { 1.f, -1.f, -1.f }, { 1.f, 1.f, 1.f }, { 1.f, 0.f }, { 1.f, 0.f, 0.f } }, + { { 1.f, 1.f, -1.f }, { 1.f, 1.f, 1.f }, { 1.f, 1.f }, { 1.f, 0.f, 0.f } }, + + // bottom + { { 1.f, 1.f, 1.f }, { 1.f, 1.f, 1.f }, { 1.f, 0.f }, { 0.f, 1.f, 0.f } }, + { { -1.f, 1.f, 1.f }, { 1.f, 1.f, 1.f }, { 0.f, 0.f }, { 0.f, 1.f, 0.f } }, + { { -1.f, 1.f, -1.f }, { 1.f, 1.f, 1.f }, { 0.f, 1.f }, { 0.f, 1.f, 0.f } }, + { { 1.f, 1.f, -1.f }, { 1.f, 1.f, 1.f }, { 1.f, 1.f }, { 0.f, 1.f, 0.f } }, + + // top + { { 1.f, -1.f, 1.f }, { 1.f, 1.f, 1.f }, { 1.f, 1.f }, { 0.f, -1.f, 0.f } }, + { { -1.f, -1.f, 1.f }, { 1.f, 1.f, 1.f }, { 0.f, 1.f }, { 0.f, -1.f, 0.f } }, + { { -1.f, -1.f, -1.f }, { 1.f, 1.f, 1.f }, { 0.f, 0.f }, { 0.f, -1.f, 0.f } }, + { { 1.f, -1.f, -1.f }, { 1.f, 1.f, 1.f }, { 1.f, 0.f }, { 0.f, -1.f, 0.f } }, + }; + + cube->get_indices() = { 0, 1, 2, + 2, 3, 0, + 4, 7, 6, + 6, 5, 4, + 8, 9, 10, + 10, 11, 8, + 12, 13, 14, + 14, 15, 12, + 16, 19, 18, + 18, 17, 16, + 20, 21, 22, + 22, 23, 20, + }; + + if (!cube->create(device)) + return nullptr; + + return cube; + } + + case mesh_type::triangle: { + + auto triangle = mesh::make(); + + triangle->get_vertices().push_back({ { 1.f, 1.f, 0.f }, { 1.f, 1.f, 1.f }, { 1.f, 1.f }, { 0.f, 0.f, 1.f } }); + triangle->get_vertices().push_back({ { -1.f, 1.f, 0.f }, { 1.f, 1.f, 1.f }, { 0.f, 1.f }, { 0.f, 0.f, 1.f } }); + triangle->get_vertices().push_back({ { 0.f, -1.f, 0.f }, { 1.f, 1.f, 1.f }, { 0.5f, 0.f }, { 0.f, 0.f, 1.f } }); + + if (!triangle->create(device)) + return nullptr; + + return triangle; + } + + case mesh_type::quad: { + + auto quad = mesh::make(); + + quad->get_vertices() = + { + { { 1.f, 1.f, 0.f }, { 1.f, 1.f, 1.f }, { 1.f, 1.f }, { 0.f, 0.f, 1.f } }, + { { -1.f, 1.f, 0.f }, { 1.f, 1.f, 1.f }, { 0.f, 1.f }, { 0.f, 0.f, 1.f } }, + { { -1.f, -1.f, 0.f }, { 1.f, 1.f, 1.f }, { 0.f, 0.f }, { 0.f, 0.f, 1.f } }, + { { 1.f, -1.f, 0.f }, { 1.f, 1.f, 1.f }, { 1.f, 0.f }, { 0.f, 0.f, 1.f } }, + }; + + quad->get_indices() = { 0, 1, 2, 2, 3, 0 }; + + if (!quad->create(device)) + return nullptr; + + return quad; + } + + case mesh_type::none: + default: + return nullptr; + } +} diff --git a/liblava/resource/mesh.hpp b/liblava/resource/mesh.hpp new file mode 100644 index 00000000..876ea452 --- /dev/null +++ b/liblava/resource/mesh.hpp @@ -0,0 +1,112 @@ +// file : liblava/resource/mesh.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/resource/buffer.hpp> +#include <liblava/core/math.hpp> +#include <liblava/core/data.hpp> + +namespace lava { + +struct vertex { + + using list = std::vector<vertex>; + + v3 position; + v3 color; + v2 uv; + v3 normal; + + bool operator==(vertex const& other) const { + + return position == other.position && color == other.color && uv == other.uv && normal == other.normal; + } +}; + +struct mesh_data { + + vertex::list vertices; + index_list indices; + + void move(v3 position) { + + for (auto& vertex : vertices) + vertex.position += position; + } + + void scale(r32 factor) { + + for (auto& vertex : vertices) + vertex.position *= factor; + } +}; + +struct mesh { + + using ptr = std::shared_ptr<mesh>; + using map = std::map<id, ptr>; + using list = std::vector<ptr>; + + static ptr make() { return std::make_shared<mesh>(); } + + explicit mesh(); + ~mesh(); + + bool create(device* device, bool mapped = false, VmaMemoryUsage memory_usage = VMA_MEMORY_USAGE_CPU_TO_GPU); + void destroy(); + + void bind(VkCommandBuffer cmd_buffer) const; + void draw(VkCommandBuffer cmd_buffer) const; + + id::ref get_id() const { return _id; } + device* get_device() { return dev; } + + bool empty() const { return data.vertices.empty(); } + + void set_data(mesh_data const& value) { data = value; } + mesh_data& get_data() { return data; } + void add_data(mesh_data const& value); + + vertex::list& get_vertices() { return data.vertices; } + vertex::list const& get_vertices() const { return data.vertices; } + ui32 get_vertices_count() const { return to_ui32(data.vertices.size()); } + + index_list& get_indices() { return data.indices; } + index_list const& get_indices() const { return data.indices; } + ui32 get_indices_count() const { return to_ui32(data.indices.size()); } + + bool reload(); + + buffer::ptr get_vertex_buffer() { return vertex_buffer; } + buffer::ptr get_index_buffer() { return index_buffer; } + +private: + id _id; + device* dev = nullptr; + + mesh_data data; + + buffer::ptr vertex_buffer; + buffer::ptr index_buffer; + + bool mapped = false; + VmaMemoryUsage memory_usage = VMA_MEMORY_USAGE_CPU_TO_GPU; +}; + +mesh::ptr load_mesh(device* device, name filename); + +BETTER_ENUM(mesh_type, type, none = 0, cube, triangle, quad) + +mesh::ptr load_mesh(device* device, mesh_type type); + +struct mesh_meta { + + string filename; // empty -> type + mesh_type type = mesh_type::none; +}; + +using mesh_registry = registry<mesh, mesh_meta>; + +} // lava diff --git a/liblava/resource/texture.cpp b/liblava/resource/texture.cpp new file mode 100644 index 00000000..e8db225f --- /dev/null +++ b/liblava/resource/texture.cpp @@ -0,0 +1,491 @@ +// file : liblava/resource/texture.cpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#include <liblava/resource/texture.hpp> +#include <liblava/resource/format.hpp> + +#include <bitmap_image.hpp> +#include <selene/img/pixel/PixelTypeAliases.hpp> +#include <selene/img/typed/ImageView.hpp> +#include <selene/img_ops/ImageConversions.hpp> + +#ifdef _WIN32 +#pragma warning(push, 4) +#pragma warning(disable : 4458) +#pragma warning(disable : 4100) +#pragma warning(disable : 5054) +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wignored-qualifiers" +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wtype-limits" +#pragma GCC diagnostic ignored "-Wempty-body" +#pragma GCC diagnostic ignored "-Wunused-result" +#endif + +#include <gli/gli.hpp> + +#ifdef _WIN32 +#pragma warning(pop) +#else +#pragma GCC diagnostic pop +#endif + +#define STB_IMAGE_IMPLEMENTATION +#include <stb_image.h> + +namespace lava { + +texture::texture() : _id(ids::next()) {} + +texture::~texture() { + + ids::free(_id); + + destroy(); +} + +bool texture::create(device* device, uv2 size, VkFormat format, layer::list const& layers_, texture_type type_) { + + layers = layers_; + type = type_; + + if (layers.empty()) { + + layer layer; + + mip_level level; + level.extent = size; + + layer.levels.push_back(level); + layers.push_back(layer); + } + + VkSamplerAddressMode sampler_address_mode = VK_SAMPLER_ADDRESS_MODE_REPEAT; + if (type._value == texture_type::array || type._value == texture_type::cube_map) + sampler_address_mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + + VkSamplerCreateInfo sampler_info + { + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .magFilter = VK_FILTER_LINEAR, + .minFilter = VK_FILTER_LINEAR, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, + .addressModeU = sampler_address_mode, + .addressModeV = sampler_address_mode, + .addressModeW = sampler_address_mode, + .mipLodBias = 0.f, + .anisotropyEnable = device->get_features().samplerAnisotropy, + .maxAnisotropy = device->get_properties().limits.maxSamplerAnisotropy, + .compareEnable = VK_FALSE, + .compareOp = VK_COMPARE_OP_NEVER, + .minLod = 0.f, + .maxLod = to_r32(layers.front().levels.size()), + .borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK, + .unnormalizedCoordinates = VK_FALSE, + }; + + + if (failed(device->call().vkCreateSampler(device->get(), &sampler_info, memory::alloc(), &sampler))) { + + log()->error("texture create vkCreateSampler failed"); + return false; + } + + _image = image::make(format); + + if (type._value == texture_type::cube_map) + _image->set_flags(VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT); + + auto view_type = VK_IMAGE_VIEW_TYPE_2D; + if (type._value == texture_type::array) { + + view_type = VK_IMAGE_VIEW_TYPE_2D_ARRAY; + + } else if (type._value == texture_type::cube_map) { + + view_type = VK_IMAGE_VIEW_TYPE_CUBE; + } + + _image->set_tiling(VK_IMAGE_TILING_LINEAR); + _image->set_level_count(to_ui32(layers.front().levels.size())); + _image->set_layer_count(to_ui32(layers.size())); + _image->set_view_type(view_type); + + if (!_image->create(device, size, VMA_MEMORY_USAGE_GPU_ONLY)) { + + log()->error("texture create failed"); + return false; + } + + descriptor.sampler = sampler; + descriptor.imageView = _image->get_view(); + descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + return true; +} + +void texture::destroy() { + + destroy_upload_buffer(); + + device* device = nullptr; + if (_image) + device = _image->get_device(); + + if (sampler) { + + if (device) + device->call().vkDestroySampler(device->get(), sampler, memory::alloc()); + + sampler = nullptr; + } + + if (_image) { + + _image->destroy(); + _image = nullptr; + } +} + +void texture::destroy_upload_buffer() { + + upload_buffer = nullptr; +} + +bool texture::upload(void const* data, size_t data_size) { + + auto device = _image->get_device(); + upload_buffer = buffer::make(); + return upload_buffer->create(device, data, data_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, false, VMA_MEMORY_USAGE_CPU_TO_GPU); +} + +bool texture::stage(VkCommandBuffer cmd_buffer) { + + if (!upload_buffer || !upload_buffer->is_valid()) { + + log()->error("texture stage failed"); + return false; + } + + VkImageSubresourceRange subresource_range + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = to_ui32(layers.front().levels.size()), + .baseArrayLayer = 0, + .layerCount = to_ui32(layers.size()), + }; + + auto device = _image->get_device(); + + set_image_layout(cmd_buffer, _image->get(), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresource_range, + VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + + std::vector<VkBufferImageCopy> regions; + + if (to_ui32(layers.front().levels.size()) > 1) { + + auto offset = 0u; + + for (auto layer = 0u; layer < layers.size(); ++layer) { + + for (auto level = 0u; level < to_ui32(layers.front().levels.size()); ++level) { + + VkImageSubresourceLayers image_subresource + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = level, + .baseArrayLayer = layer, + .layerCount = 1, + }; + + VkExtent3D image_extent + { + .width = layers[layer].levels[level].extent.x, + .height = layers[layer].levels[level].extent.y, + .depth = 1, + }; + + VkBufferImageCopy buffer_copy_region + { + .bufferOffset = offset, + .imageSubresource = image_subresource, + .imageExtent = image_extent, + }; + + regions.push_back(buffer_copy_region); + + offset += layers[layer].levels[level].size; + } + } + + } else { + + VkImageSubresourceLayers subresource_layers + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = to_ui32(layers.size()), + }; + + auto size = _image->get_size(); + + VkBufferImageCopy region + { + .bufferOffset = 0, + .bufferRowLength = size.x, + .bufferImageHeight = size.y, + .imageSubresource = subresource_layers, + .imageOffset = {}, + .imageExtent = { size.x, size.y, 1 }, + }; + + regions.push_back(region); + } + + device->call().vkCmdCopyBufferToImage(cmd_buffer, upload_buffer->get(), _image->get(), + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, to_ui32(regions.size()), regions.data()); + + set_image_layout(cmd_buffer, _image->get(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresource_range, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + + return true; +} + +bool staging::stage(VkCommandBuffer cmdBuffer, index frame) { + + if (!staged.empty() && staged.count(frame) && !staged.at(frame).empty()) { + + for (auto& texture : staged.at(frame)) + texture->destroy_upload_buffer(); + + staged.erase(frame); + } + + if (todo.empty()) + return false; + + texture::list stage_done; + + for (auto& texture : todo) { + + if (!texture->stage(cmdBuffer)) + continue; + + stage_done.push_back(texture); + } + + if (!staged.count(frame)) + staged.emplace(frame, texture::list()); + + for (auto& texture : stage_done) { + + if (!contains(staged.at(frame), texture)) + staged.at(frame).push_back(texture); + + remove(todo, texture); + } + + return true; +} + +} // lava + +lava::texture::ptr lava::load_texture(device* device, file_format filename, texture_type type) { + + auto supported = (filename.format == VK_FORMAT_R8G8B8A8_UNORM) || + (device->get_features().textureCompressionBC && (filename.format == VK_FORMAT_BC3_UNORM_BLOCK)) || + (device->get_features().textureCompressionASTC_LDR && (filename.format == VK_FORMAT_ASTC_8x8_UNORM_BLOCK)) || + (device->get_features().textureCompressionETC2 && (filename.format == VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK)); + if (!supported) + return nullptr; + + auto use_gli = has_extension(filename.path.c_str(), { "DDS", "KTX", "KMG" }); + auto use_stbi = false; + + if (!use_gli) + use_stbi = has_extension(filename.path.c_str(), { "JPG", "PNG", "TGA", "BMP", "PSD", "GIF", "HDR", "PIC" }); + + if (!use_gli && !use_stbi) + return nullptr; + + file file(filename.path.c_str()); + scope_data temp_data(file.get_size(), false); + + if (file.is_open()) { + + if (!temp_data.allocate()) + return nullptr; + + if (is_file_error(file.read(temp_data.ptr))) + return nullptr; + } + + auto texture = texture::make(); + + if (use_gli) { + + texture::layer::list layers; + + switch (type) { + + case texture_type::tex_2d: { + + gli::texture2d tex(file.is_open() ? gli::load(temp_data.ptr, temp_data.size) : gli::load(filename.path)); + assert(!tex.empty()); + if (tex.empty()) + return nullptr; + + auto mip_levels = to_ui32(tex.levels()); + + texture::layer layer; + + for (auto m = 0u; m < mip_levels; ++m) { + + texture::mip_level level; + level.extent = { tex[m].extent().x, tex[m].extent().y }; + level.size = to_ui32(tex[m].size()); + layer.levels.push_back(level); + } + + layers.push_back(layer); + + uv2 size = { tex[0].extent().x, tex[0].extent().y }; + if (!texture->create(device, size, filename.format, layers, type)) + return nullptr; + + if (!texture->upload(tex.data(), tex.size())) + return nullptr; + + break; + } + + case texture_type::array: { + + gli::texture2d_array tex(file.is_open() ? gli::load(temp_data.ptr, temp_data.size) : gli::load(filename.path)); + assert(!tex.empty()); + if (tex.empty()) + return nullptr; + + auto layer_count = to_ui32(tex.layers()); + auto mip_levels = to_ui32(tex.levels()); + + for (auto i = 0u; i < layer_count; ++i) { + + texture::layer layer; + + for (auto m = 0u; m < mip_levels; ++m) { + + texture::mip_level level; + level.extent = { tex[i][m].extent().x, tex[i][m].extent().y }; + level.size = to_ui32(tex[i][m].size()); + layer.levels.push_back(level); + } + + layers.push_back(layer); + } + + uv2 size = { tex[0].extent().x, tex[0].extent().y }; + if (!texture->create(device, size, filename.format, layers, type)) + return nullptr; + + if (!texture->upload(tex.data(), tex.size())) + return nullptr; + + break; + } + + case texture_type::cube_map: { + + gli::texture_cube tex(file.is_open() ? gli::load(temp_data.ptr, temp_data.size) : gli::load(filename.path)); + assert(!tex.empty()); + if (tex.empty()) + return nullptr; + + auto layer_count = to_ui32(tex.faces()); + auto mip_levels = to_ui32(tex.levels()); + + for (auto i = 0u; i < layer_count; ++i) { + + texture::layer layer; + + for (auto m = 0u; m < mip_levels; ++m) { + + texture::mip_level level; + level.extent = { tex[i][m].extent().x, tex[i][m].extent().y }; + level.size = to_ui32(tex[i][m].size()); + layer.levels.push_back(level); + } + + layers.push_back(layer); + } + + uv2 size = { tex[0].extent().x, tex[0].extent().y }; + if (!texture->create(device, size, filename.format, layers, type)) + return nullptr; + + if (!texture->upload(tex.data(), tex.size())) + return nullptr; + + break; + } + } + } + else { // use_stbi + + i32 tex_width, tex_height, tex_channels = 0; + stbi_uc* data = nullptr; + + if (file.is_open()) + data = stbi_load_from_memory((stbi_uc const*)temp_data.ptr, to_i32(temp_data.size), &tex_width, &tex_height, &tex_channels, STBI_rgb_alpha); + else + data = stbi_load(filename.path.c_str(), &tex_width, &tex_height, &tex_channels, STBI_rgb_alpha); + + if (!data) + return nullptr; + + uv2 size = { tex_width, tex_height }; + if (!texture->create(device, size, VK_FORMAT_R8G8B8A8_UNORM)) + return nullptr; + + auto uploadSize = tex_width * tex_height * tex_channels * sizeof(char); + auto result = texture->upload(data, uploadSize); + + stbi_image_free(data); + + if (!result) + return nullptr; + } + + return texture; +} + +lava::texture::ptr lava::create_default_texture(device* device, uv2 size) { + + auto result = texture::make(); + + if (!result->create(device, size, VK_FORMAT_R8G8B8A8_UNORM)) + return nullptr; + + bitmap_image image(size.x, size.y); + checkered_pattern(64, 64, 255, 255, 255, image); + + image.bgr_to_rgb(); + + sln::TypedLayout typed_layout((sln::PixelLength)size.x, (sln::PixelLength)size.y, (sln::Stride)(image.width() * image.bytes_per_pixel())); + + sln::ImageView<sln::PixelRGB_8u, sln::ImageModifiability::Mutable> img_rgb((uint8_t*)image.data(), typed_layout); + + sln::Image<sln::PixelRGBA_8u> const img_rgba = sln::convert_image<sln::PixelFormat::RGBA>(img_rgb, std::uint8_t{ 192 }); + + if (!result->upload(img_rgba.data(), img_rgba.total_bytes())) + return nullptr; + + return result; +} diff --git a/liblava/resource/texture.hpp b/liblava/resource/texture.hpp new file mode 100644 index 00000000..45d5fba3 --- /dev/null +++ b/liblava/resource/texture.hpp @@ -0,0 +1,103 @@ +// file : liblava/resource/texture.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/resource/buffer.hpp> +#include <liblava/resource/image.hpp> + +namespace lava { + +BETTER_ENUM(texture_type, type, none = 0, tex_2d, array, cube_map) + +struct file_format { + + using list = std::vector<file_format>; + + string path; + VkFormat format = VK_FORMAT_R8G8B8A8_UNORM; +}; + +struct texture { + + using ptr = std::shared_ptr<texture>; + using map = std::map<id, ptr>; + using list = std::vector<ptr>; + + struct mip_level { + + using list = std::vector<mip_level>; + + uv2 extent; + ui32 size = 0; + }; + + struct layer { + + using list = std::vector<layer>; + + mip_level::list levels; + }; + + explicit texture(); + ~texture(); + + static ptr make() { return std::make_shared<texture>(); } + + bool create(device* device, uv2 size, VkFormat format, layer::list const& layers = {}, texture_type type = texture_type::tex_2d); + void destroy(); + + bool upload(void const* data, size_t data_size); + bool stage(VkCommandBuffer cmd_buffer); + void destroy_upload_buffer(); + + VkDescriptorImageInfo get_descriptor() const { return descriptor; } + image::ptr get_image() { return _image; } + + uv2 get_size() const { return _image ? _image->get_size() : uv2(); } + texture_type get_type() const { return type; } + + id::ref get_id() const { return _id; } + + VkFormat get_format() const { return _image ? _image->get_format() : VK_FORMAT_UNDEFINED; } + +private: + id _id; + + image::ptr _image; + + texture_type type = texture_type::none; + layer::list layers; + + VkSampler sampler = nullptr; + VkDescriptorImageInfo descriptor = {}; + + buffer::ptr upload_buffer; +}; + +texture::ptr load_texture(device* device, file_format filename, texture_type type = texture_type::tex_2d); + +inline texture::ptr load_texture(device* device, string filename, VkFormat format = VK_FORMAT_R8G8B8A8_UNORM, texture_type type = texture_type::tex_2d) { + + return load_texture(device, { filename, format }, type); +} + +texture::ptr create_default_texture(device* device, uv2 size = { 512, 512}); + +struct staging { + + void add(texture::ptr texture) { todo.push_back(texture); } + + bool stage(VkCommandBuffer cmdBuffer, index frame); + +private: + texture::list todo; + + using frame_stage_map = std::map<index, texture::list>; + frame_stage_map staged; +}; + +using texture_registry = registry<texture, file_format>; + +} // lava diff --git a/liblava/utils.hpp b/liblava/utils.hpp new file mode 100644 index 00000000..8c7cf6a8 --- /dev/null +++ b/liblava/utils.hpp @@ -0,0 +1,12 @@ +// file : liblava/utils.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/utils/file.hpp> +#include <liblava/utils/log.hpp> +#include <liblava/utils/random.hpp> +#include <liblava/utils/telegram.hpp> +#include <liblava/utils/thread.hpp> +#include <liblava/utils/utility.hpp> diff --git a/liblava/utils/file.cpp b/liblava/utils/file.cpp new file mode 100644 index 00000000..cca1b198 --- /dev/null +++ b/liblava/utils/file.cpp @@ -0,0 +1,437 @@ +// file : liblava/utils/file.cpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#include <liblava/utils/file.hpp> +#include <liblava/utils/utility.hpp> + +#include <physfs.h> +#include <tinyfiledialogs.h> + +bool lava::read_file(std::vector<char>& out, name filename) { + + std::ifstream file(filename, std::ios::ate | std::ios::binary); + assert(file.is_open()); + + if (!file.is_open()) + return false; + + out.clear(); + + auto file_size = to_size_t(file.tellg()); + if (file_size > 0) { + + out.resize(file_size); + file.seekg(0, std::ios::beg); + file.read(out.data(), file_size); + } + + file.close(); + return true; +} + +bool lava::write_file(name filename, char const* data, size_t data_size) { + + std::ofstream file(filename, std::ofstream::binary); + assert(file.is_open()); + + if (!file.is_open()) + return false; + + file.write(data, data_size); + file.close(); + return true; +} + +bool lava::has_extension(name file_name, name extension) { + + string fn = file_name; + string ext = extension; + + string to_check = fn.substr(fn.find_last_of('.') + 1); + + std::transform(to_check.begin(), to_check.end(), to_check.begin(), ::tolower); + std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + + return to_check == ext; +} + +bool lava::has_extension(name filename, names extensions) { + + for (auto& extension : extensions) + if (has_extension(filename, extension)) + return true; + + return false; +} + +lava::string lava::get_filename_from(string_ref path, bool with_extension) { + + fs::path target(path); + return with_extension ? target.filename().string() : target.stem().string(); +} + +bool lava::remove_existing_path(string& target, string_ref path) { + + auto pos = target.find(path); + if (pos != std::string::npos) { + + target.erase(pos, path.length()); + +#ifdef _WIN32 + std::replace(target.begin(), target.end(), '\\', '/'); +#endif + + return true; + } + + return false; +} + +namespace lava { + +internal_version file_system::get_version() { + + PHYSFS_Version result; + PHYSFS_getLinkedVersion(&result); + + return { result.major, result.minor, result.patch }; +} + +name file_system::get_base_dir() { return PHYSFS_getBaseDir(); } + +string file_system::get_base_dir_str() { return string(get_base_dir()); } + +name file_system::get_pref_dir() { return PHYSFS_getPrefDir(get().org, get().app); } + +string file_system::get_res_dir() { + + string res_dir = get_base_dir(); + res_dir += get().res_path; + string_ref const_res_dir = res_dir; + + return fs::path(const_res_dir).lexically_normal().string(); +} + +bool file_system::mount(string_ref path) { return PHYSFS_mount(path.c_str(), nullptr, 1) != 0; } + +bool file_system::mount(name base_dir_path) { return mount(get_base_dir_str() + base_dir_path); } + +bool file_system::exists(name file) { return PHYSFS_exists(file) != 0; } + +name file_system::get_real_dir(name file) { return PHYSFS_getRealDir(file); } + +bool file_system::initialize(name argv_0, name org_, name app_, name ext_) { + + assert(!initialized); // only once + if (initialized) + return initialized; + + if (!initialized) { + + PHYSFS_init(argv_0); + + PHYSFS_setSaneConfig(org_, app_, ext_, 0, 0); + initialized = true; + } + + return initialized; +} + +void file_system::terminate() { + + if (!initialized) + return; + + PHYSFS_deinit(); +} + +void file_system::mount_res() { + +#if LIBLAVA_DEBUG +#if _WIN32 + res_path = "../../res/"; +#else + res_path = "../res/"; +#endif +#else + res_path = "res/"; +#endif + + if (fs::exists(get_res_dir().c_str())) + if (file_system::mount(res_path.c_str())) + log()->debug("mounted {}", get_res_dir().c_str()); + + string archive_file = "res.zip"; + if (fs::exists({ archive_file })) + if (file_system::mount(archive_file.c_str())) + log()->debug("mounted {}", archive_file.c_str()); +} + +bool file_system::create_data_folder() { + + fs::path data_path = fs::current_path(); + data_path += fs::path::preferred_separator; + data_path += "data"; + + if (!fs::exists(data_path)) + fs::create_directories(data_path); + + return fs::exists(data_path); +} + +file::file(name path_, bool write) { open(path_, write); } + +file::~file() { close(); } + +bool file::open(name path_, bool write) { + + if (!path_) + return false; + + path = path_; + write_mode = write; + + if (write_mode) + fs_file = PHYSFS_openWrite(path); + else + fs_file = PHYSFS_openRead(path); + + if (fs_file) { + + type = file_type::fs; + + } else { + + if (write) { + + o_stream = std::ofstream(path, std::ofstream::binary); + if (o_stream.is_open()) + type = file_type::f_stream; + + } else { + + i_stream = std::ifstream(path, std::ios::binary | std::ios::ate); + if (i_stream.is_open()) + type = file_type::f_stream; + } + } + + return is_open(); +} + +void file::close() { + + if (type._value == file_type::fs) { + + PHYSFS_close(fs_file); + + } else if (type._value == file_type::f_stream) { + + if (write_mode) + o_stream.close(); + else + i_stream.close(); + } +} + +bool file::is_open() const { + + if (type._value == file_type::fs) { + + return fs_file != nullptr; + + } else if (type._value == file_type::f_stream) { + + if (write_mode) + return o_stream.is_open(); + else + return i_stream.is_open(); + } + + return false; +} + +i64 file::get_size() const { + + if (type._value == file_type::fs) { + + return PHYSFS_fileLength(fs_file); + + } else if (type._value == file_type::f_stream) { + + if (write_mode) + return to_i64(o_stream.tellp()); + else + return to_i64(i_stream.tellg()); + } + + return file_error; +} + +i64 file::read(data_ptr data, ui64 size) { + + if (write_mode) + return file_error; + + if (type._value == file_type::fs) { + + return PHYSFS_readBytes(fs_file, data, size); + + } else if (type._value == file_type::f_stream) { + + i_stream.seekg(0, std::ios::beg); + i_stream.read(data, size); + return to_i64(size); + } + + return file_error; +} + +i64 file::write(data_cptr data, ui64 size) { + + if (!write_mode) + return file_error; + + if (type._value == file_type::fs) { + + return PHYSFS_writeBytes(fs_file, data, size); + + } else if (type._value == file_type::f_stream) { + + o_stream.write(data, size); + return to_i64(size); + } + + return file_error; +} + +config_file::config_file(name path) : path(path) {} + +void config_file::add_callback(config_file_callback* callback) { callbacks.push_back(callback); } + +void config_file::remove_callback(config_file_callback* callback) { remove(callbacks, callback); } + +bool config_file::load() { + + scope_data data; + if (!load_file_data(path, data)) { + + log()->error("couldn't load config file {}", path.c_str()); + return false; + } + + auto j = json::parse({ data.ptr, data.size }); + + for (auto callback : callbacks) + callback->on_load(j); + + return true; +} + +bool config_file::save() { + + file file(path.c_str(), true); + if (!file.is_open()) { + + log()->error("couldn't save config file {}", path.c_str()); + return false; + } + + json j; + + for (auto callback : callbacks) + callback->on_save(j); + + auto jString = j.dump(4); + + file.write(jString.data(), jString.size()); + + return true; +} + +} // lava + +bool lava::load_file_data(string_ref filename, scope_data& data) { + + file file(filename.c_str()); + if (!file.is_open()) { + + log()->error("couldn't open file {} for reading", filename); + return false; + } + + data.set(to_size_t(file.get_size())); + if (!data.ptr) + return false; + + if (is_file_error(file.read(data.ptr))) { + + log()->error("couldn't read file {}", filename); + return false; + } + + return true; +} + +lava::string_list lava::open_file_dialog(file_dialog const& dialog, bool multiFile) { + + auto dialog_result = tinyfd_openFileDialog(dialog.title, dialog.default_path_and_file, + to_i32(dialog.filter_pattern.size()), dialog.filter_pattern.data(), + dialog.single_filter_description, multiFile ? 1 : 0); + + if (!dialog_result) + return {}; + + string_list result; + + if (multiFile) { + + string s = dialog_result; + string delimiter = "|"; + + size_t pos = 0; + string token; + + auto count = std::count(s.begin(), s.end(), '|'); + result.resize(to_ui32(count) + 1); + + if (count > 0) { + + auto index = 0u; + while ((pos = s.find(delimiter)) != string::npos) { + + token = s.substr(0, pos); + result.at(index) = token; + s.erase(0, pos + delimiter.length()); + ++index; + } + + result.at(index) = s; + + } else { + + result.at(0) = dialog_result; + } + + } else { + + result.resize(1); + result.at(0) = dialog_result; + } + + return result; +} + +lava::string lava::save_file_dialog(file_dialog const& dialog) { + + return tinyfd_saveFileDialog(dialog.title, dialog.default_path_and_file, + to_i32(dialog.filter_pattern.size()), + dialog.filter_pattern.data(), + dialog.single_filter_description); +} + +lava::string lava::select_folder_dialog(name title, name default_path) { + + return tinyfd_selectFolderDialog(title, default_path); +} diff --git a/liblava/utils/file.hpp b/liblava/utils/file.hpp new file mode 100644 index 00000000..4d5c75fb --- /dev/null +++ b/liblava/utils/file.hpp @@ -0,0 +1,185 @@ +// file : liblava/file.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/core/data.hpp> +#include <liblava/utils/log.hpp> + +#include <fstream> +#include <filesystem> + +#include <nlohmann/json.hpp> + +// fwd +struct PHYSFS_File; + +namespace lava { + +using json = nlohmann::json; + +namespace fs = std::filesystem; + +bool read_file(std::vector<char>& out, name filename); + +bool write_file(name filename, char const* data, size_t data_size); + +bool has_extension(name file_name, name extension); + +bool has_extension(name filename, names extensions); + +string get_filename_from(string_ref path, bool with_extension = false); + +bool remove_existing_path(string& target, string_ref path); + +struct file_guard : no_copy_no_move { + + explicit file_guard(name filename = nullptr) : filename(filename) {} + explicit file_guard(string filename) : filename(filename) {} + + ~file_guard() { + + if (remove) + fs::remove(filename); + } + + string filename = nullptr; + bool remove = true; +}; + +constexpr name _zip_ = "zip"; + +struct file_system : no_copy_no_move { + + static file_system& get() { + + static file_system fs; + return fs; + } + + static internal_version get_version(); + + static name get_base_dir(); + static string get_base_dir_str(); + static name get_pref_dir(); + static string get_res_dir(); + + static bool mount(string_ref path); + static bool mount(name base_dir_path); + static bool exists(name file); + static name get_real_dir(name file); + + bool initialize(name argv_0, name org, name app, name ext); + void terminate(); + + void mount_res(); + bool create_data_folder(); + + name get_org() const { return org; } + name get_app() const { return app; } + name get_ext() const { return ext; } + + bool is_initialized() const { return initialized; } + +private: + file_system() = default; + + bool initialized = false; + + name org = nullptr; + name app = nullptr; + name ext = nullptr; + + string res_path; +}; + +BETTER_ENUM(file_type, type, none = 0, fs, f_stream) + +constexpr i64 const file_error = -1; + +inline bool is_file_error(i64 result) { return result == file_error; } + +struct file : no_copy_no_move { + + explicit file(name path = nullptr, bool write = false); + ~file(); + + bool open(name path, bool write = false); + void close(); + + bool is_open() const; + i64 get_size() const; + + i64 read(data_ptr data) { return read(data, to_ui64(get_size())); } + i64 read(data_ptr data, ui64 size); + + i64 write(data_cptr data, ui64 size); + + bool is_write_mode() const { return write_mode; } + file_type get_type() const { return type; } + + name get_path() const { return path; } + +private: + file_type type = file_type::none; + bool write_mode = false; + + name path = nullptr; + + PHYSFS_File* fs_file = nullptr; + mutable std::ifstream i_stream; + mutable std::ofstream o_stream; +}; + +bool load_file_data(string_ref filename, scope_data& data); + +struct file_data { + + explicit file_data(string_ref filename) { load_file_data(filename, _data); } + + data const& get() const { return _data; } + +private: + scope_data _data; +}; + +struct file_dialog { + + name title = ""; + name default_path_and_file = ""; + names filter_pattern = {}; + name single_filter_description = nullptr; +}; + +string_list open_file_dialog(file_dialog const& dialog = {}, bool multi_file = false); + +string save_file_dialog(file_dialog const& dialog = {}); + +string select_folder_dialog(name title = "", name default_path = ""); + +struct config_file_callback { + + using list = std::vector<config_file_callback*>; + + using func = std::function<void(json&)>; + func on_load; + func on_save; +}; + +struct config_file { + + explicit config_file(name path); + + void add_callback(config_file_callback* callback); + void remove_callback(config_file_callback* callback); + + bool load(); + bool save(); + +private: + string path; + config_file_callback::list callbacks; +}; + +} // lava diff --git a/liblava/utils/log.hpp b/liblava/utils/log.hpp new file mode 100644 index 00000000..530a0375 --- /dev/null +++ b/liblava/utils/log.hpp @@ -0,0 +1,80 @@ +// file : liblava/log.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/core/types.hpp> +#include <liblava/core/version.hpp> + +#include <spdlog/spdlog.h> +#include <spdlog/sinks/stdout_sinks.h> +#include <spdlog/sinks/file_sinks.h> + +namespace lava { + +using logger = std::shared_ptr<spdlog::logger>; + +inline logger log(name name = _lava_) { return spdlog::get(name); } + +inline string to_string(string_ref id, string_ref name) { + + return fmt::format("{} | {}", id.c_str(), name.c_str()); +} + +inline string to_string(internal_version const& version) { + + return fmt::format("{}.{}.{}", version.major, version.minor, version.patch); +} + +inline name to_string(version_stage stage) { + + switch (stage) { + + case version_stage::preview: return "preview"; + case version_stage::alpha: return "alpha"; + case version_stage::beta: return "beta"; + case version_stage::rc: return "rc"; + default: + return ""; + } +} + +inline string to_string(version const& version) { + + string stage_str = to_string(version.stage); + if ((version.rev > 1) && (version.stage != version_stage::release)) + stage_str += fmt::format(" {}", version.rev); + + if (version.release == 0) + return fmt::format("{} {}", version.year, stage_str.c_str()); + else + return fmt::format("{}.{} {}", version.year, version.release, stage_str.c_str()); +} + +constexpr name _lava_log_file_ = "lava.log"; + +struct log_config { + + name logger = _lava_; + name file = _lava_log_file_; + + i32 level = -1; + bool debug = false; +}; + +inline void setup_log(log_config config = {}) { + + if (config.debug) { + + spdlog::set_level((config.level < 0) ? spdlog::level::debug : (spdlog::level::level_enum)config.level); + spdlog::stdout_color_mt(config.logger); + + } else { + + spdlog::set_level((config.level < 0) ? spdlog::level::warn : (spdlog::level::level_enum)config.level); + spdlog::basic_logger_mt(config.logger, config.file); + } +} + +} // lava diff --git a/liblava/utils/random.hpp b/liblava/utils/random.hpp new file mode 100644 index 00000000..756cbd37 --- /dev/null +++ b/liblava/utils/random.hpp @@ -0,0 +1,59 @@ +// file : liblava/random.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/core/types.hpp> + +#include <random> + +namespace lava { + +struct random_generator { + + static random_generator& instance() { + + static random_generator generator; + return generator; + } + + i32 get(i32 low, i32 high) { + + std::uniform_int_distribution<i32> dist(low, high); + return dist(mt); + } + + template <typename T = real> + T get(T low, T high) { + + std::uniform_real_distribution<T> dist(low, high); + return dist(mt); + } + +private: + random_generator() { + + std::random_device rd; + mt = std::mt19937(rd()); + } + + std::mt19937 mt; +}; + +template <typename T> +inline T random(T low, T high) { return random_generator::instance().get(low, high); } + +struct pseudo_random_generator { + + explicit pseudo_random_generator(ui32 seed) : seed(seed) {} + + void set_seed(ui32 value) { seed = value; } + ui32 get() { return generate_fast() ^ (generate_fast() >> 7); } + +private: + ui32 seed = 0; + ui32 generate_fast() { return seed = (seed * 196314165 + 907633515); } +}; + +} // lava diff --git a/liblava/utils/telegram.hpp b/liblava/utils/telegram.hpp new file mode 100644 index 00000000..805ab1f7 --- /dev/null +++ b/liblava/utils/telegram.hpp @@ -0,0 +1,100 @@ +// file : liblava/telegram.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/utils/thread.hpp> + +#include <any> +#include <cmath> +#include <set> + +namespace lava { + +constexpr time const telegram_min_delay = 0.25; + +struct telegram { + + using ref = telegram const&; + + explicit telegram(id::ref sender, id::ref receiver, type msg, time dispatch_time = 0.0, std::any info = {}) + : sender(sender), receiver(receiver), msg(msg), dispatch_time(dispatch_time), info(std::move(info)) {} + + bool operator==(ref rhs) const { + + return (fabs(dispatch_time - rhs.dispatch_time) < telegram_min_delay) && + (sender == rhs.sender) && (receiver == rhs.receiver) && (msg == rhs.msg); + } + + bool operator<(ref rhs) const { + + if (*this == rhs) + return false; + + return (dispatch_time < rhs.dispatch_time); + } + + id sender; + id receiver; + + type msg = no_type; + + time dispatch_time = 0.0; + std::any info; +}; + +struct dispatcher { + + void setup(ui32 threadcount) { pool.setup(threadcount); } + + void teardown() { pool.teardown(); } + + void update(time current) { + + current_time = current; + dispatch_delayed_messages(current_time); + } + + void add_message(id::ref receiver, id::ref sender, type message, time delay = 0.0, std::any const& info = {}) { + + telegram msg(sender, receiver, message, current_time, info); + + if (delay <= 0.0) { + + discharge(msg); // now + return; + } + + msg.dispatch_time += delay; + messages.insert(msg); + } + + using message_func = std::function<void(telegram::ref, id::ref)>; + message_func on_message; + +private: + void discharge(telegram::ref message) { + + pool.enqueue([&, message](id::ref thread) { + if (on_message) + on_message(message, thread); + }); + } + + void dispatch_delayed_messages(time time) { + + while (!messages.empty() && (messages.begin()->dispatch_time < time) && (messages.begin()->dispatch_time > 0.0)) { + + discharge(*messages.begin()); + messages.erase(messages.begin()); + } + } + + time current_time = 0.0; + + thread_pool pool; + std::set<telegram> messages; +}; + +} // lava diff --git a/liblava/utils/thread.hpp b/liblava/utils/thread.hpp new file mode 100644 index 00000000..225f14f4 --- /dev/null +++ b/liblava/utils/thread.hpp @@ -0,0 +1,97 @@ +// file : liblava/thread.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/core/id.hpp> +#include <liblava/core/time.hpp> + +#include <condition_variable> +#include <deque> +#include <mutex> +#include <thread> + +namespace lava { + +inline void sleep(time seconds) { + + std::this_thread::sleep_for(std::chrono::milliseconds(seconds_in_ms(seconds))); +} + +struct thread_pool { + + using task = std::function<void(id::ref)>; // thread id + + void setup(ui32 count = 2) { + + for (auto i = 0u; i < count; ++i) + workers.emplace_back(worker(*this)); + } + + void teardown() { + + stop = true; + condition.notify_all(); + + for (auto& worker : workers) + worker.join(); + + workers.clear(); + } + + template <typename F> + void enqueue(F f) { + + { + std::unique_lock<std::mutex> lock(queueMutex); + tasks.push_back(task(f)); + } + condition.notify_one(); + } + +private: + struct worker { + + explicit worker(thread_pool& pool) : pool(pool) {} + + void operator()() { + + auto thread_id = ids::next(); + + task task; + while (true) { + + { + std::unique_lock<std::mutex> lock(pool.queueMutex); + + while (!pool.stop && pool.tasks.empty()) + pool.condition.wait(lock); + + if (pool.stop) + break; + + task = pool.tasks.front(); + pool.tasks.pop_front(); + } + + task(thread_id); + } + + ids::free(thread_id); + } + + private: + thread_pool& pool; + }; + + std::vector<std::thread> workers; + std::deque<task> tasks; + + std::mutex queueMutex; + std::condition_variable condition; + + bool stop = false; +}; + +} // lava diff --git a/liblava/utils/utility.hpp b/liblava/utils/utility.hpp new file mode 100644 index 00000000..fdd74709 --- /dev/null +++ b/liblava/utils/utility.hpp @@ -0,0 +1,53 @@ +// file : liblava/utility.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include <liblava/core/types.hpp> + +#include <algorithm> +#include <cstring> +#include <utility> + +namespace lava { + +inline bool exists(names_ref list, name item) { + + auto itr = std::find_if(list.begin(), list.end(), [&](name entry) { return strcmp(entry, item) == 0; }); + return itr != list.end(); +} + +template <typename T> +inline void remove(std::vector<T>& list, T item) { + + list.erase(std::remove(list.begin(), list.end(), item), list.end()); +} + +template <typename T> +inline bool contains(std::vector<T>& list, T item) { + + return std::find(list.begin(), list.end(), item) != list.end(); +} + +template <typename T> +inline void append(std::vector<T>& list, std::vector<T>& items) { + + list.insert(list.end(), items.begin(), items.end()); +} + +// reversed iterable + +template <typename T> +struct reversion_wrapper { T& iterable; }; + +template <typename T> +inline auto begin(reversion_wrapper<T> w) { return std::rbegin(w.iterable); } + +template <typename T> +inline auto end(reversion_wrapper<T> w) { return std::rend(w.iterable); } + +template <typename T> +inline reversion_wrapper<T> reverse(T&& iterable) { return { iterable }; } + +} // lava diff --git a/doc/img/lava_block_logo_200.png b/res/texture/lava_block_logo_200.png similarity index 100% rename from doc/img/lava_block_logo_200.png rename to res/texture/lava_block_logo_200.png diff --git a/doc/img/lava_block_logo_50.png b/res/texture/lava_block_logo_50.png similarity index 100% rename from doc/img/lava_block_logo_50.png rename to res/texture/lava_block_logo_50.png diff --git a/tests/driver.cpp b/tests/driver.cpp new file mode 100644 index 00000000..90eb862a --- /dev/null +++ b/tests/driver.cpp @@ -0,0 +1,67 @@ +// file : tests/driver.cpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#include <tests/driver.hpp> + +#include <iostream> + +using namespace lava; + +int run(int argc, char** argv) { + + auto& tests = driver::instance().get(); + + argh::parser cmd_line(argc, argv); + + if (cmd_line[{ "-t", "--tests" }]) { + + for (auto& t : tests) + std::cout << t.first << " - " << t.second->descr << std::endl; + + return to_i32(tests.size()); + } + + if (cmd_line.pos_args().size() > 1) { + + char* end_ptr = nullptr; + auto selected = std::strtol(cmd_line.pos_args().at(1).c_str(), &end_ptr, 10); + if (*end_ptr != '\0') { + + std::cerr << "wrong arguments" << std::endl; + return test_result::wrong_arguments; + } + + if (!tests.count(selected)) { + + std::cerr << "test " << selected << " not found" << std::endl; + return test_result::not_found; + } + + if (tests.count(selected)) + return tests.at(selected)->on_func(cmd_line); + } + + for (auto& t : reverse(tests)) { + + std::cout << "test " << t.first << " - " << t.second->descr << std::endl; + return t.second->on_func(cmd_line); + } + + std::cerr << "no tests" << std::endl; + return test_result::no_tests; +} + +int main(int argc, char* argv[]) { + + return run(argc, argv); +} + +namespace lava { + +test::test(ui32 id, name descr, func func) : id(id), descr(descr), on_func(func) { + + driver::instance().add_test(this); +} + +} // lava diff --git a/tests/driver.hpp b/tests/driver.hpp new file mode 100644 index 00000000..f21709c8 --- /dev/null +++ b/tests/driver.hpp @@ -0,0 +1,81 @@ +// file : tests/driver.hpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#include <liblava/lava.hpp> + +/* --- + +LAVA_TEST(ID, DESCRIPTION) +{ + // command line arguments + // argh::parser argh; + + ... + + return 0; // return int +} + +copy and paste: + +LAVA_TEST(1, "first test") +{ + return argh.size(); +} + +*/ + +namespace lava { + +enum test_result { + + no_tests = -100, + not_found = -101, + wrong_arguments = -102 +}; + +struct test { + + using map = std::map<index, test*>; + using func = std::function<i32(argh::parser)>; + + explicit test(ui32 id, name descr, func func); + + index id = 0; + string descr; + func on_func; +}; + +struct driver { + + static driver& instance() { + + static driver singleton; + return singleton; + } + + void add_test(test* test) { + + tests.emplace(test->id, test); + } + + test::map const& get() const { return tests; } + +private: + driver() = default; + + test::map tests; +}; + +} // lava + +#define OBJ test_ +#define FUNC _test + +#define STR_(n,m) n##m +#define STR(n,m) STR_(n,m) + +#define LAVA_TEST(ID, NAME) \ +i32 STR(FUNC,ID)(argh::parser argh); \ +lava::test STR(OBJ,ID)(ID, NAME, ::STR(FUNC,ID)); \ +i32 STR(FUNC,ID)(argh::parser argh) diff --git a/tests/tests.cpp b/tests/tests.cpp new file mode 100644 index 00000000..8a5c41ea --- /dev/null +++ b/tests/tests.cpp @@ -0,0 +1,220 @@ +// file : tests/tests.cpp +// copyright : Copyright (c) 2018-present, Lava Block OÜ +// license : MIT; see accompanying LICENSE file + +#include <tests/driver.hpp> + +using namespace lava; + +LAVA_TEST(1, "frame init") +{ + frame frame(argh); + + return frame.ready() ? 0 : error::not_ready; +} + +LAVA_TEST(2, "run loop") +{ + frame frame(argh); + if (!frame.ready()) + return error::not_ready; + + auto count = 0; + + frame.add_run([&]() { + + sleep(1.0); + count++; + + log()->debug("{} - running {} sec", count, frame.get_running_time()); + + if (count == 3) + frame.shut_down(); + + return true; + }); + + return frame.run() ? 0 : error::aborted; +} + +LAVA_TEST(3, "window input") +{ + frame frame(argh); + if (!frame.ready()) + return error::not_ready; + + window window; + if (!window.create()) + return error::create_failed; + + input input; + window.assign(&input); + + input.key_listeners.add([&](key_event::ref event) { + + if (event.key == GLFW_KEY_ESCAPE && event.action == GLFW_PRESS) + frame.shut_down(); + }); + + frame.add_run([&]() { + + handle_events(input); + + if (window.has_close_request()) + frame.shut_down(); + + return true; + }); + + return frame.run() ? 0 : error::aborted; +} + +LAVA_TEST(4, "clear color") +{ + frame frame(argh); + if (!frame.ready()) + return error::not_ready; + + window window; + if (!window.create()) + return error::create_failed; + + input input; + window.assign(&input); + + input.key_listeners.add([&](key_event::ref event) { + + if (event.key == GLFW_KEY_ESCAPE && event.action == GLFW_PRESS) + frame.shut_down(); + }); + + auto device = frame.create_device(); + if (!device) + return error::create_failed; + + auto render_target = create_target(&window, device); + if (!render_target) + return error::create_failed; + + renderer simple_renderer; + if (!simple_renderer.create(render_target->get_swapchain())) + return error::create_failed; + + auto frame_count = render_target->get_frame_count(); + + VkCommandPool cmd_pool; + VkCommandBuffers cmd_bufs(frame_count); + + auto build_cmd_bufs = [&]() { + + VkCommandPoolCreateInfo create_info + { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .queueFamilyIndex = device->get_graphics_queue().family_index, + }; + if (!check(device->call().vkCreateCommandPool(device->get(), &create_info, memory::alloc(), &cmd_pool))) + return false; + + VkCommandBufferAllocateInfo alloc_info + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = cmd_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = frame_count, + }; + if (!check(device->call().vkAllocateCommandBuffers(device->get(), &alloc_info, cmd_bufs.data()))) + return false; + + VkCommandBufferBeginInfo begin_info + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, + }; + + VkClearColorValue clear_color = { random(0.f, 1.f), random(0.f, 1.f), random(0.f, 1.f), 0.f }; + + VkImageSubresourceRange image_range + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1, + }; + + for (auto i = 0u; i < frame_count; ++i) { + + auto cmd_buf = cmd_bufs[i]; + auto target_image = render_target->get_backbuffer_image(i); + + if (!check(device->call().vkBeginCommandBuffer(cmd_buf, &begin_info))) + return false; + + insert_image_memory_barrier(device, cmd_buf, target_image, + VK_ACCESS_MEMORY_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, image_range); + + device->call().vkCmdClearColorImage(cmd_buf, target_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_color, 1, &image_range); + + insert_image_memory_barrier(device, cmd_buf, target_image, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, image_range); + + if (!check(device->call().vkEndCommandBuffer(cmd_buf))) + return false; + } + + return true; + }; + + auto clean_cmd_bufs = [&]() { + + for (auto& cmd_buf : cmd_bufs) + device->call().vkFreeCommandBuffers(device->get(), cmd_pool, 1, &cmd_buf); + + device->call().vkDestroyCommandPool(device->get(), cmd_pool, memory::alloc()); + }; + + if (!build_cmd_bufs()) + return error::create_failed; + + render_target->on_swapchain_start = build_cmd_bufs; + render_target->on_swapchain_stop = clean_cmd_bufs; + + frame.add_run([&]() { + + handle_events(input); + + if (window.has_close_request()) + return frame.shut_down(); + + if (window.has_resize_request()) + return window.handle_resize(); + + if (window.iconified()) { + + frame.set_wait_for_events(true); + return true; + + } else { + + if (frame.waiting_for_events()) + frame.set_wait_for_events(false); + } + + auto frame_index = simple_renderer.begin_frame(); + if (!frame_index) + return true; + + return simple_renderer.end_frame({ cmd_bufs[*frame_index] }); + }); + + frame.add_run_end([&]() { + + clean_cmd_bufs(); + simple_renderer.destroy(); + render_target->destroy(); + }); + + return frame.run() ? 0 : error::aborted; +} -- GitLab