From 1d44ecc0eed3750834a19d52f09ea2a026266336 Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Sat, 6 Apr 2024 14:11:26 +0200 Subject: [PATCH] initial commit --- .gitignore | 60 ++ LibConf | 92 +++ include/iwa/addon.hpp | 29 + include/iwa/addons/imgui/addon.hpp | 62 ++ include/iwa/addons/imgui/fps_widget.hpp | 22 + include/iwa/addons/imgui/misc_widgets.hpp | 34 + include/iwa/addons/imgui/widget.hpp | 20 + include/iwa/app/vulkan_application.hpp | 45 ++ include/iwa/buffer.hpp | 152 ++++ include/iwa/command.hpp | 41 ++ include/iwa/descriptor_set.hpp | 61 ++ include/iwa/device.hpp | 164 +++++ include/iwa/device_memory.hpp | 29 + include/iwa/event.hpp | 26 + include/iwa/fence.hpp | 30 + include/iwa/image.hpp | 270 +++++++ include/iwa/input.hpp | 483 +++++++++++++ include/iwa/instance.hpp | 157 ++++ include/iwa/io/bitmap.hpp | 123 ++++ include/iwa/io/font.hpp | 55 ++ include/iwa/io/mesh.hpp | 11 + include/iwa/log.hpp | 57 ++ include/iwa/object.hpp | 566 +++++++++++++++ include/iwa/pipeline.hpp | 136 ++++ include/iwa/render_pass.hpp | 59 ++ include/iwa/resource/bitmap.hpp | 73 ++ include/iwa/resource/font.hpp | 55 ++ include/iwa/semaphore.hpp | 25 + include/iwa/shader_module.hpp | 27 + include/iwa/swapchain.hpp | 68 ++ include/iwa/texture.hpp | 72 ++ include/iwa/util/color.hpp | 47 ++ include/iwa/util/dir_stack_file_includer.hpp | 116 +++ include/iwa/util/fps_calculator.hpp | 49 ++ include/iwa/util/glsl_compiler.hpp | 152 ++++ include/iwa/util/growing_descriptor_pool.hpp | 27 + include/iwa/util/image_reference.hpp | 106 +++ include/iwa/util/next_chain.hpp | 58 ++ include/iwa/util/reflect_glsl.hpp | 17 + include/iwa/util/render_loop.hpp | 67 ++ include/iwa/util/shader_meta.hpp | 683 ++++++++++++++++++ include/iwa/util/task_runner.hpp | 60 ++ include/iwa/util/texture_atlas.hpp | 104 +++ .../vertex_attribute_semantic.gen.hpp.jinja | 13 + include/iwa/util/vertex_layout.hpp | 42 ++ include/iwa/util/vkutil.hpp | 210 ++++++ include/iwa/vkwrapper.hpp | 28 + include/iwa/window.hpp | 86 +++ source/addon.cpp | 26 + source/addons/imgui/SConscript | 20 + source/addons/imgui/addon.cpp | 299 ++++++++ source/addons/imgui/fps_widget.cpp | 21 + source/app/vulkan_application.cpp | 37 + source/buffer.cpp | 101 +++ source/command.cpp | 65 ++ source/descriptor_set.cpp | 90 +++ source/device.cpp | 381 ++++++++++ source/device_memory.cpp | 39 + source/event.cpp | 28 + source/fence.cpp | 38 + source/image.cpp | 384 ++++++++++ source/input.cpp | 37 + source/instance.cpp | 462 ++++++++++++ source/io/bitmap.cpp | 207 ++++++ source/io/font.cpp | 165 +++++ source/io/mesh.cpp | 6 + source/object.cpp | 130 ++++ source/pipeline.cpp | 136 ++++ source/render_pass.cpp | 70 ++ source/resource/bitmap.cpp | 305 ++++++++ source/resource/font.cpp | 11 + source/semaphore.cpp | 19 + source/shader_module.cpp | 20 + source/swapchain.cpp | 316 ++++++++ source/texture.cpp | 47 ++ source/util/glsl_compiler.cpp | 548 ++++++++++++++ source/util/growing_descriptor_pool.cpp | 33 + source/util/image_reference.cpp | 147 ++++ source/util/reflect_glsl.cpp | 587 +++++++++++++++ source/util/render_loop.cpp | 128 ++++ source/util/shader_meta.cpp | 676 +++++++++++++++++ source/util/texture_atlas.cpp | 402 +++++++++++ source/util/vertex_layout.cpp | 29 + source/util/vkutil.cpp | 275 +++++++ source/window.cpp | 319 ++++++++ 85 files changed, 11573 insertions(+) create mode 100644 .gitignore create mode 100644 LibConf create mode 100644 include/iwa/addon.hpp create mode 100644 include/iwa/addons/imgui/addon.hpp create mode 100644 include/iwa/addons/imgui/fps_widget.hpp create mode 100644 include/iwa/addons/imgui/misc_widgets.hpp create mode 100644 include/iwa/addons/imgui/widget.hpp create mode 100644 include/iwa/app/vulkan_application.hpp create mode 100644 include/iwa/buffer.hpp create mode 100644 include/iwa/command.hpp create mode 100644 include/iwa/descriptor_set.hpp create mode 100644 include/iwa/device.hpp create mode 100644 include/iwa/device_memory.hpp create mode 100644 include/iwa/event.hpp create mode 100644 include/iwa/fence.hpp create mode 100644 include/iwa/image.hpp create mode 100644 include/iwa/input.hpp create mode 100644 include/iwa/instance.hpp create mode 100644 include/iwa/io/bitmap.hpp create mode 100644 include/iwa/io/font.hpp create mode 100644 include/iwa/io/mesh.hpp create mode 100644 include/iwa/log.hpp create mode 100644 include/iwa/object.hpp create mode 100644 include/iwa/pipeline.hpp create mode 100644 include/iwa/render_pass.hpp create mode 100644 include/iwa/resource/bitmap.hpp create mode 100644 include/iwa/resource/font.hpp create mode 100644 include/iwa/semaphore.hpp create mode 100644 include/iwa/shader_module.hpp create mode 100644 include/iwa/swapchain.hpp create mode 100644 include/iwa/texture.hpp create mode 100644 include/iwa/util/color.hpp create mode 100644 include/iwa/util/dir_stack_file_includer.hpp create mode 100644 include/iwa/util/fps_calculator.hpp create mode 100644 include/iwa/util/glsl_compiler.hpp create mode 100644 include/iwa/util/growing_descriptor_pool.hpp create mode 100644 include/iwa/util/image_reference.hpp create mode 100644 include/iwa/util/next_chain.hpp create mode 100644 include/iwa/util/reflect_glsl.hpp create mode 100644 include/iwa/util/render_loop.hpp create mode 100644 include/iwa/util/shader_meta.hpp create mode 100644 include/iwa/util/task_runner.hpp create mode 100644 include/iwa/util/texture_atlas.hpp create mode 100644 include/iwa/util/vertex_attribute_semantic.gen.hpp.jinja create mode 100644 include/iwa/util/vertex_layout.hpp create mode 100644 include/iwa/util/vkutil.hpp create mode 100644 include/iwa/vkwrapper.hpp create mode 100644 include/iwa/window.hpp create mode 100644 source/addon.cpp create mode 100644 source/addons/imgui/SConscript create mode 100644 source/addons/imgui/addon.cpp create mode 100644 source/addons/imgui/fps_widget.cpp create mode 100644 source/app/vulkan_application.cpp create mode 100644 source/buffer.cpp create mode 100644 source/command.cpp create mode 100644 source/descriptor_set.cpp create mode 100644 source/device.cpp create mode 100644 source/device_memory.cpp create mode 100644 source/event.cpp create mode 100644 source/fence.cpp create mode 100644 source/image.cpp create mode 100644 source/input.cpp create mode 100644 source/instance.cpp create mode 100644 source/io/bitmap.cpp create mode 100644 source/io/font.cpp create mode 100644 source/io/mesh.cpp create mode 100644 source/object.cpp create mode 100644 source/pipeline.cpp create mode 100644 source/render_pass.cpp create mode 100644 source/resource/bitmap.cpp create mode 100644 source/resource/font.cpp create mode 100644 source/semaphore.cpp create mode 100644 source/shader_module.cpp create mode 100644 source/swapchain.cpp create mode 100644 source/texture.cpp create mode 100644 source/util/glsl_compiler.cpp create mode 100644 source/util/growing_descriptor_pool.cpp create mode 100644 source/util/image_reference.cpp create mode 100644 source/util/reflect_glsl.cpp create mode 100644 source/util/render_loop.cpp create mode 100644 source/util/shader_meta.cpp create mode 100644 source/util/texture_atlas.cpp create mode 100644 source/util/vertex_layout.cpp create mode 100644 source/util/vkutil.cpp create mode 100644 source/window.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..360349d --- /dev/null +++ b/.gitignore @@ -0,0 +1,60 @@ +# Compile commands +compile_commands.json + +# whatever this is +.cache + +# Environment setup +/.env + +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Debug Info +*.pdb + +# for projects that use SCons for building: http://http://www.scons.org/ +.sconsign.dblite +/.sconf_temp +/config.log + +# Byte-compiled / optimized python files +__pycache__/ +*.py[cod] + +# Backup files +*.bak + +# Generated files (but not their templates) +*.gen.* +!*.gen.*.jinja diff --git a/LibConf b/LibConf new file mode 100644 index 0000000..b087658 --- /dev/null +++ b/LibConf @@ -0,0 +1,92 @@ + +Import('env') + +# LibFmt +lib_fmt = env.Cook('fmt', git_ref = 'refs/tags/10.0.0') + +# magic_enum +lib_magic_enum = env.Cook('magic_enum', git_ref = 'refs/tags/v0.9.3') + +# Glslang +lib_glslang = env.Cook('glslang', remote = 'mewin') + +# GLM +env.Append(CPPDEFINES = ['GLM_FORCE_DEPTH_ZERO_TO_ONE', 'GLM_FORCE_RADIANS']) +lib_glm = env.Cook('glm', remote = 'mewin') + +# Mijin +lib_mijin = env.Cook('mijin') + +# SDL +lib_sdl = env.Cook('SDL', git_ref = 'refs/tags/release-2.28.4') + +# stb +lib_stb = env.Cook('stb') + +# VulkanHeaders +lib_vulkan_headers = env.Cook('VulkanHeaders', remote = 'mewin', git_ref = 'refs/tags/v1.3.271-2') + +# Argparse +lib_argparse = env.Cook('argparse', git_ref = 'refs/tags/v3.0') + +# yaml-cpp +lib_yaml_cpp = env.Cook('yaml-cpp', git_ref = 'refs/tags/0.8.0') + +# Generated files +env.Jinja('public/iwa/util/vertex_attribute_semantic.gen.hpp') + +# Iwa +src_files = Split(""" + source/addon.cpp + source/buffer.cpp + source/command.cpp + source/descriptor_set.cpp + source/device.cpp + source/device_memory.cpp + source/event.cpp + source/fence.cpp + source/image.cpp + source/input.cpp + source/instance.cpp + source/object.cpp + source/pipeline.cpp + source/render_pass.cpp + source/semaphore.cpp + source/shader_module.cpp + source/swapchain.cpp + source/texture.cpp + source/window.cpp + + source/app/vulkan_application.cpp + + source/io/bitmap.cpp + source/io/font.cpp + source/io/mesh.cpp + + source/resource/bitmap.cpp + source/resource/font.cpp + + source/util/glsl_compiler.cpp + source/util/growing_descriptor_pool.cpp + source/util/image_reference.cpp + source/util/reflect_glsl.cpp + source/util/render_loop.cpp + source/util/shader_meta.cpp + source/util/texture_atlas.cpp + source/util/vertex_layout.cpp + source/util/vkutil.cpp +""") + +lib_iwa = env.UnityStaticLibrary( + target = env['LIB_DIR'] + '/iwa', + source = src_files, + dependencies = [lib_fmt, lib_glslang, lib_glm, lib_magic_enum, lib_mijin, lib_sdl, lib_stb, lib_vulkan_headers, + lib_argparse, lib_yaml_cpp] +) + +LIB_CONFIG = { + 'CPPPATH': [env.Dir('include')], + 'DEPENDENCIES': [lib_iwa] +} + +Return('LIB_CONFIG') diff --git a/include/iwa/addon.hpp b/include/iwa/addon.hpp new file mode 100644 index 0000000..a50ce0b --- /dev/null +++ b/include/iwa/addon.hpp @@ -0,0 +1,29 @@ + +#pragma once + +#if !defined(IWA_ADDON_HPP_INCLUDED) +#define IWA_ADDON_HPP_INCLUDED + +#include +#include +#include "iwa/instance.hpp" + +namespace iwa +{ +struct AddonInitArgs +{ + ObjectPtr instance; + InstanceCreationArgs& instanceCreationArgs; +}; +class Addon +{ +public: + Addon(); + virtual ~Addon() = default; + virtual void init(const AddonInitArgs& args) = 0; + virtual void cleanup() = 0; +}; +[[nodiscard]] std::span getAddons() noexcept; +} // namespace iwa + +#endif // !defined(IWA_ADDON_HPP_INCLUDED) diff --git a/include/iwa/addons/imgui/addon.hpp b/include/iwa/addons/imgui/addon.hpp new file mode 100644 index 0000000..d4cce6b --- /dev/null +++ b/include/iwa/addons/imgui/addon.hpp @@ -0,0 +1,62 @@ + +#pragma once + +#if !defined(IWA_ADDONS_IMGUI_ADDON_HPP_INCLUDED) +#define IWA_ADDONS_IMGUI_ADDON_HPP_INCLUDED + +#include +#include +#include +#include "iwa/addon.hpp" +#include "iwa/descriptor_set.hpp" +#include "iwa/swapchain.hpp" +#include "iwa/window.hpp" +#include "iwa/addons/imgui/widget.hpp" + +namespace iwa +{ +struct ImguiCreateResourcesArgs +{ + Swapchain& swapchain; + vk::Format format; +}; + +class ImGuiAddon : public Addon +{ +private: + ObjectPtr mDescriptorPool; + std::vector> mWidgets; +public: + void init(const AddonInitArgs& args) override; + void cleanup() override; + + mijin::Task<> c_createResources(const ImguiCreateResourcesArgs& args); + void renderFrame(vk::CommandBuffer cmdBuffer); + + template requires (std::is_base_of_v) + TWidget* addWidget(std::unique_ptr&& widget) + { + TWidget* raw = widget.get(); + mWidgets.push_back(std::move(widget)); + return raw; + } + template requires (std::is_base_of_v) + TWidget* emplaceWidget(TArgs&&... args) + { + return addWidget(std::make_unique(std::forward(args)...)); + } + void removeWidget(ImGuiWidget* widget); +private: + void beginFrame() noexcept; + + void handleKeyChanged(const KeyEvent& event); + void handleMouseButtonChanged(const MouseButtonEvent& event); + void handleMouseMoved(const MouseMoveEvent& event); + void handleMouseScrolled(const MouseWheelEvent& event); + void handleTextEntered(const TextInputEvent& event); +public: + static ImGuiAddon& get() noexcept; +}; +} // namespace + +#endif // !defined(IWA_ADDONS_IMGUI_ADDON_HPP_INCLUDED) diff --git a/include/iwa/addons/imgui/fps_widget.hpp b/include/iwa/addons/imgui/fps_widget.hpp new file mode 100644 index 0000000..75e17ca --- /dev/null +++ b/include/iwa/addons/imgui/fps_widget.hpp @@ -0,0 +1,22 @@ + +#pragma once + +#if !defined(IWA_ADDONS_IMGUI_FPS_WIDGET_HPP_INCLUDED) +#define IWA_ADDONS_IMGUI_FPS_WIDGET_HPP_INCLUDED + +#include +#include "iwa/addons/imgui/widget.hpp" +#include "iwa/util/fps_calculator.hpp" + +namespace iwa +{ +class ImGuiFpsWidget : public ImGuiWidget +{ +private: + FpsCalculator<100> mFpsCalculator; +public: + void draw() override; +}; +} // namespace iwa + +#endif // !defined(IWA_ADDONS_IMGUI_FPS_WIDGET_HPP_INCLUDED) diff --git a/include/iwa/addons/imgui/misc_widgets.hpp b/include/iwa/addons/imgui/misc_widgets.hpp new file mode 100644 index 0000000..77f3a8f --- /dev/null +++ b/include/iwa/addons/imgui/misc_widgets.hpp @@ -0,0 +1,34 @@ + +#pragma once + +#if !defined(IWA_ADDONS_IMGUI_MISC_WIDGETS_HPP_INCLUDED) +#define IWA_ADDONS_IMGUI_MISC_WIDGETS_HPP_INCLUDED + +#include +#include + +namespace iwa +{ +template +bool MagicCombo(const char* label, TEnum& currentValue) +{ + const std::string currentValueName(magic_enum::enum_name(currentValue)); + bool result = false; + if (ImGui::BeginCombo(label, currentValueName.c_str())) + { + for (TEnum value : magic_enum::enum_values()) + { + const std::string valueName(magic_enum::enum_name(value)); + if (ImGui::Selectable(valueName.c_str())) { + currentValue = value; + result = true; + } + } + ImGui::EndCombo(); + } + + return result; +} +} // namespace iwa + +#endif // !defined(IWA_ADDONS_IMGUI_MISC_WIDGETS_HPP_INCLUDED) diff --git a/include/iwa/addons/imgui/widget.hpp b/include/iwa/addons/imgui/widget.hpp new file mode 100644 index 0000000..8139963 --- /dev/null +++ b/include/iwa/addons/imgui/widget.hpp @@ -0,0 +1,20 @@ + +#pragma once + +#if !defined(IWA_ADDONS_IMGUI_WIDGET_HPP_INCLUDED) +#define IWA_ADDONS_IMGUI_WIDGET_HPP_INCLUDED + +#include "iwa/object.hpp" + +namespace iwa +{ +class ImGuiWidget +{ +public: + virtual ~ImGuiWidget() = default; + + virtual void draw() = 0; +}; +} // namespace iwa + +#endif // !defined(IWA_ADDONS_IMGUI_WIDGET_HPP_INCLUDED) diff --git a/include/iwa/app/vulkan_application.hpp b/include/iwa/app/vulkan_application.hpp new file mode 100644 index 0000000..004fcbc --- /dev/null +++ b/include/iwa/app/vulkan_application.hpp @@ -0,0 +1,45 @@ + +#pragma once + +#if !defined(IWA_APP_VULLKAN_APPLICATION_HPP_INCLUDED) +#define IWA_APP_VULLKAN_APPLICATION_HPP_INCLUDED + +#include +#include "iwa/device.hpp" +#include "iwa/instance.hpp" +#include "iwa/swapchain.hpp" + +namespace iwa +{ +struct ApplicationCreationFlags +{ + std::uint8_t skipDefaultArgs : 1 = 0; +}; + +struct ApplicationCreationArgs +{ + ApplicationCreationFlags flags; + InstanceCreationArgs instanceArgs; + DeviceCreationArgs deviceArgs; + WindowCreationArgs mainWindowArgs; + SwapchainCreationArgs mainWindowSwapchainArgs; + fs::path assetPath = fs::current_path() / "assets"; +}; + +class VulkanApplication : public Object +{ +protected: + ObjectPtr mInstance; + ObjectPtr mDevice; + ObjectPtr mMainWindow; + ObjectPtr mMainWindowSwapchain; +protected: + explicit VulkanApplication(const ApplicationCreationArgs& args, ObjectPtr<> owner = nullptr); +public: + virtual mijin::Task<> c_init() = 0; + + [[nodiscard]] int execute(int argc, char** argv); // NOLINT +}; +} // namespace iwa + +#endif // !defined(IWA_APP_VULLKAN_APPLICATION_HPP_INCLUDED) diff --git a/include/iwa/buffer.hpp b/include/iwa/buffer.hpp new file mode 100644 index 0000000..f59ab43 --- /dev/null +++ b/include/iwa/buffer.hpp @@ -0,0 +1,152 @@ + +#pragma once + +#if !defined(IWA_BUFFER_HPP_INCLUDED) +#define IWA_BUFFER_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include "iwa/device_memory.hpp" +#include "iwa/util/vkutil.hpp" + +namespace iwa +{ +MIJIN_DEFINE_FLAG(HostVisible); +MIJIN_DEFINE_FLAG(HostCoherent); + +struct BufferCreationArgs +{ + vk::BufferCreateFlags flags; + vk::DeviceSize size; + vk::BufferUsageFlags usage; + vk::SharingMode sharingMode = vk::SharingMode::eExclusive; + std::vector queueFamilyIndices; +}; + +class Buffer : public Object, public MixinVulkanObject +{ +public: + ObjectPtr mMemory; + vk::DeviceSize mBytesSize; +public: + Buffer(ObjectPtr owner, const BufferCreationArgs& args); + ~Buffer() noexcept override; + + [[nodiscard]] const ObjectPtr& getMemory() const noexcept { return mMemory; } + + void allocateMemory(HostVisible hostVisible = HostVisible::NO, HostCoherent hostCoherent = HostCoherent::YES); // the latter is ignored if the first one is NO + void bindMemory(ObjectPtr memory, vk::DeviceSize offset = 0); + + mijin::Task<> c_fill(std::uint32_t data, std::size_t bytes = VK_WHOLE_SIZE, std::size_t byteOffset = 0); + mijin::Task<> c_copyFrom(vk::Buffer srcBuffer, vk::BufferCopy region); + mijin::Task<> c_upload(const void* data, std::size_t bytes, std::size_t byteOffset = 0); + mijin::Task<> c_upload(const mijin::TypelessBuffer& data, std::size_t byteOffset = 0); + + template + mijin::Task<> c_upload(const TRange& range, std::size_t byteOffset = 0) + { + return c_upload(std::ranges::cdata(range), std::ranges::size(range) * sizeof(std::ranges::range_value_t), byteOffset); + } +}; + +template +struct TypedBufferCreationArgs +{ + vk::BufferCreateFlags flags; + vk::BufferUsageFlags usage; + unsigned arraySize = 1; + vk::DeviceSize stride = sizeof(T); + vk::SharingMode sharingMode = vk::SharingMode::eExclusive; + std::vector queueFamilyIndices; +}; + +template +class TypedBuffer : public Object, Buffer> +{ +public: + using super_t = typename Object, Buffer>::super_t; +protected: + unsigned mArraySize = 0; + vk::DeviceSize mStride = 0; +public: + TypedBuffer(ObjectPtr owner, const TypedBufferCreationArgs& args) + : super_t(std::move(owner), BufferCreationArgs{ + .flags = args.flags, + .size = sizeof(Type) + (args.arraySize - 1) * args.stride, + .usage = args.usage, + .sharingMode = args.sharingMode, + .queueFamilyIndices = args.queueFamilyIndices + }), mArraySize(args.arraySize), mStride(args.stride) {} + + [[nodiscard]] unsigned getArraySize() const noexcept { return mArraySize; } + [[nodiscard]] vk::DeviceSize getStride() const noexcept { return mStride; } + [[nodiscard]] mijin::StrideSpan mapToSpan() const; +}; + +template +struct TypedUniformBufferCreationArgs +{ + vk::BufferCreateFlags flags; + vk::BufferUsageFlags usage; + unsigned arraySize = 1; + vk::SharingMode sharingMode = vk::SharingMode::eExclusive; + std::vector queueFamilyIndices; +}; + +template +class TypedUniformBuffer : public Object, TypedBuffer> +{ +public: + using super_t = typename Object, TypedBuffer>::super_t; +public: + TypedUniformBuffer(ObjectPtr owner, const TypedUniformBufferCreationArgs& args) + : super_t(owner, TypedBufferCreationArgs{ + .flags = args.flags, + .usage = args.usage | vk::BufferUsageFlagBits::eUniformBuffer, + .arraySize = args.arraySize, + .stride = calcVkUniformStride(*owner), + .sharingMode = args.sharingMode, + .queueFamilyIndices = args.queueFamilyIndices + }) {} +}; + +template +using TypedStorageBufferCreationArgs = TypedUniformBufferCreationArgs; + +template +class TypedStorageBuffer : public Object, TypedBuffer> +{ +public: + using super_t = typename Object, TypedBuffer>::super_t; +public: + TypedStorageBuffer(ObjectPtr owner, const TypedUniformBufferCreationArgs& args) + : super_t(owner, TypedBufferCreationArgs{ + .flags = args.flags, + .usage = args.usage | vk::BufferUsageFlagBits::eStorageBuffer, + .arraySize = args.arraySize, + .stride = calcVkStorageBufferStride(*owner), + .sharingMode = args.sharingMode, + .queueFamilyIndices = args.queueFamilyIndices + }) {} +}; + +template +mijin::StrideSpan TypedBuffer::mapToSpan() const +{ + std::byte* rawMemory = static_cast(this->getOwner()->getVkHandle().mapMemory( + /* memory = */ *this->getMemory(), + /* offset = */ 0, + /* size = */ VK_WHOLE_SIZE + )); + return mijin::StrideSpan( + std::bit_cast(rawMemory), + std::bit_cast(rawMemory + mArraySize * mStride), + mStride + ); +} +} // namespace iwa + +#endif // !defined(IWA_BUFFER_HPP_INCLUDED) diff --git a/include/iwa/command.hpp b/include/iwa/command.hpp new file mode 100644 index 0000000..1d18c22 --- /dev/null +++ b/include/iwa/command.hpp @@ -0,0 +1,41 @@ + +#pragma once + +#if !defined(IWA_COMMAND_HPP_INCLUDED) +#define IWA_COMMAND_HPP_INCLUDED + +#include "iwa/object.hpp" +#include "iwa/vkwrapper.hpp" + +namespace iwa +{ +struct CommandPoolCreationArgs +{ + vk::CommandPoolCreateFlags flags; + std::uint32_t queueFamilyIndex; +}; + +struct CommandBufferAllocateArgs +{ + vk::CommandBufferLevel level = vk::CommandBufferLevel::ePrimary; +}; + +class CommandPool : public Object, public MixinVulkanObject +{ +public: + CommandPool(ObjectPtr owner, CommandPoolCreationArgs args); + ~CommandPool() noexcept override; + + [[nodiscard]] ObjectPtr allocateCommandBuffer(const CommandBufferAllocateArgs& args = {}); + // [[nodiscard]] std::vector allocateCommandBuffers(std::size_t count, const CommandBufferAllocateArgs& args = {}) const noexcept; +}; + +class CommandBuffer : public Object, public MixinVulkanObject +{ +public: + CommandBuffer(ObjectPtr owner, vk::CommandBuffer handle); + ~CommandBuffer() noexcept override; +}; +} // namespace iwa + +#endif // !defined(IWA_COMMAND_HPP_INCLUDED) diff --git a/include/iwa/descriptor_set.hpp b/include/iwa/descriptor_set.hpp new file mode 100644 index 0000000..cbe0772 --- /dev/null +++ b/include/iwa/descriptor_set.hpp @@ -0,0 +1,61 @@ + +#pragma once + +#if !defined(IWA_DESCRIPTOR_SET_HPP_INCLUDED) +#define IWA_DESCRIPTOR_SET_HPP_INCLUDED + +#include +#include "iwa/object.hpp" +#include "iwa/vkwrapper.hpp" + +namespace iwa +{ +struct DescriptorSetLayoutCreationArgs +{ + std::vector bindings; + std::vector bindingFlags; + vk::DescriptorSetLayoutCreateFlags flags = {}; +}; + +class DescriptorSetLayout : public Object, public MixinVulkanObject +{ +public: + DescriptorSetLayout(ObjectPtr owner, const DescriptorSetLayoutCreationArgs& args); + ~DescriptorSetLayout() noexcept override; +}; + +struct DescriptorPoolCreationArgs +{ + vk::DescriptorPoolCreateFlags flags = {}; + unsigned maxSets = 0; + std::vector poolSizes; +}; + +struct DescriptorSetAllocateArgs +{ + ObjectPtr layout; + std::uint32_t variableDescriptorCount = 0; +}; + +class DescriptorPool : public Object, public MixinVulkanObject +{ +private: + bool mCanFree = false; +public: + DescriptorPool(ObjectPtr owner, const DescriptorPoolCreationArgs& args); + ~DescriptorPool() noexcept override; + + [[nodiscard]] ObjectPtr allocateDescriptorSet(const DescriptorSetAllocateArgs& args); + + [[nodiscard]] bool getCanFree() const noexcept { return mCanFree; } +}; + +class DescriptorSet : public Object, public MixinVulkanObject +{ +public: + DescriptorSet(ObjectPtr owner, vk::DescriptorSet handle); + ~DescriptorSet() noexcept override; +}; +} // namespace iwa + +#endif // !defined(IWA_DESCRIPTOR_SET_HPP_INCLUDED) diff --git a/include/iwa/device.hpp b/include/iwa/device.hpp new file mode 100644 index 0000000..a638432 --- /dev/null +++ b/include/iwa/device.hpp @@ -0,0 +1,164 @@ + +#pragma once + +#ifndef IWA_VULKAN_DEVICE_HPP_INCLUDED +#define IWA_VULKAN_DEVICE_HPP_INCLUDED + +#include +#include +#include +#include +#include "iwa/command.hpp" +#include "iwa/device_memory.hpp" +#include "iwa/fence.hpp" +#include "iwa/vkwrapper.hpp" +#include "iwa/util/task_runner.hpp" + +#define IWA_DELETE_DEVICE_OBJECT(device, var, deleter) \ +if (var) \ +{ \ + (device)->queueDelete([handle=(var), dev=(device)->getVkHandle()]() \ + { \ + dev.deleter(handle); \ + }); \ +} + +namespace iwa +{ +struct ExtensionInfo // TODO: where to put this? +{ + const char* name; + union + { + // same thing, one for initialisation, one for runtime + bool required; + bool enabled; + }; +}; +using LayerInfo = ExtensionInfo; + +struct RendererFeatures : mijin::BitFlags +{ + bool rayTracing : 1 = false; + bool meshShaders : 1 = false; +}; + +struct PhysicalDeviceInfo +{ + vk::PhysicalDevice device; + vk::PhysicalDeviceProperties properties; + vk::SurfaceCapabilitiesKHR surfaceCapabilities; + vk::PhysicalDeviceFeatures features; + vk::PhysicalDeviceVulkan11Features vulkan11Features; + vk::PhysicalDeviceVulkan12Features vulkan12Features; + vk::PhysicalDeviceVulkan13Features vulkan13Features; + vk::PhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeatures; + vk::PhysicalDeviceRayTracingPipelineFeaturesKHR rayTracingPipelineFeatures; + vk::PhysicalDeviceMeshShaderFeaturesEXT meshShaderFeatures; + vk::PhysicalDeviceMemoryProperties memoryProperties; + vk::PhysicalDeviceRayTracingPipelinePropertiesKHR rayTracingProperties; + std::vector extensions; + std::uint32_t graphicsQueueFamily = std::numeric_limits::max(); + std::uint32_t computeQueueFamily = std::numeric_limits::max(); + RendererFeatures availableFeatures = {}; +}; + +struct PhysicalDeviceCriteria +{ + template + using feature_flag_t = vk::Bool32 T::*; + + std::vector> requiredFeatures; + std::vector> requiredVulkan11Features; + std::vector> requiredVulkan12Features; + std::vector> requiredVulkan13Features; + std::vector> requiredAccelerationStructureFeatures; + std::vector> requiredDescriptorIndexingFeatures; + std::vector> requiredRayTracingPipelineFeatures; + std::vector> requredMeshShaderFeatures; +}; + +struct DeviceCreationFlags : mijin::BitFlags +{ + /** Optimize Vulkan API calls for this device. Set to NO if you plan to create multiple devices. */ + std::uint8_t singleDevice : 1 = 1; + std::uint8_t noDefaultExtensions : 1 = 0; +}; + +struct DeviceCreationArgs +{ + PhysicalDeviceCriteria physicalDeviceCriteria; + std::vector extensions; + DeviceCreationFlags flags; +}; + +class ScratchCommandPool +{ +public: + struct Buffer + { + ObjectPtr cmdBuffer; + mijin::FuturePtr doneFuture; + }; +private: + ObjectPtr mCommandPool; + std::vector mBuffers; +public: + explicit ScratchCommandPool(Device& device); + ScratchCommandPool(const ScratchCommandPool&) = default; + ScratchCommandPool(ScratchCommandPool&&) = default; + + ScratchCommandPool& operator=(const ScratchCommandPool&) = default; + ScratchCommandPool& operator=(ScratchCommandPool&&) = default; + + [[nodiscard]] ObjectPtr allocateCommandBuffer(); + [[nodiscard]] mijin::FuturePtr getFuture(const ObjectPtr& cmdBuffer) noexcept; +}; + +class Device : public Object, public MixinVulkanObject +{ +private: + struct PendingCommandBuffer + { + ObjectPtr cmdBuffer; + ObjectPtr doneFence; + mijin::FuturePtr future; + }; + struct PendingSubmit + { + vk::Queue queue; + ObjectPtr cmdBuffer; + mijin::FuturePtr future; + }; + + vk::Queue mGraphicsQueue; + vk::Queue mComputeQueue; + std::vector mExtensions; + const PhysicalDeviceInfo* mDeviceInfo; + std::unordered_map mScratchCommandPools; + std::shared_mutex mScratchCommandPoolsMutex; + std::vector mPendingScratchCmdBuffers; + std::mutex mPendingScratchCmdBuffersMutex; + mijin::TaskHandle mUpdateLoopHandle; + mijin::MessageQueue pendingSubmits; +public: + Device(ObjectPtr owner, DeviceCreationArgs args); + ~Device() noexcept override; + + [[nodiscard]] vk::Queue getGraphicsQueue() const noexcept { return mGraphicsQueue; } + [[nodiscard]] vk::Queue getComputeQueue() const noexcept { return mComputeQueue; } + [[nodiscard]] vk::PhysicalDevice getVkPhysicalDevice() const noexcept { return mDeviceInfo->device; } + [[nodiscard]] const PhysicalDeviceInfo& getDeviceInfo() const noexcept { return *mDeviceInfo; } + [[nodiscard]] ObjectPtr allocateDeviceMemory(const DeviceMemoryAllocationArgs& args); + + [[nodiscard]] ObjectPtr beginScratchCommandBuffer(); + mijin::FuturePtr endScratchCommandBuffer(ObjectPtr cmdBuffer); + + void queueDelete(std::function deleter) noexcept; + +private: + mijin::Task<> c_updateLoop() noexcept; +}; +} + +#endif // IWA_VULKAN_DEVICE_HPP_INCLUDED diff --git a/include/iwa/device_memory.hpp b/include/iwa/device_memory.hpp new file mode 100644 index 0000000..cc456b6 --- /dev/null +++ b/include/iwa/device_memory.hpp @@ -0,0 +1,29 @@ + +#pragma once + +#if !defined(IWA_DEVICE_MEMORY_HPP_INCLUDED) +#define IWA_DEVICE_MEMORY_HPP_INCLUDED + +#include +#include "iwa/object.hpp" +#include "iwa/vkwrapper.hpp" + +namespace iwa +{ +struct DeviceMemoryAllocationArgs +{ + vk::DeviceSize allocationSize; + std::uint32_t memoryTypeIndex; +}; + +class DeviceMemory : public Object, public MixinVulkanObject +{ +public: + DeviceMemory(ObjectPtr owner, const DeviceMemoryAllocationArgs& args); + ~DeviceMemory() noexcept override; +}; + +[[nodiscard]] std::optional findMemoryType(class Device& device, const vk::MemoryRequirements& requirements, vk::MemoryPropertyFlags properties); +} // namespace iwa + +#endif // !defined(IWA_DEVICE_MEMORY_HPP_INCLUDED) diff --git a/include/iwa/event.hpp b/include/iwa/event.hpp new file mode 100644 index 0000000..30be5f2 --- /dev/null +++ b/include/iwa/event.hpp @@ -0,0 +1,26 @@ + +#if !defined(IWA_EVENT_HPP_INCLUDED) +#define IWA_EVENT_HPP_INCLUDED + +#include +#include "iwa/object.hpp" +#include "iwa/vkwrapper.hpp" + +namespace iwa +{ +struct EventCreationArgs +{ + vk::EventCreateFlags flags; +}; + +class Event : public Object, public MixinVulkanObject +{ +public: + explicit Event(ObjectPtr owner, const EventCreationArgs& args = {}); + ~Event() noexcept override; + + mijin::Task<> c_wait(); +}; +} + +#endif // !defined(IWA_EVENT_HPP_INCLUDED) diff --git a/include/iwa/fence.hpp b/include/iwa/fence.hpp new file mode 100644 index 0000000..e6bf33d --- /dev/null +++ b/include/iwa/fence.hpp @@ -0,0 +1,30 @@ + +#pragma once + +#if !defined(IWA_FENCE_HPP_INCLUDED) +#define IWA_FENCE_HPP_INCLUDED + +#include +#include "iwa/object.hpp" +#include "iwa/vkwrapper.hpp" + +namespace iwa +{ +struct FenceCreationArgs +{ + vk::FenceCreateFlags flags; +}; + +class Fence : public Object, public MixinVulkanObject +{ +public: + explicit Fence(ObjectPtr owner, const FenceCreationArgs& args = {}); + ~Fence() noexcept override; + + [[nodiscard]] bool isDone() const; + [[nodiscard]] mijin::Task<> c_wait() const; + void reset() const; +}; +} // namespace iwa + +#endif // !defined(IWA_FENCE_HPP_INCLUDED) diff --git a/include/iwa/image.hpp b/include/iwa/image.hpp new file mode 100644 index 0000000..631c51b --- /dev/null +++ b/include/iwa/image.hpp @@ -0,0 +1,270 @@ + +#pragma once + +#ifndef IWA_IMAGE_HPP_INCLUDED +#define IWA_IMAGE_HPP_INCLUDED + +#include +#include +#include +#include "iwa/device_memory.hpp" +#include "iwa/object.hpp" +#include "iwa/vkwrapper.hpp" +#include "iwa/util/vkutil.hpp" + +namespace iwa +{ +inline constexpr std::uint32_t MAX_MIP_LEVELS = std::numeric_limits::max(); + +static constexpr vk::ComponentMapping DEFAULT_COMPONENT_MAPPING = { + .r = vk::ComponentSwizzle::eIdentity, + .g = vk::ComponentSwizzle::eIdentity, + .b = vk::ComponentSwizzle::eIdentity, + .a = vk::ComponentSwizzle::eIdentity +}; + +static constexpr vk::ImageSubresourceRange DEFAULT_SUBRESOURCE_RANGE = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS +}; + +static constexpr vk::ImageSubresourceRange DEFAULT_DEPTH_SUBRESOURCE_RANGE = { + .aspectMask = vk::ImageAspectFlagBits::eDepth, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS +}; + +static constexpr vk::ImageSubresourceLayers DEFAULT_SUBRESOURCE_LAYERS = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1 +}; + +struct ImageTransition +{ + vk::PipelineStageFlags stages; + vk::ImageLayout layout; + vk::AccessFlags access; + vk::ImageSubresourceRange subResourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS + }; +}; + +static constexpr ImageTransition IMAGE_TRANSITION_FRAGMENT_READ = { + .stages = vk::PipelineStageFlagBits::eFragmentShader, + .layout = vk::ImageLayout::eShaderReadOnlyOptimal, + .access = vk::AccessFlagBits::eShaderRead +}; + +static constexpr ImageTransition IMAGE_TRANSITION_COMPUTE_WRITE = { + .stages = vk::PipelineStageFlagBits::eComputeShader, + .layout = vk::ImageLayout::eGeneral, + .access = vk::AccessFlagBits::eShaderWrite +}; + +static constexpr ImageTransition IMAGE_TRANSITION_COMPUTE_READ = { + .stages = vk::PipelineStageFlagBits::eComputeShader, + .layout = vk::ImageLayout::eGeneral, + .access = vk::AccessFlagBits::eShaderRead +}; + +static constexpr ImageTransition IMAGE_TRANSITION_COLOR_ATTACHMENT = { + .stages = vk::PipelineStageFlagBits::eColorAttachmentOutput, + .layout = vk::ImageLayout::eColorAttachmentOptimal, + .access = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eColorAttachmentRead +}; + +static constexpr ImageTransition IMAGE_TRANSITION_TRANSFER_READ = { + .stages = vk::PipelineStageFlagBits::eTransfer, + .layout = vk::ImageLayout::eTransferSrcOptimal, + .access = vk::AccessFlagBits::eTransferRead +}; + +static constexpr ImageTransition IMAGE_TRANSITION_TRANSFER_WRITE = { + .stages = vk::PipelineStageFlagBits::eTransfer, + .layout = vk::ImageLayout::eTransferDstOptimal, + .access = vk::AccessFlagBits::eTransferWrite +}; + +struct ImageCreationArgs +{ + vk::ImageCreateFlags flags = {}; + vk::ImageType imageType = vk::ImageType::e2D; + vk::Format format = vk::Format::eR8G8B8A8Unorm; + vk::Extent3D extent = {}; + uint32_t mipLevels = 1; + uint32_t arrayLayers = 1; + vk::SampleCountFlagBits samples = vk::SampleCountFlagBits::e1; + vk::ImageTiling tiling = vk::ImageTiling::eOptimal; + vk::ImageUsageFlags usage = {}; + vk::SharingMode sharingMode = vk::SharingMode::eExclusive; + std::vector queueFamilyIndices; + vk::ImageLayout initialLayout = vk::ImageLayout::eUndefined; +}; + +struct ImageWrapArgs +{ + vk::Image handle; + vk::ImageType type; + vk::Format format; + vk::ImageUsageFlags usage; + vk::Extent3D size; + unsigned mipLevels = 1; +}; + +struct ImageFromBitmapArgs +{ + const class Bitmap* bitmap; + vk::ImageCreateFlags flags = {}; + vk::ImageTiling tiling = vk::ImageTiling::eOptimal; + vk::ImageUsageFlags usage = {}; + vk::SharingMode sharingMode = vk::SharingMode::eExclusive; + std::vector queueFamilyIndices; + vk::ImageLayout initialLayout = vk::ImageLayout::eUndefined; +}; + +struct ImageViewCreationArgs +{ + vk::ImageViewCreateFlags flags; + vk::ImageViewType viewType = vk::ImageViewType::e2D; + vk::Format format = vk::Format::eUndefined; + vk::ComponentMapping components = DEFAULT_COMPONENT_MAPPING; + vk::ImageSubresourceRange subresourceRange = DEFAULT_SUBRESOURCE_RANGE; +}; + +MIJIN_DEFINE_FLAG(ResetLayout); + +class Image : public Object, public MixinVulkanObject +{ +private: + vk::ImageCreateFlags mFlags; + vk::ImageType mType; + vk::Format mFormat; + vk::ImageTiling mTiling; + vk::ImageUsageFlags mUsage; + vk::Extent3D mSize; + unsigned mArrayLayers; + unsigned mMipLevels; + bool mWrapped = false; + ObjectPtr mMemory; + + vk::ImageLayout currentLayout = vk::ImageLayout::eUndefined; + vk::PipelineStageFlags lastUsageStages = vk::PipelineStageFlagBits::eTopOfPipe; + vk::AccessFlags lastAccess = {}; +public: + Image(ObjectPtr owner, ImageCreationArgs args); + Image(ObjectPtr owner, ImageWrapArgs args); + ~Image() noexcept override; + + [[nodiscard]] vk::ImageType getType() const noexcept { return mType; } + [[nodiscard]] vk::Format getFormat() const noexcept { return mFormat; } + [[nodiscard]] vk::ImageUsageFlags getUsage() const noexcept { return mUsage; } + [[nodiscard]] const vk::Extent3D& getSize() const noexcept { return mSize; } + [[nodiscard]] unsigned getArrayLayers() const noexcept { return mArrayLayers; } + [[nodiscard]] unsigned getMipLevels() const noexcept { return mMipLevels; } + + void allocateMemory(); + void bindMemory(ObjectPtr memory, vk::DeviceSize offset = 0); + void resetUsage(ResetLayout resetLayout = ResetLayout::NO) noexcept; + void applyTransition(vk::CommandBuffer cmdBuffer, const ImageTransition& transition); + + [[nodiscard]] ObjectPtr createImageView(const ImageViewCreationArgs& args = {}); + + mijin::Task<> c_doTransition(const ImageTransition& transition); + mijin::Task<> c_upload(const void* data, std::size_t bytes, vk::Extent3D bufferImageSize, vk::Offset3D imageOffset, unsigned baseLayer = 0, unsigned layerCount = 1); + mijin::Task<> c_upload(const class Bitmap& bitmap, vk::Offset3D imageOffset = {}, unsigned baseLayer = 0, unsigned layerCount = 1); + mijin::Task<> c_blitFrom(Image& srcImage, std::vector regions, vk::Filter filter = vk::Filter::eNearest); + mijin::Task<> c_blitFrom(const class Bitmap& bitmap, std::vector regions, vk::Filter filter = vk::Filter::eNearest); + mijin::Task<> c_copyFrom(Image& srcImage, std::vector regions); +private: + [[nodiscard]] std::uint32_t clampMipLevels(std::uint32_t levels) const; + void generateMipMaps(vk::CommandBuffer cmdBuffer); + +public: + static mijin::Task> c_create(ObjectPtr owner, ImageFromBitmapArgs args); +}; + +class ImageView : public Object, public MixinVulkanObject +{ +public: + ImageView(ObjectPtr owner, const ImageViewCreationArgs& args = {}); + ~ImageView() noexcept override; +}; + +struct SamplerCreationOptions : mijin::BitFlags +{ + std::uint8_t anisotropyEnable : 1 = 0; + std::uint8_t compareEnable : 1 = 0; + std::uint8_t unnormalizedCoordinates : 1 = 0; +}; + +struct SamplerCreationArgs +{ + vk::SamplerCreateFlags flags = {}; + SamplerCreationOptions options; + vk::Filter magFilter = vk::Filter::eLinear; + vk::Filter minFilter = vk::Filter::eNearest; + vk::SamplerMipmapMode mipmapMode = vk::SamplerMipmapMode::eLinear; + vk::SamplerAddressMode addressModeU = vk::SamplerAddressMode::eRepeat; + vk::SamplerAddressMode addressModeV = vk::SamplerAddressMode::eRepeat; + vk::SamplerAddressMode addressModeW = vk::SamplerAddressMode::eRepeat; + float mipLodBias = 0.f; + float maxAnisotropy = 1.f; + vk::CompareOp compareOp = vk::CompareOp::eAlways; + float minLod = 0.f; + float maxLod = VK_LOD_CLAMP_NONE; + vk::BorderColor borderColor = vk::BorderColor::eFloatTransparentBlack; +}; + +class Sampler : public Object, public MixinVulkanObject +{ +public: + Sampler(ObjectPtr owner, const SamplerCreationArgs& args = {}); + ~Sampler() noexcept override; +}; + +inline vk::ImageSubresourceRange defaultDepthSubresourceRange(vk::Format format, bool withStencil = true) +{ + return { + .aspectMask = vk::ImageAspectFlagBits::eDepth | (withStencil && isStencilFormat(format) ? vk::ImageAspectFlagBits::eStencil : vk::ImageAspectFlagBits()), + .baseMipLevel = 0, + .levelCount = 1, // not really any miplevels for depth textures + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS + }; +} + +inline vk::ImageSubresourceLayers defaultDepthSubresourceLayers(vk::Format format) +{ + return { + .aspectMask = vk::ImageAspectFlagBits::eDepth | (isStencilFormat(format) ? vk::ImageAspectFlagBits::eStencil : vk::ImageAspectFlagBits()), + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS + }; +} + + +inline ImageTransition imageTransitionDepthAttachment(vk::Format depthFormat) +{ + return { + .stages = vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests, + .layout = isStencilFormat(depthFormat) ? vk::ImageLayout::eDepthAttachmentStencilReadOnlyOptimal : vk::ImageLayout::eDepthAttachmentOptimal, + .access = vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite, + .subResourceRange = defaultDepthSubresourceRange(depthFormat) + }; +} + +} // namespace iwa + +#endif // !included(IWA_IMAGE_HPP_INCLUDED) diff --git a/include/iwa/input.hpp b/include/iwa/input.hpp new file mode 100644 index 0000000..fd3cf6a --- /dev/null +++ b/include/iwa/input.hpp @@ -0,0 +1,483 @@ + +#pragma once + +#if !defined(IWA_INPUT_HPP_INCLUDED) +#define IWA_INPUT_HPP_INCLUDED + +#include +#include +#include + +namespace iwa +{ +// +enum class KeyCode +{ + UNKNOWN = -1, + + RETURN = SDLK_RETURN, + ESCAPE = SDLK_ESCAPE, + BACKSPACE = SDLK_BACKSPACE, + TAB = SDLK_TAB, + SPACE = SDLK_SPACE, + EXCLAIM = SDLK_EXCLAIM, + QUOTEDBL = SDLK_QUOTEDBL, + HASH = SDLK_HASH, + PERCENT = SDLK_PERCENT, + DOLLAR = SDLK_DOLLAR, + AMPERSAND = SDLK_AMPERSAND, + QUOTE = SDLK_QUOTE, + LEFTPAREN = SDLK_LEFTPAREN, + RIGHTPAREN = SDLK_RIGHTPAREN, + ASTERISK = SDLK_ASTERISK, + PLUS = SDLK_PLUS, + COMMA = SDLK_COMMA, + MINUS = SDLK_MINUS, + PERIOD = SDLK_PERIOD, + SLASH = SDLK_SLASH, + _0 = SDLK_0, + _1 = SDLK_1, + _2 = SDLK_2, + _3 = SDLK_3, + _4 = SDLK_4, + _5 = SDLK_5, + _6 = SDLK_6, + _7 = SDLK_7, + _8 = SDLK_8, + _9 = SDLK_9, + COLON = SDLK_COLON, + SEMICOLON = SDLK_SEMICOLON, + LESS = SDLK_LESS, + EQUALS = SDLK_EQUALS, + GREATER = SDLK_GREATER, + QUESTION = SDLK_QUESTION, + AT = SDLK_AT, + + /* + Skip uppercase letters + */ + + LEFTBRACKET = SDLK_LEFTBRACKET, + BACKSLASH = SDLK_BACKSLASH, + RIGHTBRACKET = SDLK_RIGHTBRACKET, + CARET = SDLK_CARET, + UNDERSCORE = SDLK_UNDERSCORE, + BACKQUOTE = SDLK_BACKQUOTE, + A = SDLK_a, + B = SDLK_b, + C = SDLK_c, + D = SDLK_d, + E = SDLK_e, + F = SDLK_f, + G = SDLK_g, + H = SDLK_h, + I = SDLK_i, + J = SDLK_j, + K = SDLK_k, + L = SDLK_l, + M = SDLK_m, + N = SDLK_n, + O = SDLK_o, + P = SDLK_p, + Q = SDLK_q, + R = SDLK_r, + S = SDLK_s, + T = SDLK_t, + U = SDLK_u, + V = SDLK_v, + W = SDLK_w, + X = SDLK_x, + Y = SDLK_y, + Z = SDLK_z, + + CAPSLOCK = SDLK_CAPSLOCK, + + F1 = SDLK_F1, + F2 = SDLK_F2, + F3 = SDLK_F3, + F4 = SDLK_F4, + F5 = SDLK_F5, + F6 = SDLK_F6, + F7 = SDLK_F7, + F8 = SDLK_F8, + F9 = SDLK_F9, + F10 = SDLK_F10, + F11 = SDLK_F11, + F12 = SDLK_F12, + + PRINTSCREEN = SDLK_PRINTSCREEN, + SCROLLLOCK = SDLK_SCROLLLOCK, + PAUSE = SDLK_PAUSE, + INSERT = SDLK_INSERT, + HOME = SDLK_HOME, + PAGEUP = SDLK_PAGEUP, + DELETE = SDLK_DELETE, + END = SDLK_END, + PAGEDOWN = SDLK_PAGEDOWN, + RIGHT = SDLK_RIGHT, + LEFT = SDLK_LEFT, + DOWN = SDLK_DOWN, + UP = SDLK_UP, + + NUMLOCKCLEAR = SDLK_NUMLOCKCLEAR, + KP_DIVIDE = SDLK_KP_DIVIDE, + KP_MULTIPLY = SDLK_KP_MULTIPLY, + KP_MINUS = SDLK_KP_MINUS, + KP_PLUS = SDLK_KP_PLUS, + KP_ENTER = SDLK_KP_ENTER, + KP_1 = SDLK_KP_1, + KP_2 = SDLK_KP_2, + KP_3 = SDLK_KP_3, + KP_4 = SDLK_KP_4, + KP_5 = SDLK_KP_5, + KP_6 = SDLK_KP_6, + KP_7 = SDLK_KP_7, + KP_8 = SDLK_KP_8, + KP_9 = SDLK_KP_9, + KP_0 = SDLK_KP_0, + KP_PERIOD = SDLK_KP_PERIOD, + + APPLICATION = SDLK_APPLICATION, + POWER = SDLK_POWER, + KP_EQUALS = SDLK_KP_EQUALS, + F13 = SDLK_F13, + F14 = SDLK_F14, + F15 = SDLK_F15, + F16 = SDLK_F16, + F17 = SDLK_F17, + F18 = SDLK_F18, + F19 = SDLK_F19, + F20 = SDLK_F20, + F21 = SDLK_F21, + F22 = SDLK_F22, + F23 = SDLK_F23, + F24 = SDLK_F24, + EXECUTE = SDLK_EXECUTE, + HELP = SDLK_HELP, + MENU = SDLK_MENU, + SELECT = SDLK_SELECT, + STOP = SDLK_STOP, + AGAIN = SDLK_AGAIN, + UNDO = SDLK_UNDO, + CUT = SDLK_CUT, + COPY = SDLK_COPY, + PASTE = SDLK_PASTE, + FIND = SDLK_FIND, + MUTE = SDLK_MUTE, + VOLUMEUP = SDLK_VOLUMEUP, + VOLUMEDOWN = SDLK_VOLUMEDOWN, + KP_COMMA = SDLK_KP_COMMA, + KP_EQUALSAS400 = SDLK_KP_EQUALSAS400, + + ALTERASE = SDLK_ALTERASE, + SYSREQ = SDLK_SYSREQ, + CANCEL = SDLK_CANCEL, + CLEAR = SDLK_CLEAR, + PRIOR = SDLK_PRIOR, + RETURN2 = SDLK_RETURN2, + SEPARATOR = SDLK_SEPARATOR, + OUT = SDLK_OUT, + OPER = SDLK_OPER, + CLEARAGAIN = SDLK_CLEARAGAIN, + CRSEL = SDLK_CRSEL, + EXSEL = SDLK_EXSEL, + + KP_00 = SDLK_KP_00, + KP_000 = SDLK_KP_000, + THOUSANDSSEPARATOR = SDLK_THOUSANDSSEPARATOR, + DECIMALSEPARATOR = SDLK_DECIMALSEPARATOR, + CURRENCYUNIT = SDLK_CURRENCYUNIT, + CURRENCYSUBUNIT = SDLK_CURRENCYSUBUNIT, + KP_LEFTPAREN = SDLK_KP_LEFTPAREN, + KP_RIGHTPAREN = SDLK_KP_RIGHTPAREN, + KP_LEFTBRACE = SDLK_KP_LEFTBRACE, + KP_RIGHTBRACE = SDLK_KP_RIGHTBRACE, + KP_TAB = SDLK_KP_TAB, + KP_BACKSPACE = SDLK_KP_BACKSPACE, + KP_A = SDLK_KP_A, + KP_B = SDLK_KP_B, + KP_C = SDLK_KP_C, + KP_D = SDLK_KP_D, + KP_E = SDLK_KP_E, + KP_F = SDLK_KP_F, + KP_XOR = SDLK_KP_XOR, + KP_POWER = SDLK_KP_POWER, + KP_PERCENT = SDLK_KP_PERCENT, + KP_LESS = SDLK_KP_LESS, + KP_GREATER = SDLK_KP_GREATER, + KP_AMPERSAND = SDLK_KP_AMPERSAND, + KP_DBLAMPERSAND = SDLK_KP_DBLAMPERSAND, + KP_VERTICALBAR = SDLK_KP_VERTICALBAR, + KP_DBLVERTICALBAR = SDLK_KP_DBLVERTICALBAR, + KP_COLON = SDLK_KP_COLON, + KP_HASH = SDLK_KP_HASH, + KP_SPACE = SDLK_KP_SPACE, + KP_AT = SDLK_KP_AT, + KP_EXCLAM = SDLK_KP_EXCLAM, + KP_MEMSTORE = SDLK_KP_MEMSTORE, + KP_MEMRECALL = SDLK_KP_MEMRECALL, + KP_MEMCLEAR = SDLK_KP_MEMCLEAR, + KP_MEMADD = SDLK_KP_MEMADD, + KP_MEMSUBTRACT = SDLK_KP_MEMSUBTRACT, + KP_MEMMULTIPLY = SDLK_KP_MEMMULTIPLY, + KP_MEMDIVIDE = SDLK_KP_MEMDIVIDE, + KP_PLUSMINUS = SDLK_KP_PLUSMINUS, + KP_CLEAR = SDLK_KP_CLEAR, + KP_CLEARENTRY = SDLK_KP_CLEARENTRY, + KP_BINARY = SDLK_KP_BINARY, + KP_OCTAL = SDLK_KP_OCTAL, + KP_DECIMAL = SDLK_KP_DECIMAL, + KP_HEXADECIMAL = SDLK_KP_HEXADECIMAL, + + LCTRL = SDLK_LCTRL, + LSHIFT = SDLK_LSHIFT, + LALT = SDLK_LALT, + LGUI = SDLK_LGUI, + RCTRL = SDLK_RCTRL, + RSHIFT = SDLK_RSHIFT, + RALT = SDLK_RALT, + RGUI = SDLK_RGUI, + + MODE = SDLK_MODE, + + AUDIONEXT = SDLK_AUDIONEXT, + AUDIOPREV = SDLK_AUDIOPREV, + AUDIOSTOP = SDLK_AUDIOSTOP, + AUDIOPLAY = SDLK_AUDIOPLAY, + AUDIOMUTE = SDLK_AUDIOMUTE, + MEDIASELECT = SDLK_MEDIASELECT, + WWW = SDLK_WWW, + MAIL = SDLK_MAIL, + CALCULATOR = SDLK_CALCULATOR, + COMPUTER = SDLK_COMPUTER, + AC_SEARCH = SDLK_AC_SEARCH, + AC_HOME = SDLK_AC_HOME, + AC_BACK = SDLK_AC_BACK, + AC_FORWARD = SDLK_AC_FORWARD, + AC_STOP = SDLK_AC_STOP, + AC_REFRESH = SDLK_AC_REFRESH, + AC_BOOKMARKS = SDLK_AC_BOOKMARKS, + + BRIGHTNESSDOWN = SDLK_BRIGHTNESSDOWN, + BRIGHTNESSUP = SDLK_BRIGHTNESSUP, + DISPLAYSWITCH = SDLK_DISPLAYSWITCH, + KBDILLUMTOGGLE = SDLK_KBDILLUMTOGGLE, + KBDILLUMDOWN = SDLK_KBDILLUMDOWN, + KBDILLUMUP = SDLK_KBDILLUMUP, + EJECT = SDLK_EJECT, + SLEEP = SDLK_SLEEP, + APP1 = SDLK_APP1, + APP2 = SDLK_APP2, + + AUDIOREWIND = SDLK_AUDIOREWIND, + AUDIOFASTFORWARD = SDLK_AUDIOFASTFORWARD, + + SOFTLEFT = SDLK_SOFTLEFT, + SOFTRIGHT = SDLK_SOFTRIGHT, + CALL = SDLK_CALL, + ENDCALL = SDLK_ENDCALL +}; + +enum class ScanCode +{ + UNKNOWN = -1, + + /* Printable keys */ + SPACE = SDL_SCANCODE_SPACE, + APOSTROPHE = SDL_SCANCODE_APOSTROPHE, + COMMA = SDL_SCANCODE_COMMA, + MINUS = SDL_SCANCODE_MINUS, + PERIOD = SDL_SCANCODE_PERIOD, + SLASH = SDL_SCANCODE_SLASH, + _0 = SDL_SCANCODE_0, + _1 = SDL_SCANCODE_1, + _2 = SDL_SCANCODE_2, + _3 = SDL_SCANCODE_3, + _4 = SDL_SCANCODE_4, + _5 = SDL_SCANCODE_5, + _6 = SDL_SCANCODE_6, + _7 = SDL_SCANCODE_7, + _8 = SDL_SCANCODE_8, + _9 = SDL_SCANCODE_9, + SEMICOLON = SDL_SCANCODE_SEMICOLON, + EQUALS = SDL_SCANCODE_EQUALS, + A = SDL_SCANCODE_A, + B = SDL_SCANCODE_B, + C = SDL_SCANCODE_C, + D = SDL_SCANCODE_D, + E = SDL_SCANCODE_E, + F = SDL_SCANCODE_F, + G = SDL_SCANCODE_G, + H = SDL_SCANCODE_H, + I = SDL_SCANCODE_I, + J = SDL_SCANCODE_J, + K = SDL_SCANCODE_K, + L = SDL_SCANCODE_L, + M = SDL_SCANCODE_M, + N = SDL_SCANCODE_N, + O = SDL_SCANCODE_O, + P = SDL_SCANCODE_P, + Q = SDL_SCANCODE_Q, + R = SDL_SCANCODE_R, + S = SDL_SCANCODE_S, + T = SDL_SCANCODE_T, + U = SDL_SCANCODE_U, + V = SDL_SCANCODE_V, + W = SDL_SCANCODE_W, + X = SDL_SCANCODE_X, + Y = SDL_SCANCODE_Y, + Z = SDL_SCANCODE_Z, + LEFTBRACKET = SDL_SCANCODE_LEFTBRACKET, + BACKSLASH = SDL_SCANCODE_BACKSLASH, + RIGHTBRACKET = SDL_SCANCODE_RIGHTBRACKET, + GRAVE = SDL_SCANCODE_GRAVE, + // WORLD_1 = SDL_SCANCODE_WORLD_1, + // WORLD_2 = SDL_SCANCODE_WORLD_2, + + /* Function keys */ + ESCAPE = SDL_SCANCODE_ESCAPE, + ENTER = SDL_SCANCODE_RETURN, + TAB = SDL_SCANCODE_TAB, + BACKSPACE = SDL_SCANCODE_BACKSPACE, + INSERT = SDL_SCANCODE_INSERT, + DELETE = SDL_SCANCODE_DELETE, + RIGHT = SDL_SCANCODE_RIGHT, + LEFT = SDL_SCANCODE_LEFT, + DOWN = SDL_SCANCODE_DOWN, + UP = SDL_SCANCODE_UP, + PAGEUP = SDL_SCANCODE_PAGEUP, + PAGEDOWN = SDL_SCANCODE_PAGEDOWN, + HOME = SDL_SCANCODE_HOME, + END = SDL_SCANCODE_END, + CAPSLOCK = SDL_SCANCODE_CAPSLOCK, + SCROLLLOCK = SDL_SCANCODE_SCROLLLOCK, + NUMLOCK = SDL_SCANCODE_NUMLOCKCLEAR, + PRINTSCREEN = SDL_SCANCODE_PRINTSCREEN, + PAUSE = SDL_SCANCODE_PAUSE, + F1 = SDL_SCANCODE_F1, + F2 = SDL_SCANCODE_F2, + F3 = SDL_SCANCODE_F3, + F4 = SDL_SCANCODE_F4, + F5 = SDL_SCANCODE_F5, + F6 = SDL_SCANCODE_F6, + F7 = SDL_SCANCODE_F7, + F8 = SDL_SCANCODE_F8, + F9 = SDL_SCANCODE_F9, + F10 = SDL_SCANCODE_F10, + F11 = SDL_SCANCODE_F11, + F12 = SDL_SCANCODE_F12, + F13 = SDL_SCANCODE_F13, + F14 = SDL_SCANCODE_F14, + F15 = SDL_SCANCODE_F15, + F16 = SDL_SCANCODE_F16, + F17 = SDL_SCANCODE_F17, + F18 = SDL_SCANCODE_F18, + F19 = SDL_SCANCODE_F19, + F20 = SDL_SCANCODE_F20, + F21 = SDL_SCANCODE_F21, + F22 = SDL_SCANCODE_F22, + F23 = SDL_SCANCODE_F23, + F24 = SDL_SCANCODE_F24, + KP_0 = SDL_SCANCODE_KP_0, + KP_1 = SDL_SCANCODE_KP_1, + KP_2 = SDL_SCANCODE_KP_2, + KP_3 = SDL_SCANCODE_KP_3, + KP_4 = SDL_SCANCODE_KP_4, + KP_5 = SDL_SCANCODE_KP_5, + KP_6 = SDL_SCANCODE_KP_6, + KP_7 = SDL_SCANCODE_KP_7, + KP_8 = SDL_SCANCODE_KP_8, + KP_9 = SDL_SCANCODE_KP_9, + KP_DECIMAL = SDL_SCANCODE_KP_DECIMAL, + KP_DIVIDE = SDL_SCANCODE_KP_DIVIDE, + KP_MULTIPLY = SDL_SCANCODE_KP_MULTIPLY, + KP_MINUS = SDL_SCANCODE_KP_MINUS, + KP_PLUS = SDL_SCANCODE_KP_PLUS, + KP_ENTER = SDL_SCANCODE_KP_ENTER, + KP_EQUALS = SDL_SCANCODE_KP_EQUALS, + LSHIFT = SDL_SCANCODE_LSHIFT, + LCONTROL = SDL_SCANCODE_LCTRL, + LALT = SDL_SCANCODE_LALT, + LGUI = SDL_SCANCODE_LGUI, + RSHIFT = SDL_SCANCODE_RSHIFT, + RCONTROL = SDL_SCANCODE_RCTRL, + RALT = SDL_SCANCODE_RALT, + RGUI = SDL_SCANCODE_RGUI, + MENU = SDL_SCANCODE_MENU, +}; +// + +struct KeyModifiers +{ + bool leftShift : 1; + bool rightShift : 1; + bool leftCtrl : 1; + bool rightCtrl : 1; + bool leftAlt : 1; + bool rightAlt : 1; + bool leftMeta : 1; + bool rightMeta : 1; +}; + +struct InputEvent +{ + +}; + +struct KeyEvent : InputEvent +{ + KeyCode keyCode; + ScanCode scanCode; + KeyModifiers modifiers; + bool down : 1; + bool repeat : 1; +}; + +enum class MouseButton +{ + LEFT = SDL_BUTTON_LEFT, + MIDDLE = SDL_BUTTON_MIDDLE, + RIGHT = SDL_BUTTON_RIGHT, + EXTRA_1 = SDL_BUTTON_X1, + EXTRA_2 = SDL_BUTTON_X2 +}; + +struct MouseMoveEvent +{ + int relativeX; + int relativeY; + int absoluteX; + int absoluteY; + bool warped; +}; + +struct MouseWheelEvent +{ + int relativeX; + int relativeY; +}; + +struct MouseButtonEvent +{ + MouseButton button; + std::uint8_t clicks : 7; + bool down : 1; +}; + +struct TextInputEvent +{ + std::string text; +}; + +struct KeyState +{ + bool pressed; +}; + +[[nodiscard]] KeyState getKeyState(ScanCode scanCode) noexcept; +void captureMouse() noexcept; +void uncaptureMouse() noexcept; +[[nodiscard]] std::pair getMouseScreenPosition() noexcept; +} // namespace iwa + +#endif // !defined(IWA_INPUT_HPP_INCLUDED) diff --git a/include/iwa/instance.hpp b/include/iwa/instance.hpp new file mode 100644 index 0000000..8ac6d09 --- /dev/null +++ b/include/iwa/instance.hpp @@ -0,0 +1,157 @@ + +#pragma once + +#ifndef IWA_VULKAN_INSTANCE_HPP_INCLUDED +#define IWA_VULKAN_INSTANCE_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include "iwa/object.hpp" +#include "iwa/device.hpp" +#include "iwa/window.hpp" +#include "iwa/vkwrapper.hpp" + +#define IWA_DELETE_INSTANCE_OBJECT(instance, var, deleter) \ +(instance)->queueDelete([handle=(var), inst=(instance)]() \ +{ \ + inst->getVkInstance().deleter(handle); \ +}) +#define IWA_CORO_ENSURE_MAIN_THREAD(instance) \ +if (!(instance).isOnMainThread()) { \ + co_await mijin::switchContext((instance).getMainTaskLoop()); \ +} + +namespace iwa +{ +struct InstanceCreationFlags : mijin::BitFlags +{ + std::uint8_t noDefaultExtensions : 1 = 0; + std::uint8_t noDefaultLayers : 1 = 0; +}; + +struct InstanceCreationArgs +{ + vk::ApplicationInfo applicationInfo = { + .pApplicationName = "Iwa VulkanApplication", + .applicationVersion = 0, + .pEngineName = "Iwa Engine", + .engineVersion = 0, + .apiVersion = VK_MAKE_API_VERSION(0, 1, 3, 0) + }; + std::vector extensions; + std::vector layers; + InstanceCreationFlags flags; +}; + +class InstanceExtension : public AbstractObject +{ +protected: + InstanceExtension() noexcept : super_t(nullptr) {} +}; + +class Instance : public Object, public MixinVulkanObject +{ +public: + using deleter_t = std::function; +private: + struct DeleteQueueEntry + { + deleter_t deleter; + int remainingFrames; + }; + + mijin::SimpleTaskLoop mMainTaskLoop; + mijin::MultiThreadedTaskLoop mWorkerTaskLoop; + mijin::StackedFileSystemAdapter mPrimaryFSAdapter; + std::vector mExtensions; + std::vector mLayers; + std::vector mPhysicalDevices; + std::vector mDeleteQueue; + std::unordered_map> mInstanceExtensions; + vk::DebugUtilsMessengerEXT mDebugMessenger = VK_NULL_HANDLE; + bool mQuitRequested = false; + std::thread::id mMainThread; +public: + explicit Instance(InstanceCreationArgs args = {}); + ~Instance() noexcept override; + + [[nodiscard]] const std::vector& getPhysicalDevices() const noexcept { return mPhysicalDevices; } + [[nodiscard]] inline bool isQuitRequested() const noexcept { return mQuitRequested; } + [[nodiscard]] inline mijin::SimpleTaskLoop& getMainTaskLoop() noexcept { return mMainTaskLoop; } + [[nodiscard]] inline mijin::MultiThreadedTaskLoop& getWorkerTaskLoop() noexcept { return mWorkerTaskLoop; } + [[nodiscard]] inline mijin::StackedFileSystemAdapter& getPrimaryFSAdapter() noexcept { return mPrimaryFSAdapter; } + + [[nodiscard]] bool isExtensionEnabled(const char* name) const noexcept; + [[nodiscard]] bool isLayerEnabled(const char* name) const noexcept; + + ObjectPtr createWindow(const WindowCreationArgs& args = {}); + ObjectPtr createDevice(DeviceCreationArgs args = {}); + + void queueDelete(deleter_t deleter) noexcept; + void tickDeleteQueue(); + void setMainThread(); + void requestQuit() noexcept; + [[nodiscard]] bool isOnMainThread() const noexcept { return mMainThread == std::this_thread::get_id(); } + + template + mijin::FuturePtr> runOnMainThread(TFunction&& function); + + template requires (std::is_base_of_v) + [[nodiscard]] TExtension& getInstanceExtension() + { + ObjectPtr& extensionPtr = mInstanceExtensions[typeid(TExtension)]; + if (extensionPtr == nullptr) + { + extensionPtr = TExtension::create(); + } + return static_cast(*extensionPtr); + } +private: + void runDeleters(bool runAll = false); + +public: + mijin::Signal<> quitRequested; + mijin::Signal deviceCreated; + mijin::Signal windowCreated; +}; + + + +template +mijin::FuturePtr> Instance::runOnMainThread(TFunction&& function) +{ + using result_t = std::invoke_result_t; + if (std::this_thread::get_id() == mMainThread) + { + mijin::FuturePtr future = std::make_shared>(); + if constexpr (std::is_same_v) + { + function(); + future->set(); + } + else + { + future->set(function()); + } + return future; + } + return getMainTaskLoop().addTask([](std::function function) -> mijin::Task + { + if constexpr (std::is_same_v) + { + function(); + co_return; + } + else + { + co_return function(); + } + }(std::forward(function))); +} +} + +#endif // IWA_VULKAN_INSTANCE_HPP_INCLUDED diff --git a/include/iwa/io/bitmap.hpp b/include/iwa/io/bitmap.hpp new file mode 100644 index 0000000..6d80ba1 --- /dev/null +++ b/include/iwa/io/bitmap.hpp @@ -0,0 +1,123 @@ + +#pragma once + +#if !defined(IWA_IO_BITMAP_HPP) +#define IWA_IO_BITMAP_HPP + +#include +#include +#include +#include +#include +#include +#include +#include "iwa/resource/bitmap.hpp" + +namespace iwa +{ +namespace fs = std::filesystem; + +enum class ImageFormatType +{ + UNORM = 0, + UINT = 1, + SRGB = 2 +}; + +enum class BitmapCodec +{ + NONE = 0, + PNG = 1, + JPEG = 2 +}; + +struct BitmapLoadOptions +{ + mijin::Stream* stream; + BitmapCodec codec = BitmapCodec::NONE; + ImageFormatType formatType = ImageFormatType::UNORM; +}; + +struct BitmapLoadFileOptions +{ + mijin::PathReference path; + BitmapCodec codec = BitmapCodec::NONE; + ImageFormatType formatType = ImageFormatType::UNORM; + + auto operator<=>(const BitmapLoadFileOptions&) const noexcept = default; +}; + +struct BitmapLoadMemoryOptions +{ + std::span data; + BitmapCodec codec = BitmapCodec::NONE; + ImageFormatType formatType = ImageFormatType::UNORM; +}; + +struct BitmapSaveOptions +{ + std::string fileName = {}; + BitmapCodec codec = BitmapCodec::NONE; + int jpegQuality = 90; + + auto operator<=>(const BitmapSaveOptions&) const noexcept = default; +}; + +// +// public functions +// +[[nodiscard]] ObjectPtr loadBitmap(const BitmapLoadOptions& options); + +[[nodiscard]] inline ObjectPtr loadBitmapFromFile(const BitmapLoadFileOptions& options) +{ + std::unique_ptr stream; + const mijin::StreamError error = options.path.open(mijin::FileOpenMode::READ, stream); + if (error != mijin::StreamError::SUCCESS) { + throw std::runtime_error("Error opening bitmap for reading."); + } + return loadBitmap({ + .stream = stream.get(), + .codec = options.codec, + .formatType = options.formatType + }); +} + +[[nodiscard]] inline ObjectPtr loadBitmapFromMemory(const BitmapLoadMemoryOptions& options) +{ + mijin::MemoryStream stream; + stream.openRO(options.data); + return loadBitmap({ + .stream = &stream, + .codec = options.codec, + .formatType = options.formatType + }); +} + +void saveBitmap(const Bitmap& bitmap, const BitmapSaveOptions& options); + +constexpr BitmapCodec bitmapCodecFromMimeType(std::string_view mimeType) +{ + if (mimeType == "image/png") { + return BitmapCodec::PNG; + } + if (mimeType == "image/jpeg") { + return BitmapCodec::JPEG; + } + return BitmapCodec::NONE; +} +} // namespace iwa + +template<> +struct std::hash +{ + std::size_t operator()(const iwa::BitmapLoadFileOptions& options) const noexcept + { + std::size_t hash = 0; + mijin::hashCombine(hash, options.path); + mijin::hashCombine(hash, options.codec); + mijin::hashCombine(hash, options.formatType); + return hash; + } +}; + +#endif // !defined(IWA_IO_BITMAP_HPP) diff --git a/include/iwa/io/font.hpp b/include/iwa/io/font.hpp new file mode 100644 index 0000000..c21ca91 --- /dev/null +++ b/include/iwa/io/font.hpp @@ -0,0 +1,55 @@ + +#pragma once + +#if !defined(IWA_IO_FONT_HPP_INCLUDED) +#define IWA_IO_FONT_HPP_INCLUDED + +#include +#include +#include "iwa/resource/font.hpp" + +namespace iwa +{ +struct FontLoadOptions +{ + mijin::Stream* stream; + float size = 20.f; +}; + +struct FontLoadFileOptions +{ + mijin::PathReference path; + float size = 20.f; + + auto operator<=>(const FontLoadFileOptions& other) const noexcept = default; +}; + +[[nodiscard]] ObjectPtr loadFont(const FontLoadOptions& options); + +[[nodiscard]] inline ObjectPtr loadFontFromFile(const FontLoadFileOptions& options) +{ + std::unique_ptr stream; + const mijin::StreamError error = options.path.open(mijin::FileOpenMode::READ, stream); + if (error != mijin::StreamError::SUCCESS) { + throw std::runtime_error("Error opening font file."); + } + return loadFont({ + .stream = stream.get(), + .size = options.size + }); +} +} // namespace iwa + +template<> +struct std::hash +{ + std::size_t operator()(const iwa::FontLoadFileOptions& options) const noexcept + { + std::size_t hash = 0; + mijin::hashCombine(hash, options.path); + mijin::hashCombine(hash, options.size); + return hash; + } +}; + +#endif // !defined(IWA_IO_FONT_HPP_INCLUDED) diff --git a/include/iwa/io/mesh.hpp b/include/iwa/io/mesh.hpp new file mode 100644 index 0000000..8ee6359 --- /dev/null +++ b/include/iwa/io/mesh.hpp @@ -0,0 +1,11 @@ + +#pragma once + +#if !defined(IWA_IO_MESH_HPP_INCLUDED) +#define IWA_IO_MESH_HPP_INCLUDED + +namespace iwa +{ +} // namespace iwa + +#endif // !defined(IWA_IO_MESH_HPP_INCLUDED) diff --git a/include/iwa/log.hpp b/include/iwa/log.hpp new file mode 100644 index 0000000..45963e6 --- /dev/null +++ b/include/iwa/log.hpp @@ -0,0 +1,57 @@ + +#pragma once + +#ifndef IWA_LOG_HPP_INCLUDED +#define IWA_LOG_HPP_INCLUDED + +#include +#include +#include +#include +#include + +namespace iwa +{ +inline void logMsg(const std::string& msg) noexcept +{ + std::puts(msg.c_str()); + (void) std::fflush(stdout); +} + +template +inline void logMsg(std::string_view msg, TArgs&&... args) noexcept +{ + fmt::print(stdout, fmt::runtime(msg), std::forward(args)...); + (void) std::fputc('\n', stdout); + (void) std::fflush(stdout); +} + +inline void vlogMsg(std::string_view msg, fmt::format_args args) +{ + fmt::vprint(stdout, msg, args); + (void) std::fputc('\n', stdout); + (void) std::fflush(stdout); +} + +template +[[noreturn]] +inline void logAndDie(std::string_view msg, TArgs&&... args) noexcept +{ + fmt::print(stderr, fmt::runtime(msg), std::forward(args)...); + (void) std::fputc('\n', stderr); + (void) std::fflush(stderr); + + std::abort(); +} + +template +inline void logVerbose([[maybe_unused]] std::string_view msg, [[maybe_unused]] TArgs&&... args) noexcept +{ +#if defined(KAZAN_VERBOSE_LOGGING) + std::printf("[VERBOSE] "); + logMsg(msg, std::forward(args)...); +#endif +} +} + +#endif // IWA_LOG_HPP_INCLUDED diff --git a/include/iwa/object.hpp b/include/iwa/object.hpp new file mode 100644 index 0000000..d43c75f --- /dev/null +++ b/include/iwa/object.hpp @@ -0,0 +1,566 @@ + +#pragma once + +#ifndef IWA_OBJECT_HPP_INCLUDED +#define IWA_OBJECT_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(IWA_OBJECTPTR_TRACKING) +# if !defined(KAZAN_RELEASE) +# define IWA_OBJECTPTR_TRACKING 1 +# else +# define IWA_OBJECTPTR_TRACKING 0 +# endif +#endif + +#define IWA_NEW_OBJECT(type, ...) (new type(__VA_ARGS__))->getPointer(/* skipCheck = */ true) + +#if IWA_OBJECTPTR_TRACKING > 1 +#include +#endif + +namespace iwa +{ +using object_id_t = std::uint64_t; +using object_destruction_handler_t = std::function; + +class BaseObject; + +template +class WeakObjectPtr; + +#if IWA_OBJECTPTR_TRACKING +namespace impl +{ +struct ObjectPtrAllocation +{ + class BaseObject* object; +#if IWA_OBJECTPTR_TRACKING > 1 + mijin::Result stacktrace; +#endif +}; +using objectptr_allocation_handle_t = std::optional::iterator>; + +#if IWA_OBJECTPTR_TRACKING > 1 +objectptr_allocation_handle_t trackObjectPtr(class BaseObject* object, mijin::Result&& stacktrace) noexcept; +#else +objectptr_allocation_handle_t trackObjectPtr(class BaseObject* object) noexcept; +#endif +void untrackObjectPtr(objectptr_allocation_handle_t handle) noexcept; +} + +#if IWA_OBJECTPTR_TRACKING > 1 +#define IWA_DECLARE_OBJECTPTR_TRACKING() impl::objectptr_allocation_handle_t mAllocationHandle; +#define IWA_TRACK_OBJECTPTR() mAllocationHandle = impl::trackObjectPtr(mObject, mijin::captureStacktrace(1)); +#define IWA_TRACK_OBJECTPTR_NEW() ptr.mAllocationHandle = impl::trackObjectPtr(ptr.mObject, mijin::captureStacktrace(1)); +#define IWA_UNTRACK_OBJECTPTR() \ +if (mAllocationHandle != impl::objectptr_allocation_handle_t()) \ +{ \ + impl::untrackObjectPtr(mAllocationHandle); \ + mAllocationHandle = impl::objectptr_allocation_handle_t(); \ +} +#define IWA_MOVE_CONSTRUCT_OBJECTPTR_HANDLE() , mAllocationHandle(std::exchange(otherPtr.mAllocationHandle, impl::objectptr_allocation_handle_t())) +#define IWA_MOVE_ASSIGN_OBJECTPTR_HANDLE() mAllocationHandle = std::exchange(otherPtr.mAllocationHandle, impl::objectptr_allocation_handle_t()); +#define IWA_UPDATE_OBJECTPTR_HANDLE() \ +if (mAllocationHandle != impl::objectptr_allocation_handle_t()) \ +{ \ + (*mAllocationHandle)->stacktrace = mijin::captureStacktrace(1); \ +} +#else +#define IWA_DECLARE_OBJECTPTR_TRACKING() impl::objectptr_allocation_handle_t mAllocationHandle; +#define IWA_TRACK_OBJECTPTR() mAllocationHandle = impl::trackObjectPtr(mObject); +#define IWA_TRACK_OBJECTPTR_NEW() ptr.mAllocationHandle = impl::trackObjectPtr(ptr.mObject); +#define IWA_UNTRACK_OBJECTPTR() \ +if (mAllocationHandle != impl::objectptr_allocation_handle_t()) \ +{ \ + impl::untrackObjectPtr(mAllocationHandle); \ + mAllocationHandle = impl::objectptr_allocation_handle_t(); \ +} +#define IWA_MOVE_CONSTRUCT_OBJECTPTR_HANDLE() , mAllocationHandle(std::exchange(otherPtr.mAllocationHandle, impl::objectptr_allocation_handle_t())) +#define IWA_MOVE_ASSIGN_OBJECTPTR_HANDLE() mAllocationHandle = std::exchange(otherPtr.mAllocationHandle, impl::objectptr_allocation_handle_t()); +#define IWA_UPDATE_OBJECTPTR_HANDLE() +#endif +#else // IWA_OBJECTPTR_TRACKING +#define IWA_DECLARE_OBJECTPTR_TRACKING() +#define IWA_TRACK_OBJECTPTR() +#define IWA_TRACK_OBJECTPTR_NEW() +#define IWA_UNTRACK_OBJECTPTR() +#define IWA_MOVE_CONSTRUCT_OBJECTPTR_HANDLE() +#define IWA_MOVE_ASSIGN_OBJECTPTR_HANDLE() +#define IWA_UPDATE_OBJECTPTR_HANDLE() +#endif // else IWA_OBJECTPTR_TRACKING + +template +class ObjectPtr +{ +private: + TObject* mObject = nullptr; + IWA_DECLARE_OBJECTPTR_TRACKING() +private: + // ObjectPtr(TObject* object) noexcept : mObject(object) {} +public: + /** default construction */ + ObjectPtr() = default; + + /** nullptr initialization */ + ObjectPtr(std::nullptr_t) noexcept : mObject(nullptr) {} + + /** copy */ + ObjectPtr(const ObjectPtr& otherPtr) : mObject(otherPtr.mObject) + { + if (mObject != nullptr) + { + mObject->increaseReferenceCount(); + IWA_TRACK_OBJECTPTR() + } + } + + /** move */ + ObjectPtr(ObjectPtr&& otherPtr) noexcept : mObject(std::exchange(otherPtr.mObject, nullptr)) + IWA_MOVE_CONSTRUCT_OBJECTPTR_HANDLE() + { + IWA_UPDATE_OBJECTPTR_HANDLE() + } + + /** implicit conversion */ + template requires(std::is_base_of_v) + ObjectPtr(const ObjectPtr& otherPtr) noexcept : mObject(otherPtr.mObject) + { + if (mObject != nullptr) + { + mObject->increaseReferenceCount(); + IWA_TRACK_OBJECTPTR() + } + } + + /** implicit conversion */ + template requires(std::is_base_of_v) + ObjectPtr(ObjectPtr&& otherPtr) noexcept : mObject(std::exchange(otherPtr.mObject, nullptr)) + IWA_MOVE_CONSTRUCT_OBJECTPTR_HANDLE() + { + IWA_UPDATE_OBJECTPTR_HANDLE() + } + + /** explicit conversion */ + template requires(!std::is_same_v && std::is_base_of_v) + explicit ObjectPtr(const ObjectPtr& otherPtr) noexcept : mObject(static_cast(otherPtr.mObject)) + { + if (mObject != nullptr) + { + mObject->increaseReferenceCount(); + IWA_TRACK_OBJECTPTR() + } + } + + /** explicit conversion */ + template requires(!std::is_same_v && std::is_base_of_v) + explicit ObjectPtr(ObjectPtr&& otherPtr) noexcept : mObject(static_cast(std::exchange(otherPtr.mObject, nullptr))) + IWA_MOVE_CONSTRUCT_OBJECTPTR_HANDLE() + { + IWA_UPDATE_OBJECTPTR_HANDLE() + } + + ~ObjectPtr() noexcept + { + if (mObject != nullptr) + { + mObject->decreaseReferenceCount(); + } + IWA_UNTRACK_OBJECTPTR() + } + + ObjectPtr& operator=(const ObjectPtr& otherPtr) noexcept + { + if (this != &otherPtr) + { + if (mObject != nullptr) + { + mObject->decreaseReferenceCount(); + } + IWA_UNTRACK_OBJECTPTR() + mObject = otherPtr.mObject; + if (mObject != nullptr) + { + mObject->increaseReferenceCount(); + IWA_TRACK_OBJECTPTR() + } + } + return *this; + } + + ObjectPtr& operator=(ObjectPtr&& otherPtr) noexcept + { + if (this != &otherPtr) + { + if (mObject != nullptr) + { + mObject->decreaseReferenceCount(); + } + IWA_UNTRACK_OBJECTPTR() + mObject = std::exchange(otherPtr.mObject, nullptr); + IWA_MOVE_ASSIGN_OBJECTPTR_HANDLE() + IWA_UPDATE_OBJECTPTR_HANDLE() + } + return *this; + } + + template requires(std::is_base_of_v) + ObjectPtr& operator=(const ObjectPtr& otherPtr) noexcept + { + if (static_cast(this) != static_cast(&otherPtr)) + { + if (mObject != nullptr) + { + mObject->decreaseReferenceCount(); + } + IWA_UNTRACK_OBJECTPTR() + mObject = otherPtr.mObject; + if (mObject != nullptr) + { + mObject->increaseReferenceCount(); + IWA_TRACK_OBJECTPTR() + } + } + return *this; + } + + template requires(std::is_base_of_v) + ObjectPtr& operator=(ObjectPtr&& otherPtr) noexcept + { + if (static_cast(this) != static_cast(&otherPtr)) + { + if (mObject != nullptr) + { + mObject->decreaseReferenceCount(); + } + mObject = std::exchange(otherPtr.mObject, nullptr); + IWA_MOVE_ASSIGN_OBJECTPTR_HANDLE() + IWA_UPDATE_OBJECTPTR_HANDLE() + } + return *this; + } + + ObjectPtr& operator=(std::nullptr_t) noexcept + { + if (mObject != nullptr) + { + mObject->decreaseReferenceCount(); + } + IWA_UNTRACK_OBJECTPTR() + mObject = nullptr; + return *this; + } + + [[nodiscard]] bool operator!() const noexcept { return mObject == nullptr; } + [[nodiscard]] TObject& operator*() const noexcept { return *mObject; } + [[nodiscard]] TObject* operator->() const noexcept { return mObject; } + [[nodiscard]] auto operator<=>(const ObjectPtr&) const noexcept = default; + [[nodiscard]] operator bool() const noexcept { return mObject != nullptr; } + [[nodiscard]] bool operator==(std::nullptr_t) const noexcept { return mObject == nullptr; } + [[nodiscard]] bool operator!=(std::nullptr_t) const noexcept { return mObject != nullptr; } + +#define MAKE_OPERATOR(op) \ + template requires (std::is_base_of_v || std::is_base_of_v) \ + auto operator op (const ObjectPtr& otherPtr) const noexcept \ + { \ + return mObject op otherPtr.mObject; \ + } + + MAKE_OPERATOR(<=>) + MAKE_OPERATOR(==) + MAKE_OPERATOR(!=) + MAKE_OPERATOR(<) + MAKE_OPERATOR(<=) + MAKE_OPERATOR(>) + MAKE_OPERATOR(>=) + +#undef MAKE_OPERATOR + + [[nodiscard]] TObject* getRaw() const noexcept { return mObject; } + [[nodiscard]] WeakObjectPtr makeWeak() const noexcept; + template requires(std::is_base_of_v) + [[nodiscard]] ObjectPtr dynamicCast() const noexcept; + + friend class BaseObject; + + template + friend class Object; + + template + friend class ObjectPtr; +}; + +namespace impl +{ +[[nodiscard]] object_id_t nextObjectId() noexcept; +void registerObject(BaseObject* object) noexcept; +void unregisterObject(BaseObject* object) noexcept; +[[nodiscard]] ObjectPtr getRegisteredObject(object_id_t objectId) noexcept; +} + +class BaseObject +{ +public: + using owner_t = BaseObject; +private: + object_id_t mId = 0; + std::atomic_uint32_t mReferenceCount = 1; +protected: + ObjectPtr mOwner = nullptr; +#if !defined(KAZAN_RELEASE) + // std::vector mChildren; +#endif +protected: + explicit BaseObject(ObjectPtr owner = nullptr) noexcept : mId(impl::nextObjectId()), mOwner(std::move(owner)) + { + impl::registerObject(this); + } +public: + BaseObject(const BaseObject&) = delete; + BaseObject(BaseObject&&) = delete; + virtual ~BaseObject() noexcept + { + impl::unregisterObject(this); + } + + BaseObject& operator=(const BaseObject&) = delete; + BaseObject& operator=(BaseObject&&) = delete; + + [[nodiscard]] inline bool canCreatePointer() const noexcept { return mReferenceCount > 0; } + [[nodiscard]] inline object_id_t getId() const noexcept { return mId; } + [[nodiscard]] inline ObjectPtr getPointer(bool skipCheck = false) noexcept; + [[nodiscard]] inline WeakObjectPtr getWeakPointer() noexcept; +protected: + inline void increaseReferenceCount() noexcept + { + ++mReferenceCount; + } + + inline void decreaseReferenceCount() noexcept + { + if (--mReferenceCount == 0) + { + delete this; + } + } + [[nodiscard]] inline std::uint32_t getReferenceCount() const noexcept { return mReferenceCount; } + + template + friend class ObjectPtr; +}; + +template +class Object : public TBase +{ +public: + using super_t = Object; + using owner_t = TOwner; +protected: + template + explicit Object(TArgs&&... args) : TBase(std::forward(args)...) {} +public: + [[nodiscard]] TOwner* getOwner() const noexcept { return static_cast(TBase::mOwner.getRaw()); } + [[nodiscard]] ObjectPtr getPointer(bool skipCheck = false) noexcept { return static_cast>(BaseObject::getPointer(skipCheck)); } + [[nodiscard]] WeakObjectPtr getWeakPointer() noexcept { return static_cast>(BaseObject::getWeakPointer()); } + + inline ObjectPtr makeUnique() + { + if (TBase::getReferenceCount() == 1) + { + return getPointer(); + } + else + { + return static_cast(this)->clone(); + } + } + + template + ObjectPtr createChild(TArgs&&... args) + { + ObjectPtr child = TObject::create(static_cast(this)->getPointer(), std::forward(args)...); +#if !defined(KAZAN_RELEASE) + // mChildren.push_back(&*child); +#endif + return child; + } +public: + template + static ObjectPtr create(TArgs&&... args) noexcept; +}; + +template +class AbstractObject : public TBase +{ +public: + using super_t = AbstractObject; + using owner_t = TOwner; +protected: + template + explicit AbstractObject(TArgs&&... args) noexcept : TBase(std::forward(args)...) {} +public: + [[nodiscard]] TOwner* getOwner() const noexcept { return static_cast(TBase::mOwner.getRaw()); } + [[nodiscard]] ObjectPtr getPointer(bool skipCheck = false) noexcept { return static_cast>(BaseObject::getPointer(skipCheck)); } + [[nodiscard]] WeakObjectPtr getWeakPointer() noexcept { return static_cast>(BaseObject::getWeakPointer()); } +}; + +template +class WeakObjectPtr; + +template +class WeakObjectPtr +{ +private: + object_id_t mId = 0; +public: + /** default construction */ + WeakObjectPtr() = default; + + /** copy */ + WeakObjectPtr(const WeakObjectPtr&) = default; + + /** implicit conversion */ + template requires(std::is_base_of_v) + WeakObjectPtr(const WeakObjectPtr& otherPtr) noexcept : mId(otherPtr.mId) {} + + /** explicit conversion */ + template requires(!std::is_same_v && std::is_base_of_v) + explicit WeakObjectPtr(const WeakObjectPtr& otherPtr) noexcept : mId(otherPtr.mId) {} + + /** construction from a regular ObjectPtr */ + template requires(std::is_base_of_v) + WeakObjectPtr(const ObjectPtr& otherPtr) noexcept : mId(otherPtr ? otherPtr->getId() : 0) {} + + /** construction directly from the object */ + template requires(std::is_base_of_v) + WeakObjectPtr(const TOtherObject& object) noexcept : mId(object.getId()) {} + + WeakObjectPtr& operator=(const WeakObjectPtr&) noexcept = default; + + template requires(std::is_base_of_v) + WeakObjectPtr& operator=(const WeakObjectPtr& otherPtr) noexcept + { + mId = otherPtr.mId; + return *this; + } + + WeakObjectPtr& operator=(std::nullptr_t) noexcept + { + mId = 0; + return *this; + } + + [[nodiscard]] bool operator!() const noexcept { return mId == 0; } + [[nodiscard]] auto operator<=>(const WeakObjectPtr&) const noexcept = default; + [[nodiscard]] bool operator==(std::nullptr_t) const noexcept { return mId == 0; } + [[nodiscard]] bool operator!=(std::nullptr_t) const noexcept { return mId != 0; } + [[nodiscard]] operator bool() const noexcept { return mId != 0; } + +#define MAKE_OPERATOR(op) \ + template requires (std::is_base_of_v || std::is_base_of_v) \ + auto operator op (const ObjectPtr& otherPtr) const noexcept \ + { \ + return mId op otherPtr.mId; \ + } + + MAKE_OPERATOR(<=>) + MAKE_OPERATOR(==) + MAKE_OPERATOR(!=) + MAKE_OPERATOR(<) + MAKE_OPERATOR(<=) + MAKE_OPERATOR(>) + MAKE_OPERATOR(>=) + +#undef MAKE_OPERATOR + + [[nodiscard]] object_id_t getId() const noexcept { return mId; } + + [[nodiscard]] ObjectPtr pin() const noexcept + { + return static_cast>(impl::getRegisteredObject(mId)); + } + + template + friend class WeakObjectPtr; +}; + +ObjectPtr BaseObject::getPointer([[maybe_unused]] bool skipCheck) noexcept +{ + // MIJIN_ASSERT(skipCheck || canCreatePointer(), "Cannot create an object pointer for an object that has not been created using ::create()!"); + if (!skipCheck) + { + increaseReferenceCount(); + } + ObjectPtr ptr; + ptr.mObject = this; + IWA_TRACK_OBJECTPTR_NEW(); + return ptr; +} + +WeakObjectPtr BaseObject::getWeakPointer() noexcept +{ + return WeakObjectPtr(*this); +} + +template +WeakObjectPtr ObjectPtr::makeWeak() const noexcept +{ + return WeakObjectPtr(*this); +} + +template +template requires(std::is_base_of_v) +ObjectPtr ObjectPtr::dynamicCast() const noexcept +{ + if (dynamic_cast(mObject)) { + return ObjectPtr(*this); + } + return nullptr; +} + +template +template +ObjectPtr Object::create(TArgs&&... args) noexcept +{ + return (new TConcrete(std::forward(args)...))->getPointer(/* skipCheck = */ true); +} + +void registerObjectDestructionHandler(const BaseObject& object, object_destruction_handler_t handler) noexcept; +} // namespace iwa + +template +struct std::hash> // NOLINT false positive +{ + std::size_t operator()(const iwa::ObjectPtr& ptr) const noexcept + { + return std::hash()(ptr.getRaw()); + } +}; + +template +struct std::hash> // NOLINT false positive +{ + std::size_t operator()(const iwa::WeakObjectPtr& ptr) const noexcept + { + return std::hash()(ptr.getId()); + } +}; + +#undef IWA_DECLARE_OBJECTPTR_TRACKING +#undef IWA_TRACK_OBJECTPTR +#undef IWA_TRACK_OBJECTPTR_NEW +#undef IWA_UNTRACK_OBJECTPTR +#undef IWA_MOVE_CONSTRUCT_OBJECTPTR_HANDLE +#undef IWA_MOVE_ASSIGN_OBJECTPTR_HANDLE +#undef IWA_UPDATE_OBJECTPTR_HANDLE + +#endif // IWA_OBJECT_HPP_INCLUDED diff --git a/include/iwa/pipeline.hpp b/include/iwa/pipeline.hpp new file mode 100644 index 0000000..f6cc7ab --- /dev/null +++ b/include/iwa/pipeline.hpp @@ -0,0 +1,136 @@ + +#pragma once + +#if !defined(IWA_PIPELINE_HPP_INCLUDED) +#define IWA_PIPELINE_HPP_INCLUDED + +#include +#include "iwa/descriptor_set.hpp" +#include "iwa/object.hpp" +#include "iwa/render_pass.hpp" +#include "iwa/shader_module.hpp" +#include "iwa/vkwrapper.hpp" + +namespace iwa +{ +inline constexpr vk::PipelineColorBlendAttachmentState DEFAULT_BLEND_ATTACHMENT = +{ + .blendEnable = VK_TRUE, + .srcColorBlendFactor = vk::BlendFactor::eSrcAlpha, + .dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, + .colorBlendOp = vk::BlendOp::eAdd, + .srcAlphaBlendFactor = vk::BlendFactor::eSrcAlpha, + .dstAlphaBlendFactor = vk::BlendFactor::eDstAlpha, + .alphaBlendOp = vk::BlendOp::eAdd, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG + | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA +}; + +inline constexpr vk::PipelineColorBlendAttachmentState DISABLED_BLEND_ATTACHMENT = +{ + .blendEnable = VK_FALSE, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG + | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA +}; + +struct PipelineLayoutCreationArgs +{ + std::vector> setLayouts; + std::vector pushConstantRanges; + vk::PipelineLayoutCreateFlags flags = {}; +}; + +class PipelineLayout : public Object, public MixinVulkanObject +{ +public: + PipelineLayout(ObjectPtr owner, const PipelineLayoutCreationArgs& args); + ~PipelineLayout() noexcept override; +}; + +class Pipeline : public AbstractObject, public MixinVulkanObject +{ +protected: + explicit Pipeline(ObjectPtr owner) noexcept; +public: + ~Pipeline() noexcept override; +}; + +struct PipelineStage +{ + ObjectPtr shader; + vk::ShaderStageFlagBits stage; + std::string name = "main"; +}; + +struct GraphicsPipelineRenderingInfo +{ + std::uint32_t viewMask = 0; + std::vector colorAttachmentFormats; + vk::Format depthFormat = vk::Format::eUndefined; + vk::Format stencilFormat = vk::Format::eUndefined; +}; + +struct VertexInput +{ + std::vector bindings; + std::vector attributes; +}; + +struct GraphicsPipelineCreationArgs +{ + std::vector stages; + VertexInput vertexInput; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly = { .topology = vk::PrimitiveTopology::eTriangleList }; + vk::PipelineViewportStateCreateInfo viewport = { .viewportCount = 1, .scissorCount = 1 }; + vk::PipelineRasterizationStateCreateInfo rasterization = + { .rasterizerDiscardEnable = VK_FALSE, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = VK_FALSE, .lineWidth = 1.f }; + vk::PipelineMultisampleStateCreateInfo multisample = + { .rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = VK_FALSE, .minSampleShading = 0.2f, + .pSampleMask = nullptr, .alphaToCoverageEnable = VK_FALSE, .alphaToOneEnable = VK_FALSE }; + vk::PipelineDepthStencilStateCreateInfo depthStencil = + { .depthTestEnable = false, .depthWriteEnable = false, .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = VK_FALSE, .minDepthBounds = 0.0, .maxDepthBounds = 1.0 }; + struct + { + std::optional logicOp; + std::vector attachements; + } colorBlend; + std::vector dynamicState = {vk::DynamicState::eViewport, vk::DynamicState::eScissor}; + std::optional renderingInfo; + ObjectPtr layout; + ObjectPtr renderPass; + std::uint32_t subpass = 0; +}; + +class GraphicsPipeline : public Object +{ +public: + GraphicsPipeline(ObjectPtr owner, const GraphicsPipelineCreationArgs& args) noexcept; +}; + +struct ComputePipelineCreationArgs +{ + PipelineStage stage; + ObjectPtr layout; +}; + +class ComputePipeline : public Object +{ +public: + ComputePipeline(ObjectPtr owner, const ComputePipelineCreationArgs& args) noexcept; +}; + +struct RayTracingPipelineCreationArgs +{ + +}; + +class RayTracingPipeline : public Object +{ +public: + RayTracingPipeline(ObjectPtr owner, const RayTracingPipelineCreationArgs& args) noexcept; +}; +} // namespace iwa + +#endif // !defined(IWA_PIPELINE_HPP_INCLUDED) diff --git a/include/iwa/render_pass.hpp b/include/iwa/render_pass.hpp new file mode 100644 index 0000000..99041ec --- /dev/null +++ b/include/iwa/render_pass.hpp @@ -0,0 +1,59 @@ + +#pragma once + +#if !defined(IWA_RENDER_PASS_HPP_INCLUDED) +#define IWA_RENDER_PASS_HPP_INCLUDED + +#include +#include "iwa/object.hpp" +#include "iwa/vkwrapper.hpp" + +namespace iwa +{ +struct SubpassDescription +{ + vk::SubpassDescriptionFlags flags = {}; + vk::PipelineBindPoint pipelineBindPoint; + std::vector inputAttachments; + std::vector colorAttachments; + std::vector resolveAttachments; + std::optional depthStencilAttachment; + std::vector preserveAttachments; +}; + +struct RenderPassCreationArgs +{ + vk::RenderPassCreateFlags flags = {}; + std::vector attachments; + std::vector subpasses; + std::vector dependencies; +}; + +class RenderPass : public Object, public MixinVulkanObject +{ +public: + RenderPass(ObjectPtr owner, const RenderPassCreationArgs& args); + ~RenderPass() noexcept override; +}; + +struct FramebufferCreationArgs +{ + vk::FramebufferCreateFlags flags = {}; + ObjectPtr renderPass; + std::vector> attachments; + std::uint32_t width = 0; + std::uint32_t height = 0; + std::uint32_t layers = 1; +}; + +class Framebuffer : public Object, public MixinVulkanObject +{ +private: + std::vector> mImageViews; +public: + Framebuffer(ObjectPtr owner, const FramebufferCreationArgs& args); + ~Framebuffer() noexcept override; +}; +} // namespace iwa + +#endif // !defined(IWA_RENDER_PASS_HPP_INCLUDED) diff --git a/include/iwa/resource/bitmap.hpp b/include/iwa/resource/bitmap.hpp new file mode 100644 index 0000000..4b64a4c --- /dev/null +++ b/include/iwa/resource/bitmap.hpp @@ -0,0 +1,73 @@ + +#pragma once + +#if !defined(IWA_RESOURCE_BITMAP_HPP_INCLUDED) +#define IWA_RESOURCE_BITMAP_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include "../object.hpp" +#include "../vkwrapper.hpp" + +namespace iwa +{ +enum class ColorChannel +{ + R = 0, + G = 1, + B = 2, + A = 3 +}; + +struct ChannelMapping +{ + ColorChannel from; + ColorChannel to; +}; + +struct BitmapCreationArgs +{ + vk::Format format; + vk::Extent2D size; + mijin::Optional initialData; +}; + +class Bitmap : public Object +{ +private: + mijin::TypelessBuffer mData; + vk::Format mFormat; + vk::Extent2D mSize; + std::unique_ptr mView; +public: + explicit Bitmap(BitmapCreationArgs args, ObjectPtr owner = nullptr); + ~Bitmap() override; + + // properties + inline vk::Format getFormat() const { return mFormat; } + inline vk::Extent2D getSize() const { return mSize; } + inline std::span getData() { return mData.makeSpan(); } + inline std::span getData() const { return mData.makeSpan(); } + + // access + // TODO: maybe add accessors for whole rows or the whole image? + [[nodiscard]] glm::vec4 getPixel(unsigned x, unsigned y) const; + [[nodiscard]] std::vector getPixels(unsigned x, unsigned y, unsigned width, unsigned height) const; + [[nodiscard]] inline std::vector getAllPixels() const { + return getPixels(0, 0, mSize.width, mSize.height); + } + + // drawing + void fill(const glm::vec4& color); + void copyChannels(const Bitmap& other, const std::vector& mappings); + void multiply(const glm::vec4& color); +private: + void createView(); +}; +} // namespace iwa + +#endif // !defined(IWA_RESOURCE_BITMAP_HPP_INCLUDED) diff --git a/include/iwa/resource/font.hpp b/include/iwa/resource/font.hpp new file mode 100644 index 0000000..e1053ae --- /dev/null +++ b/include/iwa/resource/font.hpp @@ -0,0 +1,55 @@ + +#pragma once + +#if !defined(IWA_RESOURCE_FONT_HPP_INCLUDED) +#define IWA_RESOURCE_FONT_HPP_INCLUDED + +#include +#include +#include "iwa/object.hpp" +#include "iwa/resource/bitmap.hpp" + +namespace iwa +{ +struct GlyphInfo +{ + glm::vec2 uvPos0; + glm::vec2 uvPos1; + float xOffsetBefore; + float xOffsetAfter; + float yOffsetBefore; + float yOffsetAfter; + float xAdvance; +}; + +struct FontMetrics +{ + float ascent; + float descent; + float lineGap; + float sizeFactor; +}; + +struct FontCreationArgs +{ + ObjectPtr bitmap; + std::unordered_map glyphMap; + FontMetrics metrics; +}; + +class Font : public Object +{ +private: + ObjectPtr mBitmap; + std::unordered_map mGlyphMap; + FontMetrics mMetrics; +public: + explicit Font(FontCreationArgs args); + + [[nodiscard]] const ObjectPtr& getBitmap() const noexcept { return mBitmap; } + [[nodiscard]] const std::unordered_map& getGlyphMap() const noexcept { return mGlyphMap; } + [[nodiscard]] const FontMetrics& getMetrics() const noexcept { return mMetrics; } +}; +} // namespace iwa + +#endif // !defined(IWA_RESOURCE_FONT_HPP_INCLUDED) diff --git a/include/iwa/semaphore.hpp b/include/iwa/semaphore.hpp new file mode 100644 index 0000000..4d07683 --- /dev/null +++ b/include/iwa/semaphore.hpp @@ -0,0 +1,25 @@ + +#pragma once + +#if !defined(IWA_SEMAPHORE_HPP_INCLUDED) +#define IWA_SEMAPHORE_HPP_INCLUDED + +#include "iwa/object.hpp" +#include "iwa/vkwrapper.hpp" + +namespace iwa +{ +struct SemaphoreCreationArgs +{ + vk::SemaphoreCreateFlags flags = {}; +}; + +class Semaphore : public Object, public MixinVulkanObject +{ +public: + Semaphore(ObjectPtr owner, const SemaphoreCreationArgs& args = {}); + ~Semaphore() noexcept override; +}; +} + +#endif // !defined(IWA_SEMAPHORE_HPP_INCLUDED) diff --git a/include/iwa/shader_module.hpp b/include/iwa/shader_module.hpp new file mode 100644 index 0000000..bbda1c9 --- /dev/null +++ b/include/iwa/shader_module.hpp @@ -0,0 +1,27 @@ + +#pragma once + +#if !defined(IWA_SHADER_MODULE_HPP_INCLUDED) +#define IWA_SHADER_MODULE_HPP_INCLUDED + +#include +#include +#include "iwa/object.hpp" +#include "iwa/vkwrapper.hpp" + +namespace iwa +{ +struct ShaderModuleCreationArgs +{ + std::span code; +}; + +class ShaderModule : public Object, public MixinVulkanObject +{ +public: + ShaderModule(ObjectPtr owner, const ShaderModuleCreationArgs& args); + ~ShaderModule() noexcept override; +}; +} // namespace iwa + +#endif // !defined(IWA_SHADER_MODULE_HPP_INCLUDED) diff --git a/include/iwa/swapchain.hpp b/include/iwa/swapchain.hpp new file mode 100644 index 0000000..7a1a1c2 --- /dev/null +++ b/include/iwa/swapchain.hpp @@ -0,0 +1,68 @@ + +#pragma once + +#ifndef IWA_SWAPCHAIN_HPP_INCLUDED +#define IWA_SWAPCHAIN_HPP_INCLUDED + +#include +#include +#include "iwa/image.hpp" +#include "iwa/object.hpp" +#include "iwa/semaphore.hpp" +#include "iwa/vkwrapper.hpp" +#include "iwa/window.hpp" + +namespace iwa +{ +struct SwapchainCreationArgs +{ + ObjectPtr window; + std::uint32_t parallelFrames = 3; + vk::ImageUsageFlags imageUsage = vk::ImageUsageFlagBits::eColorAttachment; +}; + +struct PresentArgs +{ + vk::Queue queue; + std::vector waitSemaphores; +}; + +class Swapchain : public Object, public MixinVulkanObject +{ +private: + static inline std::uint32_t INVALID_IMAGE_INDEX = std::numeric_limits::max(); + + std::uint32_t mCurrentImageIdx = INVALID_IMAGE_INDEX; + unsigned mCurrentFrameIdx = 0; + ObjectPtr mWindow; + std::vector> mImages; + std::vector> mImageAvailableSemaphores; + const vk::ImageUsageFlags mImageUsage; + vk::Format mFormat = vk::Format::eUndefined; + vk::Extent2D mExtent; +public: + Swapchain(ObjectPtr owner, SwapchainCreationArgs args); + ~Swapchain() noexcept override; + + [[nodiscard]] const ObjectPtr& getWindow() const noexcept { return mWindow; } + [[nodiscard]] std::size_t getNumParallelFrames() const noexcept { return mImageAvailableSemaphores.size(); } + [[nodiscard]] const ObjectPtr& getCurrentAvailableSemaphore() const noexcept { return mImageAvailableSemaphores[mCurrentFrameIdx]; } + [[nodiscard]] unsigned getCurrentFrameIdx() const noexcept { return mCurrentFrameIdx; } + [[nodiscard]] const std::vector>& getImages() const noexcept { return mImages; } + [[nodiscard]] std::uint32_t getCurrentImageIdx() const noexcept { return mCurrentImageIdx; } + [[nodiscard]] const ObjectPtr& getCurrentImage() const noexcept { return mImages[mCurrentImageIdx]; } + [[nodiscard]] vk::Format getFormat() const noexcept { return mFormat; } + [[nodiscard]] const vk::Extent2D& getExtent() const noexcept { return mExtent; } + + mijin::Task<> c_present(const PresentArgs& args); +private: + void recreate(); + void acquireImage(); + mijin::Task<> c_acquireImage(); + +public: // signals + mijin::Signal<> recreated; +}; +} + +#endif // IWA_SWAPCHAIN_HPP_INCLUDED diff --git a/include/iwa/texture.hpp b/include/iwa/texture.hpp new file mode 100644 index 0000000..d219fbe --- /dev/null +++ b/include/iwa/texture.hpp @@ -0,0 +1,72 @@ + +#pragma once + +#if !defined(IWA_TEXTURE_HPP_INCLUDED) +#define IWA_TEXTURE_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include "iwa/image.hpp" + +namespace iwa +{ +struct TextureCreationArgs +{ + ObjectPtr image; + ImageViewCreationArgs imageViewArgs = {}; + SamplerCreationArgs samplerArgs = {}; +}; + +struct SingleColorTextureArgs +{ + glm::vec4 color; + vk::Format format = vk::Format::eR8G8B8A8Unorm; + + auto operator <=>(const SingleColorTextureArgs&) const noexcept = default; +}; + +struct TextureFromBitmapArgs +{ + const Bitmap& bitmap; + ImageCreationArgs imageArgs = {}; + ImageViewCreationArgs imageViewArgs = {}; + SamplerCreationArgs samplerArgs = {}; +}; + +class Texture : public Object +{ +private: + ObjectPtr mImage; + ObjectPtr mImageView; + ObjectPtr mSampler; +public: + explicit Texture(TextureCreationArgs args); + + [[nodiscard]] const ObjectPtr& getImage() const noexcept { return mImage; } + [[nodiscard]] const ObjectPtr& getImageView() const noexcept { return mImageView; } + [[nodiscard]] const ObjectPtr& getSampler() const noexcept { return mSampler; } + + static mijin::Task> c_createSingleColor(const ObjectPtr& device, const SingleColorTextureArgs& args); + static mijin::Task> c_createFromBitmap(const ObjectPtr& device, const TextureFromBitmapArgs& args); +}; +} // namespace iwa + +namespace std +{ +template<> +struct hash +{ + std::size_t operator()(const iwa::SingleColorTextureArgs& args) const noexcept + { + std::size_t hash = 0; + mijin::hashCombine(hash, args.color); + mijin::hashCombine(hash, args.format); + return hash; + } +}; +} + +#endif // !defined(IWA_TEXTURE_HPP_INCLUDED) diff --git a/include/iwa/util/color.hpp b/include/iwa/util/color.hpp new file mode 100644 index 0000000..501f3bc --- /dev/null +++ b/include/iwa/util/color.hpp @@ -0,0 +1,47 @@ + +#pragma once + +#if !defined(IWA_UTIL_COLOR_HPP_INCLUDED) +#define IWA_UTIL_COLOR_HPP_INCLUDED + +#include +#include +#include + +namespace iwa +{ +[[nodiscard]] inline float linearToSrgb(float value) noexcept +{ + if (value < 0.f) { + return 0.f; + } + if (value < 0.04045f) { + return value / 12.92f; + } + if (value < 1.f) { + return std::pow((value + 0.055f) / 1.055f, 2.4f); + } + return 1.f; +} + +[[nodiscard]] inline glm::vec3 linearToSrgb(const glm::vec3& vector) noexcept +{ + return glm::vec3( + linearToSrgb(vector[0]), + linearToSrgb(vector[1]), + linearToSrgb(vector[2]) + ); +} + +[[nodiscard]] inline glm::vec4 linearToSrgb(const glm::vec4& vector) noexcept +{ + return glm::vec4( + linearToSrgb(vector[0]), + linearToSrgb(vector[1]), + linearToSrgb(vector[2]), + vector.a + ); +} +} // namespace iwa + +#endif // !defined(IWA_UTIL_COLOR_HPP_INCLUDED) diff --git a/include/iwa/util/dir_stack_file_includer.hpp b/include/iwa/util/dir_stack_file_includer.hpp new file mode 100644 index 0000000..76fc177 --- /dev/null +++ b/include/iwa/util/dir_stack_file_includer.hpp @@ -0,0 +1,116 @@ +// copied from Glslang source and adjusted just a bit +#pragma once + +#if !defined(KAZAN_DIR_STACK_FILE_INCLUDER_HPP_INCLUDED) +#define KAZAN_DIR_STACK_FILE_INCLUDER_HPP_INCLUDED + +#include +#include +#include +#include + +namespace iwa::impl +{ +class DirStackFileIncluder : public glslang::TShader::Includer +{ +protected: + using tUserDataElement = char; + + std::vector directoryStack; + int externalLocalDirectoryCount = 0; + std::unordered_set includedFiles; +public: + + IncludeResult* includeLocal(const char* headerName, const char* includerName, size_t inclusionDepth) override; + IncludeResult* includeSystem(const char* headerName, const char* /*includerName*/, size_t /*inclusionDepth*/) override; + + // Externally set directories. E.g., from a command-line -I. + // - Most-recently pushed are checked first. + // - All these are checked after the parse-time stack of local directories + // is checked. + // - This only applies to the "local" form of #include. + // - Makes its own copy of the path. + virtual void pushExternalLocalDirectory(const std::string& dir) + { + directoryStack.push_back(dir); + externalLocalDirectoryCount = (int)directoryStack.size(); + } + + void releaseInclude(IncludeResult* result) override + { + if (result != nullptr) + { + delete [] static_cast(result->userData); + delete result; + } + } + + virtual std::unordered_set getIncludedFiles() noexcept + { + return includedFiles; + } + +protected: + // Search for a valid "local" path based on combining the stack of include + // directories and the nominal name of the header. + virtual IncludeResult* readLocalPath(const char* headerName, const char* includerName, int depth) + { + // Discard popped include directories, and + // initialize when at parse-time first level. + directoryStack.resize(depth + externalLocalDirectoryCount); + if (depth == 1) + directoryStack.back() = getDirectory(includerName); + + // Find a directory that works, using a reverse search of the include stack. + for (auto it = directoryStack.rbegin(); it != directoryStack.rend(); ++it) { + std::string path = *it + '/' + headerName; + std::replace(path.begin(), path.end(), '\\', '/'); + std::ifstream file(path, std::ios_base::binary | std::ios_base::ate); + if (file) { + directoryStack.push_back(getDirectory(path)); + includedFiles.insert(path); + return newIncludeResult(path, file, (int)file.tellg()); + } + } + + return nullptr; + } + + // Search for a valid path. + // Not implemented yet; returning nullptr signals failure to find. + virtual IncludeResult* readSystemPath(const char* /*headerName*/) const + { + return nullptr; + } + + // Do actual reading of the file, filling in a new include result. + virtual IncludeResult* newIncludeResult(const std::string& path, std::ifstream& file, int length) const + { + char* content = new tUserDataElement [length]; + file.seekg(0, file.beg); + file.read(content, length); + return new IncludeResult(path, content, length, content); + } + + // If no path markers, return current working directory. + // Otherwise, strip file name and return path leading up to it. + virtual std::string getDirectory(const std::string& path) const + { + const std::size_t last = path.find_last_of("/\\"); + return last == std::string::npos ? "." : path.substr(0, last); + } +}; + +glslang::TShader::Includer::IncludeResult* +DirStackFileIncluder::includeLocal(const char* headerName, const char* includerName, size_t inclusionDepth) +{ + return readLocalPath(headerName, includerName, (int)inclusionDepth); +} + +glslang::TShader::Includer::IncludeResult* +DirStackFileIncluder::includeSystem(const char* headerName, const char*, size_t) +{ + return readSystemPath(headerName); +} +} // namespace iwa::impl +#endif // !defined(KAZAN_DIR_STACK_FILE_INCLUDER_HPP_INCLUDED) diff --git a/include/iwa/util/fps_calculator.hpp b/include/iwa/util/fps_calculator.hpp new file mode 100644 index 0000000..e7cbd89 --- /dev/null +++ b/include/iwa/util/fps_calculator.hpp @@ -0,0 +1,49 @@ + +#pragma once + +#if !defined(IWA_UTIL_FPS_CALCULATOR_HPP_INCLUDED) +#define IWA_UTIL_FPS_CALCULATOR_HPP_INCLUDED + +#include +#include + +namespace iwa +{ +template +class FpsCalculator +{ +private: + using clock_t = std::chrono::high_resolution_clock; + using doubledur_t = std::chrono::duration; + + clock_t::time_point mLastFrame = clock_t::now(); + std::array mLatestValues = {}; + unsigned mNextValueIndex = 0; +public: + inline void tickFrame() noexcept; + [[nodiscard]] inline double getFps() const noexcept; +}; + +template +void FpsCalculator::tickFrame() noexcept +{ + const clock_t::time_point currentFrame = clock_t::now(); + const clock_t::duration difference = currentFrame - mLastFrame; + mLastFrame = currentFrame; + mLatestValues[mNextValueIndex] = 1.0 / std::chrono::duration_cast(difference).count(); + mNextValueIndex = (mNextValueIndex + 1) % NUM_VALUES; +} + +template +double FpsCalculator::getFps() const noexcept +{ + double sum = 0; + for (const double fps : mLatestValues) + { + sum += fps; + } + return sum / NUM_VALUES; +} +} // namespace iwa + +#endif // !defined(IWA_UTIL_FPS_CALCULATOR_HPP_INCLUDED) diff --git a/include/iwa/util/glsl_compiler.hpp b/include/iwa/util/glsl_compiler.hpp new file mode 100644 index 0000000..4321f09 --- /dev/null +++ b/include/iwa/util/glsl_compiler.hpp @@ -0,0 +1,152 @@ + +#pragma once + +#if !defined(IWA_UTIL_GLSL_COMPILER_HPP_INCLUDED) +#define IWA_UTIL_GLSL_COMPILER_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include "iwa/instance.hpp" +#include "iwa/object.hpp" +#include "iwa/pipeline.hpp" +#include "iwa/shader_module.hpp" +#include "iwa/vkwrapper.hpp" +#include "iwa/util/shader_meta.hpp" + +namespace glslang +{ +class TShader; +class TProgram; +} + +namespace YAML +{ +class Node; +} + +namespace iwa +{ +struct ShaderSource +{ + std::string code; + + std::string fileName = {}; // also required for parsing (e.g. relative includes) +#if !defined(KAZAN_RELEASE) + std::string name = {}; // only for debug information! +#endif + [[nodiscard]] static ShaderSource fromStream(mijin::Stream& stream, std::string fileName = {}); + [[nodiscard]] static ShaderSource fromFile(const mijin::PathReference& file); + [[nodiscard]] static ShaderSource fromYaml(const YAML::Node& node, const mijin::PathReference& yamlFile); + // static ShaderSource fromFile(std::string fileName, std::string name = ""); +}; + +class GLSLCompilerSettings : public Object +{ +private: + std::string mCommonPreamble; +public: + [[nodiscard]] const std::string& getCommonPreamble() const noexcept { return mCommonPreamble; } + void setCommonPreamble(std::string preamble) noexcept { mCommonPreamble = std::move(preamble); } +}; + +struct GLSLShaderCreationArgs +{ + std::vector sources; + std::vector defines; + vk::ShaderStageFlagBits type; +}; + +struct GLSLShaderLinkFlags : mijin::BitFlags +{ +#if !defined(KAZAN_RELEASE) + std::uint8_t withDebugInfo : 1 = 1; +#else + std::uint8_t withDebugInfo : 1 = 0; +#endif +}; + +class GLSLShader : public Object +{ +private: + std::unique_ptr mHandle; + vk::ShaderStageFlagBits mType; + std::vector mSources; + std::vector mDefines; +public: + GLSLShader(ObjectPtr owner, GLSLShaderCreationArgs args) noexcept; + ~GLSLShader() noexcept override; + + [[nodiscard]] std::unique_ptr releaseHandle(); + [[nodiscard]] vk::ShaderStageFlagBits getType() const noexcept { return mType; } + + [[nodiscard]] ShaderMeta getPartialMeta(); +private: + void compile(); +}; + +struct GLSLSemanticMapping +{ + unsigned semantic; + unsigned semanticIdx = 0; + int newSet = -1; + int newBinding = -1; +}; + +struct GLSLShaderProgramCreationArgs +{ + std::vector> shaders; + GLSLShaderLinkFlags linkFlags; + std::vector semanticMappings; +}; + +struct PrepareGraphicsPipelineArgs +{ + const struct VertexLayout& vertexLayout; + GeneratePipelineLayoutArgs layoutArgs; + PipelineLayoutMeta pipelineLayoutMeta; + PipelineAndDescriptorSetLayouts layouts; +}; + +struct PrepareComputePipelineArgs +{ + GeneratePipelineLayoutArgs layoutArgs; + PipelineLayoutMeta pipelineLayoutMeta; + PipelineAndDescriptorSetLayouts layouts; +}; + +class GLSLShaderProgram : public Object +{ +private: + std::vector> mShaderHandles; // must keep the TShaders alive + std::unique_ptr mHandle; + ShaderMeta mMeta; + GLSLShaderLinkFlags mLinkFlags; +public: + GLSLShaderProgram(ObjectPtr owner, GLSLShaderProgramCreationArgs args); + + [[nodiscard]] glslang::TProgram* getHandle() const noexcept { return mHandle.get(); } + [[nodiscard]] const ShaderMeta& getMeta() const noexcept { return mMeta; } + [[nodiscard]] std::vector generateSpirv(vk::ShaderStageFlagBits stage) const; + [[nodiscard]] std::vector generatePipelineStages() const; + [[nodiscard]] GraphicsPipelineCreationArgs prepareGraphicsPipeline(PrepareGraphicsPipelineArgs& args) const; + [[nodiscard]] ComputePipelineCreationArgs prepareComputePipeline(PrepareComputePipelineArgs& args) const; +}; + +// struct PrepareGLSLGraphicsPipelineArgs +// { +// std::vector> shaders; +// }; +// +// struct PrepareGLSLGraphicsPipelineResult +// { +// std::vector stages; +// ShaderMeta +// }; +// +// [[nodiscard]] PrepareGLSLGraphicsPipelineResult prepareGLSLGraphicsPipeline(const PrepareGLSLGraphicsPipelineArgs& args); +} // namespace iwa + +#endif // !defined(IWA_UTIL_GLSL_COMPILER_HPP_INCLUDED) diff --git a/include/iwa/util/growing_descriptor_pool.hpp b/include/iwa/util/growing_descriptor_pool.hpp new file mode 100644 index 0000000..77ea62b --- /dev/null +++ b/include/iwa/util/growing_descriptor_pool.hpp @@ -0,0 +1,27 @@ + +#pragma once + +#if !defined(IWA_UTIL_GROWING_DESCRIPTOR_POOL_HPP_INCLUDED) +#define IWA_UTIL_GROWING_DESCRIPTOR_POOL_HPP_INCLUDED + +#include "iwa/descriptor_set.hpp" + +namespace iwa +{ +using GrowingDescriptorPoolCreationArgs = DescriptorPoolCreationArgs; + +class GrowingDescriptorPool : public Object +{ +private: + std::vector> mPools; + DescriptorPoolCreationArgs mCreationArgs; +public: + GrowingDescriptorPool(ObjectPtr owner, GrowingDescriptorPoolCreationArgs args); + + [[nodiscard]] ObjectPtr allocateDescriptorSet(const DescriptorSetAllocateArgs& args); + + [[nodiscard]] bool getCanFree() const noexcept { return (mCreationArgs.flags & vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet) != vk::DescriptorPoolCreateFlags(); } +}; +} // namespace iwa + +#endif // !defined(IWA_UTIL_GROWING_DESCRIPTOR_POOL_HPP_INCLUDED) diff --git a/include/iwa/util/image_reference.hpp b/include/iwa/util/image_reference.hpp new file mode 100644 index 0000000..9fbe978 --- /dev/null +++ b/include/iwa/util/image_reference.hpp @@ -0,0 +1,106 @@ + +#pragma once + +#if !defined(IWA_UTIL_RENDER_TARGET_HPP_INCLUDED) +#define IWA_UTIL_RENDER_TARGET_HPP_INCLUDED + +#include +#include "iwa/fence.hpp" +#include "iwa/object.hpp" +#include "iwa/semaphore.hpp" +#include "iwa/swapchain.hpp" + +namespace iwa +{ +struct ImageReferenceFrame +{ + Image* image; + ImageView* imageView; + vk::Offset2D offset = {0, 0}; +}; + +struct ImageReferenceFinalizeArgs +{ + const class CommandBuffer& cmdBuffer; + std::vector& waitSemaphores; + std::vector& signalSemaphores; +}; + +class ImageReference : public AbstractObject +{ +protected: + explicit ImageReference(ObjectPtr owner); +public: + [[nodiscard]] virtual vk::Format getFormat() = 0; + [[nodiscard]] virtual vk::Extent2D getExtent() = 0; + virtual ImageReferenceFrame getCurrentFrame() = 0; + virtual void finalize(ImageReferenceFinalizeArgs& args); + virtual mijin::Task<> c_present(); +}; + +struct SwapchainImageReferenceCreationArgs +{ + ObjectPtr swapchain; +}; + +class SwapchainImageReference : public Object +{ +private: + std::vector> mPresentReadySemaphores; + std::vector> mImageViews; + ObjectPtr mSwapchain; +public: + SwapchainImageReference(ObjectPtr owner, SwapchainImageReferenceCreationArgs args); + + [[nodiscard]] vk::Format getFormat() override; + [[nodiscard]] vk::Extent2D getExtent() override; + ImageReferenceFrame getCurrentFrame() override; + void finalize(ImageReferenceFinalizeArgs& args) override; + mijin::Task<> c_present() override; +private: + void createImageViews(); +}; + +struct DirectImageReferenceCreationArgs +{ + ObjectPtr image; + ObjectPtr imageView; +}; + +class DirectImageReference : public Object +{ +protected: + ObjectPtr mImage; + ObjectPtr mImageView; +public: + DirectImageReference(ObjectPtr owner, DirectImageReferenceCreationArgs args); + + [[nodiscard]] vk::Format getFormat() override; + [[nodiscard]] vk::Extent2D getExtent() override; + ImageReferenceFrame getCurrentFrame() override; +}; + +struct AutoResizeImageReferenceCreationArgs +{ + ObjectPtr referenceImageRef; + ImageCreationArgs imageCreationArgs = {}; + ImageViewCreationArgs imageViewCreationArgs = {}; +}; + +class AutoResizeImageReference : public Object +{ +private: + ObjectPtr mReferenceImageRef; + ImageCreationArgs mImageCreationArgs; + ImageViewCreationArgs mImageViewCreationArgs; +public: + AutoResizeImageReference(ObjectPtr owner, AutoResizeImageReferenceCreationArgs args); + + [[nodiscard]] vk::Extent2D getExtent() override; + ImageReferenceFrame getCurrentFrame() override; +private: + void createImage(); +}; +} // namespace iwa + +#endif // !defined(IWA_UTIL_RENDER_TARGET_HPP_INCLUDED) diff --git a/include/iwa/util/next_chain.hpp b/include/iwa/util/next_chain.hpp new file mode 100644 index 0000000..4e49568 --- /dev/null +++ b/include/iwa/util/next_chain.hpp @@ -0,0 +1,58 @@ + +#pragma once + +#if !defined(KAZAN_NEXT_CHAIN_HPP_INCLUDED) +#define KAZAN_NEXT_CHAIN_HPP_INCLUDED + +#include +#include +#include +#include +#include "iwa/vkwrapper.hpp" + +namespace iwa +{ +class NextChain +{ +private: + std::vector data; + std::vector offsets; +public: + template + void append(const TStruct& element) noexcept + { + const std::size_t oldSize = mijin::alignUp(data.size(), alignof(TStruct)); + data.resize(oldSize + sizeof(TStruct)); + std::memcpy(&data[oldSize], &element, sizeof(TStruct)); + offsets.push_back(oldSize); + } + + void* finalize() noexcept + { + auto itCur = offsets.begin(); + if (itCur == offsets.end()) + { + return nullptr; + } + auto itNext = std::next(itCur); + + while (true) + { + vk::BaseOutStructure& curStruct = *std::bit_cast(&data[*itCur]); + if (itNext == offsets.end()) + { + curStruct.pNext = nullptr; + break; + } + curStruct.pNext = std::bit_cast(&data[*itNext]); + itCur = itNext; + ++itNext; + } + + // first is always at offset 0 + return data.data(); + } +}; +} // namespace iwa + +#endif // !defined(KAZAN_NEXT_CHAIN_HPP_INCLUDED) diff --git a/include/iwa/util/reflect_glsl.hpp b/include/iwa/util/reflect_glsl.hpp new file mode 100644 index 0000000..b6389bb --- /dev/null +++ b/include/iwa/util/reflect_glsl.hpp @@ -0,0 +1,17 @@ + +#pragma once + +#if !defined(IWA_UTIL_REFLECT_GLSL_HPP_INCLUDED) +#define IWA_UTIL_REFLECT_GLSL_HPP_INCLUDED + +#include +#include "iwa/util/shader_meta.hpp" + +namespace iwa +{ +[[nodiscard]] ShaderMeta reflectShader(glslang::TShader& shader); +[[nodiscard]] ShaderMeta reflectProgram(glslang::TProgram& program); +[[nodiscard]] ShaderMeta reflectIntermediate(glslang::TIntermediate& intermediate, vk::ShaderStageFlagBits stage); +} // namespace iwa + +#endif // !defined(IWA_UTIL_REFLECT_GLSL_HPP_INCLUDED) diff --git a/include/iwa/util/render_loop.hpp b/include/iwa/util/render_loop.hpp new file mode 100644 index 0000000..b06b9c9 --- /dev/null +++ b/include/iwa/util/render_loop.hpp @@ -0,0 +1,67 @@ + +#pragma once + +#if !defined(IWA_UTIL_RENDER_LOOP_HPP_INCLUDED) +#define IWA_UTIL_RENDER_LOOP_HPP_INCLUDED + +#include +#include +#include "iwa/device.hpp" +#include "iwa/fence.hpp" +#include "iwa/object.hpp" +#include "iwa/swapchain.hpp" +#include "iwa/vkwrapper.hpp" +#include "iwa/util/image_reference.hpp" +#include "iwa/util/task_runner.hpp" + +namespace iwa +{ +struct RenderLoopCreationFlags : mijin::BitFlags +{ + std::uint8_t advanceDeleteQueue : 1 = 1; +}; + +struct RenderLoopCreationArgs +{ + ObjectPtr commandPool; + RenderLoopCreationFlags flags; + unsigned parallelFrames = 3; + unsigned targetFps = 0; +}; + +struct RenderLoopRenderArgs +{ + const CommandBuffer& cmdBuffer; + const unsigned frameIdx; + std::unordered_set> usedImageReferences; +}; + +class RenderLoop : public AbstractObject, public MixinTaskRunner +{ +private: + struct Alternating + { + ObjectPtr commandBuffer; + ObjectPtr renderDoneFence; + }; + std::vector mAlternating; + unsigned mFrameIdx = 0; +protected: + const bool mAdvanceDeleteQueue; +protected: + explicit RenderLoop(ObjectPtr owner, RenderLoopCreationArgs args); +public: + void start() noexcept; + + virtual mijin::Task<> c_init(); + virtual mijin::Task<> c_render(RenderLoopRenderArgs& args) = 0; + + [[nodiscard]] std::size_t getNumParallelFrames() const noexcept { return mAlternating.size(); } + + mijin::SimpleTaskLoop& getTaskLoop() const noexcept; +private: + mijin::Task<> c_renderLoop(); +}; +} // namespace iwa + +#endif // !defined(IWA_UTIL_RENDER_LOOP_HPP_INCLUDED) diff --git a/include/iwa/util/shader_meta.hpp b/include/iwa/util/shader_meta.hpp new file mode 100644 index 0000000..bbb1c99 --- /dev/null +++ b/include/iwa/util/shader_meta.hpp @@ -0,0 +1,683 @@ + +#pragma once + +#if !defined(IWA_UTIL_SHADER_META_HPP_INCLUDED) +#define IWA_UTIL_SHADER_META_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include "iwa/descriptor_set.hpp" +#include "iwa/device.hpp" +#include "iwa/instance.hpp" +#include "iwa/log.hpp" +#include "iwa/pipeline.hpp" +#include "iwa/vkwrapper.hpp" + +namespace iwa +{ +inline constexpr unsigned UNSPECIFIED_INDEX = std::numeric_limits::max(); + +enum class ImageDim +{ + ONE = VK_IMAGE_TYPE_1D, + TWO = VK_IMAGE_TYPE_2D, + THREE = VK_IMAGE_TYPE_3D, + CUBE +}; + +#if MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" +#endif +union ShaderTypeBits +{ + struct + { + bool compute : 1; + bool vertex : 1; + bool fragment : 1; + bool rayGeneration : 1; + bool rayClosestHit : 1; + bool rayAnyHit : 1; + bool rayMiss : 1; + bool rayIntersection : 1; + bool callable : 1; + bool task : 1; + bool mesh : 1; + bool tessellationControl : 1; + bool tesselationEvaluation : 1; + bool geometry : 1; + }; + std::uint16_t bits = 0; + // static_assert(static_cast(ShaderType::NUM_VALUES) < 8 * sizeof(bits)); + + [[nodiscard]] constexpr bool get(vk::ShaderStageFlagBits shaderType) const; + constexpr void set(vk::ShaderStageFlagBits shaderType, bool value); + + constexpr ShaderTypeBits& operator |=(const ShaderTypeBits& other) { + bits |= other.bits; + return *this; + } + + constexpr operator bool() const { + return bits != 0; + } + + constexpr bool operator!() const { + return bits == 0; + } + + [[nodiscard]] constexpr vk::ShaderStageFlags toVulkan() const + { + return vk::ShaderStageFlagBits{} + | (compute ? vk::ShaderStageFlagBits::eCompute : vk::ShaderStageFlagBits{}) + | (vertex ? vk::ShaderStageFlagBits::eVertex : vk::ShaderStageFlagBits{}) + | (fragment ? vk::ShaderStageFlagBits::eFragment : vk::ShaderStageFlagBits{}) + | (rayGeneration ? vk::ShaderStageFlagBits::eRaygenKHR : vk::ShaderStageFlagBits{}) + | (rayClosestHit ? vk::ShaderStageFlagBits::eClosestHitKHR : vk::ShaderStageFlagBits{}) + | (rayAnyHit ? vk::ShaderStageFlagBits::eAnyHitKHR : vk::ShaderStageFlagBits{}) + | (rayMiss ? vk::ShaderStageFlagBits::eMissKHR : vk::ShaderStageFlagBits{}) + | (rayIntersection ? vk::ShaderStageFlagBits::eIntersectionKHR : vk::ShaderStageFlagBits{}) + | (callable ? vk::ShaderStageFlagBits::eCallableKHR : vk::ShaderStageFlagBits{}) + | (task ? vk::ShaderStageFlagBits::eTaskEXT : vk::ShaderStageFlagBits{}) + | (mesh ? vk::ShaderStageFlagBits::eMeshEXT : vk::ShaderStageFlagBits{}) + | (tessellationControl ? vk::ShaderStageFlagBits::eTessellationControl : vk::ShaderStageFlagBits{}) + | (tesselationEvaluation ? vk::ShaderStageFlagBits::eTessellationEvaluation : vk::ShaderStageFlagBits{}) + | (geometry ? vk::ShaderStageFlagBits::eGeometry : vk::ShaderStageFlagBits{}); + } + [[nodiscard]] constexpr vk::ShaderStageFlagBits getFirst() const + { + if (compute) { + return vk::ShaderStageFlagBits::eCompute; + } + if (vertex) { + return vk::ShaderStageFlagBits::eVertex; + } + if (fragment) { + return vk::ShaderStageFlagBits::eFragment; + } + if (rayGeneration) { + return vk::ShaderStageFlagBits::eRaygenKHR; + } + if (rayClosestHit) { + return vk::ShaderStageFlagBits::eClosestHitKHR; + } + if (rayAnyHit) { + return vk::ShaderStageFlagBits::eAnyHitKHR; + } + if (rayMiss) { + return vk::ShaderStageFlagBits::eMissKHR; + } + if (rayIntersection) { + return vk::ShaderStageFlagBits::eIntersectionKHR; + } + if (callable) { + return vk::ShaderStageFlagBits::eCallableKHR; + } + if (task) { + return vk::ShaderStageFlagBits::eTaskEXT; + } + if (mesh) { + return vk::ShaderStageFlagBits::eMeshEXT; + } + if (tessellationControl) { + return vk::ShaderStageFlagBits::eTessellationControl; + } + if (tesselationEvaluation) { + return vk::ShaderStageFlagBits::eTessellationEvaluation; + } + if (geometry) { + return vk::ShaderStageFlagBits::eGeometry; + } + return vk::ShaderStageFlagBits(); + } + + [[nodiscard]] constexpr struct ShaderTypeBitsIterator begin() const noexcept; + [[nodiscard]] constexpr struct ShaderTypeBitsIterator end() const noexcept; + + static constexpr ShaderTypeBits make(vk::ShaderStageFlagBits type) { + ShaderTypeBits bits; + bits.set(type, true); + return bits; + } +}; + +struct ShaderTypeBitsIterator +{ + using value_type = vk::ShaderStageFlagBits; + + ShaderTypeBits value; + unsigned pos = 16; + + ShaderTypeBitsIterator() = default; + explicit constexpr ShaderTypeBitsIterator(ShaderTypeBits value_) noexcept : value(value_), pos(0) + { + for (; pos < 16; ++pos) + { + if (value.bits & (1 << pos)) + { + break; + } + } + } + + constexpr bool operator==(const ShaderTypeBitsIterator& other) const noexcept { + return pos == other.pos; + } + constexpr bool operator!=(const ShaderTypeBitsIterator& other) const noexcept { + return !(*this == other); + } + + value_type operator*() const noexcept + { + MIJIN_ASSERT(pos < 16, "Attempt to dereference invalid iterator."); + return ShaderTypeBits{.bits = static_cast(1 << pos)}.getFirst(); + } + + ShaderTypeBitsIterator& operator++() noexcept + { + ++pos; + for (; pos < 16; ++pos) + { + if (value.bits & (1 << pos)) + { + break; + } + } + return *this; + } +}; + +#if MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG +#pragma GCC diagnostic pop +#endif + +enum class ShaderVariableBaseType +{ + NONE = 0, + SIMPLE = 1, + IMAGE = 2, + STRUCT = 3, + MATRIX = 4, + ACCELERATION_STRUCTURE = 5 +}; + +struct ShaderVariableSimpleType +{ + vk::Format format; +}; + +struct ShaderVariableImageType +{ + ImageDim dimensions; + vk::Format format; +}; + +struct ShaderVariableStructMember; +struct ShaderVariableStructType +{ + std::vector members; + + ShaderVariableStructType(); + ~ShaderVariableStructType(); +}; + +enum class ShaderVariableMatrixType +{ + UNDEFINED = 0, + MAT2 = 1, + MAT3 = 2, + MAT4 = 3 +}; + +struct ShaderVariableType +{ + ShaderVariableBaseType baseType = ShaderVariableBaseType::NONE; + ShaderVariableImageType image = {}; + ShaderVariableSimpleType simple = {}; + ShaderVariableStructType struct_ = {}; + ShaderVariableMatrixType matrixType = ShaderVariableMatrixType::UNDEFINED; + unsigned arraySize = 1; + bool dynamicArraySize : 1 = false; + + static ShaderVariableType fromVkFormat(vk::Format format) + { + return ShaderVariableType{ + .baseType = ShaderVariableBaseType::SIMPLE, + .simple = { + .format = format + } + }; + } + + inline bool operator==(const ShaderVariableType& other) const; + inline bool operator!=(const ShaderVariableType& other) const { + return !(*this == other); + } + + std::size_t calcHash(std::size_t appendTo = 0) const; +}; + +struct ShaderVariableStructMember +{ + std::string name; + std::size_t offset; + ShaderVariableType type; + unsigned semantic = UNSPECIFIED_INDEX; + unsigned semanticIdx = 0; + + // inline ~ShaderVariableStructMember(); +}; + +struct ShaderVariableOffsetComparator +{ + constexpr bool operator()(const ShaderVariableStructMember& first, const ShaderVariableStructMember& second) const + { + return first.offset < second.offset; + } +}; + +struct ShaderVariable +{ + ShaderVariableType type; + vk::DescriptorType descriptorType; + unsigned binding = UNSPECIFIED_INDEX; + unsigned semantic = UNSPECIFIED_INDEX; + unsigned semanticIndex = 0; + std::string name; + + void verifyCompatible(const ShaderVariable& other) const; + std::size_t calcHash(std::size_t appendTo = 0) const; +}; + +struct ShaderPushConstantBlock +{ + ShaderVariableType type; + std::uint32_t offset = 0; + + inline operator bool() const { + return type.baseType != ShaderVariableBaseType::NONE; + } + inline bool operator!() const { + return type.baseType == ShaderVariableBaseType::NONE; + } +}; + +struct ShaderVariableFindResult +{ + unsigned setIndex = 0; + unsigned bindIndex = 0; +}; + +struct ShaderVariableSet +{ + ShaderTypeBits usedInStages; // primarily for use in pipelines which may have multiple stages (in contrast to shaders and fragments) + unsigned setIndex = UNSPECIFIED_INDEX; + std::vector variables; + + [[nodiscard]] bool find(std::string_view varName, ShaderVariableFindResult& outResult) const noexcept; + [[nodiscard]] bool find(unsigned semantic, unsigned semanticIdx, ShaderVariableFindResult& outResult) const noexcept; + [[nodiscard]] bool find(unsigned semantic, ShaderVariableFindResult& outResult) const noexcept { + return find(semantic, 0, outResult); + } + + [[nodiscard]] const ShaderVariable& getVariableAtBinding(unsigned bindingIdx) const; + [[nodiscard]] const ShaderVariable* getVariableAtBindingOpt(unsigned bindingIdx) const; + [[nodiscard]] const ShaderVariable* getVariableAtSemanticOpt(unsigned semantic, unsigned semanticIdx) const; + + std::size_t calcHash(std::size_t appendTo = 0) const; +}; + +struct ShaderAttribute +{ + vk::ShaderStageFlagBits stage; + ShaderVariableType type; + unsigned location = UNSPECIFIED_INDEX; + unsigned semantic = UNSPECIFIED_INDEX; + unsigned semanticIndex = 0; + std::string name; +}; + +struct ShaderSpecializationConstant +{ + ShaderVariableType type; + unsigned id; +}; + +struct DescriptorSetMeta +{ + vk::DescriptorSetLayoutCreateFlags flags = {}; + std::vector bindings; + std::vector bindingFlags; + std::vector descriptorTypes; + + [[nodiscard]] ObjectPtr createDescriptorSetLayout(Device& device) const; +}; + +struct PipelineAndDescriptorSetLayouts +{ + std::vector> descriptorSetLayouts; + ObjectPtr pipelineLayout; + + [[nodiscard]] std::vector> createDescriptorSets(DescriptorPool& pool) const; + [[nodiscard]] ObjectPtr createDescriptorSet(DescriptorPool& pool, unsigned setIdx) const; +}; + +struct PipelineLayoutMeta +{ + std::vector descriptorSets; + vk::PushConstantRange pushConstantRange; + + [[nodiscard]] PipelineAndDescriptorSetLayouts createPipelineLayout(Device& device) const; +}; + +struct NamedVertexInput +{ + struct Attribute + { + unsigned binding; + unsigned offset; + }; + std::vector bindings; + std::unordered_map attributes; +}; + +struct GenerateDescriptorSetLayoutArgs +{ + std::unordered_map descriptorCounts = {}; + vk::DescriptorSetLayoutCreateFlags flags = {}; +}; + +struct GeneratePipelineLayoutArgs +{ + std::unordered_map descriptorSets; +}; + +struct ShaderMeta +{ + static constexpr const int STRUCT_VERSION = 1; + + std::vector interfaceVariableSets; + std::vector inputAttributes; + std::vector outputAttributes; + ShaderTypeBits stages; + ShaderTypeBits pushConstantStages; + ShaderPushConstantBlock pushConstantBlock; + unsigned localSizeX = 0; + unsigned localSizeY = 0; + unsigned localSizeZ = 0; +private: + mutable std::size_t hash = 0; +public: + [[nodiscard]] inline ShaderVariableSet& getOrCreateInterfaceVariableSet(unsigned setIdx); + + void extend(ShaderMeta other); + [[nodiscard]] bool findInterfaceVariable(std::string_view varName, ShaderVariableFindResult& outResult) const noexcept; + [[nodiscard]] bool findInterfaceVariable(unsigned semantic, unsigned semanticIdx, ShaderVariableFindResult& outResult) const noexcept; + [[nodiscard]] bool findInterfaceVariable(unsigned semantic, ShaderVariableFindResult& outResult) const noexcept { + return findInterfaceVariable(semantic, 0, outResult); + } + [[nodiscard]] const ShaderVariableSet& getInterfaceVariableSet(unsigned setIdx) const; + [[nodiscard]] const ShaderVariableSet* getInterfaceVariableSetOpt(unsigned setIdx) const; + [[nodiscard]] const ShaderVariableType& getInterfaceVariableType(unsigned setIdx, unsigned bindingIdx) const; + [[nodiscard]] inline const ShaderVariableType& getInterfaceVariableType(const ShaderVariableFindResult& findResult) const { + return getInterfaceVariableType(findResult.setIndex, findResult.bindIndex); + } + + [[nodiscard]] VertexInput generateVertexInput(const NamedVertexInput& namedInput) const noexcept; + [[nodiscard]] VertexInput generateVertexInputFromLayout(const struct VertexLayout& layout) const noexcept; + [[nodiscard]] DescriptorSetMeta generateDescriptorSetLayout(const ShaderVariableSet& set, const GenerateDescriptorSetLayoutArgs& args = {}) const; + [[nodiscard]] inline DescriptorSetMeta generateDescriptorSetLayout(unsigned setIdx, const GenerateDescriptorSetLayoutArgs& args = {}) const { + return generateDescriptorSetLayout(getInterfaceVariableSet(setIdx), args); + } + [[nodiscard]] PipelineLayoutMeta generatePipelineLayout(const GeneratePipelineLayoutArgs& args = {}) const; + [[nodiscard]] bool empty() const; + + std::size_t getHash() const; + + void extendPushConstant(ShaderPushConstantBlock pushConstantBlock, ShaderTypeBits stages); + void addInputAttribute(ShaderAttribute attribute); + void addOutputAttribute(ShaderAttribute attribute); +}; + +// ShaderVariableType::~ShaderVariableType() // NOLINT clang-tidy just doesn't understand my genius +// { +// // defined here since +// } + +// ShaderVariableStructMember::~ShaderVariableStructMember() {} + +[[nodiscard]] unsigned calcShaderTypeSize(const ShaderVariableType& type, bool ignoreArraySize = false) noexcept; + +constexpr bool ShaderTypeBits::get(vk::ShaderStageFlagBits shaderType) const +{ + switch(shaderType) + { + case vk::ShaderStageFlagBits::eCompute: + return compute; + case vk::ShaderStageFlagBits::eVertex: + return vertex; + case vk::ShaderStageFlagBits::eFragment: + return fragment; + case vk::ShaderStageFlagBits::eRaygenKHR: + return rayGeneration; + case vk::ShaderStageFlagBits::eClosestHitKHR: + return rayClosestHit; + case vk::ShaderStageFlagBits::eAnyHitKHR: + return rayAnyHit; + case vk::ShaderStageFlagBits::eMissKHR: + return rayMiss; + case vk::ShaderStageFlagBits::eIntersectionKHR: + return rayIntersection; + case vk::ShaderStageFlagBits::eCallableKHR: + return callable; + case vk::ShaderStageFlagBits::eTaskEXT: + return task; + case vk::ShaderStageFlagBits::eMeshEXT: + return mesh; + case vk::ShaderStageFlagBits::eTessellationControl: + return tessellationControl; + case vk::ShaderStageFlagBits::eTessellationEvaluation: + return tesselationEvaluation; + case vk::ShaderStageFlagBits::eGeometry: + return geometry; + case vk::ShaderStageFlagBits::eAllGraphics: + case vk::ShaderStageFlagBits::eAll: + case vk::ShaderStageFlagBits::eSubpassShadingHUAWEI: + case vk::ShaderStageFlagBits::eClusterCullingHUAWEI: + break; // let it fail + } + logAndDie("Invalid shader type in ShaderTypeBits::get()"); +} + +constexpr void ShaderTypeBits::set(vk::ShaderStageFlagBits shaderType, bool value) +{ + switch(shaderType) + { + case vk::ShaderStageFlagBits::eCompute: + compute = value; + return; + case vk::ShaderStageFlagBits::eVertex: + vertex = value; + return; + case vk::ShaderStageFlagBits::eFragment: + fragment = value; + return; + case vk::ShaderStageFlagBits::eRaygenKHR: + rayGeneration = value; + return; + case vk::ShaderStageFlagBits::eClosestHitKHR: + rayClosestHit = value; + return; + case vk::ShaderStageFlagBits::eAnyHitKHR: + rayAnyHit = value; + return; + case vk::ShaderStageFlagBits::eMissKHR: + rayMiss = value; + return; + case vk::ShaderStageFlagBits::eIntersectionKHR: + rayIntersection = value; + return; + case vk::ShaderStageFlagBits::eCallableKHR: + callable = value; + return; + case vk::ShaderStageFlagBits::eTaskEXT: + task = value; + return; + case vk::ShaderStageFlagBits::eMeshEXT: + mesh = value; + return; + case vk::ShaderStageFlagBits::eTessellationControl: + tessellationControl = value; + return; + case vk::ShaderStageFlagBits::eTessellationEvaluation: + tesselationEvaluation = value; + return; + case vk::ShaderStageFlagBits::eGeometry: + geometry = value; + return; + case vk::ShaderStageFlagBits::eAllGraphics: + case vk::ShaderStageFlagBits::eAll: + case vk::ShaderStageFlagBits::eSubpassShadingHUAWEI: + case vk::ShaderStageFlagBits::eClusterCullingHUAWEI: + break; // let it fail + } + logAndDie("Invalid shader type in ShaderTypeBits::set()"); +} + +constexpr ShaderTypeBitsIterator ShaderTypeBits::begin() const noexcept +{ + return ShaderTypeBitsIterator(*this); +} + +constexpr ShaderTypeBitsIterator ShaderTypeBits::end() const noexcept +{ + return ShaderTypeBitsIterator(); +} + +inline bool ShaderVariableType::operator==(const ShaderVariableType& other) const +{ + if (baseType != other.baseType) { + return false; + } + if (dynamicArraySize != other.dynamicArraySize) { + return false; + } + if (!dynamicArraySize && (arraySize != other.arraySize)) { + return false; + } + switch (baseType) + { + case ShaderVariableBaseType::NONE: + return true; + case ShaderVariableBaseType::SIMPLE: + return simple.format == other.simple.format; + case ShaderVariableBaseType::IMAGE: + return image.format == other.image.format + && image.dimensions == other.image.dimensions; + case ShaderVariableBaseType::MATRIX: + return matrixType == other.matrixType; + case ShaderVariableBaseType::ACCELERATION_STRUCTURE: + return true; + case ShaderVariableBaseType::STRUCT: + if (struct_.members.size() != other.struct_.members.size()) { + return false; + } + + assert(std::is_sorted(struct_.members.begin(), struct_.members.end(), ShaderVariableOffsetComparator())); + assert(std::is_sorted(other.struct_.members.begin(), other.struct_.members.end(), ShaderVariableOffsetComparator())); + for (std::size_t idx = 0; idx < struct_.members.size(); ++idx) + { + if (struct_.members[idx].offset != other.struct_.members[idx].offset) { + return false; + } + if (struct_.members[idx].type != other.struct_.members[idx].type) { + return false; + } + // name doesn't really matter, does it? + } + return true; + } + logAndDie("Unhandled base type in ShaderVariableType::operator==()!"); +} + +inline ShaderVariableSet& getOrCreateSet(std::vector& sets, unsigned setIdx) +{ + for (ShaderVariableSet& set : sets) + { + if (set.setIndex == setIdx) { + return set; + } + } + ShaderVariableSet& newSet = sets.emplace_back(); + newSet.setIndex = setIdx; + return newSet; +} + +inline ShaderVariableSet& ShaderMeta::getOrCreateInterfaceVariableSet(unsigned setIdx) +{ + return getOrCreateSet(interfaceVariableSets, setIdx); +} +} // namespace iwa + +template<> +struct fmt::formatter +{ + constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) + { + auto it = ctx.begin(); + auto end = ctx.end(); + + if (it != end && *it != '}') throw format_error("invalid format"); + + return it; + } + + template + auto format(const iwa::ShaderVariableType& varType, TContext& ctx) const -> decltype(ctx.out()) + { + auto it = ctx.out(); + it = fmt::format_to(it, "["); + switch (varType.baseType) + { + case iwa::ShaderVariableBaseType::NONE: + it = fmt::format_to(it, "]"); + return it; + case iwa::ShaderVariableBaseType::SIMPLE: + it = fmt::format_to(it, "{}", ""); // magic_enum::enum_name(varType.simple.format)); + break; + case iwa::ShaderVariableBaseType::IMAGE: + it = fmt::format_to(it, "", "", // magic_enum::enum_name(varType.image.dimensions), + ""); // magic_enum::enum_name(varType.image.format)); + break; + case iwa::ShaderVariableBaseType::MATRIX: + it = fmt::format_to(it, "{}", magic_enum::enum_name(varType.matrixType)); + break; + case iwa::ShaderVariableBaseType::ACCELERATION_STRUCTURE: + it = fmt::format_to(it, "acceleration structure"); + break; + case iwa::ShaderVariableBaseType::STRUCT: + it = fmt::format_to(it, ""); + break; + } + if (varType.dynamicArraySize) + { + it = fmt::format_to(it, "[]"); + } else if (varType.arraySize > 0) + { + it = fmt::format_to(it, "[{}]", varType.arraySize); + } + it = fmt::format_to(it, "]"); + return it; + } +}; + +#endif // !defined(IWA_UTIL_SHADER_META_HPP_INCLUDED) diff --git a/include/iwa/util/task_runner.hpp b/include/iwa/util/task_runner.hpp new file mode 100644 index 0000000..01753f5 --- /dev/null +++ b/include/iwa/util/task_runner.hpp @@ -0,0 +1,60 @@ + +#pragma once + +#if !defined(IWA_UTIL_TASK_RUNNER_HPP_INCLUDED) +#define IWA_UTIL_TASK_RUNNER_HPP_INCLUDED + +#include + +namespace iwa +{ +template +class MixinTaskRunner +{ +private: + std::vector mTasks; +public: + MixinTaskRunner() noexcept; + + ~MixinTaskRunner() noexcept + { + for (const mijin::TaskHandle& handle : mTasks) + { + handle.cancel(); + } + } +public: + template + mijin::FuturePtr addTask(mijin::TaskBase&& task, mijin::TaskHandle* outHandle = nullptr) noexcept + { + mijin::TaskLoop& loop = static_cast(this)->getTaskLoop(); + mijin::TaskHandle handle; + mijin::FuturePtr result = loop.addTask(std::move(task), &handle); + if (outHandle != nullptr) + { + *outHandle = handle; + } + mTasks.push_back(std::move(handle)); + return result; + } +}; + +template +MixinTaskRunner::MixinTaskRunner() noexcept +{ + // addTask([&]() -> mijin::Task<> + // { + // while (true) + // { + // co_await mijin::c_suspend(); + // auto newEnd = std::remove_if(mTasks.begin(), mTasks.end(), [](const mijin::TaskHandle& handle) + // { + // return !handle.isValid(); + // }); + // mTasks.erase(newEnd, mTasks.end()); + // } + // }()); +} +} // namespace iwa + +#endif // !defined(IWA_UTIL_TASK_RUNNER_HPP_INCLUDED) diff --git a/include/iwa/util/texture_atlas.hpp b/include/iwa/util/texture_atlas.hpp new file mode 100644 index 0000000..460a040 --- /dev/null +++ b/include/iwa/util/texture_atlas.hpp @@ -0,0 +1,104 @@ + +#pragma once + +#if !defined(IWA_UTIL_TEXTURE_ATLAS_HPP_INCLUDED) +#define IWA_UTIL_TEXTURE_ATLAS_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include "iwa/image.hpp" +#include "iwa/object.hpp" +#include "iwa/vkwrapper.hpp" + +namespace iwa +{ +struct TextureAtlasCreationArgs +{ + vk::Extent2D layerSize; +}; + +struct TextureSlotCreationArgs +{ + vk::Rect2D usedSpace; + unsigned layer; + glm::vec2 uvOffset; + glm::vec2 uvScale; +}; + +struct AtlasedImageCreationArgs +{ + vk::Extent2D size; + unsigned initialLayers = 1; + vk::Format format; + unsigned mipLevels = 1; + vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eSampled; +}; + +struct TextureAtlasLayer +{ + std::vector freeSpaces; +}; + +class TextureSlot : public Object +{ +private: + vk::Rect2D mUsedSpace; + unsigned mLayer; + glm::vec2 mUvOffset; + glm::vec2 mUvScale; +public: + TextureSlot(ObjectPtr owner, const TextureSlotCreationArgs& args); + + [[nodiscard]] const vk::Rect2D& getUsedSpace() const noexcept { return mUsedSpace; } + [[nodiscard]] unsigned getLayer() const noexcept { return mLayer; } + [[nodiscard]] const glm::vec2& getUvOffset() const noexcept { return mUvOffset; } + [[nodiscard]] const glm::vec2& getUvScale() const noexcept { return mUvScale; } +}; + +class TextureAtlas : public Object +{ +private: + std::vector mLayers; + vk::Extent2D mLayerSize; +public: + TextureAtlas(ObjectPtr<> owner, const TextureAtlasCreationArgs& args); + explicit TextureAtlas(const TextureAtlasCreationArgs& args) : TextureAtlas(nullptr, args) {} + + [[nodiscard]] const vk::Extent2D& getLayerSize() const noexcept { return mLayerSize; } + [[nodiscard]] ObjectPtr allocateSlot(vk::Extent2D slotSize); +}; + +class AtlasedImage : public Object +{ +private: + ObjectPtr mAtlas; + ObjectPtr mImage; + ObjectPtr mImageView; + mutable mijin::TaskMutex mImageMutex; + vk::Format mFormat; + unsigned mMipLevels; + vk::ImageUsageFlags mUsage; +public: + AtlasedImage(ObjectPtr owner, const AtlasedImageCreationArgs& args); + + [[nodiscard]] const ObjectPtr& getImage() const noexcept { return mImage; } + [[nodiscard]] const ObjectPtr& getImageView() const noexcept { return mImageView; } + + mijin::Task> c_allocateSlot(vk::Extent2D slotSize); + mijin::Task<> c_upload(const TextureSlot& slot, const class Bitmap& bitmap) const noexcept; + mijin::Task<> c_upload(const TextureSlot& slot, const void* data, std::size_t bytes, const vk::Extent2D& bufferImageSize) const noexcept; + mijin::Task<> c_blit(const TextureSlot& slot, Image& srcImage) const noexcept; + mijin::Task<> c_blit(const TextureSlot& slot, const class Bitmap& bitmap) const noexcept; + mijin::Task<> c_copy(const TextureSlot& slot, Image& srcImage) const noexcept; +private: + ObjectPtr allocateImage(unsigned layers); + +public: + mijin::Signal<> imageRecreated; +}; +} // namespace iwa + +#endif // !defined(IWA_UTIL_TEXTURE_ATLAS_HPP_INCLUDED) diff --git a/include/iwa/util/vertex_attribute_semantic.gen.hpp.jinja b/include/iwa/util/vertex_attribute_semantic.gen.hpp.jinja new file mode 100644 index 0000000..47a2cbe --- /dev/null +++ b/include/iwa/util/vertex_attribute_semantic.gen.hpp.jinja @@ -0,0 +1,13 @@ +{% import 'enums.jinja' as enums -%} +// auto-generated using Jinja, do not edit! +#pragma once + +#if !defined(IWA_UTIL_VERTEX_ATTRIBUTE_SEMANTIC_GEN_HPP_INCLUDED) +#define IWA_UTIL_VERTEX_ATTRIBUTE_SEMANTIC_GEN_HPP_INCLUDED + +namespace iwa +{ +{{ enums.cpp_enum('vertex_input_semantic') }} +} + +#endif // defined(IWA_UTIL_VERTEX_ATTRIBUTE_SEMANTIC_GEN_HPP_INCLUDED) diff --git a/include/iwa/util/vertex_layout.hpp b/include/iwa/util/vertex_layout.hpp new file mode 100644 index 0000000..9ba612f --- /dev/null +++ b/include/iwa/util/vertex_layout.hpp @@ -0,0 +1,42 @@ + +#pragma once + +#if !defined(IWA_UTIL_VERTEX_LAYOUT_HPP_INCLUDED) +#define IWA_UTIL_VERTEX_LAYOUT_HPP_INCLUDED + +#include +#include "iwa/vkwrapper.hpp" +#include "iwa/util/vertex_attribute_semantic.gen.hpp" +#include "iwa/util/vkutil.hpp" + +#define IWA_VERTEX_ATTRIB_IDX(struct_, member_, semantic_, idx_) \ +iwa::VertexAttribute \ +{ \ + .semantic = iwa::VertexAttributeSemantic::semantic_, \ + .semanticIdx = idx_, \ + .offset = offsetof(struct_, member_), \ + .format = iwa::vkMemberFormat(&struct_::member_) \ +} +#define IWA_VERTEX_ATTRIB(struct_, member_, semantic_) IWA_VERTEX_ATTRIB_IDX(struct_, member_, semantic_, 0) + +namespace iwa +{ +struct VertexAttribute +{ + VertexAttributeSemantic semantic = VertexAttributeSemantic::CUSTOM; + unsigned semanticIdx = 0; + unsigned offset = 0; + vk::Format format = vk::Format::eUndefined; +}; + +struct VertexLayout +{ + std::vector attributes; + unsigned stride = 0; + + [[nodiscard]] mijin::Optional findAttribute(VertexAttributeSemantic semantic, unsigned semanticIdx = 0) noexcept; + [[nodiscard]] mijin::Optional findAttribute(VertexAttributeSemantic semantic, unsigned semanticIdx = 0) const noexcept; +}; +} // namespace iwa + +#endif // !defined(_VERTEX_LAYOUT_HPP_INCLUDED) diff --git a/include/iwa/util/vkutil.hpp b/include/iwa/util/vkutil.hpp new file mode 100644 index 0000000..5e1249d --- /dev/null +++ b/include/iwa/util/vkutil.hpp @@ -0,0 +1,210 @@ + +#pragma once + +#if !defined(IWA_UTIL_VKUTIL_HPP_INCLUDED) +#define IWA_UTIL_VKUTIL_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include "iwa/vkwrapper.hpp" + +namespace iwa +{ +inline constexpr std::array DEPTH_FORMATS = { + vk::Format::eD24UnormS8Uint, + vk::Format::eD32Sfloat, + vk::Format::eD32SfloatS8Uint, + vk::Format::eD16Unorm, + vk::Format::eD16UnormS8Uint, + vk::Format::eX8D24UnormPack32 +}; + +inline constexpr std::array STENCIL_FORMATS = { + vk::Format::eS8Uint, + vk::Format::eD24UnormS8Uint, + vk::Format::eD32SfloatS8Uint, + vk::Format::eD16UnormS8Uint +}; + +namespace impl +{ +template +constexpr vk::Format getVkFormat() noexcept +{ + if constexpr (std::is_same_v) { + return vk::Format::eR8Uint; + } + else if constexpr (std::is_same_v) { + return vk::Format::eR8Sint; + } + else if constexpr (std::is_same_v) { + return vk::Format::eR16Uint; + } + else if constexpr (std::is_same_v) { + return vk::Format::eR16Sint; + } + else if constexpr (std::is_same_v) { + return vk::Format::eR32Uint; + } + else if constexpr (std::is_same_v) { + return vk::Format::eR32Sint; + } + else if constexpr (std::is_same_v) { + return vk::Format::eR64Uint; + } + else if constexpr (std::is_same_v) { + return vk::Format::eR64Sint; + } + else if constexpr (std::is_same_v) { + return vk::Format::eR32Sfloat; + } + else if constexpr (std::is_same_v) { + return vk::Format::eR64Sfloat; + } + else if constexpr (std::is_same_v) { + return vk::Format::eR32G32Sfloat; + } + else if constexpr (std::is_same_v) { + return vk::Format::eR32G32B32Sfloat; + } + else if constexpr (std::is_same_v) { + return vk::Format::eR32G32B32A32Sfloat; + } + else if constexpr (std::is_same_v) { + return vk::Format::eR32G32Sint; + } + else if constexpr (std::is_same_v) { + return vk::Format::eR32G32B32Sint; + } + else if constexpr (std::is_same_v) { + return vk::Format::eR32G32B32A32Sint; + } + else { + static_assert(mijin::always_false_v, "No Vulkan format for that type."); + } +} + +template +constexpr vk::IndexType getVkIndexType() noexcept +{ + if constexpr (std::is_same_v) { + return vk::IndexType::eUint8EXT; + } + else if constexpr (std::is_same_v) { + return vk::IndexType::eUint16; + } + else if constexpr (std::is_same_v) { + return vk::IndexType::eUint32; + } + else { + static_assert(mijin::always_false_v, "No Vulkan index type for that type."); + } +} +} // namespace impl + +template +static constexpr vk::Format vk_format_v = impl::getVkFormat(); + +template +static constexpr vk::IndexType vk_index_type_v = impl::getVkIndexType(); + +template +static constexpr vk::Format vkMemberFormat(U T::*) noexcept { return vk_format_v; } + +[[nodiscard]] unsigned vkFormatSize(vk::Format format) noexcept; +[[nodiscard]] unsigned vkIndexTypeSize(vk::IndexType indexType) noexcept; +[[nodiscard]] bool isDepthFormat(vk::Format format) noexcept; +[[nodiscard]] bool isStencilFormat(vk::Format format) noexcept; // NOLINT(readability-redundant-declaration) forward declared in image.hpp, but it wouldn't make sense to remove it from here + +template +std::optional vkEnumFromStringOpt(std::string_view string) noexcept +{ + std::string enumName; + enumName.reserve(string.size() + 1); + bool first = true; + for (const char chr : string) + { + if (first) + { + enumName.push_back(std::toupper(chr)); + first = false; + } + else { + enumName.push_back(chr); + } + } + return vk::from_string(enumName); +} + +template +TEnum vkEnumFromString(std::string_view string, const char* error = "Invalid enum value.") +{ + const std::optional value = vkEnumFromStringOpt(string); + if (!value.has_value()) { + throw std::runtime_error(error); + } + return *value; +} + +template +[[nodiscard]] const T* findInNextChain(const void* pNext, vk::StructureType sType = T().sType) noexcept +{ + while (pNext) + { + const vk::BaseInStructure* inStruct = static_cast(pNext); + if (inStruct->sType == sType) + { + return static_cast(pNext); + } + pNext = inStruct->pNext; + } + return nullptr; +} + +// TODO +// std::size_t calcVkStructHash(const void* structure, std::size_t appendTo = 0); + +[[nodiscard]] vk::SampleCountFlagBits samplesToVk(unsigned samples) noexcept; +[[nodiscard]] vk::Format detectDepthBufferFormat(class Device& device, unsigned samples = 1) noexcept; +[[nodiscard]] std::vector detectSupportedSampleCounts(class Device& device) noexcept; + +template +vk::DeviceSize calcVkUniformStride(class Device& device) +{ + vk::DeviceSize stride = mijin::alignUp(sizeof(T), alignof(T)); + return mijin::alignUp(stride, mijin::delayEvaluation(device).getDeviceInfo().properties.limits.minUniformBufferOffsetAlignment); +} + +template +vk::DeviceSize calcVkStorageBufferStride(class Device& device) +{ + vk::DeviceSize stride = mijin::alignUp(sizeof(T), alignof(T)); + return mijin::alignUp(stride, mijin::delayEvaluation(device).getDeviceInfo().properties.limits.minStorageBufferOffsetAlignment); +} + +[[nodiscard]] inline bool vkIsSrgbFormat(vk::Format format) noexcept +{ + switch (format) + { + case vk::Format::eR8Srgb: + case vk::Format::eR8G8Srgb: + case vk::Format::eR8G8B8Srgb: + case vk::Format::eR8G8B8A8Srgb: + case vk::Format::eB8G8R8Srgb: + case vk::Format::eB8G8R8A8Srgb: + case vk::Format::eA8B8G8R8SrgbPack32: + // TODO: all the weird compressed formats, I don't need them yet + return true; + default: + return false; + } +} + +} // namespace iwa + +#endif // !defined(IWA_UTIL_VKUTIL_HPP_INCLUDED) diff --git a/include/iwa/vkwrapper.hpp b/include/iwa/vkwrapper.hpp new file mode 100644 index 0000000..fdfef56 --- /dev/null +++ b/include/iwa/vkwrapper.hpp @@ -0,0 +1,28 @@ + +#pragma once + +#ifndef IWA_VULKAN_VKWRAPPER_HPP_INCLUDED +#define IWA_VULKAN_VKWRAPPER_HPP_INCLUDED + +// disable what we don't need +#define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS +#define VULKAN_HPP_NO_SMART_HANDLE +#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 +#include "vulkan/vulkan.hpp" + +namespace iwa +{ +template +class MixinVulkanObject +{ +protected: + THandle mHandle; +protected: + explicit MixinVulkanObject(THandle handle = VK_NULL_HANDLE) noexcept : mHandle(handle) {} +public: + [[nodiscard]] const THandle& getVkHandle() const noexcept { return mHandle; } + operator THandle() const noexcept { return mHandle; } +}; +} + +#endif // IWA_VULKAN_VKWRAPPER_HPP_INCLUDED diff --git a/include/iwa/window.hpp b/include/iwa/window.hpp new file mode 100644 index 0000000..1ab05ac --- /dev/null +++ b/include/iwa/window.hpp @@ -0,0 +1,86 @@ + +#pragma once + +#if !defined(IWA_WINDOW_HPP_INCLUDED) +#define IWA_WINDOW_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include "iwa/input.hpp" +#include "iwa/object.hpp" +#include "iwa/vkwrapper.hpp" + +namespace iwa +{ +enum class MouseMode +{ + NORMAL = 0, + CAPTURED = 1 +}; + +struct WindowCreationFlags : mijin::BitFlags +{ + bool hidden : 1 = false; + bool resizable : 1 = true; + bool borderless : 1 = false; + bool alwayOnTop : 1 = false; + bool skipTaskbar : 1 = false; +}; + +struct WindowBorder +{ + int left; + int right; + int top; + int bottom; +}; + +struct WindowCreationArgs +{ + std::string title = "Iwa Window"; + WindowCreationFlags flags; + int width = 1280; + int height = 720; +}; + +class Window : public Object +{ +private: + SDL_Window* mHandle = nullptr; + vk::SurfaceKHR mSurface = VK_NULL_HANDLE; +public: + Window(ObjectPtr owner, const WindowCreationArgs& args); + ~Window() noexcept override; + + [[nodiscard]] bool isVisible() const noexcept; + void setVisible(bool visible) noexcept; + [[nodiscard]] std::pair getSize() const noexcept; + void setSize(int width, int height) noexcept; + [[nodiscard]] std::pair getPosition() const noexcept; + void setPosition(int xPos, int yPos) noexcept; + [[nodiscard]] WindowBorder getWindowBorder() const noexcept; + + [[nodiscard]] bool isFocused() const noexcept; + void focus() noexcept; + void setMouseMode(MouseMode mouseMode) noexcept; + void setModalFor(mijin::Optional parent) noexcept; + [[nodiscard]] SDL_Window* getSDLWindow() const noexcept { return mHandle; } + [[nodiscard]] vk::SurfaceKHR getVkSurface() const noexcept { return mSurface; } + + mijin::Signal keyChanged; + mijin::Signal mouseButtonChanged; + mijin::Signal mouseMoved; + mijin::Signal mouseScrolled; + mijin::Signal textEntered; + mijin::Signal<> focusGained; + mijin::Signal<> focusLost; + mijin::Signal<> mouseEntered; + mijin::Signal<> mouseLeft; + mijin::Signal<> closeRequested; +}; +} + +#endif //IWA_WINDOW_HPP_INCLUDED diff --git a/source/addon.cpp b/source/addon.cpp new file mode 100644 index 0000000..6b8c34b --- /dev/null +++ b/source/addon.cpp @@ -0,0 +1,26 @@ + +#include "iwa/addon.hpp" + +#include + +namespace iwa +{ +namespace +{ +std::vector& getAddonsVector() noexcept +{ + static std::vector addons; + return addons; +} +} + +Addon::Addon() +{ + getAddonsVector().push_back(this); +} + +std::span getAddons() noexcept +{ + return getAddonsVector(); +} +} // namespace iwa diff --git a/source/addons/imgui/SConscript b/source/addons/imgui/SConscript new file mode 100644 index 0000000..e2d2201 --- /dev/null +++ b/source/addons/imgui/SConscript @@ -0,0 +1,20 @@ + +Import('env') + +# Imgui +lib_imgui = env.Cook('imgui', backends = ['vulkan', 'sdl2'], git_ref = 'refs/tags/v1.90') + +src_files = Split(""" + fps_widget.cpp +""") +add_src_files = [env.File('addon.cpp')] + +lib_iwa_imgui = env.UnityStaticLibrary( + target = env['LIB_DIR'] + '/iwa_imgui', + source = src_files, + add_source = add_src_files, + dependencies = [env['lib_iwa'], lib_imgui] +) +env['lib_iwa_imgui'] = lib_iwa_imgui + +Return('env') diff --git a/source/addons/imgui/addon.cpp b/source/addons/imgui/addon.cpp new file mode 100644 index 0000000..ea2d9da --- /dev/null +++ b/source/addons/imgui/addon.cpp @@ -0,0 +1,299 @@ + +#include "iwa/addons/imgui/addon.hpp" + +#include +#include +#include + +namespace iwa +{ +namespace +{ +ImGuiAddon gImguiAddon; + +PFN_vkVoidFunction imguiLoaderCallback(const char* functionName, void* userData) +{ + return static_cast(userData)->getVkHandle().getProcAddr(functionName); +} + +ImGuiKey keyToImGui(const KeyCode keyCode) +{ + switch (keyCode) + { + // + case KeyCode::TAB: return ImGuiKey_Tab; + case KeyCode::LEFT: return ImGuiKey_LeftArrow; + case KeyCode::RIGHT: return ImGuiKey_RightArrow; + case KeyCode::UP: return ImGuiKey_UpArrow; + case KeyCode::DOWN: return ImGuiKey_DownArrow; + case KeyCode::PAGEUP: return ImGuiKey_PageUp; + case KeyCode::PAGEDOWN: return ImGuiKey_PageDown; + case KeyCode::HOME: return ImGuiKey_Home; + case KeyCode::END: return ImGuiKey_End; + case KeyCode::INSERT: return ImGuiKey_Insert; + case KeyCode::DELETE: return ImGuiKey_Delete; + case KeyCode::BACKSPACE: return ImGuiKey_Backspace; + case KeyCode::SPACE: return ImGuiKey_Space; + case KeyCode::RETURN: return ImGuiKey_Enter; + case KeyCode::ESCAPE: return ImGuiKey_Escape; + case KeyCode::QUOTE: return ImGuiKey_Apostrophe; + case KeyCode::COMMA: return ImGuiKey_Comma; + case KeyCode::MINUS: return ImGuiKey_Minus; + case KeyCode::PERIOD: return ImGuiKey_Period; + case KeyCode::SLASH: return ImGuiKey_Slash; + case KeyCode::SEMICOLON: return ImGuiKey_Semicolon; + case KeyCode::EQUALS: return ImGuiKey_Equal; + case KeyCode::LEFTBRACKET: return ImGuiKey_LeftBracket; + case KeyCode::BACKSLASH: return ImGuiKey_Backslash; + case KeyCode::RIGHTBRACKET: return ImGuiKey_RightBracket; + case KeyCode::BACKQUOTE: return ImGuiKey_GraveAccent; + case KeyCode::CAPSLOCK: return ImGuiKey_CapsLock; + case KeyCode::SCROLLLOCK: return ImGuiKey_ScrollLock; + case KeyCode::NUMLOCKCLEAR: return ImGuiKey_NumLock; + case KeyCode::PRINTSCREEN: return ImGuiKey_PrintScreen; + case KeyCode::PAUSE: return ImGuiKey_Pause; + case KeyCode::KP_0: return ImGuiKey_Keypad0; + case KeyCode::KP_1: return ImGuiKey_Keypad1; + case KeyCode::KP_2: return ImGuiKey_Keypad2; + case KeyCode::KP_3: return ImGuiKey_Keypad3; + case KeyCode::KP_4: return ImGuiKey_Keypad4; + case KeyCode::KP_5: return ImGuiKey_Keypad5; + case KeyCode::KP_6: return ImGuiKey_Keypad6; + case KeyCode::KP_7: return ImGuiKey_Keypad7; + case KeyCode::KP_8: return ImGuiKey_Keypad8; + case KeyCode::KP_9: return ImGuiKey_Keypad9; + case KeyCode::KP_PERIOD: return ImGuiKey_KeypadDecimal; + case KeyCode::KP_DIVIDE: return ImGuiKey_KeypadDivide; + case KeyCode::KP_MULTIPLY: return ImGuiKey_KeypadMultiply; + case KeyCode::KP_MINUS: return ImGuiKey_KeypadSubtract; + case KeyCode::KP_PLUS: return ImGuiKey_KeypadAdd; + case KeyCode::KP_ENTER: return ImGuiKey_KeypadEnter; + case KeyCode::KP_EQUALS: return ImGuiKey_KeypadEqual; + case KeyCode::LCTRL: return ImGuiKey_LeftCtrl; + case KeyCode::LSHIFT: return ImGuiKey_LeftShift; + case KeyCode::LALT: return ImGuiKey_LeftAlt; + case KeyCode::LGUI: return ImGuiKey_LeftSuper; + case KeyCode::RCTRL: return ImGuiKey_RightCtrl; + case KeyCode::RSHIFT: return ImGuiKey_RightShift; + case KeyCode::RALT: return ImGuiKey_RightAlt; + case KeyCode::RGUI: return ImGuiKey_RightSuper; + case KeyCode::APPLICATION: return ImGuiKey_Menu; + case KeyCode::_0: return ImGuiKey_0; + case KeyCode::_1: return ImGuiKey_1; + case KeyCode::_2: return ImGuiKey_2; + case KeyCode::_3: return ImGuiKey_3; + case KeyCode::_4: return ImGuiKey_4; + case KeyCode::_5: return ImGuiKey_5; + case KeyCode::_6: return ImGuiKey_6; + case KeyCode::_7: return ImGuiKey_7; + case KeyCode::_8: return ImGuiKey_8; + case KeyCode::_9: return ImGuiKey_9; + case KeyCode::A: return ImGuiKey_A; + case KeyCode::B: return ImGuiKey_B; + case KeyCode::C: return ImGuiKey_C; + case KeyCode::D: return ImGuiKey_D; + case KeyCode::E: return ImGuiKey_E; + case KeyCode::F: return ImGuiKey_F; + case KeyCode::G: return ImGuiKey_G; + case KeyCode::H: return ImGuiKey_H; + case KeyCode::I: return ImGuiKey_I; + case KeyCode::J: return ImGuiKey_J; + case KeyCode::K: return ImGuiKey_K; + case KeyCode::L: return ImGuiKey_L; + case KeyCode::M: return ImGuiKey_M; + case KeyCode::N: return ImGuiKey_N; + case KeyCode::O: return ImGuiKey_O; + case KeyCode::P: return ImGuiKey_P; + case KeyCode::Q: return ImGuiKey_Q; + case KeyCode::R: return ImGuiKey_R; + case KeyCode::S: return ImGuiKey_S; + case KeyCode::T: return ImGuiKey_T; + case KeyCode::U: return ImGuiKey_U; + case KeyCode::V: return ImGuiKey_V; + case KeyCode::W: return ImGuiKey_W; + case KeyCode::X: return ImGuiKey_X; + case KeyCode::Y: return ImGuiKey_Y; + case KeyCode::Z: return ImGuiKey_Z; + case KeyCode::F1: return ImGuiKey_F1; + case KeyCode::F2: return ImGuiKey_F2; + case KeyCode::F3: return ImGuiKey_F3; + case KeyCode::F4: return ImGuiKey_F4; + case KeyCode::F5: return ImGuiKey_F5; + case KeyCode::F6: return ImGuiKey_F6; + case KeyCode::F7: return ImGuiKey_F7; + case KeyCode::F8: return ImGuiKey_F8; + case KeyCode::F9: return ImGuiKey_F9; + case KeyCode::F10: return ImGuiKey_F10; + case KeyCode::F11: return ImGuiKey_F11; + case KeyCode::F12: return ImGuiKey_F12; + case KeyCode::F13: return ImGuiKey_F13; + case KeyCode::F14: return ImGuiKey_F14; + case KeyCode::F15: return ImGuiKey_F15; + case KeyCode::F16: return ImGuiKey_F16; + case KeyCode::F17: return ImGuiKey_F17; + case KeyCode::F18: return ImGuiKey_F18; + case KeyCode::F19: return ImGuiKey_F19; + case KeyCode::F20: return ImGuiKey_F20; + case KeyCode::F21: return ImGuiKey_F21; + case KeyCode::F22: return ImGuiKey_F22; + case KeyCode::F23: return ImGuiKey_F23; + case KeyCode::F24: return ImGuiKey_F24; + case KeyCode::AC_BACK: return ImGuiKey_AppBack; + case KeyCode::AC_FORWARD: return ImGuiKey_AppForward; + default: return ImGuiKey_None; + // + } +} + +ImGuiMouseButton mouseButtonToImGui(const MouseButton button) +{ + switch (button) + { + case MouseButton::LEFT: + return ImGuiMouseButton_Left; + case MouseButton::RIGHT: + return ImGuiMouseButton_Right; + case MouseButton::MIDDLE: + return ImGuiMouseButton_Middle; + case MouseButton::EXTRA_1: + return ImGuiMouseButton_Middle + 1; + case MouseButton::EXTRA_2: + return ImGuiMouseButton_Middle + 2; + } + return -1; +} +} + +void ImGuiAddon::init(const AddonInitArgs& args) +{ + (void) args; + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGui::StyleColorsDark(); +} + +void ImGuiAddon::cleanup() +{ + ImGui::DestroyContext(); +} + +mijin::Task<> ImGuiAddon::c_createResources(const ImguiCreateResourcesArgs& args) +{ + const unsigned QUEUED_FRAMES = 3; // TODO: is this okay? + const unsigned MAX_IMAGES = 256; + Device& device = *args.swapchain.getOwner(); + + mDescriptorPool = device.createChild(DescriptorPoolCreationArgs{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_IMAGES, + .poolSizes = { + vk::DescriptorPoolSize{.type = vk::DescriptorType::eCombinedImageSampler, .descriptorCount = MAX_IMAGES} + } + }); + registerObjectDestructionHandler(args.swapchain, [this, device = device.getPointer()] + { + // destroy the descriptor pool when the window is destroyed so we don't keep the device alive + device->getVkHandle().waitIdle(); + ImGui_ImplVulkan_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + mDescriptorPool = nullptr; + mWidgets.clear(); + }); + + if (!ImGui_ImplSDL2_InitForVulkan(args.swapchain.getWindow()->getSDLWindow())) { + throw std::runtime_error("Error initializing ImGui for SDL2."); + } + + ImGui_ImplVulkan_InitInfo initInfo{ + .Instance = device.getOwner()->getVkHandle(), + .PhysicalDevice = device.getVkPhysicalDevice(), + .Device = device.getVkHandle(), + .QueueFamily = device.getDeviceInfo().graphicsQueueFamily, + .Queue = device.getGraphicsQueue(), + .PipelineCache = VK_NULL_HANDLE, + .DescriptorPool = mDescriptorPool->getVkHandle(), + .Subpass = 0, + .MinImageCount = QUEUED_FRAMES, + .ImageCount = QUEUED_FRAMES, + .MSAASamples = VK_SAMPLE_COUNT_1_BIT, + .UseDynamicRendering = true, + .ColorAttachmentFormat = static_cast(args.format), + .Allocator = nullptr, + .CheckVkResultFn = nullptr, + }; + + const bool success = ImGui_ImplVulkan_LoadFunctions(imguiLoaderCallback, device.getOwner()) + && ImGui_ImplVulkan_Init(&initInfo, VK_NULL_HANDLE); + if (!success) { + throw std::runtime_error("Error initializing ImGui for Vulkan."); + } + + // setup input + args.swapchain.getWindow()->keyChanged.connect(*this, &ImGuiAddon::handleKeyChanged); + args.swapchain.getWindow()->mouseButtonChanged.connect(*this, &ImGuiAddon::handleMouseButtonChanged); + args.swapchain.getWindow()->mouseMoved.connect(*this, &ImGuiAddon::handleMouseMoved); + args.swapchain.getWindow()->mouseScrolled.connect(*this, &ImGuiAddon::handleMouseScrolled); + args.swapchain.getWindow()->textEntered.connect(*this, &ImGuiAddon::handleTextEntered); + + // first frame + beginFrame(); + co_return; +} + +void ImGuiAddon::renderFrame(vk::CommandBuffer cmdBuffer) +{ + for (const std::unique_ptr& widget : mWidgets) { + widget->draw(); + } + ImGui::Render(); + ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), cmdBuffer); + beginFrame(); +} + +void ImGuiAddon::removeWidget(ImGuiWidget* widget) +{ + auto it = std::ranges::find_if(mWidgets, [widget](const std::unique_ptr& widgetPtr) + { + return widgetPtr.get() == widget; + }); + mWidgets.erase(it); +} + +void ImGuiAddon::beginFrame() noexcept +{ + ImGui_ImplVulkan_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); +} + +void ImGuiAddon::handleKeyChanged(const KeyEvent& event) +{ + ImGui::GetIO().AddKeyEvent(keyToImGui(event.keyCode), event.down); +} + +void ImGuiAddon::handleMouseButtonChanged(const MouseButtonEvent& event) +{ + ImGui::GetIO().AddMouseButtonEvent(mouseButtonToImGui(event.button), event.down); +} + +void ImGuiAddon::handleMouseMoved(const MouseMoveEvent& event) +{ + ImGui::GetIO().AddMousePosEvent(static_cast(event.absoluteX), static_cast(event.absoluteY)); +} + +void ImGuiAddon::handleMouseScrolled(const MouseWheelEvent& event) +{ + ImGui::GetIO().AddMouseWheelEvent(static_cast(event.relativeX), static_cast(event.relativeY)); +} + +void ImGuiAddon::handleTextEntered(const TextInputEvent& event) +{ + ImGui::GetIO().AddInputCharactersUTF8(event.text.c_str()); +} + +ImGuiAddon& ImGuiAddon::get() noexcept +{ + return gImguiAddon; +} +} // namespace iwa diff --git a/source/addons/imgui/fps_widget.cpp b/source/addons/imgui/fps_widget.cpp new file mode 100644 index 0000000..8c1e616 --- /dev/null +++ b/source/addons/imgui/fps_widget.cpp @@ -0,0 +1,21 @@ + +#include "iwa/addons/imgui/fps_widget.hpp" + +#include +#include + +namespace iwa +{ +void ImGuiFpsWidget::draw() +{ + mFpsCalculator.tickFrame(); + + const std::string fpsText = fmt::format("{}", static_cast(mFpsCalculator.getFps())); + const ImVec2 textSize = ImGui::CalcTextSize(fpsText.c_str()); + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(ImVec2(textSize.x + 2 * ImGui::GetStyle().WindowPadding.x, 0)); + ImGui::Begin("#fps", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs); + ImGui::TextUnformatted(fpsText.c_str()); + ImGui::End(); +} +} // namespace iwa diff --git a/source/app/vulkan_application.cpp b/source/app/vulkan_application.cpp new file mode 100644 index 0000000..d7f5e0d --- /dev/null +++ b/source/app/vulkan_application.cpp @@ -0,0 +1,37 @@ + +#include "iwa/app/vulkan_application.hpp" + +#include +#include + +namespace iwa +{ +VulkanApplication::VulkanApplication(const ApplicationCreationArgs& args, ObjectPtr<> owner) : super_t(std::move(owner)) +{ + mInstance = Instance::create(args.instanceArgs); + mDevice = mInstance->createDevice(args.deviceArgs); + mMainWindow = mInstance->createWindow(args.mainWindowArgs); + + SwapchainCreationArgs swapchainCreationArgs = args.mainWindowSwapchainArgs; + MIJIN_ASSERT(swapchainCreationArgs.window == nullptr, "Main window swapchain args shouldn't contain a window."); + swapchainCreationArgs.window = mMainWindow; + mMainWindowSwapchain = mDevice->createChild(swapchainCreationArgs); + + if (!args.assetPath.empty()) + { + mInstance->getPrimaryFSAdapter().emplaceAdapter>( + /* root = */ fs::current_path() / "assets" + ); + } +} + +int VulkanApplication::execute(int argc, char** argv) // NOLINT +{ + (void) argc; + (void) argv; + mInstance->getMainTaskLoop().addTask(c_init()); + mInstance->getMainTaskLoop().runUntilDone(); + + return 0; +} +} // namespace iwa diff --git a/source/buffer.cpp b/source/buffer.cpp new file mode 100644 index 0000000..48fbf97 --- /dev/null +++ b/source/buffer.cpp @@ -0,0 +1,101 @@ + +#include "iwa/buffer.hpp" + +#include "iwa/device.hpp" + +namespace iwa +{ +Buffer::Buffer(ObjectPtr owner, const BufferCreationArgs& args) : super_t(std::move(owner)) +{ + mHandle = getOwner()->getVkHandle().createBuffer(vk::BufferCreateInfo + { + .flags = args.flags, + .size = args.size, + .usage = args.usage, + .sharingMode = args.sharingMode, + .queueFamilyIndexCount = static_cast(args.queueFamilyIndices.size()), + .pQueueFamilyIndices = args.queueFamilyIndices.data() + }); +} + +Buffer::~Buffer() noexcept +{ + IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyBuffer); +} + +void Buffer::allocateMemory(HostVisible hostVisible, HostCoherent hostCoherent) +{ + const vk::MemoryRequirements memoryRequirements = getOwner()->getVkHandle().getBufferMemoryRequirements(mHandle); + const vk::MemoryPropertyFlags memoryFlags = hostVisible ? + (vk::MemoryPropertyFlagBits::eHostVisible | (hostCoherent ? vk::MemoryPropertyFlagBits::eHostCoherent : vk::MemoryPropertyFlags())) : + vk::MemoryPropertyFlagBits::eDeviceLocal; + const std::optional memoryTypeIdx = findMemoryType(*getOwner(), memoryRequirements, memoryFlags); + if (!memoryTypeIdx.has_value()) + { + throw std::runtime_error("Could not find a suitable memory type."); + } + + ObjectPtr memory = getOwner()->allocateDeviceMemory( + { + .allocationSize = memoryRequirements.size, + .memoryTypeIndex = memoryTypeIdx.value() + }); + bindMemory(std::move(memory)); +} + +void Buffer::bindMemory(ObjectPtr memory, vk::DeviceSize offset) +{ + mMemory = std::move(memory); + getOwner()->getVkHandle().bindBufferMemory(mHandle, *mMemory, offset); +} + +mijin::Task<> Buffer::c_fill(std::uint32_t data, std::size_t bytes, std::size_t byteOffset) +{ + ObjectPtr cmdBufferPtr = getOwner()->beginScratchCommandBuffer(); + const vk::CommandBuffer cmdBuffer = *cmdBufferPtr; + cmdBuffer.fillBuffer(mHandle, byteOffset, bytes, data); + + co_await getOwner()->endScratchCommandBuffer(std::move(cmdBufferPtr)); +} + +mijin::Task<> Buffer::c_copyFrom(vk::Buffer srcBuffer, vk::BufferCopy region) +{ + ObjectPtr cmdBufferPtr = getOwner()->beginScratchCommandBuffer(); + const vk::CommandBuffer cmdBuffer = *cmdBufferPtr; + cmdBuffer.copyBuffer(srcBuffer, mHandle, region); + + co_await getOwner()->endScratchCommandBuffer(std::move(cmdBufferPtr)); +} + +mijin::Task<> Buffer::c_upload(const void* data, std::size_t bytes, std::size_t byteOffset) +{ + // assert(bytes == SIZE_REST || bytes + byteOffset <= byteSize); + // if (bytes == SIZE_REST) { + // bytes = byteSize - byteOffset; + // } + // create scratch buffer + vk::Device device = *getOwner(); + ObjectPtr scratchBuffer = getOwner()->createChild(BufferCreationArgs{ + .size = bytes, + .usage = vk::BufferUsageFlagBits::eTransferSrc + }); + scratchBuffer->allocateMemory(HostVisible::YES); + + // copy to scratch buffer + void* mapped = device.mapMemory(*scratchBuffer->getMemory(), 0, bytes); + std::memcpy(mapped, data, bytes); + device.unmapMemory(*scratchBuffer->getMemory()); + + // copy to actual buffer + co_await c_copyFrom(*scratchBuffer, vk::BufferCopy{ + .srcOffset = 0, + .dstOffset = byteOffset, + .size = bytes + }); +} + +mijin::Task<> Buffer::c_upload(const mijin::TypelessBuffer& data, std::size_t byteOffset) +{ + return c_upload(data.data(), data.byteSize(), byteOffset); +} +} // namespace iwa diff --git a/source/command.cpp b/source/command.cpp new file mode 100644 index 0000000..b9fa059 --- /dev/null +++ b/source/command.cpp @@ -0,0 +1,65 @@ + +#include "iwa/command.hpp" + +#include "iwa/device.hpp" + +namespace iwa +{ +CommandPool::CommandPool(ObjectPtr owner, CommandPoolCreationArgs args) : super_t(std::move(owner)) +{ + mHandle = getOwner()->getVkHandle().createCommandPool(vk::CommandPoolCreateInfo{ + .flags = args.flags, + .queueFamilyIndex = args.queueFamilyIndex + }); +} + +CommandPool::~CommandPool() noexcept +{ + IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyCommandPool); +} + +ObjectPtr CommandPool::allocateCommandBuffer(const CommandBufferAllocateArgs& args) +{ + vk::CommandBuffer commandBuffer; + const vk::CommandBufferAllocateInfo allocateInfo + { + .commandPool = mHandle, + .level = args.level, + .commandBufferCount = 1 + }; + vk::resultCheck(getOwner()->getVkHandle().allocateCommandBuffers(&allocateInfo, &commandBuffer), + "vkAllocateCommandBuffers failed"); + return createChild(commandBuffer); +} + +// std::vector CommandPool::allocateCommandBuffers( +// std::size_t count, +// const CommandBufferAllocateArgs& args) const noexcept +// { +// return getOwner()->getVkHandle().allocateCommandBuffers(vk::CommandBufferAllocateInfo{ +// .commandPool = mHandle, +// .level = args.level, +// .commandBufferCount = static_cast(count) +// }); +// } + +CommandBuffer::CommandBuffer(ObjectPtr owner, vk::CommandBuffer handle) + : super_t(std::move(owner)), MixinVulkanObject(handle) +{ + +} + +CommandBuffer::~CommandBuffer() noexcept +{ + if (mHandle) + { + getOwner()->getOwner()->queueDelete([ + poolHandle = getOwner()->getVkHandle(), + handle = mHandle, + device = getOwner()->getOwner()->getVkHandle()] + { + device.freeCommandBuffers(poolHandle, handle); + }); + } +} +} // namespace iwa diff --git a/source/descriptor_set.cpp b/source/descriptor_set.cpp new file mode 100644 index 0000000..ea87017 --- /dev/null +++ b/source/descriptor_set.cpp @@ -0,0 +1,90 @@ + +#include "iwa/descriptor_set.hpp" + +#include "iwa/device.hpp" + +namespace iwa +{ + +DescriptorSetLayout::DescriptorSetLayout(ObjectPtr owner, const DescriptorSetLayoutCreationArgs& args) + : super_t(std::move(owner)) +{ + vk::DescriptorSetLayoutCreateInfo createInfo{ + .flags = args.flags, + .bindingCount = static_cast(args.bindings.size()), + .pBindings = args.bindings.data() + }; + vk::DescriptorSetLayoutBindingFlagsCreateInfo flagsInfo; + if (!args.bindingFlags.empty()) + { + MIJIN_ASSERT(args.bindings.size() == args.bindingFlags.size(), "Binding flags must be empty or same size as bindings."); + flagsInfo.bindingCount = static_cast(args.bindingFlags.size()), + flagsInfo.pBindingFlags = args.bindingFlags.data(); + createInfo.pNext = &flagsInfo; + } + mHandle = getOwner()->getVkHandle().createDescriptorSetLayout(createInfo); +} + +DescriptorSetLayout::~DescriptorSetLayout() noexcept +{ + IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyDescriptorSetLayout) +} + +DescriptorPool::DescriptorPool(ObjectPtr owner, const DescriptorPoolCreationArgs& args) + : super_t(std::move(owner)), mCanFree(args.flags & vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet) +{ + mHandle = getOwner()->getVkHandle().createDescriptorPool(vk::DescriptorPoolCreateInfo + { + .flags = args.flags, + .maxSets = args.maxSets, + .poolSizeCount = static_cast(args.poolSizes.size()), + .pPoolSizes = args.poolSizes.data() + }); +} + +DescriptorPool::~DescriptorPool() noexcept +{ + IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyDescriptorPool); +} + +ObjectPtr DescriptorPool::allocateDescriptorSet(const DescriptorSetAllocateArgs& args) +{ + vk::DescriptorSet descriptorSet; + vk::DescriptorSetAllocateInfo allocateInfo + { + .descriptorPool = mHandle, + .descriptorSetCount = 1, + .pSetLayouts = &args.layout->getVkHandle(), + }; + vk::DescriptorSetVariableDescriptorCountAllocateInfo variableSetInfo; + if (args.variableDescriptorCount > 0) + { + variableSetInfo.descriptorSetCount = 1; + variableSetInfo.pDescriptorCounts = &args.variableDescriptorCount; + allocateInfo.pNext = &variableSetInfo; + } + vk::resultCheck(getOwner()->getVkHandle().allocateDescriptorSets(&allocateInfo, &descriptorSet), + "vkAllocateDescriptorSets failed"); + return createChild(descriptorSet); +} + +DescriptorSet::DescriptorSet(ObjectPtr owner, vk::DescriptorSet handle) + : super_t(std::move(owner)), MixinVulkanObject(handle) +{ + +} + +DescriptorSet::~DescriptorSet() noexcept +{ + if (mHandle && getOwner()->getCanFree()) + { + getOwner()->getOwner()->queueDelete([ + poolHandle = getOwner()->getVkHandle(), + handle = mHandle, + device = getOwner()->getOwner()] + { + device->getVkHandle().freeDescriptorSets(poolHandle, handle); + }); + } +} +} // namespace iwa diff --git a/source/device.cpp b/source/device.cpp new file mode 100644 index 0000000..a971659 --- /dev/null +++ b/source/device.cpp @@ -0,0 +1,381 @@ + +#include "iwa/device.hpp" + +#include "iwa/log.hpp" +#include "iwa/instance.hpp" + +namespace iwa +{ +namespace +{ +void buildDefaultDeviceExtensionList(std::vector& outExtensions) noexcept +{ + outExtensions.push_back({.name = VK_KHR_SWAPCHAIN_EXTENSION_NAME, .required = true}); +} + +bool checkQueueFamilies(const PhysicalDeviceInfo& deviceInfo, [[maybe_unused]] int& score) +{ + return deviceInfo.graphicsQueueFamily != std::numeric_limits::max() + && deviceInfo.computeQueueFamily != std::numeric_limits::max(); +} + +bool checkDeviceExtensions(const std::vector& extensions, const PhysicalDeviceInfo& deviceInfo, int& score) +{ + auto isExtensionSupported = [&deviceInfo](const char* extension) + { + for (const vk::ExtensionProperties& props : deviceInfo.extensions) + { + if (std::strncmp(props.extensionName, extension, VK_MAX_EXTENSION_NAME_SIZE) == 0) + { + return true; + } + } + return false; + }; + + for (const ExtensionInfo& extInfo : extensions) + { + if (isExtensionSupported(extInfo.name)) + { + score += 10; + } + else if (extInfo.required) + { + logVerbose("Vulkan device {} not supported as it is missing required extension {}.", deviceInfo.properties.deviceName, extInfo.name); + return false; + } + } + + return true; +} + +int scorePhysicalDevice( + const PhysicalDeviceCriteria& deviceCriteria, + const std::vector& extensions, + const PhysicalDeviceInfo& deviceInfo) +{ + int score = 0; + + switch (deviceInfo.properties.deviceType) + { + case vk::PhysicalDeviceType::eDiscreteGpu: + score = 1000; + break; + case vk::PhysicalDeviceType::eIntegratedGpu: + score = 100; + break; + default: break; + } + +#define CHECK_FEATURE_STRUCT(infoName, requiredName) \ + for (auto feature : deviceCriteria.requiredName) \ + { \ + if (!(deviceInfo.infoName.*feature)) \ + { \ + return -1; \ + } \ + } + CHECK_FEATURE_STRUCT(features, requiredFeatures) + CHECK_FEATURE_STRUCT(vulkan11Features, requiredVulkan11Features) + CHECK_FEATURE_STRUCT(vulkan12Features, requiredVulkan12Features) + CHECK_FEATURE_STRUCT(vulkan13Features, requiredVulkan13Features) + CHECK_FEATURE_STRUCT(accelerationStructureFeatures, requiredAccelerationStructureFeatures) + CHECK_FEATURE_STRUCT(rayTracingPipelineFeatures, requiredRayTracingPipelineFeatures) + CHECK_FEATURE_STRUCT(meshShaderFeatures, requredMeshShaderFeatures) + +#undef CHECK_FEATURE_STRUCT + + if (!checkQueueFamilies(deviceInfo, score)) + { + logVerbose("Vulkan device {} not supported as no suitable queue family configuration could be found.", deviceInfo.properties.deviceName); + return -1; + } + + if (!checkDeviceExtensions(extensions, deviceInfo, score)) + { + return -1; + } + + return score; +} + +std::size_t findBestPhysicalDevice( + PhysicalDeviceCriteria& deviceCriteria, + const std::vector& extensions, + const std::vector& deviceInfos) +{ + int bestScore = -1; + std::size_t bestIdx = 0; + + for (std::size_t idx = 0; idx < deviceInfos.size(); ++idx) + { + const int score = scorePhysicalDevice(deviceCriteria, extensions, deviceInfos[idx]); + if (score > bestScore) + { + bestScore = score; + bestIdx = idx; + } + } + + if (bestScore < 0) + { + logAndDie("Could not find a suitable Vulkan device!"); + } + + return bestIdx; +} + +std::vector buildDeviceExtensionNameList(const PhysicalDeviceInfo& deviceInfo, std::vector& extensions) +{ + auto isExtensionSupported = [&](const char* extension) + { + for (const vk::ExtensionProperties& props : deviceInfo.extensions) + { + if (std::strncmp(props.extensionName, extension, VK_MAX_EXTENSION_NAME_SIZE) == 0) + { + return true; + } + } + return false; + }; + + std::vector enabledExtensions; + for (ExtensionInfo& extInfo : extensions) + { + const bool supported = isExtensionSupported(extInfo.name); + if (!supported && extInfo.required) + { + // this shouldn't be possible, we already checked when scoring the device + logAndDie("Required Vulkan device extension not supported: {}.", extInfo.name); + } + if (supported) { + enabledExtensions.push_back(extInfo.name); + } + extInfo.enabled = supported; + } + return enabledExtensions; +} + +std::vector buildQueueCreateInfoList(const PhysicalDeviceInfo& deviceInfo) +{ + static const float QUEUE_PRIORITY_ONE = 1.0f; + std::vector createInfos; + + createInfos.emplace_back() + .setQueueFamilyIndex(deviceInfo.graphicsQueueFamily) + .setQueueCount(1) + .setPQueuePriorities(&QUEUE_PRIORITY_ONE); + + return createInfos; +} +} // namespace + +Device::Device(ObjectPtr owner, DeviceCreationArgs args) + : super_t(std::move(owner)), + mExtensions(std::move(args.extensions)) +{ + if (!args.flags.noDefaultExtensions) + { + buildDefaultDeviceExtensionList(mExtensions); + } + + const std::vector& physicalDevices = getOwner()->getPhysicalDevices(); + const std::size_t physicalDeviceIdx = findBestPhysicalDevice(args.physicalDeviceCriteria, mExtensions, physicalDevices); + mDeviceInfo = &physicalDevices[physicalDeviceIdx]; + const std::vector enabledExtensions = buildDeviceExtensionNameList(*mDeviceInfo, mExtensions); + const std::vector queueCreateInfos = buildQueueCreateInfoList(*mDeviceInfo); + + void* pNext = nullptr; +#define APPEND_FEATURE_STRUCT(type, name, requiredArray) \ + type name{.pNext = pNext}; \ + if (!args.physicalDeviceCriteria.requiredArray.empty()) \ + { \ + for (auto feature : args.physicalDeviceCriteria.requiredArray) \ + { \ + (name).*(feature) = VK_TRUE; \ + } \ + pNext = &(name); \ + } + APPEND_FEATURE_STRUCT(vk::PhysicalDeviceVulkan11Features, vulkan11Features, requiredVulkan11Features) + APPEND_FEATURE_STRUCT(vk::PhysicalDeviceVulkan12Features, vulkan12Features, requiredVulkan12Features) + APPEND_FEATURE_STRUCT(vk::PhysicalDeviceVulkan13Features, vulkan13Features, requiredVulkan13Features) + APPEND_FEATURE_STRUCT(vk::PhysicalDeviceAccelerationStructureFeaturesKHR, accelerationStructureFeatures, requiredAccelerationStructureFeatures) + APPEND_FEATURE_STRUCT(vk::PhysicalDeviceRayTracingPipelineFeaturesKHR, rayTracingPipelineFeature, requiredRayTracingPipelineFeatures) + APPEND_FEATURE_STRUCT(vk::PhysicalDeviceMeshShaderFeaturesEXT, meshShaderFeatures, requredMeshShaderFeatures) +#undef APPEND_FEATURE_STRUCT + + vk::PhysicalDeviceFeatures enabledFeatures; // NOLINT(*-const-correctness) false positive + for (auto feature : args.physicalDeviceCriteria.requiredFeatures) + { + enabledFeatures.*feature = VK_TRUE; + } + + const vk::DeviceCreateInfo deviceCreateInfo = + { + .pNext = pNext, + .queueCreateInfoCount = static_cast(queueCreateInfos.size()), + .pQueueCreateInfos = queueCreateInfos.data(), + .enabledExtensionCount = static_cast(enabledExtensions.size()), + .ppEnabledExtensionNames = enabledExtensions.data(), + .pEnabledFeatures = &enabledFeatures + }; + mHandle = mDeviceInfo->device.createDevice(deviceCreateInfo); + if (args.flags.singleDevice) + { + VULKAN_HPP_DEFAULT_DISPATCHER.init(mHandle); + } + + mGraphicsQueue = mHandle.getQueue(mDeviceInfo->graphicsQueueFamily, 0); + if (mDeviceInfo->graphicsQueueFamily == mDeviceInfo->computeQueueFamily) + { + mComputeQueue = mGraphicsQueue; + } + else + { + mComputeQueue = mHandle.getQueue(mDeviceInfo->computeQueueFamily, 0); + } + + getOwner()->getMainTaskLoop().addTask(c_updateLoop(), &mUpdateLoopHandle); + getOwner()->deviceCreated.emit(*this); +} + +Device::~Device() noexcept +{ + mUpdateLoopHandle.cancel(); + mScratchCommandPools.clear(); // hack2: due to the below hack the pools would get destroyed after the device, reset them manually + if (mHandle) + { + mHandle.waitIdle(); + mPendingScratchCmdBuffers.clear(); + getOwner()->queueDelete([handle=mHandle]() + { + handle.destroy(); + }); + } +} + +ScratchCommandPool::ScratchCommandPool(Device& device) +{ + mCommandPool = device.createChild(CommandPoolCreationArgs{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = device.getDeviceInfo().graphicsQueueFamily + }); +} + +ObjectPtr ScratchCommandPool::allocateCommandBuffer() +{ + for (Buffer& buffer : mBuffers) + { + if (buffer.doneFuture->ready()) + { + buffer.doneFuture = std::make_shared>(); + buffer.cmdBuffer->getVkHandle().reset(); + return buffer.cmdBuffer; + } + } + + // nothing found, allocate a new one + return mBuffers.emplace_back(mCommandPool->allocateCommandBuffer(), std::make_shared>()).cmdBuffer; +} + +mijin::FuturePtr ScratchCommandPool::getFuture(const ObjectPtr& cmdBuffer) noexcept +{ + for (const Buffer& buffer : mBuffers) + { + if (buffer.cmdBuffer == cmdBuffer) + { + return buffer.doneFuture; + } + } + logAndDie("Someone passed an invalid cmdBuffer to getFuture()!"); +} + +ObjectPtr Device::allocateDeviceMemory(const DeviceMemoryAllocationArgs& args) +{ + return createChild(args); +} + +ObjectPtr Device::beginScratchCommandBuffer() +{ + ObjectPtr cmdBuffer; + { + const std::shared_lock readLock(mScratchCommandPoolsMutex); + auto it = mScratchCommandPools.find(std::this_thread::get_id()); + if (it != mScratchCommandPools.end()) + { + cmdBuffer = it->second.allocateCommandBuffer(); + } + } + + if (cmdBuffer == nullptr) + { + // create the scratch command pool + const std::unique_lock writeLock(mScratchCommandPoolsMutex); + ScratchCommandPool& pool = mScratchCommandPools.emplace(std::this_thread::get_id(), ScratchCommandPool(*this)).first->second; + cmdBuffer = pool.allocateCommandBuffer(); + + // hack: an object should normally not contain pointers to its own child as that leads to cyclic dependencies + // decrease reference count by 1 to migitate that + decreaseReferenceCount(); + } + + const vk::CommandBufferBeginInfo beginInfo = + { + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit + }; + cmdBuffer->getVkHandle().begin(beginInfo); + return cmdBuffer; +} + +mijin::FuturePtr Device::endScratchCommandBuffer(ObjectPtr cmdBuffer) +{ + mijin::FuturePtr future; + { + const std::shared_lock readLock(mScratchCommandPoolsMutex); + future = mScratchCommandPools.at(std::this_thread::get_id()).getFuture(cmdBuffer); + } + cmdBuffer->getVkHandle().end(); + + getOwner()->runOnMainThread([cmdBuffer = std::move(cmdBuffer), self = getPointer(), future]() mutable + { + ObjectPtr doneFence = Fence::create(self); + + const vk::SubmitInfo submitInfo = + { + .commandBufferCount = 1, + .pCommandBuffers = &cmdBuffer->getVkHandle() + }; + self->getGraphicsQueue().submit(submitInfo, *doneFence); + self->mPendingScratchCmdBuffers.push_back({ + .cmdBuffer = std::move(cmdBuffer), + .doneFence = std::move(doneFence), + .future = std::move(future) + }); + }); + return future; +} + +void Device::queueDelete(std::function deleter) noexcept +{ + getOwner()->queueDelete(std::move(deleter)); +} + +mijin::Task<> Device::c_updateLoop() noexcept +{ + while(!getOwner()->isQuitRequested()) + { + for (auto it = mPendingScratchCmdBuffers.begin(); it != mPendingScratchCmdBuffers.end();) + { + if (it->doneFence->isDone()) + { + it->future->set(); + it = mPendingScratchCmdBuffers.erase(it); + } else + { + ++it; + } + } + co_await mijin::c_suspend(); + } +} +} \ No newline at end of file diff --git a/source/device_memory.cpp b/source/device_memory.cpp new file mode 100644 index 0000000..ce6b050 --- /dev/null +++ b/source/device_memory.cpp @@ -0,0 +1,39 @@ + +#include "iwa/device_memory.hpp" + +#include "iwa/device.hpp" + +namespace iwa +{ +DeviceMemory::DeviceMemory(ObjectPtr owner, const DeviceMemoryAllocationArgs& args) : super_t(std::move(owner)) +{ + mHandle = getOwner()->getVkHandle().allocateMemory(vk::MemoryAllocateInfo + { + .allocationSize = args.allocationSize, + .memoryTypeIndex = args.memoryTypeIndex + }); +} + +DeviceMemory::~DeviceMemory() noexcept +{ + IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, freeMemory); +} + +std::optional findMemoryType(Device& device, const vk::MemoryRequirements& requirements, vk::MemoryPropertyFlags properties) +{ + const vk::PhysicalDeviceMemoryProperties& memoryProperties = device.getDeviceInfo().memoryProperties; + + for (std::uint32_t idx = 0; idx < memoryProperties.memoryTypeCount; ++idx) + { + if ((requirements.memoryTypeBits & (1 << idx)) == 0) { + continue; // not suitable for this buffer + } + if ((memoryProperties.memoryTypes[idx].propertyFlags & properties) != properties) { + continue; // does not fulfill required properties + } + return idx; + } + + return std::nullopt; +} +} // namespace iwa diff --git a/source/event.cpp b/source/event.cpp new file mode 100644 index 0000000..f190800 --- /dev/null +++ b/source/event.cpp @@ -0,0 +1,28 @@ + +#include "iwa/event.hpp" + +#include "iwa/device.hpp" + +namespace iwa +{ +Event::Event(ObjectPtr owner, const EventCreationArgs& args) : super_t(std::move(owner)) +{ + mHandle = getOwner()->getVkHandle().createEvent(vk::EventCreateInfo + { + .flags = args.flags + }); +} + +Event::~Event() noexcept +{ + IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyEvent); +} + +mijin::Task<> Event::c_wait() +{ + while (getOwner()->getVkHandle().getEventStatus(mHandle) != vk::Result::eEventSet) + { + co_await mijin::c_suspend(); + } +} +} \ No newline at end of file diff --git a/source/fence.cpp b/source/fence.cpp new file mode 100644 index 0000000..4cec648 --- /dev/null +++ b/source/fence.cpp @@ -0,0 +1,38 @@ + +#include "iwa/fence.hpp" + +#include "iwa/device.hpp" + +namespace iwa +{ +Fence::Fence(ObjectPtr owner, const FenceCreationArgs& args) : super_t(std::move(owner)) +{ + mHandle = getOwner()->getVkHandle().createFence(vk::FenceCreateInfo{ + .flags = args.flags + }); +} + +Fence::~Fence() noexcept +{ + IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyFence) +} + +bool Fence::isDone() const +{ + return getOwner()->getVkHandle().waitForFences(mHandle, VK_TRUE, 0) != vk::Result::eTimeout; +} + +mijin::Task<> Fence::c_wait() const +{ + while (getOwner()->getVkHandle().waitForFences(mHandle, VK_TRUE, 0) == vk::Result::eTimeout) + { + co_await mijin::c_suspend(); + } + co_return; +} + +void Fence::reset() const +{ + getOwner()->getVkHandle().resetFences(mHandle); +} +} // namespace iwa diff --git a/source/image.cpp b/source/image.cpp new file mode 100644 index 0000000..23dcc4f --- /dev/null +++ b/source/image.cpp @@ -0,0 +1,384 @@ + +#include "iwa/image.hpp" + +#include +#include "iwa/resource/bitmap.hpp" +#include "iwa/buffer.hpp" +#include "iwa/device.hpp" +#include "iwa/util/vkutil.hpp" + +namespace iwa +{ +Image::Image(ObjectPtr owner, ImageCreationArgs args) : super_t(std::move(owner)), mFlags(args.flags), mType(args.imageType), + mFormat(args.format), mTiling(args.tiling), mUsage(args.usage), mSize(args.extent), mArrayLayers(args.arrayLayers), + mMipLevels(clampMipLevels(args.mipLevels)) +{ + if (mMipLevels > 1) { + mUsage |= vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eTransferSrc; + } + mHandle = getOwner()->getVkHandle().createImage(vk::ImageCreateInfo{ + .flags = args.flags, + .imageType = args.imageType, + .format = args.format, + .extent = args.extent, + .mipLevels = mMipLevels, + .arrayLayers = args.arrayLayers, + .samples = args.samples, + .tiling = args.tiling, + .usage = mUsage, + .sharingMode = args.sharingMode, + .queueFamilyIndexCount = static_cast(args.queueFamilyIndices.size()), + .pQueueFamilyIndices = args.queueFamilyIndices.data(), + .initialLayout = args.initialLayout + }); +} + +Image::Image(ObjectPtr owner, ImageWrapArgs args) + : super_t(std::move(owner)), MixinVulkanObject(args.handle), mType(args.type), mFormat(args.format), mUsage(args.usage), + mSize(args.size), mMipLevels(args.mipLevels), mWrapped(true) +{ + +} + +Image::~Image() noexcept +{ + if (!mWrapped) + { + IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyImage) + } +} + +void Image::resetUsage(ResetLayout resetLayout) noexcept +{ + lastUsageStages = vk::PipelineStageFlagBits::eTopOfPipe; + lastAccess = vk::AccessFlags(); + if (resetLayout) + { + currentLayout = vk::ImageLayout::eUndefined; + } +} + +void Image::allocateMemory() +{ + const vk::MemoryRequirements memoryRequirements = getOwner()->getVkHandle().getImageMemoryRequirements(mHandle); + const vk::MemoryPropertyFlags memoryFlags = vk::MemoryPropertyFlagBits::eDeviceLocal; + const std::optional memoryTypeIdx = findMemoryType(*getOwner(), memoryRequirements, memoryFlags); + if (!memoryTypeIdx.has_value()) + { + throw std::runtime_error("Could not find a suitable memory type."); + } + + ObjectPtr memory = getOwner()->allocateDeviceMemory( + { + .allocationSize = memoryRequirements.size, + .memoryTypeIndex = memoryTypeIdx.value() + }); + bindMemory(std::move(memory)); +} + +void Image::bindMemory(ObjectPtr memory, vk::DeviceSize offset) +{ + mMemory = std::move(memory); + getOwner()->getVkHandle().bindImageMemory(mHandle, *mMemory, offset); +} + +void Image::applyTransition(vk::CommandBuffer cmdBuffer, const ImageTransition& transition) +{ + assert(transition.layout != vk::ImageLayout::eUndefined); + + if (transition.layout != currentLayout) + { + cmdBuffer.pipelineBarrier( + /* srcStageMask = */ lastUsageStages, + /* dstStageMask = */ transition.stages, + /* dependencyFlags = */ vk::DependencyFlags(), + /* memoryBarriers = */ {}, + /* bufferMemoryBarriers = */ {}, + /* imageMemoryBarriers = */ { + vk::ImageMemoryBarrier{ + .srcAccessMask = lastAccess, + .dstAccessMask = transition.access, + .oldLayout = currentLayout, + .newLayout = transition.layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = mHandle, + .subresourceRange = transition.subResourceRange + } + }); + } + + lastUsageStages = transition.stages; + lastAccess = transition.access; + currentLayout = transition.layout; +} + +ObjectPtr Image::createImageView(const ImageViewCreationArgs& args) +{ + return createChild(args); +} + + +mijin::Task<> Image::c_upload(const void* data, std::size_t bytes, vk::Extent3D bufferImageSize, vk::Offset3D imageOffset, + unsigned baseLayer, unsigned layerCount) +{ + MIJIN_ASSERT(bytes >= static_cast(bufferImageSize.width) * bufferImageSize.height * bufferImageSize.depth + * vkFormatSize(mFormat), "Buffer for image upload is too small!"); + + // TODO: optimize this whole process + // create scratch buffer + vk::Device device = *getOwner(); + ObjectPtr scratchBuffer = getOwner()->createChild(BufferCreationArgs{ + .size = bytes, + .usage = vk::BufferUsageFlagBits::eTransferSrc + }); + scratchBuffer->allocateMemory(HostVisible::YES); + + // copy to scratch buffer + void* mapped = device.mapMemory(*scratchBuffer->getMemory(), 0, bytes); + std::memcpy(mapped, data, bytes); + device.unmapMemory(*scratchBuffer->getMemory()); + + ObjectPtr cmdBufferPtr = getOwner()->beginScratchCommandBuffer(); + const vk::CommandBuffer cmdBuffer = *cmdBufferPtr; + applyTransition(cmdBuffer, { + .stages = vk::PipelineStageFlagBits::eTransfer, + .layout = vk::ImageLayout::eTransferDstOptimal, + .access = vk::AccessFlagBits::eTransferWrite + }); + + // copy to actual buffer + cmdBuffer.copyBufferToImage(*scratchBuffer, mHandle, vk::ImageLayout::eTransferDstOptimal, vk::BufferImageCopy{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = baseLayer, + .layerCount = layerCount + }, + .imageOffset = { + .x = imageOffset.x, + .y = imageOffset.y, + .z = imageOffset.z + }, + .imageExtent = { + .width = bufferImageSize.width, + .height = bufferImageSize.height, + .depth = bufferImageSize.depth + } + }); + if (mMipLevels > 1) + { + generateMipMaps(cmdBuffer); + } + co_await getOwner()->endScratchCommandBuffer(std::move(cmdBufferPtr)); +} + +mijin::Task<> Image::c_doTransition(const ImageTransition& transition) +{ + ObjectPtr cmdBufferPtr = getOwner()->beginScratchCommandBuffer(); + const vk::CommandBuffer cmdBuffer = *cmdBufferPtr; + applyTransition(cmdBuffer, transition); + + co_await getOwner()->endScratchCommandBuffer(std::move(cmdBufferPtr)); +} + +mijin::Task<> Image::c_upload(const Bitmap& bitmap, vk::Offset3D imageOffset, unsigned baseLayer, unsigned layerCount) +{ + MIJIN_ASSERT(vkFormatSize(mFormat) == vkFormatSize(bitmap.getFormat()), "Bitmap format size doesn't match image format size."); + + const vk::Extent3D bufferImageSize = { + .width = bitmap.getSize().width, + .height = bitmap.getSize().height, + .depth = 1 + }; + return c_upload(bitmap.getData().data(), bitmap.getData().size_bytes(), bufferImageSize, imageOffset, baseLayer, layerCount); +} + +mijin::Task<> Image::c_blitFrom(Image& srcImage, std::vector regions, vk::Filter filter) +{ + ObjectPtr cmdBufferPtr = getOwner()->beginScratchCommandBuffer(); + const vk::CommandBuffer cmdBuffer = *cmdBufferPtr; + + applyTransition(cmdBuffer, IMAGE_TRANSITION_TRANSFER_WRITE); + srcImage.applyTransition(cmdBuffer, IMAGE_TRANSITION_TRANSFER_READ); + + cmdBuffer.blitImage( + /* srcImage = */ srcImage, + /* srcImageLayout = */ vk::ImageLayout::eTransferSrcOptimal, + /* dstImage = */ *this, + /* dstImageLayout = */ vk::ImageLayout::eTransferDstOptimal, + /* regions = */ regions, + /* filter = */ filter + ); + co_await getOwner()->endScratchCommandBuffer(std::move(cmdBufferPtr)); +} + +mijin::Task<> Image::c_blitFrom(const Bitmap& bitmap, std::vector regions, vk::Filter filter) +{ + ObjectPtr scratchImage = co_await c_create(getOwner()->getPointer(), ImageFromBitmapArgs{ + .bitmap = &bitmap, + .usage = vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eTransferSrc + }); + co_await c_blitFrom(*scratchImage, std::move(regions), filter); +} + +mijin::Task<> Image::c_copyFrom(Image& srcImage, std::vector regions) +{ + ObjectPtr cmdBufferPtr = getOwner()->beginScratchCommandBuffer(); + const vk::CommandBuffer cmdBuffer = *cmdBufferPtr; + + applyTransition(cmdBuffer, IMAGE_TRANSITION_TRANSFER_WRITE); + srcImage.applyTransition(cmdBuffer, IMAGE_TRANSITION_TRANSFER_READ); + + cmdBuffer.copyImage( + /* srcImage = */ srcImage, + /* srcImageLayout = */ vk::ImageLayout::eTransferSrcOptimal, + /* dstImage = */ *this, + /* dstImageLayout = */ vk::ImageLayout::eTransferDstOptimal, + /* regions = */ regions + ); + co_await getOwner()->endScratchCommandBuffer(std::move(cmdBufferPtr)); +} + +std::uint32_t Image::clampMipLevels(std::uint32_t levels) const +{ + if (levels <= 1) { + return 1; + } + const vk::ImageFormatProperties props = getOwner()->getVkPhysicalDevice().getImageFormatProperties(mFormat, mType, mTiling, mUsage, mFlags); + return std::min(levels, std::min(props.maxMipLevels, static_cast(std::log2(std::max(mSize.width, mSize.height)))+1)); +} + +void Image::generateMipMaps(vk::CommandBuffer cmdBuffer) +{ + std::vector regions; + regions.resize(mMipLevels - 1); + + applyTransition(cmdBuffer, { + .stages = vk::PipelineStageFlagBits::eTransfer, + .layout = vk::ImageLayout::eGeneral, // TODO: using transfer dst/src optimal would be better, but I don't want to deal with different layout in the mips + .access = vk::AccessFlagBits::eTransferWrite | vk::AccessFlagBits::eTransferRead + }); + + unsigned mipWidth = mSize.width / 2; + unsigned mipHeight = mSize.height / 2; + for (unsigned level = 1; level < mMipLevels; ++level) + { + vk::ImageBlit& region = regions[level - 1]; + region.srcSubresource = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1 + }; + region.srcOffsets[0].x = 0; + region.srcOffsets[0].y = 0; + region.srcOffsets[0].z = 0; + region.srcOffsets[1].x = static_cast(mSize.width); + region.srcOffsets[1].y = static_cast(mSize.height); + region.srcOffsets[1].z = 1; + region.dstSubresource = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = level, + .baseArrayLayer = 0, + .layerCount = 1 + }; + region.dstOffsets[0].x = 0; + region.dstOffsets[0].y = 0; + region.dstOffsets[0].z = 0; + region.dstOffsets[1].x = static_cast(mipWidth); + region.dstOffsets[1].y = static_cast(mipHeight); + region.dstOffsets[1].z = 1; + + if (mipWidth > 1) { + mipWidth /= 2; + } + if (mipHeight > 1) { + mipHeight /= 2; + } + } + + cmdBuffer.blitImage( + /* srcImage = */ mHandle, + /* srcImageLayout = */ vk::ImageLayout::eGeneral, + /* dstImage = */ mHandle, + /* dstImageLayout = */ vk::ImageLayout::eGeneral, + /* regions = */ regions, + /* filter = */ vk::Filter::eLinear + ); +} + +mijin::Task> Image::c_create(ObjectPtr owner, ImageFromBitmapArgs args) +{ + ObjectPtr image = owner->createChild(ImageCreationArgs{ + .flags = args.flags, + .imageType = vk::ImageType::e2D, + .format = args.bitmap->getFormat(), + .extent = { + .width = args.bitmap->getSize().width, + .height = args.bitmap->getSize().height, + .depth = 1 + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = args.tiling, + .usage = args.usage | vk::ImageUsageFlagBits::eTransferDst, + .sharingMode = args.sharingMode, + .queueFamilyIndices = std::move(args.queueFamilyIndices), + .initialLayout = args.initialLayout + }); + image->allocateMemory(); + co_await image->c_upload(*args.bitmap); + co_return image; +} + +ImageView::ImageView(ObjectPtr owner, const ImageViewCreationArgs& args) : super_t(std::move(owner)) +{ + mHandle = getOwner()->getOwner()->getVkHandle().createImageView(vk::ImageViewCreateInfo + { + .flags = args.flags, + .image = *getOwner(), + .viewType = args.viewType, + .format = args.format != vk::Format::eUndefined ? args.format : getOwner()->getFormat(), + .components = args.components, + .subresourceRange = args.subresourceRange + }); +} + +ImageView::~ImageView() noexcept +{ + IWA_DELETE_DEVICE_OBJECT(getOwner()->getOwner(), mHandle, destroyImageView); +} + +Sampler::Sampler(ObjectPtr owner, const SamplerCreationArgs& args) : super_t(std::move(owner)) +{ + mHandle = getOwner()->getVkHandle().createSampler(vk::SamplerCreateInfo + { + .flags = args.flags, + .magFilter = args.magFilter, + .minFilter = args.minFilter, + .mipmapMode = args.mipmapMode, + .addressModeU = args.addressModeU, + .addressModeV = args.addressModeV, + .addressModeW = args.addressModeW, + .mipLodBias = args.mipLodBias, + .anisotropyEnable = args.options.anisotropyEnable, + .maxAnisotropy = args.maxAnisotropy, + .compareEnable = args.options.compareEnable, + .compareOp = args.compareOp, + .minLod = args.minLod, + .maxLod = args.maxLod, + .borderColor = args.borderColor, + .unnormalizedCoordinates = args.options.unnormalizedCoordinates + }); +} + +Sampler::~Sampler() noexcept +{ + IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroySampler); +} +} // namespace iwa diff --git a/source/input.cpp b/source/input.cpp new file mode 100644 index 0000000..36205ee --- /dev/null +++ b/source/input.cpp @@ -0,0 +1,37 @@ + +#include "iwa/input.hpp" + +#include "iwa/log.hpp" + +namespace iwa +{ +KeyState getKeyState(ScanCode scanCode) noexcept +{ + const int index = static_cast(scanCode); + int numKeys = 0; + const Uint8* keys = SDL_GetKeyboardState(&numKeys); + if (index >= numKeys) { + return {}; + } + return { + .pressed = keys[index] > 0 + }; +} + +void captureMouse() noexcept +{ + SDL_CaptureMouse(SDL_TRUE); +} + +void uncaptureMouse() noexcept +{ + SDL_CaptureMouse(SDL_FALSE); +} + +std::pair getMouseScreenPosition() noexcept +{ + std::pair position; + SDL_GetGlobalMouseState(&position.first, &position.second); + return position; +} +} // namespace iwa diff --git a/source/instance.cpp b/source/instance.cpp new file mode 100644 index 0000000..3f5585e --- /dev/null +++ b/source/instance.cpp @@ -0,0 +1,462 @@ + +#include "iwa/instance.hpp" + +#include +#include +#include "iwa/addon.hpp" +#include "iwa/log.hpp" +#include "iwa/window.hpp" + +#include + +VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE + +namespace iwa +{ +namespace +{ +void buildDefaultInstanceExtensionList(std::vector& outExtensions) noexcept +{ +#if !defined(KAZAN_RELEASE) + outExtensions.push_back({.name = VK_EXT_DEBUG_UTILS_EXTENSION_NAME, .required = false}); +#endif + // if (!getEngineOptions().headless) + { + outExtensions.push_back({.name = VK_KHR_SURFACE_EXTENSION_NAME, .required = true}); +#if MIJIN_TARGET_OS == MIJIN_OS_LINUX + // There are two possible APIs for X11 surfaces (SDL supports both) + // just try to enable both and hope SDL will be happy. + // Both are optional, in case one is not supported, but in the end + // one of them will be needed. + outExtensions.push_back({.name = "VK_KHR_xcb_surface", .required = false}); + outExtensions.push_back({.name = "VK_KHR_xlib_surface", .required = false}); +#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS + g_instanceExtensions.push_back({.name = "VK_KHR_win32_surface", .required = true}); +#endif + } +} + +void buildDefaultInstanceLayerList(std::vector& outLayers) noexcept +{ +#if !defined(KAZAN_RELEASE) + outLayers.push_back({.name = "VK_LAYER_KHRONOS_validation", .required = false}); +#else + (void) outLayers; +#endif +} + +std::vector checkInstanceExtensions(std::vector& extensions) +{ + std::vector properties = vk::enumerateInstanceExtensionProperties(nullptr); + + auto isExtensionSupported = [&properties](const char* extension) + { + for (const vk::ExtensionProperties& props : properties) + { + if (std::strncmp(props.extensionName, extension, VK_MAX_EXTENSION_NAME_SIZE) == 0) + { + return true; + } + } + return false; + }; + + std::vector enabledExtensions; + for (ExtensionInfo& extInfo : extensions) + { + const bool supported = isExtensionSupported(extInfo.name); + if (!supported && extInfo.required) + { + logAndDie("Required Vulkan instance extension not supported: {}.", extInfo.name); + } + if (supported) { + enabledExtensions.push_back(extInfo.name); + } + extInfo.enabled = supported; + } + return enabledExtensions; +} + +std::vector checkInstanceLayers(std::vector& layers) +{ + std::vector enabledLayers; +#if !defined(KAZAN_RELEASE) + std::vector properties = vk::enumerateInstanceLayerProperties(); + + auto isLayerSupported = [&properties](const char* extension) + { + for (const vk::LayerProperties& props : properties) + { + if (std::strncmp(props.layerName, extension, VK_MAX_EXTENSION_NAME_SIZE) == 0) + { + return true; + } + } + return false; + }; + + for (LayerInfo& layerInfo : layers) + { + const bool supported = isLayerSupported(layerInfo.name); + if (!supported && layerInfo.required) + { + logAndDie("Required Vulkan instance layer not supported: {}.", layerInfo.name); + } + if (supported) { + enabledLayers.push_back(layerInfo.name); + } + layerInfo.enabled = supported; + } +#endif + return enabledLayers; +} +vk::Bool32 VKAPI_PTR vulkanMessengerCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT severity, + VkDebugUtilsMessageTypeFlagsEXT /* types */, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* /* pUserData */ +) +{ + switch (pCallbackData->messageIdNumber) + { + case 1303270965: // We use general layout to copy texture to mips. I know it's not optimal, but it's allowed and + // works for me ¯\_(ツ)_/¯ + case 148949623: // The spec says that a) you can't create descriptor sets from layouts with the push-descriptor bit + // set and b) you have to bind a descriptor set when using said set layout in vkCmdPushDescriptorSetKHR. + // So you have to use a set, but can't create it... Something is off. + // Apart from that c) it also says that dstSet is ignored when using vkCmdPushDescriptorSetKHR, so + // maybe the validation layers are just wrong. + // a) https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDescriptorSetAllocateInfo.html#VUID-VkDescriptorSetAllocateInfo-pSetLayouts-00308 + // b) https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkWriteDescriptorSet.html#VUID-VkWriteDescriptorSet-dstSet-00320 + // c) https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdPushDescriptorSetKHR.html + return VK_FALSE; + } + logMsg("VK> {}", pCallbackData->pMessage); + if (/* g_breakOnVulkanError && */ severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) + { + if (const mijin::Result trace = mijin::captureStacktrace(0); trace.isSuccess()) + { + logMsg("{}", *trace); + } + MIJIN_TRAP(); + } + + return VK_FALSE; +} + +std::pair detectQueueFamilies(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR dummySurface) +{ + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + for (std::uint32_t idx = 0; idx < queueFamilyProperties.size(); ++idx) + { + const vk::QueueFamilyProperties& properties = queueFamilyProperties[idx]; + const vk::Bool32 surfaceSupport = dummySurface ? physicalDevice.getSurfaceSupportKHR(idx, dummySurface) : VK_TRUE; // no dummy surface -> no surface support needed + if (surfaceSupport + && (properties.queueFlags & vk::QueueFlagBits::eGraphics) + && (properties.queueFlags & vk::QueueFlagBits::eCompute)) + { + return std::make_pair(idx, idx); + } + } + + return std::make_pair(std::numeric_limits::max(), std::numeric_limits::max()); +} + +PhysicalDeviceInfo getPhysicalDeviceInfo(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR dummySurface) +{ + PhysicalDeviceInfo deviceInfo; + + deviceInfo.device = physicalDevice; + deviceInfo.memoryProperties = physicalDevice.getMemoryProperties(); + deviceInfo.extensions = physicalDevice.enumerateDeviceExtensionProperties(); + + auto isExtensionSupported = [&](const char* extension) + { + for (const vk::ExtensionProperties& props : deviceInfo.extensions) + { + if (std::strncmp(props.extensionName, extension, VK_MAX_EXTENSION_NAME_SIZE) == 0) { + return true; + } + } + return false; + }; + const bool rayTracingSupported = isExtensionSupported(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME) + && isExtensionSupported(VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME) + && isExtensionSupported(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME); + const bool meshShadersSupported = isExtensionSupported(VK_EXT_MESH_SHADER_EXTENSION_NAME); + + vk::PhysicalDeviceProperties2 deviceProperties = { + .pNext = &deviceInfo.rayTracingProperties + }; + physicalDevice.getProperties2(&deviceProperties); + deviceInfo.properties = deviceProperties.properties; + +#define ADD_TO_NEXT_CHAIN(struct_name) \ + *ppNext = &(struct_name); ppNext = &(struct_name).pNext + vk::PhysicalDeviceFeatures2 deviceFeatures; + void** ppNext = &deviceFeatures.pNext; + ADD_TO_NEXT_CHAIN(deviceInfo.vulkan11Features); + ADD_TO_NEXT_CHAIN(deviceInfo.vulkan12Features); + ADD_TO_NEXT_CHAIN(deviceInfo.vulkan13Features); + if (rayTracingSupported) + { + ADD_TO_NEXT_CHAIN(deviceInfo.accelerationStructureFeatures); + ADD_TO_NEXT_CHAIN(deviceInfo.rayTracingPipelineFeatures); + } + if (meshShadersSupported) { + ADD_TO_NEXT_CHAIN(deviceInfo.meshShaderFeatures); + } + +#undef ADD_TO_NEXT_CHAIN + physicalDevice.getFeatures2(&deviceFeatures); + deviceInfo.features = deviceFeatures.features; + + // fill the renderer features + deviceInfo.availableFeatures.rayTracing = rayTracingSupported; + deviceInfo.availableFeatures.meshShaders = meshShadersSupported && deviceInfo.meshShaderFeatures.meshShader; + + deviceInfo.surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(dummySurface); + std::tie(deviceInfo.graphicsQueueFamily, deviceInfo.computeQueueFamily) = detectQueueFamilies(physicalDevice, dummySurface); + + return deviceInfo; +} + +std::vector getPhysicalDeviceInfos(Instance& instance) +{ + const std::vector devices = instance.getVkHandle().enumeratePhysicalDevices(); + std::vector deviceInfos; + deviceInfos.reserve(devices.size()); + + const ObjectPtr dummyWindow = instance.createWindow({.flags={.hidden=true}}); + const vk::SurfaceKHR dummySurface = dummyWindow->getVkSurface(); + + for (const vk::PhysicalDevice device : devices) + { + deviceInfos.push_back(getPhysicalDeviceInfo(device, dummySurface)); + } + + return deviceInfos; +} +} // namespace + +Instance::Instance(InstanceCreationArgs args) + : super_t(nullptr), + mExtensions(std::move(args.extensions)), + mLayers(std::move(args.layers)) +{ + setMainThread(); + + const AddonInitArgs addonInitArgs = { + .instance = getPointer(), + .instanceCreationArgs = args + }; + for (Addon* addon : getAddons()) + { + addon->init(addonInitArgs); + } + + VULKAN_HPP_DEFAULT_DISPATCHER.init(); + + if (!args.flags.noDefaultExtensions) + { + buildDefaultInstanceExtensionList(mExtensions); + } + if (!args.flags.noDefaultLayers) + { + buildDefaultInstanceLayerList(mLayers); + } + + const std::vector enabledLayerNames = checkInstanceLayers(mLayers); + const std::vector enabledExtensionNames = checkInstanceExtensions(mExtensions); + const vk::InstanceCreateInfo createInfo = + { + .pApplicationInfo = &args.applicationInfo, + .enabledLayerCount = static_cast(enabledLayerNames.size()), + .ppEnabledLayerNames = enabledLayerNames.data(), + .enabledExtensionCount = static_cast(enabledExtensionNames.size()), + .ppEnabledExtensionNames = enabledExtensionNames.data() + }; + mHandle = vk::createInstance(createInfo); + VULKAN_HPP_DEFAULT_DISPATCHER.init(mHandle); + + // dump info about enabled extensions and layers + logMsg("Enabled Vulkan instance extensions: {}", enabledExtensionNames); + logMsg("Enabled Vulkan instance layers: {}", enabledLayerNames); + + // also dump the actual instance version + const std::uint32_t apiVersion = vk::enumerateInstanceVersion(); + logMsg("Vulkan instance version: {}.{}.{}.\n", + VK_API_VERSION_MAJOR(apiVersion), + VK_API_VERSION_MINOR(apiVersion), + VK_API_VERSION_PATCH(apiVersion) + ); + + if (isExtensionEnabled(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) + { + // create debug messenger + const vk::DebugUtilsMessengerCreateInfoEXT dumCreateInfo = + { + .messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError, + .messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation, + .pfnUserCallback = &vulkanMessengerCallback + }; + mDebugMessenger = mHandle.createDebugUtilsMessengerEXT(dumCreateInfo); + } + + mPhysicalDevices = getPhysicalDeviceInfos(*this); + mWorkerTaskLoop.start(10); +} + +Instance::~Instance() noexcept +{ + for (Addon* addon : getAddons()) + { + addon->cleanup(); + } + while (!mDeleteQueue.empty()) + { + runDeleters(true); + } + + if (mDebugMessenger) + { + mHandle.destroyDebugUtilsMessengerEXT(mDebugMessenger); + } + mHandle.destroy(); +} + +bool Instance::isExtensionEnabled(const char* name) const noexcept +{ + for (const ExtensionInfo& extInfo : mExtensions) + { + if (std::strcmp(extInfo.name, name) == 0) + { + return extInfo.enabled; + } + } + return false; +} + +bool Instance::isLayerEnabled(const char* name) const noexcept +{ + for (const LayerInfo& layerInfo : mLayers) + { + if (std::strcmp(layerInfo.name, name) == 0) + { + return layerInfo.enabled; + } + } + return false; +} + +ObjectPtr Instance::createWindow(const WindowCreationArgs& args) +{ + return createChild(args); +} + +ObjectPtr Instance::createDevice(DeviceCreationArgs args) +{ + return createChild(std::move(args)); +} + +void Instance::queueDelete(deleter_t deleter) noexcept +{ + MIJIN_ASSERT(deleter, "Don't pass an empty function into queueDelete()!"); + if (!deleter) + { + return; + } + + runOnMainThread([deleter = std::move(deleter), this]() mutable + { + static const int DELETE_DELAY_FRAMES = 5; // TODO: ? + mDeleteQueue.push_back({ + .deleter = std::move(deleter), + .remainingFrames = DELETE_DELAY_FRAMES + }); + }); +} + +void Instance::tickDeleteQueue() +{ + for (DeleteQueueEntry& entry : mDeleteQueue) + { + --entry.remainingFrames; + } + runDeleters(); +} + +void Instance::setMainThread() +{ + mMainThread = std::this_thread::get_id(); +} + +void Instance::requestQuit() noexcept +{ + if (mQuitRequested) + { + return; + } + + mQuitRequested = true; + quitRequested.emit(); + + mMainTaskLoop.addTask([](Instance* instance) -> mijin::Task<> + { + for (int tick = 0; tick < 100; ++tick) + { + if (instance->getMainTaskLoop().getNumTasks() == 1) + { + // just us left, bye bye! + co_return; + } + co_await mijin::c_suspend(); + } + const std::size_t activeTasks = instance->getMainTaskLoop().getActiveTasks() - 1; // substract yourself + if (activeTasks > 0) + { + logMsg("{} tasks did not finish when shutting down!", activeTasks - 1); +#if MIJIN_COROUTINE_ENABLE_DEBUG_INFO + const std::vector tasks = instance->getMainTaskLoop().getAllTasks(); + for (auto [index, handle] : mijin::enumerate(tasks)) + { + if (handle == mijin::getCurrentTask()) + { + continue; + } + const mijin::Optional stack = handle.getCreationStack(); + if (!stack.empty()) + { + logMsg("Task {} creation stack:\n{}", index, *stack); + } + } +#endif // MIJIN_COROUTINE_ENABLE_DEBUG_INFO + MIJIN_TRAP(); + } + else + { + const std::size_t totalTasks = instance->getMainTaskLoop().getNumTasks() - 1; + logMsg("{} tasks still waiting on program exit.", totalTasks); + } + instance->getMainTaskLoop().cancelAllTasks(); + co_return; + }(this)); +} + +void Instance::runDeleters(bool runAll) +{ + for (auto it = mDeleteQueue.begin(); it != mDeleteQueue.end();) + { + if (runAll || it->remainingFrames <= 0) + { + it->deleter(); + it = mDeleteQueue.erase(it); + } + else + { + ++it; + } + } +} +} diff --git a/source/io/bitmap.cpp b/source/io/bitmap.cpp new file mode 100644 index 0000000..8c1a684 --- /dev/null +++ b/source/io/bitmap.cpp @@ -0,0 +1,207 @@ + +#include "iwa/io/bitmap.hpp" + +#include +#include +#include + +#if MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif // MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_WRITE_IMPLEMENTATION +#define STBI_FAILURE_USERMSG +#include +#include +#undef STB_IMAGE_IMPLEMENTATION +#undef STB_IMAGE_WRITE_IMPLEMENTATION +#if MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG +#pragma GCC diagnostic pop +#endif // MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG + +#include "iwa/log.hpp" + +namespace iwa +{ +namespace +{ +vk::Format formatByComponents(int components, ImageFormatType formatType) +{ + switch (formatType) + { + case ImageFormatType::UNORM: + switch (components) + { + case 1: + return vk::Format::eR8Unorm; + case 2: + return vk::Format::eR8G8Unorm; + case 3: + return vk::Format::eR8G8B8Unorm; + case 4: + return vk::Format::eR8G8B8A8Unorm; + } + break; + case ImageFormatType::UINT: + switch (components) + { + case 1: + return vk::Format::eR8Uint; + case 2: + return vk::Format::eR8G8Uint; + case 3: + return vk::Format::eR8G8B8Uint; + case 4: + return vk::Format::eR8G8B8A8Uint; + } + break; + case ImageFormatType::SRGB: + switch (components) + { + case 1: + return vk::Format::eR8Srgb; + case 2: + return vk::Format::eR8G8Srgb; + case 3: + return vk::Format::eR8G8B8Srgb; + case 4: + return vk::Format::eR8G8B8A8Srgb; + } + break; + } + + logAndDie("Could not detect image format."); +} + +ObjectPtr loadBitmapFromSTBI(stbi_uc* data, std::size_t width, std::size_t height, std::size_t /* components */, + const BitmapLoadOptions& options) +{ + if (data == nullptr) + { + logAndDie("Could not load texture: {}", stbi_failure_reason()); + } + + const BitmapCreationArgs args = + { + .format = formatByComponents(/*components */ 4, options.formatType), + .size = {static_cast(width), static_cast(height)} + }; + + ObjectPtr bitmap = Bitmap::create(args); + std::memcpy(bitmap->getData().data(), data, bitmap->getData().size()); + stbi_image_free(data); + return bitmap; +} + +int stbiReadCallback(void* user, char* data, int size) +{ + mijin::Stream& stream = *static_cast(user); + std::size_t bytesRead = 0; + const mijin::StreamError error = stream.readRaw(data, size, /* partial = */ true, &bytesRead); + if (error != mijin::StreamError::SUCCESS) + { + // TODO: return what? + return 0; + } + return static_cast(bytesRead); +} + +void stbiSkipCallback(void* user, int bytes) +{ + mijin::Stream& stream = *static_cast(user); + (void) stream.seek(bytes, mijin::SeekMode::RELATIVE); +} + +int stbiEofCallback(void* user) +{ + mijin::Stream& stream = *static_cast(user); + return stream.isAtEnd(); +} +} // namespace + +ObjectPtr loadBitmap(const BitmapLoadOptions& options) +{ + int width, height, components; // NOLINT(cppcoreguidelines-init-variables) + const stbi_io_callbacks callbacks{ + .read = &stbiReadCallback, + .skip = &stbiSkipCallback, + .eof = &stbiEofCallback + }; + stbi_uc* data = stbi_load_from_callbacks( + /* clbk = */ &callbacks, + /* user = */ options.stream, + /* x = */ &width, + /* y = */ &height, + /* channels_in_file = */ &components, + /* desired_channels = */ 4); + // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage) Yes, width isn't initialized if STB fails. No, that is not a problem + return loadBitmapFromSTBI(data, width, height, components, options); +} + +void saveBitmap(const Bitmap& bitmap, const BitmapSaveOptions& options) +{ + namespace fs = std::filesystem; + + BitmapCodec codec = options.codec; + if (codec == BitmapCodec::NONE) + { + std::string extension = fs::path(options.fileName).extension().string(); + std::ranges::transform(extension, extension.begin(), [](char chr) + { return static_cast(std::tolower(chr)); }); + if (extension == ".png") + { + codec = BitmapCodec::PNG; + } else if (extension == ".jpg" || extension == ".jpeg") + { + codec = BitmapCodec::JPEG; + } else + { + logAndDie("Couldn't guess image file codec from file extension: {}.", extension); + } + } + + int comp = 0; + switch (bitmap.getFormat()) + { + case vk::Format::eR8G8B8A8Unorm: + case vk::Format::eR8G8B8A8Srgb: + comp = 4; + break; + case vk::Format::eR8G8B8Unorm: + case vk::Format::eR8G8B8Srgb: + comp = 3; + break; + default: + logAndDie("Cannot write this image format (yet)."); + break; + } + + switch (codec) + { + case BitmapCodec::NONE: + assert(0); + break; + case BitmapCodec::PNG: + stbi_write_png( + /* filename = */ options.fileName.c_str(), + /* w = */ static_cast(bitmap.getSize().width), + /* h = */ static_cast(bitmap.getSize().height), + /* comp = */ comp, + /* data = */ bitmap.getData().data(), + /* stride_in_bytes = */ comp * static_cast(bitmap.getSize().width)); + break; + case BitmapCodec::JPEG: + stbi_write_jpg( + /* filename = */ options.fileName.c_str(), + /* x = */ static_cast(bitmap.getSize().width), + /* y = */ static_cast(bitmap.getSize().height), + /* comp = */ comp, + /* data = */ bitmap.getData().data(), + /* quality = */ options.jpegQuality + ); + break; + } +} +} // namespace iwa \ No newline at end of file diff --git a/source/io/font.cpp b/source/io/font.cpp new file mode 100644 index 0000000..8790e09 --- /dev/null +++ b/source/io/font.cpp @@ -0,0 +1,165 @@ + +#include "iwa/io/font.hpp" + +#include + +#define STB_RECT_PACK_IMPLEMENTATION +#define STB_TRUETYPE_IMPLEMENTATION +#include +#include +#undef STB_RECT_PACK_IMPLEMENTATION +#undef STB_TRUETYPE_IMPLEMENTATION + +namespace iwa +{ +namespace +{ +std::vector getRenderCodePoints() +{ + std::vector result; + for (int chr = 32; chr <= 255; ++chr) { + result.push_back(chr); + } + return result; +} +} + +ObjectPtr loadFont(const FontLoadOptions& options) +{ + mijin::TypelessBuffer fontData; + if (const mijin::StreamError error = options.stream->readRest(fontData); error != mijin::StreamError::SUCCESS) { + throw std::runtime_error("IO error reading font from stream."); + } + + // init font data + stbtt_fontinfo fontInfo; + const int fontOffset = stbtt_GetFontOffsetForIndex(static_cast(fontData.data()), 0); + stbtt_InitFont(&fontInfo, static_cast(fontData.data()), fontOffset); + + // prepare data for packing + std::vector codepoints = getRenderCodePoints(); + std::vector chardata; + std::vector rects; + + chardata.resize(codepoints.size()); + rects.resize(codepoints.size()); + stbtt_pack_range range = { + .font_size = options.size, + .first_unicode_codepoint_in_range = 0, + .array_of_unicode_codepoints = codepoints.data(), + .num_chars = static_cast(codepoints.size()), + .chardata_for_range = chardata.data() + }; + + // TODO: this is just a guess, maybe there is a better way to detect this + int oversample = 1; + if (options.size < 30) { + oversample = 4; + } + else if (options.size < 35) { + oversample = 3; + } + else if (options.size < 40) { + oversample = 4; + } + + int textureSize = 64; + mijin::TypelessBuffer pixels; + while (textureSize <= 4096) + { + pixels.resize(textureSize * textureSize * sizeof(unsigned char)); // NOLINT + + // reset char data + for (stbtt_packedchar& packedChar : chardata) { + packedChar.x0 = packedChar.y0 = packedChar.x1 = packedChar.y1 = 0; + } + + // try packing with this size + stbtt_pack_context packContext; + stbtt_PackBegin( + /* spc = */ &packContext, + /* pixels = */ static_cast(pixels.data()), + /* width = */ textureSize, + /* height = */ textureSize, + /* stride_in_bytes = */ 0, + /* padding = */ 1, + /* alloc_context = */ nullptr + ); + stbtt_PackSetOversampling(&packContext, oversample, oversample); // TODO: make adjustable for better font quality + + const int nRects = stbtt_PackFontRangesGatherRects(&packContext, &fontInfo, &range, 1, rects.data()); + stbtt_PackFontRangesPackRects(&packContext, rects.data(), nRects); + + bool allPacked = true; + for (int rectIdx = 0; rectIdx < nRects; ++rectIdx) + { + if (!rects[rectIdx].was_packed) { + allPacked = false; + break; + } + } + if (!allPacked) + { + textureSize *= 2; + continue; + } + + stbtt_PackFontRangesRenderIntoRects( + /* spc = */ &packContext, + /* info = */ &fontInfo, + /* ranges = */ &range, + /* num_ranges = */ 1, + /* rects = */ rects.data() + ); + + stbtt_PackEnd(&packContext); + break; + } + + // now create a bitmap from pixels + ObjectPtr bitmap = Bitmap::create(BitmapCreationArgs{ + .format = vk::Format::eR8Unorm, + .size = { + .width = static_cast(textureSize), + .height = static_cast(textureSize) + }, + .initialData = std::move(pixels) + }); + std::unordered_map glyphMap; + glyphMap.reserve(chardata.size()); + + for (auto [codepoint, chrdata] : mijin::zip(codepoints, chardata)) + { + glyphMap.emplace(static_cast(codepoint), GlyphInfo{ + .uvPos0 = { + static_cast(chrdata.x0) / static_cast(textureSize), + static_cast(chrdata.y0) / static_cast(textureSize) + }, + .uvPos1 = { + static_cast(chrdata.x1) / static_cast(textureSize), + static_cast(chrdata.y1) / static_cast(textureSize) + }, + .xOffsetBefore = chrdata.xoff, + .xOffsetAfter = chrdata.xoff2, + .yOffsetBefore = chrdata.yoff, + .yOffsetAfter = chrdata.yoff2, + .xAdvance = chrdata.xadvance + }); + } + + const float scale = options.size > 0 ? stbtt_ScaleForPixelHeight(&fontInfo, options.size) : stbtt_ScaleForMappingEmToPixels(&fontInfo, -options.size); + int ascent = 0, descent = 0, lineGap = 0; + stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap); + + return Font::create(FontCreationArgs{ + .bitmap = std::move(bitmap), + .glyphMap = std::move(glyphMap), + .metrics = { + .ascent = scale * static_cast(ascent), + .descent = scale * static_cast(descent), + .lineGap = scale * static_cast(lineGap), + .sizeFactor = 1.f / static_cast(oversample) + } + }); +} +} // namespace iwa diff --git a/source/io/mesh.cpp b/source/io/mesh.cpp new file mode 100644 index 0000000..7ec4153 --- /dev/null +++ b/source/io/mesh.cpp @@ -0,0 +1,6 @@ + +#include "iwa/io/mesh.hpp" + +namespace iwa +{ +} // namespace iwa diff --git a/source/object.cpp b/source/object.cpp new file mode 100644 index 0000000..aa393ed --- /dev/null +++ b/source/object.cpp @@ -0,0 +1,130 @@ + +#include "iwa/object.hpp" + +#if IWA_OBJECTPTR_TRACKING +#include +#endif +#include +#include +#include + +namespace iwa::impl +{ +namespace +{ +std::atomic gNextObjectId; +std::unordered_map gAllObjects; +std::shared_mutex gAllObjectMutex; +std::unordered_map gDestructionHandlers; +std::shared_mutex gDestructionHandlersMutex; + +#if IWA_OBJECTPTR_TRACKING +std::list gObjectPtrAllocations; +std::mutex gObjectPtrAllocationsMutex; + +struct ObjectPtrAllocationsChecker +{ + ~ObjectPtrAllocationsChecker() + { + const std::unique_lock lock(gObjectPtrAllocationsMutex); + if (!gObjectPtrAllocations.empty()) + { + std::cerr << "ObjectPtrs pending deletion:\n"; + for (const ObjectPtrAllocation& allocation : gObjectPtrAllocations) + { + std::cerr << typeid(*allocation.object).name() /* << "@" << allocation.stacktrace */ <<"\n"; +#if IWA_OBJECTPTR_TRACKING > 1 + if (allocation.stacktrace.isSuccess()) + { + std::cerr << *allocation.stacktrace << "\n"; + } +#endif + } + std::cerr.flush(); + MIJIN_TRAP(); + } + } +} gObjectPtrAllocationsChecker; +#endif // IWA_OBJECTPTR_TRACKING +} + +#if IWA_OBJECTPTR_TRACKING +#if IWA_OBJECTPTR_TRACKING > 1 +objectptr_allocation_handle_t trackObjectPtr(BaseObject* object, mijin::Result&& stacktrace) noexcept +{ + const std::unique_lock lock(gObjectPtrAllocationsMutex); + gObjectPtrAllocations.emplace_back(object, std::move(stacktrace)); + return std::prev(gObjectPtrAllocations.end()); +} +#else +objectptr_allocation_handle_t trackObjectPtr(BaseObject* object) noexcept +{ + const std::unique_lock lock(gObjectPtrAllocationsMutex); + gObjectPtrAllocations.emplace_back(object); + return std::prev(gObjectPtrAllocations.end()); +} +#endif + +void untrackObjectPtr(objectptr_allocation_handle_t handle) noexcept +{ + const std::unique_lock lock(gObjectPtrAllocationsMutex); + gObjectPtrAllocations.erase(*handle); // NOLINT(bugprone-unchecked-optional-access) +} +#endif // IWA_OBJECTPTR_TRACKING + +object_id_t nextObjectId() noexcept +{ + return ++gNextObjectId; +} + +void registerObject(BaseObject* object) noexcept +{ + const std::unique_lock lock(gAllObjectMutex); + MIJIN_ASSERT(gAllObjects.find(object->getId()) == gAllObjects.end(), "Duplicate object id!"); + gAllObjects.emplace(object->getId(), object); +} + +void unregisterObject(BaseObject* object) noexcept +{ + { + const std::unique_lock lock(gAllObjectMutex); + gAllObjects.erase(object->getId()); + } + + std::unordered_map::iterator itHandler; + { + const std::shared_lock readLock(gDestructionHandlersMutex); + itHandler = gDestructionHandlers.find(object->getId()); + } + + if (itHandler != gDestructionHandlers.end()) + { + object_destruction_handler_t handler; + { + const std::unique_lock writeLock(gDestructionHandlersMutex); + handler = std::move(itHandler->second); + gDestructionHandlers.erase(itHandler); + } + handler(); + } +} + +ObjectPtr getRegisteredObject(object_id_t objectId) noexcept +{ + const std::shared_lock lock(gAllObjectMutex); + auto it = gAllObjects.find(objectId); + if (it != gAllObjects.end()) { + return it->second->getPointer(); + } + return {}; +} +} // namespace iwa::impl + +namespace iwa +{ +void registerObjectDestructionHandler(const BaseObject& object, object_destruction_handler_t handler) noexcept +{ + const std::unique_lock writeLock(impl::gDestructionHandlersMutex); + impl::gDestructionHandlers.emplace(object.getId(), std::move(handler)); +} // namespace iwa +} diff --git a/source/pipeline.cpp b/source/pipeline.cpp new file mode 100644 index 0000000..c3973d9 --- /dev/null +++ b/source/pipeline.cpp @@ -0,0 +1,136 @@ + +#include "iwa/pipeline.hpp" + +#include +#include +#include "iwa/device.hpp" +#include "iwa/util/glsl_compiler.hpp" +#include "iwa/util/next_chain.hpp" + +namespace iwa +{ +PipelineLayout::PipelineLayout(ObjectPtr owner, const PipelineLayoutCreationArgs& args) : super_t(std::move(owner)) +{ + std::vector setLayoutHandles; + setLayoutHandles.reserve(args.setLayouts.size()); + for (const ObjectPtr& setLayout : args.setLayouts) + { + setLayoutHandles.push_back(setLayout->getVkHandle()); + } + + mHandle = getOwner()->getVkHandle().createPipelineLayout(vk::PipelineLayoutCreateInfo{ + .flags = args.flags, + .setLayoutCount = static_cast(setLayoutHandles.size()), + .pSetLayouts = setLayoutHandles.data(), + .pushConstantRangeCount = static_cast(args.pushConstantRanges.size()), + .pPushConstantRanges = args.pushConstantRanges.data() + }); +} + +PipelineLayout::~PipelineLayout() noexcept +{ + IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyPipelineLayout) +} + +Pipeline::Pipeline(ObjectPtr owner) noexcept : super_t(std::move(owner)) +{ +} + +Pipeline::~Pipeline() noexcept +{ + IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyPipeline) +} + +GraphicsPipeline::GraphicsPipeline(ObjectPtr owner, const GraphicsPipelineCreationArgs& args) noexcept + : super_t(std::move(owner)) +{ + std::vector vkStages; + + for (const PipelineStage& stage : args.stages) + { + vk::PipelineShaderStageCreateInfo& vkStage = vkStages.emplace_back(); + vkStage.stage = stage.stage; + vkStage.module = *stage.shader; + vkStage.pName = stage.name.c_str(); + } + + const vk::PipelineVertexInputStateCreateInfo vkVertexInput = + { + .vertexBindingDescriptionCount = static_cast(args.vertexInput.bindings.size()), + .pVertexBindingDescriptions = args.vertexInput.bindings.data(), + .vertexAttributeDescriptionCount = static_cast(args.vertexInput.attributes.size()), + .pVertexAttributeDescriptions = args.vertexInput.attributes.data() + }; + + const vk::PipelineColorBlendStateCreateInfo vkColorBlend = + { + .logicOpEnable = args.colorBlend.logicOp.has_value(), + .logicOp = args.colorBlend.logicOp.has_value() ? *args.colorBlend.logicOp : vk::LogicOp(), + .attachmentCount = static_cast(args.colorBlend.attachements.size()), + .pAttachments = args.colorBlend.attachements.data() + }; + + const vk::PipelineDynamicStateCreateInfo vkDynamic = + { + .dynamicStateCount = static_cast(args.dynamicState.size()), + .pDynamicStates = args.dynamicState.data() + }; + + NextChain nextChain; + if (args.renderingInfo.has_value()) + { + const GraphicsPipelineRenderingInfo& rinfo = *args.renderingInfo; + nextChain.append(vk::PipelineRenderingCreateInfo{ + .viewMask = rinfo.viewMask, + .colorAttachmentCount = static_cast(rinfo.colorAttachmentFormats.size()), + .pColorAttachmentFormats = rinfo.colorAttachmentFormats.data(), + .depthAttachmentFormat = rinfo.depthFormat, + .stencilAttachmentFormat = rinfo.stencilFormat + }); + } + + const vk::GraphicsPipelineCreateInfo vkCreateInfo = + { + .pNext = nextChain.finalize(), + .stageCount = static_cast(vkStages.size()), + .pStages = vkStages.data(), + .pVertexInputState = &vkVertexInput, + .pInputAssemblyState = &args.inputAssembly, + .pViewportState = &args.viewport, + .pRasterizationState = &args.rasterization, + .pMultisampleState = &args.multisample, + .pDepthStencilState = &args.depthStencil, + .pColorBlendState = &vkColorBlend, + .pDynamicState = &vkDynamic, + .layout = *args.layout, + .renderPass = args.renderPass ? *args.renderPass : vk::RenderPass(), + .subpass = args.subpass + }; + + const auto result = getOwner()->getVkHandle().createGraphicsPipeline(VK_NULL_HANDLE, vkCreateInfo); + MIJIN_ASSERT(result.result == vk::Result::eSuccess, "Graphics pipeline creation failed."); + mHandle = result.value; +} + +ComputePipeline::ComputePipeline(ObjectPtr owner, const ComputePipelineCreationArgs& args) noexcept + : super_t(std::move(owner)) +{ + const vk::ComputePipelineCreateInfo vkCreateInfo{ + .stage = { + .stage = vk::ShaderStageFlagBits::eCompute, + .module = *args.stage.shader, + .pName = args.stage.name.c_str() + }, + .layout = *args.layout + }; + const vk::ResultValue result = getOwner()->getVkHandle().createComputePipeline(VK_NULL_HANDLE, vkCreateInfo); + MIJIN_ASSERT(result.result == vk::Result::eSuccess, "Compute pipeline creation failed."); + mHandle = result.value; +} + +RayTracingPipeline::RayTracingPipeline(ObjectPtr owner, const RayTracingPipelineCreationArgs& args) noexcept + : super_t(std::move(owner)) +{ + (void) args; +} +} // namespace iwa diff --git a/source/render_pass.cpp b/source/render_pass.cpp new file mode 100644 index 0000000..6d99ba5 --- /dev/null +++ b/source/render_pass.cpp @@ -0,0 +1,70 @@ + +#include "iwa/render_pass.hpp" + +#include "iwa/device.hpp" +#include "iwa/image.hpp" + +namespace iwa +{ +RenderPass::RenderPass(ObjectPtr owner, const RenderPassCreationArgs& args) : super_t(std::move(owner)) +{ + std::vector vkSubpasses; + vkSubpasses.reserve(args.subpasses.size()); + for (const SubpassDescription& subpass : args.subpasses) + { + MIJIN_ASSERT(subpass.resolveAttachments.empty() || subpass.resolveAttachments.size() == subpass.colorAttachments.size(), + "Number of resolve attachments must be either 0 or the same as color attachments."); + vkSubpasses.push_back({ + .flags = subpass.flags, + .pipelineBindPoint = subpass.pipelineBindPoint, + .inputAttachmentCount = static_cast(subpass.inputAttachments.size()), + .pInputAttachments = subpass.inputAttachments.data(), + .colorAttachmentCount = static_cast(subpass.colorAttachments.size()), + .pColorAttachments = subpass.colorAttachments.data(), + .pResolveAttachments = subpass.resolveAttachments.data(), + .pDepthStencilAttachment = subpass.depthStencilAttachment.has_value() ? &subpass.depthStencilAttachment.value() : nullptr, + .preserveAttachmentCount = static_cast(subpass.preserveAttachments.size()), + .pPreserveAttachments = subpass.preserveAttachments.data() + }); + } + mHandle = getOwner()->getVkHandle().createRenderPass(vk::RenderPassCreateInfo{ + .flags = args.flags, + .attachmentCount = static_cast(args.attachments.size()), + .pAttachments = args.attachments.data(), + .subpassCount = static_cast(vkSubpasses.size()), + .pSubpasses = vkSubpasses.data(), + .dependencyCount = static_cast(args.dependencies.size()), + .pDependencies = args.dependencies.data() + }); +} + +RenderPass::~RenderPass() noexcept +{ + IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyRenderPass); +} + +Framebuffer::Framebuffer(ObjectPtr owner, const FramebufferCreationArgs& args) + : super_t(std::move(owner)), mImageViews(args.attachments) +{ + std::vector vkImageViews; + vkImageViews.reserve(mImageViews.size()); + for (const ObjectPtr& imageView : mImageViews) + { + vkImageViews.push_back(*imageView); + } + mHandle = getOwner()->getVkHandle().createFramebuffer(vk::FramebufferCreateInfo{ + .flags = args.flags, + .renderPass = *args.renderPass, + .attachmentCount = static_cast(vkImageViews.size()), + .pAttachments = vkImageViews.data(), + .width = args.width, + .height = args.height, + .layers = args.layers + }); +} + +Framebuffer::~Framebuffer() noexcept +{ + IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyFramebuffer); +} +} // namespace iwa diff --git a/source/resource/bitmap.cpp b/source/resource/bitmap.cpp new file mode 100644 index 0000000..3a73972 --- /dev/null +++ b/source/resource/bitmap.cpp @@ -0,0 +1,305 @@ + +#include "iwa/resource/bitmap.hpp" + +#include +#include "iwa/log.hpp" +#include "iwa/util/vkutil.hpp" + +namespace iwa +{ +enum class InterpolationMethod +{ + NEAREST +}; + +class BitmapViewBase +{ +protected: + Bitmap* mBitmap; +protected: + explicit inline BitmapViewBase(Bitmap* bitmap_) : mBitmap(bitmap_) {} +public: + BitmapViewBase(const BitmapViewBase&) = delete; + BitmapViewBase(BitmapViewBase&&) = default; + + BitmapViewBase& operator=(const BitmapViewBase&) = delete; + BitmapViewBase& operator=(BitmapViewBase&&) = default; + + virtual ~BitmapViewBase() = default; // just to make clang happy, should be optimised out again + [[nodiscard]] virtual glm::vec4 getPixel(unsigned x, unsigned y) const = 0; + [[nodiscard]] virtual glm::vec4 getPixelRelative(float x, float y, InterpolationMethod interpolation = InterpolationMethod::NEAREST) const = 0; + [[nodiscard]] virtual std::vector getPixels(unsigned x, unsigned y, unsigned width, unsigned height) const = 0; + virtual void fill(const glm::vec4& color) = 0; + virtual void copyChannels(const Bitmap& other, const std::vector& mappings) = 0; + virtual void multiply(const glm::vec4& color) = 0; +}; + +template> +class BitmapView : public BitmapViewBase +{ +private: + static constexpr std::size_t pixelSize = sizeof(TData) * numComponents; +public: + explicit inline BitmapView(Bitmap* bitmap_) : BitmapViewBase(bitmap_) {} + TData* getPixelRaw(unsigned x, unsigned y) + { + const std::size_t offset = x + y * mBitmap->getSize().width; + return reinterpret_cast(&mBitmap->getData()[offset * pixelSize]); + } + + [[nodiscard]] const TData* getPixelRaw(unsigned x, unsigned y) const + { + const std::size_t offset = x + y * mBitmap->getSize().width; + return reinterpret_cast(&mBitmap->getData()[offset * pixelSize]); + } + + [[nodiscard]] TData convertChannel(float channel) const + { + if constexpr (normalized) { + return static_cast(channel); + } else { + return static_cast(255 * channel); + } + } + + [[nodiscard]] float convertChannelBack(TData channel) const + { + if constexpr (normalized) { + return static_cast(channel); + } + else { + return channel / 255.f; + } + } + + [[nodiscard]] TData clampChannel(TData value) + { + if constexpr (normalized) { + return std::clamp(value, TData(0), TData(1)); + } + else { + return std::clamp(value, TData(0), TData(255)); + } + } + + void convertColor(const glm::vec4& color, std::array& outConverted) const + { + for (int comp = 0; comp < numComponents; ++comp) + { + outConverted[comp] = convertChannel(color[comp]); + } + } + + [[nodiscard]] glm::vec4 convertColorBack(const TData* raw) const + { + glm::vec4 result; + for (int comp = 0; comp < numComponents; ++comp) + { + result[comp] = convertChannelBack(raw[comp]); + } + return result; + } + + // overrides + [[nodiscard]] glm::vec4 getPixel(unsigned x, unsigned y) const override + { + const TData* pixelRaw = getPixelRaw(x, y); + return convertColorBack(pixelRaw); + } + + [[nodiscard]] glm::vec4 getPixelRelative(float x, float y, InterpolationMethod interpolation = InterpolationMethod::NEAREST) const override + { + const vk::Extent2D size = mBitmap->getSize(); + (void) interpolation; // TODO + const unsigned myX = std::clamp(static_cast(std::round(x * static_cast(size.width))), 0u, size.width - 1); + const unsigned myY = std::clamp(static_cast(std::round(y * static_cast(size.height))), 0u, size.height - 1); + + return getPixel(myX, myY); + } + + [[nodiscard]] std::vector getPixels(unsigned x, unsigned y, unsigned width, unsigned height) const override + { + std::vector pixels; + pixels.resize(static_cast(width) * height); + + for (unsigned ypos = y; ypos < y + height; ++ypos) + { + for (unsigned xpos = x; xpos < x + width; ++xpos) + { + pixels[xpos + height * ypos] = getPixel(xpos, ypos); + } + } + return pixels; + } + + void fill(const glm::vec4& color) override + { + std::array convertedColor; // NOLINT(cppcoreguidelines-pro-type-member-init) + convertColor(color, convertedColor); + + for (unsigned y = 0; y < mBitmap->getSize().height; ++y) + { + for (unsigned x = 0; x < mBitmap->getSize().width; ++x) + { + TData* pixel = getPixelRaw(x, y); + for (int comp = 0; comp < numComponents; ++comp) + { + pixel[comp] = convertedColor[comp]; + } + } + } + }; + + // TODO: maybe introduce a "dual-mView" class that includes both types at once? + void copyChannels(const Bitmap& other, const std::vector& mappings) override + { + const vk::Extent2D size = mBitmap->getSize(); + const std::vector otherPixels = other.getAllPixels(); + + if(otherPixels.size() == static_cast(size.width) * size.height) + { + for (unsigned y = 0; y < size.height; ++y) + { + for (unsigned x = 0; x < size.width; ++x) + { + const glm::vec4 color = otherPixels[x + y * size.height]; + for (const ChannelMapping& mapping : mappings) + { + assert(static_cast(mapping.to) < numComponents); + TData* myPixel = getPixelRaw(x, y); + myPixel[static_cast(mapping.to)] = convertChannel(color[static_cast(mapping.from)]); + } + } + } + } + else + { + const vk::Extent2D otherSize = other.getSize(); + const float factorX = static_cast(otherSize.width) / static_cast(size.width); + const float factorY = static_cast(otherSize.height) / static_cast(size.height); + + for (unsigned y = 0; y < size.height; ++y) + { + for (unsigned x = 0; x < size.width; ++x) + { + const unsigned otherX = std::clamp(static_cast(std::round(factorX * static_cast(x))), 0u, otherSize.width - 1); + const unsigned otherY = std::clamp(static_cast(std::round(factorY * static_cast(x))), 0u, otherSize.height - 1); + const glm::vec4 color = otherPixels[otherX + otherY * size.height]; + for (const ChannelMapping& mapping : mappings) + { + assert(static_cast(mapping.to) < numComponents); + TData* myPixel = getPixelRaw(x, y); + myPixel[static_cast(mapping.to)] = convertChannel(color[static_cast(mapping.from)]); + } + } + } + } + } + + void multiply(const glm::vec4& color) override + { + for (unsigned y = 0; y < mBitmap->getSize().height; ++y) + { + for (unsigned x = 0; x < mBitmap->getSize().width; ++x) + { + TData* pixel = getPixelRaw(x, y); + for (int comp = 0; comp < numComponents; ++comp) + { + pixel[comp] = clampChannel(static_cast(pixel[comp] * color[comp])); + } + } + } + } +}; + +void Bitmap::createView() +{ + switch(mFormat) + { + case vk::Format::eR8Uint: + case vk::Format::eR8Unorm: + case vk::Format::eR8Srgb: + mView = std::make_unique>(this); + break; + case vk::Format::eR8G8Uint: + case vk::Format::eR8G8Unorm: + case vk::Format::eR8G8Srgb: + mView = std::make_unique>(this); + break; + case vk::Format::eR8G8B8Uint: + case vk::Format::eR8G8B8Unorm: + case vk::Format::eR8G8B8Srgb: + mView = std::make_unique>(this); + break; + case vk::Format::eR8G8B8A8Uint: + case vk::Format::eR8G8B8A8Unorm: + case vk::Format::eR8G8B8A8Srgb: + mView = std::make_unique>(this); + break; + case vk::Format::eR32Sfloat: + mView = std::make_unique>(this); + break; + case vk::Format::eR32G32Sfloat: + mView = std::make_unique>(this); + break; + case vk::Format::eR32G32B32Sfloat: + mView = std::make_unique>(this); + break; + case vk::Format::eR32G32B32A32Sfloat: + mView = std::make_unique>(this); + break; + default: + logAndDie("Missing format for bitmap mView!"); + } +} + +// +// public functions +// +Bitmap::Bitmap(BitmapCreationArgs args, ObjectPtr owner) + : super_t(std::move(owner)), mFormat(args.format), mSize(args.size) +{ + const std::size_t dataSize = static_cast(args.size.width) * args.size.height * vkFormatSize(args.format); + if (args.initialData.empty()) + { + mData.resize(dataSize); + } + else + { + MIJIN_ASSERT(args.initialData->byteSize() == dataSize, "Bitmap initial data size is invalid."); + mData = std::move(*args.initialData); + } + + createView(); +} + +Bitmap::~Bitmap() // NOLINT(modernize-use-equals-default) +{ + // needs to be implemented for the mView to be deleted correctly +} + +glm::vec4 Bitmap::getPixel(unsigned int x, unsigned int y) const +{ + return mView->getPixel(x, y); +} + +std::vector Bitmap::getPixels(unsigned x, unsigned y, unsigned width, unsigned height) const +{ + return mView->getPixels(x, y, width, height); +} + +void Bitmap::fill(const glm::vec4& color) +{ + mView->fill(color); +} + +void Bitmap::copyChannels(const Bitmap& other, const std::vector& mappings) +{ + mView->copyChannels(other, mappings); +} + +void Bitmap::multiply(const glm::vec4& color) +{ + mView->multiply(color); +} +} // namespace iwa diff --git a/source/resource/font.cpp b/source/resource/font.cpp new file mode 100644 index 0000000..1e2f3a8 --- /dev/null +++ b/source/resource/font.cpp @@ -0,0 +1,11 @@ + +#include "iwa/resource/font.hpp" + +namespace iwa +{ +Font::Font(FontCreationArgs args) + : mBitmap(std::move(args.bitmap)), mGlyphMap(std::move(args.glyphMap)), mMetrics(args.metrics) +{ + +} +} // namespace iwa diff --git a/source/semaphore.cpp b/source/semaphore.cpp new file mode 100644 index 0000000..cfa4a82 --- /dev/null +++ b/source/semaphore.cpp @@ -0,0 +1,19 @@ + +#include "iwa/semaphore.hpp" + +#include "iwa/device.hpp" + +namespace iwa +{ +Semaphore::Semaphore(ObjectPtr owner, const SemaphoreCreationArgs& args) : super_t(std::move(owner)) +{ + mHandle = getOwner()->getVkHandle().createSemaphore(vk::SemaphoreCreateInfo{ + .flags = args.flags + }); +} + +Semaphore::~Semaphore() noexcept +{ + IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroySemaphore) +} +} // namespace iwa diff --git a/source/shader_module.cpp b/source/shader_module.cpp new file mode 100644 index 0000000..ce4e790 --- /dev/null +++ b/source/shader_module.cpp @@ -0,0 +1,20 @@ + +#include "iwa/shader_module.hpp" + +#include "iwa/device.hpp" + +namespace iwa +{ +ShaderModule::ShaderModule(ObjectPtr owner, const ShaderModuleCreationArgs& args) : super_t(std::move(owner)) +{ + mHandle = getOwner()->getVkHandle().createShaderModule(vk::ShaderModuleCreateInfo{ + .codeSize = static_cast(args.code.size_bytes()), + .pCode = args.code.data() + }); +} + +ShaderModule::~ShaderModule() noexcept +{ + IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyShaderModule) +} +} // namespace iwa diff --git a/source/swapchain.cpp b/source/swapchain.cpp new file mode 100644 index 0000000..0689c79 --- /dev/null +++ b/source/swapchain.cpp @@ -0,0 +1,316 @@ + +#include "iwa/swapchain.hpp" + +#include +#include +#include "iwa/device.hpp" +#include "iwa/log.hpp" + +namespace iwa +{ +namespace +{ +std::pair detectPresentMode(const Device& device, const Window& window, const vk::SurfaceCapabilitiesKHR& surfaceCapabilities) +{ + std::vector presentModes = device.getVkPhysicalDevice().getSurfacePresentModesKHR(window.getVkSurface()); + + vk::PresentModeKHR presentMode = vk::PresentModeKHR::eImmediate; + std::uint32_t imageCount = 2; + if (std::ranges::find(presentModes, vk::PresentModeKHR::eMailbox) != presentModes.end()) + { + presentMode = vk::PresentModeKHR::eMailbox; + imageCount = 3; + } + imageCount = std::max(imageCount, surfaceCapabilities.minImageCount); + if (surfaceCapabilities.maxImageCount > 0) + { + imageCount = std::min(imageCount, surfaceCapabilities.maxImageCount); + } + return std::make_pair(presentMode, imageCount); +} + +vk::SurfaceFormatKHR detectImageFormat(const Device& device, const Window& window, const vk::ImageUsageFlags imageUsage) +{ + std::vector surfaceFormats = device.getVkPhysicalDevice().getSurfaceFormatsKHR(window.getVkSurface()); + + int resultIdx = -1; + for (std::size_t idx = 0; idx < surfaceFormats.size(); ++idx) + { + const vk::SurfaceFormatKHR surfaceFormat = surfaceFormats[idx]; + vk::ImageFormatProperties formatProperties; + + try + { + formatProperties = device.getVkPhysicalDevice().getImageFormatProperties( + /* format = */ surfaceFormat.format, + /* type = */ vk::ImageType::e2D, + /* tiling = */ vk::ImageTiling::eOptimal, + /* usage = */ imageUsage + ); + } + catch(vk::FormatNotSupportedError&) + { + continue; // not supported + } + + if (resultIdx < 0) + { + resultIdx = static_cast(idx); + } + else if (surfaceFormat.format == vk::Format::eB8G8R8A8Srgb + && surfaceFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) + { + resultIdx = static_cast(idx); + break; + } + } + + if (resultIdx < 0) + { + logAndDie("Error creating Vulkan swapchain, no compatible image format found."); + } + + return surfaceFormats[resultIdx]; +} + +vk::Extent2D detectImageExtent(const Window& window, const vk::SurfaceCapabilitiesKHR& surfaceCapabilities) +{ + // default to current extent (chosen by the driver) + vk::Extent2D extent = surfaceCapabilities.currentExtent; + + // alternatively ask SDL about it + if (extent.width == 0xFFFFFFFF && extent.height == 0xFFFFFFFF) + { + int width, height; // NOLINT(readability-isolate-declaration, cppcoreguidelines-init-variables) + SDL_Vulkan_GetDrawableSize(window.getSDLWindow(), &width, &height); + + extent.width = std::clamp(static_cast(width), surfaceCapabilities.minImageExtent.width, surfaceCapabilities.maxImageExtent.width); + extent.height = std::clamp(static_cast(height), surfaceCapabilities.minImageExtent.height, surfaceCapabilities.maxImageExtent.height); + } + + return extent; +} +} // namespace + +Swapchain::Swapchain(ObjectPtr owner, SwapchainCreationArgs args) + : super_t(std::move(owner)), mWindow(std::move(args.window)), mImageUsage(args.imageUsage) +{ + MIJIN_ASSERT(mWindow, "Invalid SwapchainCreationArgs: window cannot be NULL."); + + mImageAvailableSemaphores.resize(args.parallelFrames); + recreate(); +} +Swapchain::~Swapchain() noexcept +{ + IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroySwapchainKHR) +} + +mijin::Task<> Swapchain::c_present(const PresentArgs& args) +{ + while (!mWindow->isVisible()) { + co_await mijin::c_suspend(); + } + + const vk::PresentInfoKHR presentInfo = + { + .waitSemaphoreCount = static_cast(args.waitSemaphores.size()), + .pWaitSemaphores = args.waitSemaphores.data(), + .swapchainCount = 1, + .pSwapchains = &mHandle, + .pImageIndices = &mCurrentImageIdx + }; + + vk::Result result; // NOLINT(cppcoreguidelines-init-variables) + try { + result = args.queue.presentKHR(presentInfo); + } catch(vk::OutOfDateKHRError&) { + result = vk::Result::eErrorOutOfDateKHR; + } + mCurrentImageIdx = INVALID_IMAGE_INDEX; + + // next image, please + mCurrentFrameIdx = (mCurrentFrameIdx + 1) % static_cast(getNumParallelFrames()); + if (result == vk::Result::eSuccess) { + co_await c_acquireImage(); + } + else { + recreate(); + } +} + +void Swapchain::recreate() +{ + const vk::SurfaceCapabilitiesKHR surfaceCapabilities = getOwner()->getVkPhysicalDevice().getSurfaceCapabilitiesKHR(mWindow->getVkSurface()); + const bool firstCreate = !mHandle; + + // doing this here seems to fix the device loss during recreation + getOwner()->getVkHandle().waitIdle(); + + vk::SwapchainCreateInfoKHR createInfo; + + // surface + createInfo.surface = mWindow->getVkSurface(); + + // old swapchain + createInfo.oldSwapchain = mHandle; + + // some defaults + createInfo.imageSharingMode = vk::SharingMode::eExclusive; + createInfo.clipped = VK_TRUE; + createInfo.preTransform = surfaceCapabilities.currentTransform; + + // queue families + createInfo.queueFamilyIndexCount = 1; + createInfo.pQueueFamilyIndices = &getOwner()->getDeviceInfo().graphicsQueueFamily; + + // present mode and image count + std::tie(createInfo.presentMode, createInfo.minImageCount) = detectPresentMode(*getOwner(), *mWindow, surfaceCapabilities); + + // image usage + createInfo.imageUsage = mImageUsage; + MIJIN_ASSERT((surfaceCapabilities.supportedUsageFlags & createInfo.imageUsage) == createInfo.imageUsage, "Invalid image usage flags when creating surface."); + + // image format and color space + const vk::SurfaceFormatKHR surfaceFormat = detectImageFormat(*getOwner(), *mWindow, createInfo.imageUsage); + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + + // image extent + createInfo.imageExtent = detectImageExtent(*mWindow, surfaceCapabilities); + createInfo.imageArrayLayers = 1; + + // composite alpha + createInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; + MIJIN_ASSERT((surfaceCapabilities.supportedCompositeAlpha & createInfo.compositeAlpha) == createInfo.compositeAlpha, "Invalid composite alpha when creating surface."); + + mHandle = getOwner()->getVkHandle().createSwapchainKHR(createInfo); + mFormat = createInfo.imageFormat; + mExtent = createInfo.imageExtent; + + //for (ObjectPtr& image : mImages) + //{ + // image->unwrap(); + // unregisterImage(&image); + //} + mImages.clear(); + + // retrieve swapchain images + const std::vector imageHandles = getOwner()->getVkHandle().getSwapchainImagesKHR(mHandle); + mImages.reserve(imageHandles.size()); + + // and create image views + // destroyImageViews(); + // imageViews.reserve(imageHandles.size()); + + for (const vk::Image image : imageHandles) + { + mImages.emplace_back(getOwner()->createChild(ImageWrapArgs{ + .handle = image, + .type = vk::ImageType::e2D, + .format = createInfo.imageFormat, + .usage = createInfo.imageUsage, + .size = { + createInfo.imageExtent.width, + createInfo.imageExtent.height, + 1 + } + })); + + + // const ImageViewCreateInfo ivCreateInfo = + // { + // .nativeImage = image, + // .viewType = vk::ImageViewType::e2D, + // .format = createInfo.imageFormat, + // .components = + // { + // .r = vk::ComponentSwizzle::eIdentity, + // .g = vk::ComponentSwizzle::eIdentity, + // .b = vk::ComponentSwizzle::eIdentity, + // .a = vk::ComponentSwizzle::eIdentity + // }, + // .subresourceRange = + // { + // .aspectMask = vk::ImageAspectFlagBits::eColor, + // .baseMipLevel = 0, + // .levelCount = 1, + // .baseArrayLayer = 0, + // .layerCount = 1 + // } + // }; + // imageViews.emplace_back().create(ivCreateInfo); + + // images.back().setDebugName(fmt::format("Swapchain Image #{}", images.size() - 1)); + // registerImage(&images.back()); + } + + // create "image available" semaphores + for (ObjectPtr& semaphore : mImageAvailableSemaphores) + { + semaphore = getOwner()->createChild(); + } + + if (!firstCreate) + { + getOwner()->getVkHandle().destroySwapchainKHR(createInfo.oldSwapchain); + } + + // update size + // imageSize.width = createInfo.imageExtent.width; + // imageSize.height = createInfo.imageExtent.height; + // imageFormat = createInfo.imageFormat; + + // already request the first frame + acquireImage(); + + recreated.emit(); +} + +void Swapchain::acquireImage() +{ + MIJIN_ASSERT(mCurrentImageIdx == INVALID_IMAGE_INDEX, "Attempting to acquire a new image before presenting."); + + try + { + const vk::ResultValue result = getOwner()->getVkHandle().acquireNextImageKHR(mHandle, std::numeric_limits::max(), *mImageAvailableSemaphores[mCurrentFrameIdx]); + if (result.result != vk::Result::eSuccess) + { + assert(result.result == vk::Result::eSuboptimalKHR); + recreate(); + return; + } + mCurrentImageIdx = result.value; + mImages[mCurrentImageIdx]->resetUsage(ResetLayout::YES); + } + catch(vk::OutOfDateKHRError&) { + recreate(); + } +} + +mijin::Task<> Swapchain::c_acquireImage() +{ + try + { + vk::ResultValue result = vk::ResultValue(vk::Result::eTimeout, 0); + while(true) + { + result = getOwner()->getVkHandle().acquireNextImageKHR(mHandle, 0, *mImageAvailableSemaphores[mCurrentFrameIdx]); + if (result.result != vk::Result::eTimeout && result.result != vk::Result::eNotReady) { + break; + } + co_await mijin::c_suspend(); + } + if (result.result != vk::Result::eSuccess) + { + MIJIN_ASSERT(result.result == vk::Result::eSuboptimalKHR, "Error acquiring swapchain image."); + recreate(); + co_return; + } + mCurrentImageIdx = result.value; + mImages[mCurrentImageIdx]->resetUsage(ResetLayout::YES); + } + catch(vk::OutOfDateKHRError&) { + recreate(); + } +} +} diff --git a/source/texture.cpp b/source/texture.cpp new file mode 100644 index 0000000..71dc068 --- /dev/null +++ b/source/texture.cpp @@ -0,0 +1,47 @@ + +#include "iwa/texture.hpp" + +#include "iwa/device.hpp" +#include "iwa/resource/bitmap.hpp" + +namespace iwa +{ +Texture::Texture(TextureCreationArgs args) : super_t(nullptr), mImage(std::move(args.image)), + mImageView(mImage->createImageView(args.imageViewArgs)), mSampler(mImage->getOwner()->createChild(args.samplerArgs)) +{ + +} + +mijin::Task> Texture::c_createSingleColor(const ObjectPtr& device, const SingleColorTextureArgs& args) +{ + ObjectPtr bitmap = Bitmap::create(BitmapCreationArgs{ + .format = args.format, + .size = {.width = 1, .height = 1} + }); + bitmap->fill(args.color); + co_return co_await c_createFromBitmap(device, {.bitmap = *bitmap}); +} + +mijin::Task> Texture::c_createFromBitmap(const ObjectPtr& device, const TextureFromBitmapArgs& args) +{ + ImageCreationArgs imageCreationArgs = args.imageArgs; + imageCreationArgs.format = args.bitmap.getFormat(); + imageCreationArgs.extent.width = args.bitmap.getSize().width; + imageCreationArgs.extent.height = args.bitmap.getSize().height; + imageCreationArgs.extent.depth = 1; + imageCreationArgs.usage |= vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst; + ObjectPtr image = device->createChild(imageCreationArgs); + image->allocateMemory(); + co_await image->c_upload(args.bitmap); + co_await image->c_doTransition({ + .stages = vk::PipelineStageFlagBits::eFragmentShader, + .layout = vk::ImageLayout::eShaderReadOnlyOptimal, + .access = vk::AccessFlagBits::eShaderRead + }); + co_return create(TextureCreationArgs{ + .image = std::move(image), + .imageViewArgs = args.imageViewArgs, + .samplerArgs = args.samplerArgs + }); +} +} // namespace iwa diff --git a/source/util/glsl_compiler.cpp b/source/util/glsl_compiler.cpp new file mode 100644 index 0000000..90c972a --- /dev/null +++ b/source/util/glsl_compiler.cpp @@ -0,0 +1,548 @@ + +#include "iwa/util/glsl_compiler.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "iwa/device.hpp" +#include "iwa/instance.hpp" +#include "iwa/log.hpp" +#include "iwa/util/dir_stack_file_includer.hpp" +#include "iwa/util/reflect_glsl.hpp" + +namespace fs = std::filesystem; + +namespace iwa +{ +namespace +{ +class CustomFileIncluder : public impl::DirStackFileIncluder +{ +private: + fs::path workingDir; + mijin::FileSystemAdapter& mFsAdapter; +public: + explicit CustomFileIncluder(mijin::FileSystemAdapter& fsAdapter) noexcept: mFsAdapter(fsAdapter) + {} + +public: + void setWorkingDir(const fs::path& workingDir_) + { workingDir = workingDir_; } + +protected: // overrides + IncludeResult* readLocalPath(const char* headerName, const char* includerName, int depth) override + { + // Discard popped include directories, and + // initialize when at parse-time first level. + directoryStack.resize(depth + externalLocalDirectoryCount); + if (depth == 1) + { + directoryStack.back() = getDirectory(includerName); + } + + // Find a directory that works, using a reverse search of the include stack. + for (auto it = directoryStack.rbegin(); it != directoryStack.rend(); ++it) + { + std::string path = *it + '/' + headerName; + std::replace(path.begin(), path.end(), '\\', '/'); + + std::unique_ptr stream; + mijin::StreamError error = mijin::StreamError::UNKNOWN_ERROR; + if (workingDir != fs::path()) + { + // try relative include first + error = mFsAdapter.open(workingDir / path, mijin::FileOpenMode::READ, stream); + } + if (error != mijin::StreamError::SUCCESS) + { + error = mFsAdapter.open(path, mijin::FileOpenMode::READ, stream); + } + if (error == mijin::StreamError::SUCCESS) + { + directoryStack.push_back(getDirectory(path)); + includedFiles.insert(path); + return newCustomIncludeResult(path, *stream); + } + } + + return nullptr; + } + + // Do actual reading of the file, filling in a new include result. + IncludeResult* newCustomIncludeResult(const std::string& path, mijin::Stream& stream) const + { + (void) stream.seek(0, mijin::SeekMode::RELATIVE_TO_END); + const std::size_t length = stream.tell(); + (void) stream.seek(0); + + char* content = new tUserDataElement[length]; // NOLINT(cppcoreguidelines-owning-memory) + const mijin::StreamError error = stream.readRaw(content, length); + if (error != mijin::StreamError::SUCCESS) + { + logAndDie("Error reading include file."); + } + return new IncludeResult(path, content, length, content); // NOLINT(cppcoreguidelines-owning-memory) + } +}; + +class SemanticIoResolver : public glslang::TDefaultIoResolverBase +{ +private: + const std::vector& mMappings; +public: + SemanticIoResolver(const glslang::TIntermediate& intermediate, const std::vector& mappings) + : glslang::TDefaultIoResolverBase(intermediate), mMappings(mappings) {} + + bool validateBinding(EShLanguage /* stage */, glslang::TVarEntryInfo& /* ent */) override { return true; } + + glslang::TResourceType getResourceType(const glslang::TType& type) override { + if (isImageType(type)) { + return glslang::EResImage; + } + if (isTextureType(type)) { + return glslang::EResTexture; + } + if (isSsboType(type)) { + return glslang::EResSsbo; + } + if (isSamplerType(type)) { + return glslang::EResSampler; + } + if (isUboType(type)) { + return glslang::EResUbo; + } + return glslang::EResCount; + } + + int resolveBinding(EShLanguage stage, glslang::TVarEntryInfo& ent) override + { + const glslang::TType& type = ent.symbol->getType(); + if (type.getQualifier().hasSemantic()) + { + const unsigned semantic = type.getQualifier().layoutSemantic; + const unsigned semanticIdx = type.getQualifier().hasSemanticIndex() ? type.getQualifier().layoutSemanticIndex : 0; + auto it = std::ranges::find_if(mMappings, [&](const GLSLSemanticMapping& mapping) + { + return mapping.semantic == semantic && mapping.semanticIdx == semanticIdx; + }); + if (it != mMappings.end()) { + return ent.newBinding = it->newBinding; + } + } + + // default implementation + const int set = getLayoutSet(type); + // On OpenGL arrays of opaque types take a seperate binding for each element + const int numBindings = referenceIntermediate.getSpv().openGl != 0 && type.isSizedArray() ? type.getCumulativeArraySize() : 1; + const glslang::TResourceType resource = getResourceType(type); + if (resource < glslang::EResCount) { + if (type.getQualifier().hasBinding()) { + return ent.newBinding = reserveSlot( + set, getBaseBinding(stage, resource, set) + type.getQualifier().layoutBinding, numBindings); + } + if (ent.live && doAutoBindingMapping()) { + // find free slot, the caller did make sure it passes all vars with binding + // first and now all are passed that do not have a binding and needs one + return ent.newBinding = getFreeSlot(set, getBaseBinding(stage, resource, set), numBindings); + } + } + return ent.newBinding = -1; + } + int resolveSet(EShLanguage stage, glslang::TVarEntryInfo& ent) override + { + const glslang::TType& type = ent.symbol->getType(); + if (type.getQualifier().hasSemantic()) + { + const unsigned semantic = type.getQualifier().layoutSemantic; + const unsigned semanticIdx = type.getQualifier().hasSemanticIndex() ? type.getQualifier().layoutSemanticIndex : 0; + auto it = std::ranges::find_if(mMappings, [&](const GLSLSemanticMapping& mapping) + { + return mapping.semantic == semantic && mapping.semanticIdx == semanticIdx; + }); + if (it == mMappings.end()) { + return glslang::TDefaultIoResolverBase::resolveSet(stage, ent); + } + return ent.newSet = it->newSet; + } + return glslang::TDefaultIoResolverBase::resolveSet(stage, ent); + } + void addStage(EShLanguage stage, glslang::TIntermediate& stageIntermediate) override + { + nextInputLocation = nextOutputLocation = 0; + glslang::TDefaultIoResolverBase::addStage(stage, stageIntermediate); + } +}; + +void initGlslang() noexcept +{ + static bool inited = false; + if (inited) + { + return; + } + inited = true; + if (!glslang::InitializeProcess()) + { + logAndDie("Error initializing Glslang."); + } +} +} // namespace + +EShLanguage typeToGlslang(vk::ShaderStageFlagBits type) noexcept +{ + switch (type) + { + case vk::ShaderStageFlagBits::eCompute: + return EShLangCompute; + case vk::ShaderStageFlagBits::eVertex: + return EShLangVertex; + case vk::ShaderStageFlagBits::eFragment: + return EShLangFragment; + case vk::ShaderStageFlagBits::eRaygenKHR: + return EShLangRayGen; + case vk::ShaderStageFlagBits::eClosestHitKHR: + return EShLangClosestHit; + case vk::ShaderStageFlagBits::eAnyHitKHR: + return EShLangAnyHit; + case vk::ShaderStageFlagBits::eMissKHR: + return EShLangMiss; + case vk::ShaderStageFlagBits::eIntersectionKHR: + return EShLangIntersect; + case vk::ShaderStageFlagBits::eCallableKHR: + return EShLangCallable; + case vk::ShaderStageFlagBits::eTaskEXT: + return EShLangTask; + case vk::ShaderStageFlagBits::eMeshEXT: + return EShLangMesh; + case vk::ShaderStageFlagBits::eTessellationControl: + return EShLangTessControl; + case vk::ShaderStageFlagBits::eTessellationEvaluation: + return EShLangTessEvaluation; + case vk::ShaderStageFlagBits::eGeometry: + return EShLangGeometry; + case vk::ShaderStageFlagBits::eAllGraphics: + case vk::ShaderStageFlagBits::eAll: + case vk::ShaderStageFlagBits::eSubpassShadingHUAWEI: + case vk::ShaderStageFlagBits::eClusterCullingHUAWEI: + break; // let it fail + } + + logAndDie("Invalid value passed to typeToGlslang!"); +} + +ShaderSource ShaderSource::fromStream(mijin::Stream& stream, std::string fileName) +{ + ShaderSource result = { + .fileName = std::move(fileName) + }; + if (const mijin::StreamError error = stream.readAsString(result.code); error != mijin::StreamError::SUCCESS) + { + // TODO: custom exception type, for stacktrace and stuff + throw std::runtime_error("Error reading shader source."); + } + return result; +} + +ShaderSource ShaderSource::fromFile(const mijin::PathReference& file) +{ + std::unique_ptr stream; + if (const mijin::StreamError error = file.open(mijin::FileOpenMode::READ, stream); error != mijin::StreamError::SUCCESS) + { + throw std::runtime_error("Error opening file for reading shader source."); + } + return fromStream(*stream, file.getPath().string()); +} + +ShaderSource ShaderSource::fromYaml(const YAML::Node& node, const mijin::PathReference& yamlFile) +{ + if (node.Tag() == "!load") + { + return fromFile(yamlFile.getAdapter()->getPath(node.as())); + } + const std::string& source = node["source"].as(); + std::string fileName; + if (const YAML::Node fileNameNode = node["fileName"]; !fileNameNode.IsNull()) + { + fileName = fileNameNode.as(); + } + return { + .code = source, + .fileName = std::move(fileName) + }; +} + +GLSLShader::GLSLShader(ObjectPtr owner, GLSLShaderCreationArgs args) noexcept + : super_t(std::move(owner)), mType(args.type), mSources(std::move(args.sources)), mDefines(std::move(args.defines)) +{ + MIJIN_ASSERT(!mSources.empty(), "Cannot compile without sources."); + compile(); +} + +GLSLShader::~GLSLShader() noexcept = default; + +std::unique_ptr GLSLShader::releaseHandle() +{ + if (mHandle == nullptr) { + compile(); + } + return std::exchange(mHandle, nullptr); +} + +ShaderMeta GLSLShader::getPartialMeta() +{ + if (mHandle == nullptr) { + compile(); + } + return reflectShader(*mHandle); +} + +void GLSLShader::compile() +{ + initGlslang(); + + const EShLanguage stage = typeToGlslang(mType); + + std::unique_ptr shader = std::make_unique(stage); // NOLINT(cppcoreguidelines-owning-memory) + + std::vector sources; + std::vector lengths; + std::vector names; + sources.reserve(mSources.size() + 1); + lengths.reserve(mSources.size() + 1); + names.reserve(mSources.size() + 1); + + std::string preamble = getOwner()->getInstanceExtension().getCommonPreamble(); + for (const std::string& define : mDefines) { + preamble.append(fmt::format("\n#define {}\n", define)); + } + sources.push_back(preamble.c_str()); + lengths.push_back(static_cast(preamble.size())); + names.push_back(""); + + for (const ShaderSource& source : mSources) + { + sources.push_back(source.code.c_str()); + lengths.push_back(static_cast(source.code.size())); + names.push_back(source.fileName.c_str()); + } + shader->setStringsWithLengthsAndNames(sources.data(), lengths.data(), names.data(), static_cast(sources.size())); + shader->setDebugInfo(true); + shader->setEnvInput(glslang::EShSourceGlsl, stage, glslang::EShClientVulkan, glslang::EShTargetVulkan_1_3); + shader->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_3); + shader->setEnvTarget(glslang::EShTargetLanguage::EShTargetSpv, glslang::EShTargetSpv_1_6); + shader->setAutoMapLocations(true); + shader->setAutoMapBindings(true); + + const EShMessages PREPROCESS_MESSAGES = static_cast(EShMsgDefault +#if !defined(KAZAN_RELEASE) + | EShMsgDebugInfo +#endif + ); + std::string completeCode; + CustomFileIncluder includer(getOwner()->getPrimaryFSAdapter()); // TODO: this type seems to be doing stupid things, investigate + const std::string sourceFileAbsStr = mSources[0].fileName; // just for you MSVC <3 + if (!sourceFileAbsStr.empty()) { + includer.setWorkingDir(fs::path(sourceFileAbsStr).parent_path()); + } + const bool couldPreprocess = shader->preprocess( + /* builtInResources = */ GetDefaultResources(), + /* defaultVersion = */ 450, + /* defaultProfile = */ ECoreProfile, + /* forceDefaultVersionAndProfile = */ false, + /* forwardCompatible = */ false, + /* message = */ PREPROCESS_MESSAGES, + /* outputString = */ &completeCode, + /* includer = */ includer + ); + if (!couldPreprocess) + { + logMsg("GLSL preprocessing failed:\ninfo log:\n{}\ndebug log:\n{}", + shader->getInfoLog(), shader->getInfoDebugLog() + ); + logAndDie("Error preprocessing shader."); + } + +#if 0 + ShaderPreprocessResult preprocessResult = preprocessShader(completeCode); + + for (std::string& module : preprocessResult.importedModules) { + importedModules.insert(std::move(module)); + } + + for (std::string& option : preprocessResult.supportedOptions) { + supportedOptions.insert(std::move(option)); + } +#endif + + const char* newSource = completeCode.c_str(); +#if defined(KAZAN_RELEASE) + shader->setStrings(&newSource, 1); // replace source with the preprocessed one +#else + const int newSourceLen = static_cast(std::strlen(newSource)); + const char* newSourceName = sourceFileAbsStr.c_str(); + shader->setStringsWithLengthsAndNames(&newSource, &newSourceLen, &newSourceName, 1); +#endif + const EShMessages PARSE_MESSAGES = static_cast(EShMsgDefault +#if !defined(KAZAN_RELEASE) + | EShMsgDebugInfo +#endif + ); + const bool couldParse = shader->parse( + /* builtinResources = */ GetDefaultResources(), + /* defaultVersion = */ 450, + /* forwardCompatible = */ false, + /* messages = */ PARSE_MESSAGES + ); + if (!couldParse) + { + logMsg("GLSL parsing failed:\ninfo log:\n{}\ndebug log:\n{}", + shader->getInfoLog(), shader->getInfoDebugLog() + ); + logAndDie("Error parsing shader."); + } + + mHandle = std::move(shader); +} + +GLSLShaderProgram::GLSLShaderProgram(ObjectPtr owner, GLSLShaderProgramCreationArgs args) + : super_t(std::move(owner)), mLinkFlags(args.linkFlags) +{ + MIJIN_ASSERT_FATAL(!args.shaders.empty(), "At least one shader per program is required!"); + + mHandle = std::make_unique(); + for (const ObjectPtr& shader : args.shaders) + { + mShaderHandles.push_back(shader->releaseHandle()); + mHandle->addShader(mShaderHandles.back().get()); + } + + const EShMessages linkMessages = static_cast(EShMsgSpvRules | EShMsgVulkanRules + | (args.linkFlags.withDebugInfo ? EShMsgDebugInfo : EShMessages(0))); + + if (!mHandle->link(linkMessages)) + { + logAndDie("GLSL linking failed!\ninfo log:\n{}\ndebug log:\n{}", + mHandle->getInfoLog(), mHandle->getInfoDebugLog() + ); + } + + glslang::TIntermediate* referenceIntermediate = mHandle->getIntermediate(typeToGlslang(args.shaders[0]->getType())); + SemanticIoResolver ioResolver(*referenceIntermediate, args.semanticMappings); + if (!mHandle->mapIO(&ioResolver)) + { + logAndDie("GLSL io mapping failed!\ninfo log:\n{}\ndebug log:\n{}", + mHandle->getInfoLog(), mHandle->getInfoDebugLog() + ); + } + + mMeta = reflectProgram(*mHandle); +} + +std::vector GLSLShaderProgram::generateSpirv(vk::ShaderStageFlagBits stage) const +{ + const EShLanguage glslLang = typeToGlslang(stage); + + glslang::SpvOptions options = {}; + if (mLinkFlags.withDebugInfo) + { + options.generateDebugInfo = true; + options.stripDebugInfo = false; + options.disableOptimizer = true; + options.emitNonSemanticShaderDebugInfo = true; + options.emitNonSemanticShaderDebugSource = false; // TODO: this should be true, but makes GLSLang crash + } + else + { + options.generateDebugInfo = false; + options.stripDebugInfo = true; + options.disableOptimizer = false; + options.emitNonSemanticShaderDebugInfo = true; // TODO: this should be false, but that also crashes GLSLang ... + options.emitNonSemanticShaderDebugSource = false; + } + options.optimizeSize = false; + options.disassemble = false; + options.validate = true; + + spv::SpvBuildLogger logger; + const glslang::TIntermediate* intermediate = mHandle->getIntermediate(glslLang); + if (intermediate == nullptr) + { + throw std::runtime_error("Attempting to generate SpirV from invalid shader stage."); + } + std::vector spirv; + glslang::GlslangToSpv(*intermediate, spirv, &logger, &options); + + const std::string messages = logger.getAllMessages(); + if (!messages.empty()) + { + logMsg("SpirV messages: {}", messages); + } + + return spirv; +} + +std::vector GLSLShaderProgram::generatePipelineStages() const +{ + std::vector stages; + for (const vk::ShaderStageFlagBits stage : mMeta.stages) + { + const std::vector spirv = generateSpirv(stage); + stages.push_back({ + .shader = getOwner()->createChild(ShaderModuleCreationArgs{.code = spirv}), + .stage = stage + }); + } + return stages; +} + +GraphicsPipelineCreationArgs GLSLShaderProgram::prepareGraphicsPipeline(PrepareGraphicsPipelineArgs& args) const +{ + args.pipelineLayoutMeta = mMeta.generatePipelineLayout(args.layoutArgs); + args.layouts = args.pipelineLayoutMeta.createPipelineLayout(*getOwner()); + return { + .stages = generatePipelineStages(), + .vertexInput = mMeta.generateVertexInputFromLayout(args.vertexLayout), + .layout = args.layouts.pipelineLayout + }; +} + +ComputePipelineCreationArgs GLSLShaderProgram::prepareComputePipeline(PrepareComputePipelineArgs& args) const +{ + args.pipelineLayoutMeta = mMeta.generatePipelineLayout(args.layoutArgs); + args.layouts = args.pipelineLayoutMeta.createPipelineLayout(*getOwner()); + return { + .stage = generatePipelineStages()[0], + .layout = args.layouts.pipelineLayout + }; +} + +// GraphicsPipelineCreationArgs prepareGLSLGraphicsPipeline(const PrepareGLSLGraphicsPipelineArgs& args) +// { +// return { +// .stages = +// { +// PipelineStage{.shader = vertexShaderModule, .stage = vk::ShaderStageFlagBits::eVertex}, +// PipelineStage{.shader = fragmentShaderModule, .stage = vk::ShaderStageFlagBits::eFragment} +// }, +// .vertexInput = std::move(vertexInput), +// .inputAssembly = +// { +// .topology = vk::PrimitiveTopology::eTriangleStrip +// }, +// .colorBlend = +// { +// .attachements = {DEFAULT_BLEND_ATTACHMENT} +// }, +// .renderingInfo = GraphicsPipelineRenderingInfo{ +// .colorAttachmentFormats = {mRenderTarget->getFormat()} +// }, +// .layout = mPipelineLayout +// }; +// } +} // namespace iwa diff --git a/source/util/growing_descriptor_pool.cpp b/source/util/growing_descriptor_pool.cpp new file mode 100644 index 0000000..e4f6ee4 --- /dev/null +++ b/source/util/growing_descriptor_pool.cpp @@ -0,0 +1,33 @@ + +#include "iwa/util/growing_descriptor_pool.hpp" + +#include + +#include "iwa/device.hpp" + +namespace iwa +{ +GrowingDescriptorPool::GrowingDescriptorPool(ObjectPtr owner, GrowingDescriptorPoolCreationArgs args) + : super_t(std::move(owner)), mCreationArgs(std::move(args)) +{ + +} + +ObjectPtr GrowingDescriptorPool::allocateDescriptorSet(const DescriptorSetAllocateArgs& args) +{ + for (const ObjectPtr& pool : mPools) + { + try + { + return pool->allocateDescriptorSet(args); + } + catch (vk::FragmentedPoolError&) {} + catch (vk::OutOfPoolMemoryError&) {} + // any other error shall be forwarded + } + + // couldn't allocate in any of the existing pools, create a new one + mPools.push_back(getOwner()->createChild(mCreationArgs)); + return mPools.back()->allocateDescriptorSet(args); // raise any error that may occur +} +} // namespace iwa diff --git a/source/util/image_reference.cpp b/source/util/image_reference.cpp new file mode 100644 index 0000000..8a772e6 --- /dev/null +++ b/source/util/image_reference.cpp @@ -0,0 +1,147 @@ + +#include "iwa/util/image_reference.hpp" + +#include +#include "iwa/command.hpp" +#include "iwa/device.hpp" + +namespace iwa +{ +ImageReference::ImageReference(ObjectPtr owner) : super_t(std::move(owner)) +{ + +} + +void ImageReference::finalize(ImageReferenceFinalizeArgs& /* args */) {} + +mijin::Task<> ImageReference::c_present() +{ + co_return; +} + +SwapchainImageReference::SwapchainImageReference(ObjectPtr owner, SwapchainImageReferenceCreationArgs args) + : super_t(std::move(owner)), mSwapchain(std::move(args.swapchain)) +{ + mPresentReadySemaphores.resize(mSwapchain->getNumParallelFrames()); + for (ObjectPtr& semaphore : mPresentReadySemaphores) + { + semaphore = getOwner()->createChild(); + } + createImageViews(); + mSwapchain->recreated.connect([this]() + { + createImageViews(); + }); +} + +vk::Format SwapchainImageReference::getFormat() +{ + return mSwapchain->getFormat(); +} + +vk::Extent2D SwapchainImageReference::getExtent() +{ + return mSwapchain->getExtent(); +} + +ImageReferenceFrame SwapchainImageReference::getCurrentFrame() +{ + return ImageReferenceFrame{ + .image = mSwapchain->getCurrentImage().getRaw(), + .imageView = mImageViews[mSwapchain->getCurrentImageIdx()].getRaw() + }; +} + +void SwapchainImageReference::finalize(ImageReferenceFinalizeArgs& args) +{ + args.waitSemaphores.push_back(*mSwapchain->getCurrentAvailableSemaphore()); + args.signalSemaphores.push_back(*mPresentReadySemaphores[mSwapchain->getCurrentFrameIdx()]); + + mSwapchain->getCurrentImage()->applyTransition(args.cmdBuffer, ImageTransition{ + .stages = vk::PipelineStageFlagBits::eBottomOfPipe, + .layout = vk::ImageLayout::ePresentSrcKHR, + .access = {} + }); +} + +mijin::Task<> SwapchainImageReference::c_present() +{ + // and present + co_await mSwapchain->c_present({ + .queue = getOwner()->getGraphicsQueue(), + .waitSemaphores = {mPresentReadySemaphores[mSwapchain->getCurrentFrameIdx()]->getVkHandle()} + }); +} + +void SwapchainImageReference::createImageViews() +{ + mImageViews.resize(mSwapchain->getImages().size()); + for (auto [image, imageView] : mijin::zip(mSwapchain->getImages(), mImageViews)) + { + imageView = image->createImageView(); + } +} + + +DirectImageReference::DirectImageReference(ObjectPtr owner, DirectImageReferenceCreationArgs args) + : super_t(std::move(owner)), mImage(std::move(args.image)), mImageView(std::move(args.imageView)) +{ + +} + +vk::Format DirectImageReference::getFormat() +{ + return mImage->getFormat(); +} + +vk::Extent2D DirectImageReference::getExtent() +{ + return { + .width = mImage->getSize().width, + .height = mImage->getSize().height + }; +} + +ImageReferenceFrame DirectImageReference::getCurrentFrame() +{ + return ImageReferenceFrame{ + .image = mImage.getRaw(), + .imageView = mImageView.getRaw() + }; +} + +AutoResizeImageReference::AutoResizeImageReference(ObjectPtr owner, AutoResizeImageReferenceCreationArgs args) + : super_t(std::move(owner), DirectImageReferenceCreationArgs{}), mReferenceImageRef(std::move(args.referenceImageRef)), + mImageCreationArgs(std::move(args.imageCreationArgs)), mImageViewCreationArgs(args.imageViewCreationArgs) +{ + createImage(); +} + +vk::Extent2D AutoResizeImageReference::getExtent() +{ + return mReferenceImageRef->getExtent(); +} + +ImageReferenceFrame AutoResizeImageReference::getCurrentFrame() +{ + const vk::Extent2D extent = mReferenceImageRef->getExtent(); + if (extent.width != mImage->getSize().width || extent.height != mImage->getSize().height) { + createImage(); + } + return ImageReferenceFrame{ + .image = mImage.getRaw(), + .imageView = mImageView.getRaw() + }; +} + +void AutoResizeImageReference::createImage() +{ + const vk::Extent2D extent = mReferenceImageRef->getExtent(); + mImageCreationArgs.extent.width = extent.width; + mImageCreationArgs.extent.height = extent.height; + mImageCreationArgs.extent.depth = 1; + mImage = getOwner()->createChild(mImageCreationArgs); + mImage->allocateMemory(); + mImageView = mImage->createImageView(mImageViewCreationArgs); +} +} // namespace iwa diff --git a/source/util/reflect_glsl.cpp b/source/util/reflect_glsl.cpp new file mode 100644 index 0000000..50b93ab --- /dev/null +++ b/source/util/reflect_glsl.cpp @@ -0,0 +1,587 @@ + +#include "iwa/util/reflect_glsl.hpp" + +#include +#include +#include +#include + +namespace iwa +{ +namespace +{ +class MetaCollectingTraverser : public glslang::TIntermTraverser +{ +private: + ShaderMeta& meta; + vk::ShaderStageFlagBits shaderType; +public: + inline MetaCollectingTraverser(ShaderMeta& meta_, vk::ShaderStageFlagBits shaderType_) : meta(meta_), shaderType(shaderType_) + {} + + bool visitBinary(glslang::TVisit, glslang::TIntermBinary* node) override; + bool visitUnary(glslang::TVisit, glslang::TIntermUnary* node) override; + bool visitAggregate(glslang::TVisit, glslang::TIntermAggregate* node) override; + bool visitSelection(glslang::TVisit, glslang::TIntermSelection* node) override; + void visitConstantUnion(glslang::TIntermConstantUnion* node) override; + void visitSymbol(glslang::TIntermSymbol* node) override; + bool visitLoop(glslang::TVisit, glslang::TIntermLoop* node) override; + bool visitBranch(glslang::TVisit, glslang::TIntermBranch* node) override; + bool visitSwitch(glslang::TVisit, glslang::TIntermSwitch* node) override; +}; + +vk::Format convertGlslangBaseType(const glslang::TType& type) +{ + switch (type.getBasicType()) + { + case glslang::EbtInt: + return vk::Format::eR32Sint; + case glslang::EbtUint: + return vk::Format::eR32Uint; + case glslang::EbtFloat: + return vk::Format::eR32Sfloat; + case glslang::EbtDouble: + return vk::Format::eR64Sfloat; + default: + break; + } + + logAndDie("Don't know how to convert Glslang basic type :*("); +} + +vk::Format convertGlslangLayoutFormat(glslang::TLayoutFormat layoutFormat) +{ + switch (layoutFormat) + { + case glslang::TLayoutFormat::ElfNone: + return vk::Format::eUndefined; + + // Float image + case glslang::TLayoutFormat::ElfRgba32f: + return vk::Format::eR32G32B32A32Sfloat; + case glslang::TLayoutFormat::ElfRgba16f: + return vk::Format::eR16G16B16A16Sfloat; + case glslang::TLayoutFormat::ElfR32f: + return vk::Format::eR32Sfloat; + case glslang::TLayoutFormat::ElfRgba8: + return vk::Format::eR8G8B8A8Unorm; + case glslang::TLayoutFormat::ElfRgba8Snorm: + return vk::Format::eR8G8B8A8Snorm; + + + case glslang::TLayoutFormat::ElfRg32f: + return vk::Format::eR32G32Sfloat; + case glslang::TLayoutFormat::ElfRg16f: + return vk::Format::eR16G16Sfloat; + case glslang::TLayoutFormat::ElfR11fG11fB10f: + return vk::Format::eB10G11R11UfloatPack32; // TODO: ? + case glslang::TLayoutFormat::ElfR16f: + return vk::Format::eR16Sfloat; + case glslang::TLayoutFormat::ElfRgba16: + return vk::Format::eR16G16B16A16Unorm; + case glslang::TLayoutFormat::ElfRgb10A2: + return vk::Format::eA2R10G10B10SnormPack32; // TODO: ? + case glslang::TLayoutFormat::ElfRg16: + return vk::Format::eR16G16Unorm; + case glslang::TLayoutFormat::ElfRg8: + return vk::Format::eR8G8Unorm; + case glslang::TLayoutFormat::ElfR16: + return vk::Format::eR16Unorm; + case glslang::TLayoutFormat::ElfR8: + return vk::Format::eR8Unorm; + case glslang::TLayoutFormat::ElfRgba16Snorm: + return vk::Format::eR16G16B16A16Snorm; + case glslang::TLayoutFormat::ElfRg16Snorm: + return vk::Format::eR16G16Unorm; + case glslang::TLayoutFormat::ElfRg8Snorm: + return vk::Format::eR8G8Snorm; + case glslang::TLayoutFormat::ElfR16Snorm: + return vk::Format::eR16G16Snorm; + case glslang::TLayoutFormat::ElfR8Snorm: + return vk::Format::eR8Snorm; + + // Int image + case glslang::TLayoutFormat::ElfRgba32i: + return vk::Format::eR32G32B32A32Sint; + case glslang::TLayoutFormat::ElfRgba16i: + return vk::Format::eR16G16B16A16Sint; + case glslang::TLayoutFormat::ElfRgba8i: + return vk::Format::eR8G8B8A8Sint; + case glslang::TLayoutFormat::ElfR32i: + return vk::Format::eR32Sint; + + case glslang::TLayoutFormat::ElfRg32i: + return vk::Format::eR32G32Sint; + case glslang::TLayoutFormat::ElfRg16i: + return vk::Format::eR16G16Sint; + case glslang::TLayoutFormat::ElfRg8i: + return vk::Format::eR8G8Sint; + case glslang::TLayoutFormat::ElfR16i: + return vk::Format::eR16Sint; + case glslang::TLayoutFormat::ElfR8i: + return vk::Format::eR8Sint; + case glslang::TLayoutFormat::ElfR64i: + return vk::Format::eR64Sint; + + // Uint image + case glslang::TLayoutFormat::ElfRgba32ui: + return vk::Format::eR32G32B32A32Uint; + case glslang::TLayoutFormat::ElfRgba16ui: + return vk::Format::eR16G16B16A16Uint; + case glslang::TLayoutFormat::ElfRgba8ui: + return vk::Format::eR8G8B8A8Uint; + case glslang::TLayoutFormat::ElfR32ui: + return vk::Format::eR32Uint; + + + case glslang::TLayoutFormat::ElfRg32ui: + return vk::Format::eR32G32Uint; + case glslang::TLayoutFormat::ElfRg16ui: + return vk::Format::eR16G16Uint; + case glslang::TLayoutFormat::ElfRgb10a2ui: + return vk::Format::eA2R10G10B10UintPack32; + case glslang::TLayoutFormat::ElfRg8ui: + return vk::Format::eR8G8Uint; + case glslang::TLayoutFormat::ElfR16ui: + return vk::Format::eR16Uint; + case glslang::TLayoutFormat::ElfR8ui: + return vk::Format::eR8Uint; + case glslang::TLayoutFormat::ElfR64ui: + return vk::Format::eR64Uint; + + // other/unknown + case glslang::TLayoutFormat::ElfSize1x8: + case glslang::TLayoutFormat::ElfSize1x16: + case glslang::TLayoutFormat::ElfSize1x32: + case glslang::TLayoutFormat::ElfSize2x32: + case glslang::TLayoutFormat::ElfSize4x32: + case glslang::TLayoutFormat::ElfEsFloatGuard: + case glslang::TLayoutFormat::ElfFloatGuard: + case glslang::TLayoutFormat::ElfEsIntGuard: + case glslang::TLayoutFormat::ElfIntGuard: + case glslang::TLayoutFormat::ElfEsUintGuard: + case glslang::TLayoutFormat::ElfExtSizeGuard: + case glslang::TLayoutFormat::ElfCount: + break; + } + + logAndDie("Unexpected format in convertGlslangLayoutFormat()."); // : {}", layoutFormat); +} + +vk::Format convertGlslangVectorType(glslang::TBasicType basicType, int vectorSize) +{ + switch (basicType) + { + case glslang::EbtFloat: + switch (vectorSize) + { + case 2: + return vk::Format::eR32G32Sfloat; + case 3: + return vk::Format::eR32G32B32Sfloat; + case 4: + return vk::Format::eR32G32B32A32Sfloat; + default: + break; + } + break; + case glslang::EbtDouble: + switch (vectorSize) + { + case 2: + return vk::Format::eR64G64Sfloat; + case 3: + return vk::Format::eR64G64B64Sfloat; + case 4: + return vk::Format::eR64G64B64A64Sfloat; + default: + break; + } + break; + case glslang::EbtInt: + switch (vectorSize) + { + case 2: + return vk::Format::eR32G32Sint; + case 3: + return vk::Format::eR32G32B32Sint; + case 4: + return vk::Format::eR32G32B32A32Sint; + default: + break; + } + break; + case glslang::EbtUint: + switch (vectorSize) + { + case 2: + return vk::Format::eR32G32Uint; + case 3: + return vk::Format::eR32G32B32Uint; + case 4: + return vk::Format::eR32G32B32A32Uint; + default: + break; + } + break; + case glslang::EbtBool: // NOLINT(bugprone-branch-clone) TODO: ??? + break; + default: + break; + } + + logAndDie("Don't know how to convert Glslang vector type :("); +} + +vk::Format convertGlslangVectorType(const glslang::TType& type) +{ + assert(type.isVector()); + return convertGlslangVectorType(type.getBasicType(), type.getVectorSize()); +} + +ShaderVariableMatrixType convertGlslangMatrixType(const glslang::TType& type) +{ + assert(type.isMatrix()); + assert(type.getMatrixCols() == type.getMatrixRows()); // only supported types yet... + switch (type.getMatrixCols()) + { + case 2: + return ShaderVariableMatrixType::MAT2; + case 3: + return ShaderVariableMatrixType::MAT3; + case 4: + return ShaderVariableMatrixType::MAT4; + default: + break; + } + + logAndDie("Don't know how to convert Glslang matrix type -.-"); +} + +ImageDim convertGlslangSamplerDim(glslang::TSamplerDim dim) +{ + switch (dim) + { + case glslang::TSamplerDim::Esd1D: + return ImageDim::ONE; + case glslang::TSamplerDim::Esd2D: + return ImageDim::TWO; + case glslang::TSamplerDim::Esd3D: + return ImageDim::THREE; + case glslang::TSamplerDim::EsdCube: + return ImageDim::CUBE; + default: + break; + } + + logAndDie("Don't know how to convert Glslang sampler dimensions ..."); +} + +ShaderVariableType convertGlslangType(const glslang::TType& type) +{ + ShaderVariableType result; + if (type.isVector()) + { + result.baseType = ShaderVariableBaseType::SIMPLE; + result.simple.format = convertGlslangVectorType(type); + } else if (type.isMatrix()) + { + result.baseType = ShaderVariableBaseType::MATRIX; + result.matrixType = convertGlslangMatrixType(type); + } else if (type.isStruct()) + { + const std::size_t numMembers = type.getStruct()->size(); + result.baseType = ShaderVariableBaseType::STRUCT; + result.struct_.members.reserve(numMembers); + + std::size_t currentOffset = 0; + for (const glslang::TTypeLoc& typeLoc: *type.getStruct()) + { + ShaderVariableStructMember& member = result.struct_.members.emplace_back(); + member.name = typeLoc.type->getFieldName(); + member.type = convertGlslangType(*typeLoc.type); + member.offset = currentOffset; + if (typeLoc.type->getQualifier().hasSemantic()) + { + member.semantic = typeLoc.type->getQualifier().layoutSemantic; + } + if (typeLoc.type->getQualifier().hasSemanticIndex()) + { + member.semanticIdx = typeLoc.type->getQualifier().layoutSemanticIndex; + } + currentOffset = member.offset + calcShaderTypeSize(member.type); // TODO: padding + } + + } else if (type.getBasicType() == glslang::EbtSampler) + { + const glslang::TSampler& sampler = type.getSampler(); + result.baseType = ShaderVariableBaseType::IMAGE; + result.image.dimensions = convertGlslangSamplerDim(sampler.dim); + result.image.format = convertGlslangLayoutFormat(type.getQualifier().layoutFormat); + } else + { + result.baseType = ShaderVariableBaseType::SIMPLE; + result.simple.format = convertGlslangBaseType(type); + } + + if (type.isArray()) + { + if (type.isArrayVariablyIndexed()) + { + result.arraySize = 0; + result.dynamicArraySize = true; + } else + { + assert(type.getArraySizes()->getNumDims() == 1); // don't support multi dimensional arrays yet + result.arraySize = type.getOuterArraySize(); + } + } + + return result; +} + +vk::DescriptorType getGlslangDescriptorType(const glslang::TType& type) +{ + if (type.getBasicType() == glslang::EbtSampler) + { + if (type.getSampler().combined) + { + return vk::DescriptorType::eCombinedImageSampler; + } + if (type.getSampler().isImage()) + { + return vk::DescriptorType::eStorageImage; + } + } else if (type.isStruct()) + { + if (type.getQualifier().isUniform()) + { + return vk::DescriptorType::eUniformBuffer; + } + return vk::DescriptorType::eStorageBuffer; + } + logAndDie("No idea what to do with this type :/"); +} + +bool MetaCollectingTraverser::visitBinary(glslang::TVisit, glslang::TIntermBinary* node) +{ + (void) node; + return false; +} + +bool MetaCollectingTraverser::visitUnary(glslang::TVisit, glslang::TIntermUnary* node) +{ + (void) node; + return false; +} + +bool MetaCollectingTraverser::visitAggregate(glslang::TVisit, glslang::TIntermAggregate* node) +{ + switch (node->getOp()) + { + case glslang::EOpSequence: + return true; + case glslang::EOpFunction: + break; + case glslang::EOpLinkerObjects: + return true; + default: + break; + } + return false; +} + +bool MetaCollectingTraverser::visitSelection(glslang::TVisit, glslang::TIntermSelection* node) +{ + (void) node; + return false; +} + +void MetaCollectingTraverser::visitConstantUnion(glslang::TIntermConstantUnion* node) +{ + (void) node; +} + +void MetaCollectingTraverser::visitSymbol(glslang::TIntermSymbol* node) +{ + const bool isLinkerObject = getParentNode() + && getParentNode()->getAsAggregate() + && getParentNode()->getAsAggregate()->getOp() == glslang::EOpLinkerObjects; + if (isLinkerObject) + { + if (node->getQualifier().builtIn) + { + return; + } + + if (node->getQualifier().isUniformOrBuffer()) + { + if (node->getQualifier().isPushConstant()) + { + ShaderPushConstantBlock pushConstantBlock; + pushConstantBlock.type = convertGlslangType(node->getType()); + assert(pushConstantBlock.type.baseType == ShaderVariableBaseType::STRUCT); + meta.extendPushConstant(pushConstantBlock, ShaderTypeBits::make(shaderType)); + return; + } + const unsigned setIdx = node->getQualifier().hasSet() ? node->getQualifier().layoutSet : UNSPECIFIED_INDEX; + const unsigned binding = node->getQualifier().hasBinding() ? node->getQualifier().layoutBinding : UNSPECIFIED_INDEX; + ShaderVariableSet& set = meta.getOrCreateInterfaceVariableSet(setIdx); + assert(setIdx == UNSPECIFIED_INDEX || !set.getVariableAtBindingOpt(binding)); // multiple bindings at the same index? + set.usedInStages.set(shaderType, true); + + ShaderVariable& var = set.variables.emplace_back(); + var.binding = binding; + var.name = node->getName(); + if (node->getQualifier().hasSemantic()) + { + var.semantic = node->getQualifier().layoutSemantic; + } + if (node->getQualifier().hasSemanticIndex()) + { + var.semanticIndex = node->getQualifier().layoutSemanticIndex; + } + + // uniform blocks are identified by the name of their type + if (var.name.empty() || var.name.starts_with("anon@")) + { + const glslang::TString& typeName = node->getType().getTypeName(); + if (!typeName.empty()) + { + var.name = typeName; + } + } + var.descriptorType = getGlslangDescriptorType(node->getType()); + var.type = convertGlslangType(node->getType()); + } + else if (node->getQualifier().storage == glslang::EvqVaryingIn) + { + ShaderAttribute attribute; + attribute.stage = shaderType; + attribute.type = convertGlslangType(node->getType()); + attribute.location = node->getQualifier().hasLocation() ? node->getQualifier().layoutLocation : UNSPECIFIED_INDEX; + attribute.name = node->getName(); + if (node->getQualifier().hasSemantic()) + { + attribute.semantic = node->getQualifier().layoutSemantic; + } + if (node->getQualifier().hasSemanticIndex()) + { + attribute.semanticIndex = node->getQualifier().layoutSemanticIndex; + } + meta.addInputAttribute(std::move(attribute)); + } + else if (node->getQualifier().storage == glslang::EvqVaryingOut) + { + ShaderAttribute attribute; + attribute.stage = shaderType; + attribute.type = convertGlslangType(node->getType()); + attribute.location = node->getQualifier().hasLocation() ? node->getQualifier().layoutLocation : UNSPECIFIED_INDEX; + attribute.name = node->getName(); + if (node->getQualifier().hasSemantic()) + { + attribute.semantic = node->getQualifier().layoutSemantic; + } + if (node->getQualifier().hasSemanticIndex()) + { + attribute.semanticIndex = node->getQualifier().layoutSemanticIndex; + } + meta.addOutputAttribute(std::move(attribute)); + } + } +} + +bool MetaCollectingTraverser::visitLoop(glslang::TVisit, glslang::TIntermLoop* node) +{ + (void) node; + return false; +} + +bool MetaCollectingTraverser::visitBranch(glslang::TVisit, glslang::TIntermBranch* node) +{ + (void) node; + return false; +} + +bool MetaCollectingTraverser::visitSwitch(glslang::TVisit, glslang::TIntermSwitch* node) +{ + (void) node; + return false; +} + +vk::ShaderStageFlagBits shaderStageFromGlslang(EShLanguage language) +{ + switch (language) + { + case EShLangVertex: + return vk::ShaderStageFlagBits::eVertex; + case EShLangTessControl: + return vk::ShaderStageFlagBits::eTessellationControl; + case EShLangTessEvaluation: + return vk::ShaderStageFlagBits::eTessellationEvaluation; + case EShLangGeometry: + return vk::ShaderStageFlagBits::eGeometry; + case EShLangFragment: + return vk::ShaderStageFlagBits::eFragment; + case EShLangCompute: + return vk::ShaderStageFlagBits::eCompute; + case EShLangRayGen: + return vk::ShaderStageFlagBits::eRaygenKHR; + case EShLangIntersect: + return vk::ShaderStageFlagBits::eIntersectionKHR; + case EShLangAnyHit: + return vk::ShaderStageFlagBits::eAnyHitKHR; + case EShLangClosestHit: + return vk::ShaderStageFlagBits::eClosestHitKHR; + case EShLangMiss: + return vk::ShaderStageFlagBits::eMissKHR; + case EShLangCallable: + return vk::ShaderStageFlagBits::eCallableKHR; + case EShLangTask: + return vk::ShaderStageFlagBits::eTaskEXT; + case EShLangMesh: + return vk::ShaderStageFlagBits::eMeshEXT; + case EShLangCount: + break; // fall through + } + logAndDie("Invalid value passed to shaderStageFromGlslang!"); +} +} + +ShaderMeta reflectShader(glslang::TShader& shader) +{ + return reflectIntermediate(*shader.getIntermediate(), shaderStageFromGlslang(shader.getStage())); +} + +ShaderMeta reflectProgram(glslang::TProgram& program) +{ + ShaderMeta result; + for (int stage = 0; stage < EShLangCount; ++stage) + { + glslang::TIntermediate* intermediate = program.getIntermediate(static_cast(stage)); + if (intermediate == nullptr) { + continue; + } + result.extend(reflectIntermediate(*intermediate, shaderStageFromGlslang(static_cast(stage)))); + } + return result; +} + +ShaderMeta reflectIntermediate(glslang::TIntermediate& intermediate, vk::ShaderStageFlagBits stage) +{ + ShaderMeta meta; + MetaCollectingTraverser traverser(meta, stage); + intermediate.getTreeRoot()->traverse(&traverser); + + meta.stages.set(stage, true); + + if (stage == vk::ShaderStageFlagBits::eCompute) + { + meta.localSizeX = static_cast(intermediate.getLocalSize(0)); + meta.localSizeY = static_cast(intermediate.getLocalSize(1)); + meta.localSizeZ = static_cast(intermediate.getLocalSize(2)); + } + + return meta; +} +} // namespace iwa diff --git a/source/util/render_loop.cpp b/source/util/render_loop.cpp new file mode 100644 index 0000000..3c3b913 --- /dev/null +++ b/source/util/render_loop.cpp @@ -0,0 +1,128 @@ + +#include "iwa/util/render_loop.hpp" + +#include +#include "iwa/device.hpp" +#include "iwa/instance.hpp" + +namespace iwa +{ +namespace +{ +// BIG BIG TODO: This is a dumb workaround for sharing images (e.g. the UI image) between multiple renderers. +// The reason is that the layout change mechanism doesn't work if multiple command buffers (that are executed +// sequentially) are recorded in parallel. +// A possible fix could be to move the state tracking mechanism to the renderer and generate the barriers +// before submitting. +mijin::TaskMutex gRenderMutex; +} + +RenderLoop::RenderLoop(ObjectPtr owner, RenderLoopCreationArgs args) + : super_t(std::move(owner)), mAdvanceDeleteQueue(args.flags.advanceDeleteQueue) +{ + mAlternating.resize(args.parallelFrames); + + ObjectPtr commandPool = std::move(args.commandPool); + if (!commandPool) + { + commandPool = getOwner()->createChild(CommandPoolCreationArgs{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = getOwner()->getDeviceInfo().graphicsQueueFamily + }); + } + for (Alternating& alt : mAlternating) + { + alt.commandBuffer = commandPool->allocateCommandBuffer(); + alt.renderDoneFence = getOwner()->createChild(FenceCreationArgs{.flags = vk::FenceCreateFlagBits::eSignaled}); + } +} + +void RenderLoop::start() noexcept +{ + addTask(c_renderLoop()); +} + +mijin::Task<> RenderLoop::c_init() +{ + co_return; +} + +mijin::SimpleTaskLoop& RenderLoop::getTaskLoop() const noexcept +{ + return getOwner()->getOwner()->getMainTaskLoop(); +} + +mijin::Task<> RenderLoop::c_renderLoop() +{ + co_await c_init(); + + while (!getOwner()->getOwner()->isQuitRequested()) + { + Alternating& alt = mAlternating.at(mFrameIdx); + + // wait for the command buffer to be ready + co_await alt.renderDoneFence->c_wait(); + + // reset the fence + alt.renderDoneFence->reset(); + + vk::CommandBuffer cmdBuffer = alt.commandBuffer->getVkHandle(); + cmdBuffer.begin(vk::CommandBufferBeginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit + }); + + // record the commands + RenderLoopRenderArgs renderArgs = { + .cmdBuffer = *alt.commandBuffer, + .frameIdx = mFrameIdx + }; + { // gRenderMutex lock + const mijin::TaskMutexLock lock = co_await gRenderMutex.c_lock(); + co_await c_render(renderArgs); + + std::vector waitSemaphores; + std::vector signalSemaphores; + + ImageReferenceFinalizeArgs finalizeArgs{ + .cmdBuffer = *alt.commandBuffer, + .waitSemaphores = waitSemaphores, + .signalSemaphores = signalSemaphores + }; + for (const ObjectPtr& imageRef: renderArgs.usedImageReferences) + { + imageRef->finalize(finalizeArgs); + } + + cmdBuffer.end(); + + // submit them + const vk::PipelineStageFlags waitStage = vk::PipelineStageFlagBits::eFragmentShader; + getOwner()->getGraphicsQueue().submit(vk::SubmitInfo{ + .waitSemaphoreCount = static_cast(waitSemaphores.size()), + .pWaitSemaphores = waitSemaphores.data(), + .pWaitDstStageMask = &waitStage, + .commandBufferCount = 1, + .pCommandBuffers = &cmdBuffer, + .signalSemaphoreCount = static_cast(signalSemaphores.size()), + .pSignalSemaphores = signalSemaphores.data() + }, *alt.renderDoneFence); + } // gRenderMutex lock + + // finally present + for (const ObjectPtr& imageRef : renderArgs.usedImageReferences) + { + co_await imageRef->c_present(); + } + + // tick deleters + // TODO: what if there are multiple render loops? + if (mAdvanceDeleteQueue) + { + getOwner()->getOwner()->tickDeleteQueue(); + } + + mFrameIdx = (mFrameIdx + 1) % mAlternating.size(); + } + co_return; +} +} // namespace iwa diff --git a/source/util/shader_meta.cpp b/source/util/shader_meta.cpp new file mode 100644 index 0000000..de91b94 --- /dev/null +++ b/source/util/shader_meta.cpp @@ -0,0 +1,676 @@ + +#include "iwa/util/shader_meta.hpp" + +#include "iwa/log.hpp" +#include "kazan/resource/mesh.hpp" +#include "iwa/util/glsl_compiler.hpp" +#include "iwa/util/vkutil.hpp" + +namespace +{ +template +inline std::size_t calcCrcSizeAppend(T, std::size_t) noexcept +{ + MIJIN_TRAP(); // TODO + return 0; +} +} + +namespace iwa +{ +namespace +{ +vk::ShaderStageFlags typeBitsToVkStages(ShaderTypeBits bits) +{ + vk::ShaderStageFlags flags = {}; + if (bits.compute) + { + flags |= vk::ShaderStageFlagBits::eCompute; + } + if (bits.vertex) + { + flags |= vk::ShaderStageFlagBits::eVertex; + } + if (bits.fragment) + { + flags |= vk::ShaderStageFlagBits::eFragment; + } + if (bits.rayGeneration) + { + flags |= vk::ShaderStageFlagBits::eRaygenKHR; + } + if (bits.rayClosestHit) + { + flags |= vk::ShaderStageFlagBits::eClosestHitKHR; + } + if (bits.rayAnyHit) + { + flags |= vk::ShaderStageFlagBits::eAnyHitKHR; + } + if (bits.rayMiss) + { + flags |= vk::ShaderStageFlagBits::eMissKHR; + } + if (bits.rayIntersection) + { + flags |= vk::ShaderStageFlagBits::eIntersectionKHR; + } + if (bits.callable) + { + flags |= vk::ShaderStageFlagBits::eCallableKHR; + } + return flags; +} + +void addShaderAttribute(std::vector& attributes, ShaderAttribute&& attribute) +{ + bool doInsert = true; + for (const ShaderAttribute& myAttribute: attributes) + { + if (myAttribute.stage == attribute.stage && myAttribute.location == attribute.location && myAttribute.location != UNSPECIFIED_INDEX) + { + // same location, type must be the same + if (myAttribute.type != attribute.type) + { + logAndDie( + "Attempting to merge incompatible shader metas, attributes {} and {} are incompatible. {} != {}", + myAttribute.name, attribute.name, myAttribute.type, attribute.type); + } + doInsert = false; // member already exists, don't insert + continue; + } + } + + if (!doInsert) + { + return; + } + + auto it = attributes.begin(); + for (; it != attributes.end(); ++it) + { + if (static_cast(it->stage) > static_cast(attribute.stage) + || (it->stage == attribute.stage && it->location > attribute.location)) + { + break; // insert here + } + } + attributes.insert(it, std::move(attribute)); +} +} +ShaderVariableStructType::ShaderVariableStructType() {} // NOLINT(modernize-use-equals-default) +ShaderVariableStructType::~ShaderVariableStructType() {} // NOLINT(modernize-use-equals-default) + +void ShaderMeta::extendPushConstant(ShaderPushConstantBlock pushConstantBlock_, ShaderTypeBits stages) +{ + if (pushConstantBlock_.type.baseType == ShaderVariableBaseType::NONE) { + return; + } + if (pushConstantBlock.type.baseType == ShaderVariableBaseType::NONE) + { + pushConstantBlock = std::move(pushConstantBlock_); + pushConstantStages = stages; + return; + } + + // now comes the actual merging + assert(pushConstantBlock.type.baseType == ShaderVariableBaseType::STRUCT); + assert(pushConstantBlock_.type.baseType == ShaderVariableBaseType::STRUCT); + assert(stages); + + for (ShaderVariableStructMember& member : pushConstantBlock_.type.struct_.members) + { + bool doInsert = true; + for (const ShaderVariableStructMember& myMember : pushConstantBlock.type.struct_.members) + { + if (myMember.offset == member.offset) + { + // same offset, type must be the same + if (myMember.type != member.type) + { + logAndDie("Attempting to merge incompatible push constant blocks, members {} and {} are incompatible. {} != {}", + myMember.name, member.name, myMember.type, member.type); + } + doInsert = false; // member already exists, don't insert + continue; + } + + // otherwise check for overlaps + if ((myMember.offset < member.offset && myMember.offset + calcShaderTypeSize(myMember.type) > member.offset) + || (myMember.offset > member.offset && myMember.offset < member.offset + calcShaderTypeSize(member.type))) + { + logAndDie("Attempting to merge incompatible push constant blocks, members {} and {} are overlapping.", + myMember.name, member.name); + } + } + + if (!doInsert) { + continue; + } + + auto it = pushConstantBlock.type.struct_.members.begin(); + for (; it != pushConstantBlock.type.struct_.members.end(); ++it) + { + if (it->offset > member.offset) { + break; // insert here + } + } + pushConstantBlock.type.struct_.members.insert(it, std::move(member)); + } + + pushConstantStages |= stages; +} + +void ShaderMeta::addInputAttribute(ShaderAttribute attribute) +{ + addShaderAttribute(inputAttributes, std::move(attribute)); +} + +void ShaderMeta::addOutputAttribute(ShaderAttribute attribute) +{ + addShaderAttribute(outputAttributes, std::move(attribute)); +} + +ObjectPtr DescriptorSetMeta::createDescriptorSetLayout(Device& device) const +{ + assert(bindings.size() == bindingFlags.size()); + return device.createChild(DescriptorSetLayoutCreationArgs{ + .bindings = bindings, + .bindingFlags = bindingFlags, + .flags = flags, + }); +} + +std::vector> PipelineAndDescriptorSetLayouts::createDescriptorSets(DescriptorPool& pool) const +{ + std::vector> result; + result.reserve(descriptorSetLayouts.size()); + + for (const ObjectPtr& layout : descriptorSetLayouts) + { + result.push_back(pool.allocateDescriptorSet({ + .layout = layout + })); + } + + return result; +} + +ObjectPtr PipelineAndDescriptorSetLayouts::createDescriptorSet(DescriptorPool& pool, unsigned setIdx) const +{ + MIJIN_ASSERT(setIdx < descriptorSetLayouts.size(), "Invalid set index."); + return pool.allocateDescriptorSet({ + .layout = descriptorSetLayouts[setIdx] + }); +} + +PipelineAndDescriptorSetLayouts PipelineLayoutMeta::createPipelineLayout(Device& device) const +{ + std::vector> descSetLayouts; + descSetLayouts.reserve(descriptorSets.size()); + + for (const DescriptorSetMeta& dslMeta : descriptorSets) + { + descSetLayouts.push_back(dslMeta.createDescriptorSetLayout(device)); + } + + std::vector pushConstantRanges; + if (pushConstantRange.stageFlags) + { + pushConstantRanges.push_back(pushConstantRange); + } + + ObjectPtr pipelineLayout = device.createChild(PipelineLayoutCreationArgs{ + .setLayouts = descSetLayouts, + .pushConstantRanges = std::move(pushConstantRanges) + }); + return + { + .descriptorSetLayouts = std::move(descSetLayouts), + .pipelineLayout = std::move(pipelineLayout) + }; +} + +void ShaderVariable::verifyCompatible(const ShaderVariable& other) const +{ + std::vector errors; + if (other.binding != binding) { + errors.push_back(fmt::format("Variable bindings do not match: {} != {}.", binding, other.binding)); // NOLINT + } + if (other.descriptorType != descriptorType) { + errors.push_back(fmt::format("Descriptor types do not match: {} != {}.", + magic_enum::enum_name(descriptorType), + magic_enum::enum_name(other.descriptorType))); + } + if (other.name != name) { + logMsg("Warning: shader variable names do not match, variable will only be referrable to by one of them! ({} != {})", + name, other.name); + } + if (other.type != type) { + errors.push_back(fmt::format("Variable types do not match: {} != {}.", type, other.type)); + } + + if (errors.empty()) { + return; + } + + logMsg("Error(s) verifying shader variable compatibility:"); + for (const std::string& error : errors) { + logMsg(error); + } + std::abort(); +} + +std::size_t ShaderVariable::calcHash(std::size_t appendTo) const +{ + (void) appendTo; + MIJIN_TRAP(); // TODO + return 0; +#if 0 + std::size_t hash = appendTo; + hash = type.calcHash(hash); + hash = calcCrcSizeAppend(descriptorType, hash); + hash = calcCrcSizeAppend(binding, hash); + hash = calcCrcSizeAppend(name, hash); + return hash; +#endif +} + +#if 0 +ShaderSource ShaderSource::fromFile(std::string fileName, std::string name) +{ + (void) fileName; + (void) name; + MIJIN_TRAP(); // TODO + return {}; + std::string code = readFileText(fileName); + return { + .code = std::move(code), + .fileName = std::move(fileName), +#if !defined(KAZAN_RELEASE) + .name = std::move(name) +#endif + }; +} +#endif + +bool ShaderVariableSet::find(std::string_view varName, ShaderVariableFindResult& outResult) const noexcept +{ + for (const ShaderVariable& var : variables) + { + if (var.name == varName) + { + outResult.setIndex = setIndex; + outResult.bindIndex = var.binding; + return true; + } + } + return false; +} + +bool ShaderVariableSet::find(unsigned semantic, unsigned semanticIdx, ShaderVariableFindResult& outResult) const noexcept +{ + for (const ShaderVariable& var : variables) + { + if (var.semantic == semantic && var.semanticIndex == semanticIdx) + { + outResult.setIndex = setIndex; + outResult.bindIndex = var.binding; + return true; + } + } + return false; +} + +const ShaderVariable& ShaderVariableSet::getVariableAtBinding(unsigned bindingIdx) const +{ + for (const ShaderVariable& var : variables) + { + if (var.binding == bindingIdx) + { + return var; + } + } + + logAndDie("Could not find shader variable with binding {}!", bindingIdx); +} + +const ShaderVariable* ShaderVariableSet::getVariableAtBindingOpt(unsigned bindingIdx) const +{ + for (const ShaderVariable& var : variables) + { + if (var.binding == bindingIdx) + { + return &var; + } + } + + return nullptr; +} + +const ShaderVariable* ShaderVariableSet::getVariableAtSemanticOpt(unsigned semantic, unsigned semanticIdx) const +{ + for (const ShaderVariable& var : variables) + { + if (var.semantic == semantic && var.semanticIndex == semanticIdx) + { + return &var; + } + } + + return nullptr; +} + +std::size_t ShaderVariableSet::calcHash(std::size_t appendTo) const +{ + std::size_t hash = appendTo; + for (const ShaderVariable& var : variables) { + hash = var.calcHash(hash); + } + return hash; +} + +void ShaderMeta::extend(ShaderMeta other) +{ + for (ShaderVariableSet& set : other.interfaceVariableSets) + { + ShaderVariableSet& mySet = getOrCreateInterfaceVariableSet(set.setIndex); + mySet.usedInStages.bits |= set.usedInStages.bits; + + for (ShaderVariable& variable : set.variables) + { + const ShaderVariable* myVariable = nullptr; + if (variable.binding != UNSPECIFIED_INDEX) + { + myVariable = mySet.getVariableAtBindingOpt(variable.binding); + } + else if (variable.semantic != UNSPECIFIED_INDEX) + { + myVariable = mySet.getVariableAtSemanticOpt(variable.semantic, variable.semanticIndex); + } + if (myVariable) + { + myVariable->verifyCompatible(variable); + continue; + } + mySet.variables.push_back(std::move(variable)); + } + } + + for (ShaderAttribute& attribute : other.inputAttributes) + { + addInputAttribute(std::move(attribute)); + } + + for (ShaderAttribute& attribute : other.outputAttributes) + { + addOutputAttribute(std::move(attribute)); + } + + extendPushConstant(other.pushConstantBlock, other.pushConstantStages); + stages |= other.stages; + + if (localSizeX == 0 && localSizeY == 0 && localSizeZ == 0) + { + localSizeX = other.localSizeX; + localSizeY = other.localSizeY; + localSizeZ = other.localSizeZ; + } + else if ((other.localSizeX != 0 || other.localSizeY != 0 || other.localSizeZ != 0) && + (localSizeX != other.localSizeX || localSizeY != other.localSizeY || localSizeZ != other.localSizeZ)) + { + logAndDie("Error merging shader metas, conflicting local size!"); + } + + hash = 0; +} + +bool ShaderMeta::findInterfaceVariable(std::string_view varName, ShaderVariableFindResult& outResult) const noexcept +{ + for (const ShaderVariableSet& set : interfaceVariableSets) + { + if (set.find(varName, outResult)) { + return true; + } + } + + return false; +} + +bool ShaderMeta::findInterfaceVariable(unsigned semantic, unsigned semanticIdx, ShaderVariableFindResult& outResult) const noexcept +{ + for (const ShaderVariableSet& set : interfaceVariableSets) + { + if (set.find(semantic, semanticIdx, outResult)) { + return true; + } + } + + return false; +} + +const ShaderVariableSet& ShaderMeta::getInterfaceVariableSet(unsigned setIdx) const +{ + const ShaderVariableSet* variableSet = getInterfaceVariableSetOpt(setIdx); + MIJIN_ASSERT(variableSet != nullptr, "Could not find interface variable set."); + return *variableSet; +} + +const ShaderVariableSet* ShaderMeta::getInterfaceVariableSetOpt(unsigned setIdx) const +{ + for (const ShaderVariableSet& set : interfaceVariableSets) + { + if (set.setIndex == setIdx) { + return &set; + } + } + return nullptr; +} + +const ShaderVariableType& ShaderMeta::getInterfaceVariableType(unsigned setIdx, unsigned bindingIdx) const +{ + return getInterfaceVariableSet(setIdx).getVariableAtBinding(bindingIdx).type; +} + +VertexInput ShaderMeta::generateVertexInput(const NamedVertexInput& namedInput) const noexcept +{ + VertexInput result{ + .bindings = namedInput.bindings + }; + + for (const ShaderAttribute& attribute : inputAttributes) + { + if (attribute.stage != vk::ShaderStageFlagBits::eVertex) { + continue; + } + MIJIN_ASSERT_FATAL(attribute.type.baseType == ShaderVariableBaseType::SIMPLE, "Vertex shader input must be a simple type."); + auto itAttribute = namedInput.attributes.find(attribute.name); + MIJIN_ASSERT_FATAL(itAttribute != namedInput.attributes.end(), "Missing attribute in input."); + result.attributes.push_back(vk::VertexInputAttributeDescription{ + .location = attribute.location, + .binding = itAttribute->second.binding, + .format = attribute.type.simple.format, + .offset = itAttribute->second.offset + }); + } + return result; +} + +VertexInput ShaderMeta::generateVertexInputFromLayout(const VertexLayout& layout) const noexcept +{ + VertexInput result{ + .bindings = { + vk::VertexInputBindingDescription{ + .binding = 0, + .stride = layout.stride, + .inputRate = vk::VertexInputRate::eVertex + } + } + }; + + for (const ShaderAttribute& attribute : inputAttributes) + { + if (attribute.stage != vk::ShaderStageFlagBits::eVertex) { + continue; + } + if (attribute.semantic == UNSPECIFIED_INDEX) { + continue; + } + MIJIN_ASSERT_FATAL(attribute.type.baseType == ShaderVariableBaseType::SIMPLE, "Vertex shader input must be a simple type."); + auto itAttribute = std::ranges::find_if(layout.attributes, [&attribute](const VertexAttribute& attrib) { + return static_cast(attrib.semantic) == attribute.semantic && attrib.semanticIdx == attribute.semanticIndex; + }); + MIJIN_ASSERT_FATAL(itAttribute != layout.attributes.end(), "Missing attribute in vertex layout."); + result.attributes.push_back(vk::VertexInputAttributeDescription{ + .location = attribute.location, + .binding = 0, + .format = attribute.type.simple.format, + .offset = itAttribute->offset + }); + } + return result; +} + +DescriptorSetMeta ShaderMeta::generateDescriptorSetLayout(const ShaderVariableSet& set, const GenerateDescriptorSetLayoutArgs& args) const +{ + DescriptorSetMeta setInfo{ + .flags = args.flags + }; + + for (const ShaderVariable& var : set.variables) + { + auto itVar = std::ranges::find_if(setInfo.bindings, [&](const vk::DescriptorSetLayoutBinding& binding) { + return binding.binding == var.binding; + }); + assert(itVar == setInfo.bindings.end()); // should have been merged! + if (itVar != setInfo.bindings.end()) + { + itVar->stageFlags |= typeBitsToVkStages(set.usedInStages); + continue; // TODO: verify the bindings are compatible + } + vk::DescriptorSetLayoutBinding& binding = setInfo.bindings.emplace_back(); + vk::DescriptorBindingFlags& flags = setInfo.bindingFlags.emplace_back(); + binding.binding = var.binding; + binding.descriptorType = var.descriptorType; + binding.descriptorCount = 1; + binding.stageFlags = typeBitsToVkStages(set.usedInStages); + + // support for dynamically sized descriptors + auto itCounts = args.descriptorCounts.find(var.binding); + if (itCounts != args.descriptorCounts.end() && itCounts->second > 0) + { + binding.descriptorCount = itCounts->second; + flags |= vk::DescriptorBindingFlagBits::ePartiallyBound; + } + + if (setInfo.descriptorTypes.size() <= var.binding) { + setInfo.descriptorTypes.resize(var.binding + 1); + } + setInfo.descriptorTypes[var.binding] = var.descriptorType; + } + + return setInfo; +} + +PipelineLayoutMeta ShaderMeta::generatePipelineLayout(const GeneratePipelineLayoutArgs& args) const +{ + static const std::vector NO_DESCRIPTOR_COUNTS = {}; + static const GenerateDescriptorSetLayoutArgs NO_DESCRIPTOR_SET_ARGS = {}; + + PipelineLayoutMeta result; + for (const ShaderVariableSet& set : interfaceVariableSets) + { + if (set.setIndex >= result.descriptorSets.size()) { + result.descriptorSets.resize(set.setIndex + 1); + } + auto itSet = args.descriptorSets.find(set.setIndex); + const GenerateDescriptorSetLayoutArgs setArgs = + itSet != args.descriptorSets.end() + ? itSet->second + : NO_DESCRIPTOR_SET_ARGS; + result.descriptorSets[set.setIndex] = generateDescriptorSetLayout(set, setArgs); + } + + if (pushConstantBlock.type.baseType != ShaderVariableBaseType::NONE) + { + assert(pushConstantStages); + result.pushConstantRange.stageFlags = typeBitsToVkStages(pushConstantStages); + result.pushConstantRange.size = pushConstantBlock.offset + calcShaderTypeSize(pushConstantBlock.type); + } + + return result; +} + +bool ShaderMeta::empty() const +{ + static_assert(ShaderMeta::STRUCT_VERSION == 1, "Update me"); + return interfaceVariableSets.empty() + && inputAttributes.empty() + && outputAttributes.empty() + && pushConstantStages == ShaderTypeBits() + && pushConstantBlock.type.baseType == ShaderVariableBaseType::NONE + && localSizeX == 0 + && localSizeY == 0 + && localSizeZ == 0; +} + +std::size_t ShaderMeta::getHash() const +{ + if (hash == 0) + { + hash = 1; // TODO + MIJIN_TRAP(); +#if 0 + for (const ShaderVariableSet& variableSet : interfaceVariableSets) { + hash = variableSet.calcHash(hash); + } + hash = calcCrcSizeAppend(pushConstantStages.bits, hash); + hash = pushConstantBlock.type.calcHash(hash); + hash = calcCrcSizeAppend(pushConstantBlock.offset, hash); + hash = calcCrcSizeAppend(localSizeX, hash); + hash = calcCrcSizeAppend(localSizeY, hash); + hash = calcCrcSizeAppend(localSizeZ, hash); +#endif + } + + return hash; +} + +unsigned calcShaderTypeSize(const ShaderVariableType& type, bool ignoreArraySize) noexcept +{ + unsigned size = 0; + switch (type.baseType) + { + case ShaderVariableBaseType::SIMPLE: + size = vkFormatSize(type.simple.format); + break; + case ShaderVariableBaseType::MATRIX: + switch (type.matrixType) + { + case ShaderVariableMatrixType::MAT2: + size = 16; + break; + case ShaderVariableMatrixType::MAT3: + size = 36; + break; + case ShaderVariableMatrixType::MAT4: + size = 64; + break; + default: + logAndDie("Lol, what's this?"); + } + break; + case ShaderVariableBaseType::STRUCT: + assert(!type.struct_.members.empty()); + size = static_cast(type.struct_.members.back().offset + calcShaderTypeSize(type.struct_.members.back().type)); + break; + default: + logAndDie("How would I know?"); + } + if (!ignoreArraySize) { + size *= type.arraySize; + } + return size; +} + +} // namespace iwa diff --git a/source/util/texture_atlas.cpp b/source/util/texture_atlas.cpp new file mode 100644 index 0000000..7c5d3e4 --- /dev/null +++ b/source/util/texture_atlas.cpp @@ -0,0 +1,402 @@ + +#include "iwa/util/texture_atlas.hpp" + +#include +#include "iwa/device.hpp" +#include "iwa/instance.hpp" +#include "iwa/resource/bitmap.hpp" + +namespace iwa +{ +TextureSlot::TextureSlot(ObjectPtr owner, const TextureSlotCreationArgs& args) + : super_t(std::move(owner)), mUsedSpace(args.usedSpace), mLayer(args.layer), mUvOffset(args.uvOffset), mUvScale(args.uvScale) +{ + +} + +TextureAtlas::TextureAtlas(ObjectPtr<> owner, const TextureAtlasCreationArgs& args) + : super_t(std::move(owner)), mLayerSize(args.layerSize) +{ + // start with a single layer with one free space that takes up the entire layer + mLayers.push_back({ + .freeSpaces = { + vk::Rect2D{ + .offset = { .x = 0, .y = 0 }, + .extent = args.layerSize + } + } + }); +} + +ObjectPtr TextureAtlas::allocateSlot(vk::Extent2D slotSize) +{ + // only uses multiples of 2 + // TODO: check if this actually improves the results + const vk::Extent2D size = { + .width = std::bit_ceil(slotSize.width), + .height = std::bit_ceil(slotSize.height) + }; + + // check if it can even fit + if (size.width > mLayerSize.width || size.height > mLayerSize.height) { + throw std::runtime_error("Cannot allocate texture slot, size too big."); + } + + // find the best fit (minimize product of "wasted" space) + unsigned lowestWasteSum = std::numeric_limits::max(); + unsigned lowestWasteProduct = std::numeric_limits::max(); + std::vector::iterator foundLayer = mLayers.end(); + std::vector::iterator foundSpace; + for (auto itLayer = mLayers.begin(); itLayer != mLayers.end(); ++itLayer) + { + for (auto itSpace = itLayer->freeSpaces.begin(); itSpace != itLayer->freeSpaces.end(); ++itSpace) + { + if (itSpace->extent.width < size.width || itSpace->extent.height < size.height) { + continue; + } + const unsigned wasteWidth = itSpace->extent.width - size.width; + const unsigned wasteHeight = itSpace->extent.height - size.height; + const unsigned wasteProduct = wasteWidth * wasteHeight; + if (wasteProduct <= lowestWasteProduct) + { + const unsigned wasteSum = wasteWidth + wasteHeight; + if (wasteProduct < lowestWasteProduct || wasteSum < lowestWasteSum) + { + lowestWasteSum = wasteSum; + lowestWasteProduct = wasteProduct; + foundLayer = itLayer; + foundSpace = itSpace; + } + } + } // for (itLayer->freeSpaces) + } // for (mLayers) + + // if no space was found, make space + if (foundLayer == mLayers.end()) + { + mLayers.resize(mLayers.size() + 1); + mLayers.back().freeSpaces.push_back({ + .offset = { .x = 0, .y = 0}, + .extent = mLayerSize + }); + foundLayer = std::prev(mLayers.end()); + foundSpace = foundLayer->freeSpaces.begin(); + } + + // save in case the iterator gets invalidated + const vk::Rect2D space = *foundSpace; + + // remove it + foundLayer->freeSpaces.erase(foundSpace); + + // now split the space, if necessary + const bool splitX = space.extent.width > size.width; + const bool splitY = space.extent.height > size.height; + if (splitX) + { + foundLayer->freeSpaces.push_back({ + .offset = { + .x = static_cast(space.offset.x + size.width), + .y = space.offset.y + }, + .extent = { + .width = space.extent.width - size.width, + .height = size.height + } + }); + } + if (splitY) + { + foundLayer->freeSpaces.push_back({ + .offset = { + .x = space.offset.x, + .y = static_cast(space.offset.y + size.height) + }, + .extent = { + .width = size.width, + .height = space.extent.height - size.height + } + }); + } + if (splitX && splitY) + { + foundLayer->freeSpaces.push_back({ + .offset = { + .x = static_cast(space.offset.x + size.width), + .y = static_cast(space.offset.y + size.height) + }, + .extent = { + .width = space.extent.width - size.width, + .height = space.extent.height - size.height + } + }); + } + + // return the result + return createChild(TextureSlotCreationArgs{ + .usedSpace = { + .offset = space.offset, + .extent = slotSize + }, + .layer = static_cast(std::distance(mLayers.begin(), foundLayer)), + .uvOffset = { + static_cast(space.offset.x) / static_cast(mLayerSize.width), + static_cast(space.offset.y) / static_cast(mLayerSize.height) + }, + .uvScale = { + static_cast(slotSize.width) / static_cast(mLayerSize.width), + static_cast(slotSize.height) / static_cast(mLayerSize.height) + } + }); +} + +AtlasedImage::AtlasedImage(ObjectPtr owner, const AtlasedImageCreationArgs& args) + : super_t(std::move(owner)), mAtlas(TextureAtlas::create(TextureAtlasCreationArgs{.layerSize = args.size})), + mFormat(args.format), mMipLevels(args.mipLevels), mUsage(args.usage | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst) +{ + mImage = allocateImage(args.initialLayers); + mImageView = mImage->createImageView({ + .viewType = vk::ImageViewType::e2DArray + }); +} + +mijin::Task> AtlasedImage::c_allocateSlot(vk::Extent2D slotSize) +{ + IWA_CORO_ENSURE_MAIN_THREAD(*getOwner()->getOwner()); + + ObjectPtr slot = mAtlas->allocateSlot(slotSize); + if (slot->getLayer() >= mImage->getArrayLayers()) + { + const mijin::TaskMutexLock lock = co_await mImageMutex.c_lock(); + + // image is too small, resize it + // this includes a complete copy of the existing image + ObjectPtr newImage = allocateImage(slot->getLayer() + 1); + ObjectPtr cmdBufferPtr = getOwner()->beginScratchCommandBuffer(); + vk::CommandBuffer cmdBuffer = *cmdBufferPtr; + mImage->applyTransition(cmdBuffer, IMAGE_TRANSITION_TRANSFER_READ); + newImage->applyTransition(cmdBuffer, IMAGE_TRANSITION_TRANSFER_WRITE); + + // copy ALL the mip levels + std::vector regions; + regions.reserve(mImage->getMipLevels()); + for (unsigned level = 0; level < mImage->getMipLevels(); ++level) + { + const vk::ImageSubresourceLayers copySubresource{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = level, + .baseArrayLayer = 0, + .layerCount = mImage->getArrayLayers() + }; + regions.push_back({ + .srcSubresource = copySubresource, + .srcOffset = {.x = 0, .y = 0, .z = 0}, + .dstSubresource = copySubresource, + .dstOffset = {.x = 0, .y = 0, .z = 0}, + .extent = { + .width = mAtlas->getLayerSize().width, + .height = mAtlas->getLayerSize().height, + .depth = 1 + } + }); + } + cmdBuffer.copyImage( + /* srcImage = */ *mImage, + /* srcImageLayout = */ vk::ImageLayout::eTransferSrcOptimal, + /* dstImage = */ *newImage, + /* dstImageLayout = */ vk::ImageLayout::eTransferDstOptimal, + /* regions = */ regions + ); + co_await getOwner()->endScratchCommandBuffer(cmdBufferPtr); + mImage = std::move(newImage); + mImageView = mImage->createImageView({ + .viewType = vk::ImageViewType::e2DArray + }); + imageRecreated.emit(); + } + co_return slot; +} + +mijin::Task<> AtlasedImage::c_upload(const TextureSlot& slot, const Bitmap& bitmap) const noexcept +{ + IWA_CORO_ENSURE_MAIN_THREAD(*getOwner()->getOwner()); + MIJIN_ASSERT(slot.getUsedSpace().extent.width >= bitmap.getSize().width + && slot.getUsedSpace().extent.height >= bitmap.getSize().height, "Can't upload image, invalid size."); + + const mijin::TaskMutexLock lock = co_await mImageMutex.c_lock(); + co_await mImage->c_upload( + /* bitmap = */ bitmap, + /* imageOffset = */ { + .x = slot.getUsedSpace().offset.x, + .y = slot.getUsedSpace().offset.y, + .z = 0 + }, + /* baseLayer = */ slot.getLayer() + ); +} + +mijin::Task<> AtlasedImage::c_upload(const TextureSlot& slot, const void* data, std::size_t bytes, const vk::Extent2D& bufferImageSize) const noexcept +{ + IWA_CORO_ENSURE_MAIN_THREAD(*getOwner()->getOwner()); + MIJIN_ASSERT(slot.getUsedSpace().extent.width >= bufferImageSize.width + && slot.getUsedSpace().extent.height >= bufferImageSize.height, "Can't upload image, invalid size."); + + const mijin::TaskMutexLock lock = co_await mImageMutex.c_lock(); + co_await mImage->c_upload( + /* data = */ data, + /* bytes = */ bytes, + /* bufferImageSize = */ { + .width = bufferImageSize.width, + .height = bufferImageSize.height, + .depth = 1 + }, + /* imageOffset = */ { + .x = slot.getUsedSpace().offset.x, + .y = slot.getUsedSpace().offset.y, + .z = 0 + }, + /* baseLayer = */ slot.getLayer() + ); +} + +mijin::Task<> AtlasedImage::c_blit(const TextureSlot& slot, Image& srcImage) const noexcept +{ + IWA_CORO_ENSURE_MAIN_THREAD(*getOwner()->getOwner()); + MIJIN_ASSERT(slot.getUsedSpace().extent.width >= srcImage.getSize().width + && slot.getUsedSpace().extent.height >= srcImage.getSize().height + && srcImage.getSize().depth == 1, "Can't upload image, invalid size."); + + const mijin::TaskMutexLock lock = co_await mImageMutex.c_lock(); + co_await mImage->c_blitFrom( + /* srcImage = */ srcImage, + /* regions = */ { + vk::ImageBlit{ + .srcSubresource = DEFAULT_SUBRESOURCE_LAYERS, + .srcOffsets = std::array{ + vk::Offset3D{ + .x = 0, .y = 0, .z = 0 + }, + vk::Offset3D{ + .x = static_cast(srcImage.getSize().width), + .y = static_cast(srcImage.getSize().height), + .z = 1 + } + }, + .dstSubresource = vk::ImageSubresourceLayers{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = slot.getLayer(), + .layerCount = 1 + }, + .dstOffsets = std::array{ + vk::Offset3D{ + .x = slot.getUsedSpace().offset.x, + .y = slot.getUsedSpace().offset.y, + .z = 0 + }, + vk::Offset3D{ + .x = slot.getUsedSpace().offset.x + static_cast(slot.getUsedSpace().extent.width), + .y = slot.getUsedSpace().offset.y + static_cast(slot.getUsedSpace().extent.height), + .z = 1 + } + } + } + } + ); +} + +mijin::Task<> AtlasedImage::c_blit(const TextureSlot& slot, const Bitmap& bitmap) const noexcept +{ + IWA_CORO_ENSURE_MAIN_THREAD(*getOwner()->getOwner()); + MIJIN_ASSERT(slot.getUsedSpace().extent.width >= bitmap.getSize().width + && slot.getUsedSpace().extent.height >= bitmap.getSize().height, "Can't upload image, invalid size."); + + const mijin::TaskMutexLock lock = co_await mImageMutex.c_lock(); + co_await mImage->c_blitFrom( + /* bitmap = */ bitmap, + /* regions = */ { + vk::ImageBlit{ + .srcSubresource = DEFAULT_SUBRESOURCE_LAYERS, + .srcOffsets = std::array{ + vk::Offset3D{ + .x = 0, .y = 0, .z = 0 + }, + vk::Offset3D{ + .x = static_cast(bitmap.getSize().width), + .y = static_cast(bitmap.getSize().height), + .z = 1 + } + }, + .dstSubresource = vk::ImageSubresourceLayers{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = slot.getLayer(), + .layerCount = 1 + }, + .dstOffsets = std::array{ + vk::Offset3D{ + .x = slot.getUsedSpace().offset.x, + .y = slot.getUsedSpace().offset.y, + .z = 0 + }, + vk::Offset3D{ + .x = slot.getUsedSpace().offset.x + static_cast(slot.getUsedSpace().extent.width), + .y = slot.getUsedSpace().offset.y + static_cast(slot.getUsedSpace().extent.height), + .z = 1 + } + } + } + } + ); +} + +mijin::Task<> AtlasedImage::c_copy(const TextureSlot& slot, Image& srcImage) const noexcept +{ + IWA_CORO_ENSURE_MAIN_THREAD(*getOwner()->getOwner()); + MIJIN_ASSERT(slot.getUsedSpace().extent.width >= srcImage.getSize().width + && slot.getUsedSpace().extent.height >= srcImage.getSize().height + && srcImage.getSize().depth == 1, "Can't upload image, invalid size."); + + const mijin::TaskMutexLock lock = co_await mImageMutex.c_lock(); + co_await mImage->c_copyFrom( + /* srcImage = */ srcImage, + /* regions = */ { + vk::ImageCopy{ + .srcSubresource = DEFAULT_SUBRESOURCE_LAYERS, + .srcOffset = { + .x = 0, .y = 0, .z = 0 + }, + .dstSubresource = vk::ImageSubresourceLayers{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = slot.getLayer(), + .layerCount = 1 + }, + .dstOffset = { + .x = slot.getUsedSpace().offset.x, + .y = slot.getUsedSpace().offset.y, + .z = 0 + }, + .extent = srcImage.getSize() + } + } + ); +} + +ObjectPtr AtlasedImage::allocateImage(unsigned layers) +{ + ObjectPtr image = getOwner()->createChild(ImageCreationArgs{ + .format = mFormat, + .extent = { + .width = mAtlas->getLayerSize().width, + .height = mAtlas->getLayerSize().height, + .depth = 1 + }, + .mipLevels = mMipLevels, + .arrayLayers = layers, + .usage = mUsage + }); + image->allocateMemory(); + return image; +} +} // namespace iwa diff --git a/source/util/vertex_layout.cpp b/source/util/vertex_layout.cpp new file mode 100644 index 0000000..1bc1d24 --- /dev/null +++ b/source/util/vertex_layout.cpp @@ -0,0 +1,29 @@ + +#include "iwa/util/vertex_layout.hpp" + +namespace iwa +{ +mijin::Optional VertexLayout::findAttribute(VertexAttributeSemantic semantic, unsigned semanticIdx) noexcept +{ + for (VertexAttribute& attribute : attributes) + { + if (attribute.semantic == semantic && attribute.semanticIdx == semanticIdx) + { + return attribute; + } + } + return mijin::NULL_OPTIONAL; +} + +mijin::Optional VertexLayout::findAttribute(VertexAttributeSemantic semantic, unsigned semanticIdx) const noexcept +{ + for (const VertexAttribute& attribute : attributes) + { + if (attribute.semantic == semantic && attribute.semanticIdx == semanticIdx) + { + return attribute; + } + } + return mijin::NULL_OPTIONAL; +} +} // namespace iwa diff --git a/source/util/vkutil.cpp b/source/util/vkutil.cpp new file mode 100644 index 0000000..44c5a92 --- /dev/null +++ b/source/util/vkutil.cpp @@ -0,0 +1,275 @@ + +#include "iwa/util/vkutil.hpp" + +#include "iwa/device.hpp" +#include "iwa/log.hpp" + +namespace iwa +{ +unsigned vkFormatSize(vk::Format format) noexcept +{ + switch (format) + { + // 8 bit integer + case vk::Format::eR8Uint: + case vk::Format::eR8Sint: + case vk::Format::eR8Unorm: + case vk::Format::eR8Srgb: + return 1; + case vk::Format::eR8G8Uint: + case vk::Format::eR8G8Sint: + case vk::Format::eR8G8Unorm: + case vk::Format::eR8G8Srgb: + return 2; + case vk::Format::eR8G8B8Uint: + case vk::Format::eR8G8B8Sint: + case vk::Format::eR8G8B8Unorm: + case vk::Format::eR8G8B8Srgb: + return 3; + case vk::Format::eR8G8B8A8Uint: + case vk::Format::eR8G8B8A8Sint: + case vk::Format::eR8G8B8A8Unorm: + case vk::Format::eR8G8B8A8Srgb: + return 4; + // 16 bit integer + case vk::Format::eR16Uint: + case vk::Format::eR16Sint: + case vk::Format::eR16Unorm: + return 2; + case vk::Format::eR16G16Uint: + case vk::Format::eR16G16Sint: + case vk::Format::eR16G16Unorm: + return 4; + case vk::Format::eR16G16B16Uint: + case vk::Format::eR16G16B16Sint: + case vk::Format::eR16G16B16Unorm: + return 6; + case vk::Format::eR16G16B16A16Uint: + case vk::Format::eR16G16B16A16Sint: + case vk::Format::eR16G16B16A16Unorm: + return 8; + // 32 bit integer + case vk::Format::eR32Uint: + case vk::Format::eR32Sint: + return 4; + case vk::Format::eR32G32Uint: + case vk::Format::eR32G32Sint: + return 8; + case vk::Format::eR32G32B32Uint: + case vk::Format::eR32G32B32Sint: + return 12; + case vk::Format::eR32G32B32A32Uint: + case vk::Format::eR32G32B32A32Sint: + return 16; + // 64 bit integer + case vk::Format::eR64Uint: + case vk::Format::eR64Sint: + return 8; + case vk::Format::eR64G64Uint: + case vk::Format::eR64G64Sint: + return 16; + case vk::Format::eR64G64B64Uint: + case vk::Format::eR64G64B64Sint: + return 24; + case vk::Format::eR64G64B64A64Uint: + case vk::Format::eR64G64B64A64Sint: + return 32; + // 16 bit float + case vk::Format::eR16Sfloat: + return 2; + case vk::Format::eR16G16Sfloat: + return 4; + case vk::Format::eR16G16B16Sfloat: + return 6; + case vk::Format::eR16G16B16A16Sfloat: + return 8; + // 32 bit float + case vk::Format::eR32Sfloat: + return 4; + case vk::Format::eR32G32Sfloat: + return 8; + case vk::Format::eR32G32B32Sfloat: + return 12; + case vk::Format::eR32G32B32A32Sfloat: + return 16; + // 64 bit float + case vk::Format::eR64Sfloat: + return 8; + case vk::Format::eR64G64Sfloat: + return 16; + case vk::Format::eR64G64B64Sfloat: + return 24; + case vk::Format::eR64G64B64A64Sfloat: + return 32; + default: + logAndDie("I've never seen this format :("); + } +} + +unsigned vkIndexTypeSize(vk::IndexType indexType) noexcept +{ + switch (indexType) + { + case vk::IndexType::eNoneKHR: + return 0; + case vk::IndexType::eUint8EXT: + return 1; + case vk::IndexType::eUint16: + return 2; + case vk::IndexType::eUint32: + return 4; + default: + logAndDie("What is this sorcery?"); + } +} + +bool isDepthFormat(vk::Format format) noexcept +{ + for (const vk::Format depthFormat : DEPTH_FORMATS) { + if (format == depthFormat) { + return true; + } + } + return false; +} + +bool isStencilFormat(vk::Format format) noexcept +{ + for (const vk::Format stencilFormat : STENCIL_FORMATS) { + if (format == stencilFormat) { + return true; + } + } + return false; +} + +#if 0 +std::string formatVkVariant(const VkVariantMWN& variant) +{ + switch (variant.type) + { + case VK_VARIANT_TYPE_UNKNOWN_MWN: + return "???"; + case VK_VARIANT_TYPE_NONE_MWN: + return ""; + case VK_VARIANT_TYPE_BOOL_MWN: + return variant.uintValue ? "true" : "false"; + case VK_VARIANT_TYPE_UINT8_MWN: + case VK_VARIANT_TYPE_UINT16_MWN: + case VK_VARIANT_TYPE_UINT32_MWN: + case VK_VARIANT_TYPE_UINT64_MWN: + return std::to_string(variant.uintValue); + case VK_VARIANT_TYPE_INT8_MWN: + case VK_VARIANT_TYPE_INT16_MWN: + case VK_VARIANT_TYPE_INT32_MWN: + case VK_VARIANT_TYPE_INT64_MWN: + return std::to_string(variant.intValue); + case VK_VARIANT_TYPE_FLOAT_MWN: + case VK_VARIANT_TYPE_DOUBLE_MWN: + return std::to_string(variant.doubleValue); + case VK_VARIANT_TYPE_STRING_MWN: + return fmt::format("\"{}\"", variant.stringValue); + case VK_VARIANT_TYPE_VOID_POINTER_MWN: + return fmt::format("{}", fmt::ptr(variant.voidPointerValue)); // TODO: this doesnt make sense, store the original pointer! + case VK_VARIANT_TYPE_POINTER_MWN: + return fmt::format("{}", fmt::ptr(variant.pointerValue)); // TODO: this doesnt make sense, store the original pointer! + case VK_VARIANT_TYPE_ARRAY_MWN: + return fmt::format("", variant.arrayValue.numElements); + case VK_VARIANT_TYPE_IN_STRUCTURE_MWN: + return ""; + case VK_VARIANT_TYPE_OUT_STRUCTURE_MWN: + return ""; + case VK_VARIANT_TYPE_OBJECT_MWN: + return ""; + default: + assert(0); + return "???"; + } +} +#endif + +#if 0 +std::size_t calcVkStructHash(const void* structure, std::size_t appendTo) +{ + if (structure == nullptr) { + return appendTo; + } + + const vk::BaseInStructure* inStruct = static_cast(structure); + + std::size_t hash = appendTo; + switch (inStruct->sType) + { + case vk::StructureType::eDescriptorSetLayoutBindingFlagsCreateInfo: { + const auto& flagsInfo = *static_cast(structure); + for (std::uint32_t bindingIdx = 0; bindingIdx < flagsInfo.bindingCount; ++bindingIdx) { + hash = calcCrcSizeAppend(flagsInfo.pBindingFlags[bindingIdx], hash); + } + } + break; + default: + assert(false); // missing struct here, bad + break; + } + + return calcVkStructHash(inStruct->pNext, hash); +} +#endif + +vk::SampleCountFlagBits samplesToVk(unsigned samples) noexcept +{ + switch (samples) + { + case 1: + return vk::SampleCountFlagBits::e1; + case 2: + return vk::SampleCountFlagBits::e2; + case 4: + return vk::SampleCountFlagBits::e4; + case 8: + return vk::SampleCountFlagBits::e8; + case 16: + return vk::SampleCountFlagBits::e16; + case 32: + return vk::SampleCountFlagBits::e32; + case 64: + return vk::SampleCountFlagBits::e64; + default: + logAndDie("Invalid sample count: {}.", samples); + } +} + +vk::Format detectDepthBufferFormat(Device& device, unsigned samples) noexcept +{ + const vk::SampleCountFlagBits sampleCount = samplesToVk(samples); + for (const vk::Format depthFormat : DEPTH_FORMATS) + { + try + { + const vk::ImageFormatProperties props = device.getVkPhysicalDevice().getImageFormatProperties(depthFormat, vk::ImageType::e2D, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment); + if (props.sampleCounts & sampleCount) { + return depthFormat; + } + } + catch(vk::FormatNotSupportedError&) + { + continue; // not supported + } + } + return vk::Format::eUndefined; +} + +std::vector detectSupportedSampleCounts(Device& device) noexcept +{ + std::vector result = {1}; + + for (const unsigned samples : {2, 4, 8, 16, 32, 64}) + { + if (detectDepthBufferFormat(device, samples) != vk::Format::eUndefined) { + result.push_back(samples); + } + } + + return result; +} +} // namespace iwa diff --git a/source/window.cpp b/source/window.cpp new file mode 100644 index 0000000..7492ee3 --- /dev/null +++ b/source/window.cpp @@ -0,0 +1,319 @@ + +#include "iwa/window.hpp" + +#include +#include "iwa/log.hpp" +#include "iwa/instance.hpp" + +namespace iwa +{ +inline constexpr const char* WINDOW_DATA_NAME = "iwa_window"; + +namespace +{ +Window* gLastEventWindow = nullptr; + +Window* getWindowFromEvent(Uint32 windowID) noexcept +{ + SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID); + Window* iwaWindow = static_cast(SDL_GetWindowData(sdlWindow, WINDOW_DATA_NAME)); + + if (iwaWindow != nullptr) + { + gLastEventWindow = iwaWindow; + } + return gLastEventWindow; +} + +void handleWindowEvent(const SDL_Event& event) +{ + Window* iwaWindow = getWindowFromEvent(event.window.windowID); + if (iwaWindow == nullptr) { + return; + } + + switch (event.window.event) + { + case SDL_WINDOWEVENT_FOCUS_GAINED: + iwaWindow->focusGained.emit(); + break; + case SDL_WINDOWEVENT_FOCUS_LOST: + iwaWindow->focusLost.emit(); + break; + case SDL_WINDOWEVENT_ENTER: + iwaWindow->mouseEntered.emit(); + break; + case SDL_WINDOWEVENT_LEAVE: + iwaWindow->mouseLeft.emit(); + break; + case SDL_WINDOWEVENT_CLOSE: + iwaWindow->closeRequested.emit(); + break; + } +} + +void handleKeyEvent(const SDL_Event& sdlEvent) +{ + Window* iwaWindow = getWindowFromEvent(sdlEvent.key.windowID); + if (iwaWindow == nullptr) { + return; + } + + const KeyEvent event = { + .keyCode = static_cast(sdlEvent.key.keysym.sym), + .scanCode = static_cast(sdlEvent.key.keysym.scancode), + .modifiers = { + .leftShift = (sdlEvent.key.keysym.mod & KMOD_LSHIFT) != 0, + .rightShift = (sdlEvent.key.keysym.mod & KMOD_RSHIFT) != 0, + .leftCtrl = (sdlEvent.key.keysym.mod & KMOD_LCTRL) != 0, + .rightCtrl = (sdlEvent.key.keysym.mod & KMOD_RCTRL) != 0, + .leftAlt = (sdlEvent.key.keysym.mod & KMOD_LALT) != 0, + .rightAlt = (sdlEvent.key.keysym.mod & KMOD_RALT) != 0, + .leftMeta = (sdlEvent.key.keysym.mod & KMOD_LGUI) != 0, + .rightMeta = (sdlEvent.key.keysym.mod & KMOD_RGUI) != 0, + }, + .down = sdlEvent.type == SDL_KEYDOWN, + .repeat = sdlEvent.key.repeat > 0 + }; + iwaWindow->keyChanged.emit(event); +} + +void handleMouseMotion(const SDL_Event& sdlEvent) +{ + Window* iwaWindow = getWindowFromEvent(sdlEvent.motion.windowID); + if (iwaWindow == nullptr) { + return; + } + + const MouseMoveEvent event = { + .relativeX = sdlEvent.motion.xrel, + .relativeY = sdlEvent.motion.yrel, + .absoluteX = sdlEvent.motion.x, + .absoluteY = sdlEvent.motion.y, + .warped = false // TODO? + }; + iwaWindow->mouseMoved.emit(event); +} + +void handleMouseButtonEvent(const SDL_Event& sdlEvent) +{ + Window* iwaWindow = getWindowFromEvent(sdlEvent.button.windowID); + if (iwaWindow == nullptr) { + return; + } + + const MouseButtonEvent event = { + .button = static_cast(sdlEvent.button.button), + .clicks = sdlEvent.button.clicks, + .down = sdlEvent.type == SDL_MOUSEBUTTONDOWN + }; + iwaWindow->mouseButtonChanged.emit(event); +} + +void handleMouseWheelEvent(const SDL_Event& sdlEvent) +{ + Window* iwaWindow = getWindowFromEvent(sdlEvent.wheel.windowID); + if (iwaWindow == nullptr) { + return; + } + + const MouseWheelEvent event = { + .relativeX = sdlEvent.wheel.x, + .relativeY = sdlEvent.wheel.y + }; + iwaWindow->mouseScrolled.emit(event); +} + +void handleTextInputEvent(const SDL_Event& sdlEvent) +{ + Window* iwaWindow = getWindowFromEvent(sdlEvent.text.windowID); + if (iwaWindow == nullptr) { + return; + } + + const TextInputEvent event = { + .text = sdlEvent.text.text + }; + iwaWindow->textEntered.emit(event); +} + +void handleEvent(const SDL_Event& event) +{ + switch (event.type) + { + case SDL_WINDOWEVENT: + handleWindowEvent(event); + break; + case SDL_KEYDOWN: + case SDL_KEYUP: + handleKeyEvent(event); + break; + case SDL_MOUSEMOTION: + handleMouseMotion(event); + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + handleMouseButtonEvent(event); + break; + case SDL_MOUSEWHEEL: + handleMouseWheelEvent(event); + break; + case SDL_TEXTINPUT: + handleTextInputEvent(event); + break; + } +} +mijin::Task<> c_sdlLoop(ObjectPtr instance) +{ + while (!instance->isQuitRequested()) + { + SDL_Event event; + while (SDL_PollEvent(&event)) + { + handleEvent(event); + } + co_await mijin::c_suspend(); + } + // SDL_Quit(); + co_return; +} + +void initSDL(Instance& instance) +{ + static bool sdlInited = false; + if (sdlInited) { + return; + } + sdlInited = true; + if (SDL_Init(0) != 0) + { + logAndDie("Error initializing SDL: {}.", SDL_GetError()); + } + SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0"); + instance.getMainTaskLoop().addTask(c_sdlLoop(instance.getPointer())); +} +} + +Window::Window(ObjectPtr owner, const WindowCreationArgs& args) : super_t(std::move(owner)) +{ + initSDL(*getOwner()); + const Uint32 flags = SDL_WINDOW_VULKAN + | (args.flags.hidden ? SDL_WINDOW_HIDDEN : 0) + | (args.flags.resizable ? SDL_WINDOW_RESIZABLE : 0) + | (args.flags.borderless ? SDL_WINDOW_BORDERLESS : 0) + | (args.flags.alwayOnTop ? SDL_WINDOW_ALWAYS_ON_TOP : 0) + | (args.flags.skipTaskbar ? SDL_WINDOW_UTILITY : 0); + mHandle = SDL_CreateWindow( + /* title = */ args.title.c_str(), + /* x = */ SDL_WINDOWPOS_CENTERED, + /* y = */ SDL_WINDOWPOS_CENTERED, + /* w = */ args.width, + /* h = */ args.height, + /* flags = */ flags + ); + if (mHandle == nullptr) + { + logAndDie("Error creating SDL window: {}.", SDL_GetError()); + } + SDL_SetWindowData(mHandle, WINDOW_DATA_NAME, this); + + VkSurfaceKHR surface = VK_NULL_HANDLE; + if (!SDL_Vulkan_CreateSurface(mHandle, getOwner()->getVkHandle(), &surface)) + { + logAndDie("Error creating Vulkan surface for SDL window: {}", SDL_GetError()); + } + mSurface = surface; + getOwner()->windowCreated.emit(*this); +} + +Window::~Window() noexcept +{ + getOwner()->queueDelete([surface=mSurface, handle=mHandle, instance= getOwner()] + { + if (surface) + { + instance->getVkHandle().destroySurfaceKHR(surface); + } + if (handle) + { + SDL_DestroyWindow(handle); + } + }); +} + +bool Window::isVisible() const noexcept +{ + return (SDL_GetWindowFlags(mHandle) & SDL_WINDOW_HIDDEN) == 0; +} + +void Window::setVisible(bool visible) noexcept +{ + if (visible) { + SDL_ShowWindow(mHandle); + } + else { + SDL_HideWindow(mHandle); + } +} + +std::pair Window::getSize() const noexcept +{ + std::pair size; + SDL_GetWindowSize(mHandle, &size.first, &size.second); + return size; +} + +void Window::setSize(int width, int height) noexcept +{ + SDL_SetWindowSize(mHandle, std::max(width, 1), std::max(height, 1)); +} + +std::pair Window::getPosition() const noexcept +{ + std::pair position; + SDL_GetWindowPosition(mHandle, &position.first, &position.second); + return position; +} + +void Window::setPosition(int xPos, int yPos) noexcept +{ + SDL_SetWindowPosition(mHandle, xPos, yPos); +} + +WindowBorder Window::getWindowBorder() const noexcept +{ + WindowBorder windowBorder; + SDL_GetWindowBordersSize(mHandle, &windowBorder.top, &windowBorder.left, &windowBorder.bottom, &windowBorder.right); + return windowBorder; +} + +bool Window::isFocused() const noexcept +{ + return (SDL_GetWindowFlags(mHandle) & SDL_WINDOW_INPUT_FOCUS) != 0; +} + +void Window::focus() noexcept +{ + SDL_RaiseWindow(mHandle); +} + +void Window::setMouseMode(MouseMode mouseMode) noexcept +{ + switch (mouseMode) + { + case MouseMode::NORMAL: + SDL_SetRelativeMouseMode(SDL_FALSE); + SDL_SetWindowMouseGrab(mHandle, SDL_FALSE); + break; + case MouseMode::CAPTURED: + SDL_SetRelativeMouseMode(SDL_TRUE); + SDL_SetWindowMouseGrab(mHandle, SDL_TRUE); + break; + } +} + +void Window::setModalFor(mijin::Optional parent) noexcept +{ + SDL_SetWindowModalFor(mHandle, parent.empty() ? nullptr : parent->getSDLWindow()); +} +} \ No newline at end of file