├── include └── vpp │ ├── vk.hpp │ ├── util │ ├── span.hpp │ ├── file.hpp │ └── allocation.hpp │ ├── meson.build │ ├── vpp.hpp │ ├── resourceOneDev.hpp │ ├── config.hpp.in │ ├── resourceDev.hpp │ ├── fwd.hpp │ ├── commandAllocator.hpp │ ├── memoryResource.hpp │ ├── pipeline.hpp │ ├── bufferOps.hpp │ ├── init.hpp │ ├── resource.hpp │ ├── queue.hpp │ ├── debugReport.hpp │ ├── physicalDevice.hpp │ ├── formats.hpp │ ├── swapchain.hpp │ ├── submit.hpp │ ├── shader.hpp │ ├── buffer.hpp │ ├── memory.hpp │ ├── descriptor.hpp │ ├── memoryMap.hpp │ ├── image.hpp │ └── trackedDescriptor.hpp ├── subprojects ├── ny.wrap ├── nytl.wrap ├── vkpp.wrap └── dlg.wrap ├── .gitignore ├── docs ├── examples │ ├── data │ │ ├── intro.frag.spv │ │ ├── intro.vert.spv │ │ ├── intro.frag │ │ ├── intro.vert │ │ ├── intro-old.vert │ │ ├── intro.frag.h │ │ └── intro.vert.h │ ├── meson.build │ ├── render.hpp │ ├── intro_glfw.cpp │ └── render.cpp ├── tests │ ├── meson.build │ ├── copy.cpp │ ├── framebuffer.cpp │ ├── init.hpp │ ├── address.cpp │ ├── memory.cpp │ ├── objects.cpp │ ├── submit.cpp │ ├── allocator.cpp │ ├── image.cpp │ ├── pipeline.cpp │ └── trackedDescriptor.cpp ├── concepts │ ├── bufferRange.hpp │ ├── renderPassInfo.hpp │ ├── rpInstance.hpp │ ├── framebuffer.hpp │ ├── allocationCallbacks.cpp │ ├── handle.hpp │ └── sparse.hpp └── valgrind.supp ├── .editorconfig ├── meson_options.txt ├── .clang_complete ├── .travis.yml ├── src └── vpp │ ├── meson.build │ ├── resource.cpp │ ├── util │ └── file.cpp │ ├── queue.cpp │ ├── commandAllocator.cpp │ ├── bufferOps.cpp │ ├── memoryResource.cpp │ ├── buffer.cpp │ ├── shader.cpp │ ├── pipeline.cpp │ ├── image.cpp │ ├── submit.cpp │ ├── memoryMap.cpp │ ├── physicalDevice.cpp │ └── debugReport.cpp ├── LICENSE ├── meson.build └── README.md /include/vpp/vk.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | -------------------------------------------------------------------------------- /subprojects/ny.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=ny 3 | url=https://github.com/nyorain/ny.git 4 | revision=master -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | subprojects/*/ 3 | .build-tools.cson 4 | *.swp 5 | .clangd/ 6 | compile_commands.json 7 | -------------------------------------------------------------------------------- /docs/examples/data/intro.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nyorain/vpp/HEAD/docs/examples/data/intro.frag.spv -------------------------------------------------------------------------------- /docs/examples/data/intro.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nyorain/vpp/HEAD/docs/examples/data/intro.vert.spv -------------------------------------------------------------------------------- /subprojects/nytl.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=nytl 3 | url=https://github.com/nyorain/nytl.git 4 | revision=v0.6.0 5 | -------------------------------------------------------------------------------- /subprojects/vkpp.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=vkpp 3 | url=https://github.com/nyorain/vkpp.git 4 | revision=master 5 | -------------------------------------------------------------------------------- /subprojects/dlg.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=dlg 3 | url=https://github.com/nyorain/dlg.git 4 | revision=6d32f02611d34d647fc1fab5cbeda856e0a1471b 5 | -------------------------------------------------------------------------------- /include/vpp/util/span.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace vpp { 7 | // using nytl::Span; 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | indent_style = tab 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('example_ny', type: 'boolean', value: 'false') 2 | option('example_glfw', type: 'boolean', value: 'false') 3 | option('tests', type: 'boolean', value: 'false') 4 | option('one_device', type: 'boolean', value: 'false') 5 | -------------------------------------------------------------------------------- /docs/examples/data/intro.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_ARB_separate_shader_objects : enable 4 | #extension GL_ARB_shading_language_420pack : enable 5 | 6 | layout (location = 0) in vec4 inColor; 7 | layout (location = 0) out vec4 outFragColor; 8 | 9 | void main() 10 | { 11 | outFragColor = inColor; 12 | } 13 | -------------------------------------------------------------------------------- /docs/tests/meson.build: -------------------------------------------------------------------------------- 1 | tests = [ 2 | 'allocator', 3 | 'copy', 4 | 'framebuffer', 5 | 'memory', 6 | 'image', 7 | 'objects', 8 | 'submit', 9 | 'sharedBuffer', 10 | 'pipeline', 11 | 'trackedDescriptor', 12 | 'address', 13 | ] 14 | 15 | foreach test : tests 16 | test(test, executable('test_' + test, test + '.cpp', dependencies: vpp_dep)) 17 | endforeach 18 | -------------------------------------------------------------------------------- /docs/examples/data/intro.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_ARB_separate_shader_objects : enable 4 | #extension GL_ARB_shading_language_420pack : enable 5 | 6 | layout(location = 0) in vec2 inPos; 7 | layout(location = 1) in vec4 inCol; 8 | 9 | layout (location = 0) out vec4 outColor; 10 | 11 | void main() 12 | { 13 | outColor = inCol; 14 | gl_Position = vec4(inPos, 0.0, 1.0); 15 | } 16 | -------------------------------------------------------------------------------- /docs/examples/meson.build: -------------------------------------------------------------------------------- 1 | if example_ny 2 | ny_dep = dependency('ny', fallback: ['ny', 'ny_dep']) 3 | executable('intro_ny', 4 | sources: ['intro_ny.cpp', 'render.cpp'], 5 | dependencies: [ny_dep, vpp_dep]) 6 | endif 7 | 8 | if example_glfw 9 | glfw_dep = dependency('glfw3') 10 | executable('intro_glfw', 11 | sources: ['intro_glfw.cpp', 'render.cpp'], 12 | dependencies: [glfw_dep, vpp_dep]) 13 | endif 14 | -------------------------------------------------------------------------------- /docs/concepts/bufferRange.hpp: -------------------------------------------------------------------------------- 1 | // Idea for a BufferRange rework. 2 | // Renames BufferRange to SharedBufferRange and adds new BufferRange class/struct 3 | // that represents a not owned view that can be passed to operations as 4 | // abstraction over owned/not-owned/shared-owned buffers. 5 | // 6 | // Boils down to something like this: 7 | struct BufferRange { 8 | vpp::Buffer* buf; 9 | vk::DeviceSize offset; 10 | vk::DeviceSize size; 11 | }; 12 | 13 | // Or maybe make SharedBufferRange derive from BufferRange? Probably makes sense. 14 | -------------------------------------------------------------------------------- /docs/concepts/renderPassInfo.hpp: -------------------------------------------------------------------------------- 1 | /// RenderPassCreateInfo utility with defaults, like in pipelineInfo 2 | struct RenderPassInfo { 3 | public: 4 | std::vector attachments; 5 | std::vector subpasses; 6 | std::vector dependencies; 7 | 8 | const auto& info() const; 9 | 10 | protected: 11 | vk::RenderPassCreateInfo info_; 12 | }; 13 | 14 | RenderPassInfo rpDefault(vk::Format dst); 15 | RenderPassInfo rpMultisapmle(vk::Format dst, vk::Format msTarget, 16 | vk::SampleCountBits); 17 | -------------------------------------------------------------------------------- /.clang_complete: -------------------------------------------------------------------------------- 1 | -I./include 2 | -I./src 3 | -I./build/include 4 | -I./build/subprojects/vkpp/include 5 | -I./build/subprojects/ny/include 6 | -I./build/subprojects/nytl/include 7 | -I./build/subprojects/vkpp/include 8 | -I./subprojects/dlg/include 9 | -I./subprojects/vkpp/include 10 | -I./subprojects/ny/include 11 | -I./subprojects/nytl 12 | -Wall 13 | -Wextra 14 | -Wpedantic 15 | -Wno-missing-braces 16 | -Wno-pragma-once-outside-header 17 | -Wno-unused-const-variable 18 | -std=c++17 19 | -I/the_following_is_for_vpp_built_as_subproject/ 20 | -I../../build/subprojects/vkpp/include 21 | -I../vkpp/include 22 | -------------------------------------------------------------------------------- /docs/examples/data/intro-old.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_ARB_separate_shader_objects : enable 4 | #extension GL_ARB_shading_language_420pack : enable 5 | 6 | layout (location = 0) out vec4 outColor; 7 | 8 | struct Value { 9 | vec2 pos; 10 | vec4 color; 11 | }; 12 | 13 | const Value[] values = { 14 | {{-0.75, -.75}, {.9, .3, .3, 1.0}}, 15 | {{0, .75}, {.3, .9, .3, 1.0}}, 16 | {{.75, -.75}, {.3, .3, .9, 1.0}}, 17 | }; 18 | 19 | void main() 20 | { 21 | gl_Position = vec4(values[gl_VertexIndex].pos, 0.0, 1.0); 22 | gl_Position.y *= -1; 23 | outColor = values[gl_VertexIndex].color; 24 | } 25 | -------------------------------------------------------------------------------- /docs/tests/copy.cpp: -------------------------------------------------------------------------------- 1 | #include "init.hpp" 2 | #include "bugged.hpp" 3 | #include 4 | 5 | // this should not give any warnings in the debug layer 6 | // used to make sure move/copy initialization works correctly 7 | // and does not leak resources 8 | TEST(copy) { 9 | auto& dev = *globals.device; 10 | 11 | vk::BufferCreateInfo bufInfo; 12 | bufInfo.size = 1024; 13 | bufInfo.usage = vk::BufferUsageBits::uniformBuffer; 14 | auto buffer = vpp::Buffer(dev.devMemAllocator(), bufInfo); 15 | buffer = {dev.devMemAllocator(), bufInfo}; 16 | buffer = {dev.devMemAllocator(), bufInfo}; 17 | buffer = {dev.devMemAllocator(), bufInfo}; 18 | } 19 | -------------------------------------------------------------------------------- /include/vpp/meson.build: -------------------------------------------------------------------------------- 1 | install_subdir('.', 2 | install_dir: 'include/vpp', 3 | exclude_files: ['meson.build', 'config.hpp.in']) 4 | 5 | # config file 6 | version = meson.project_version().split('.') 7 | 8 | conf_data = configuration_data() 9 | conf_data.set('vmajor', version[0]) 10 | conf_data.set('vminor', version[1]) 11 | conf_data.set('vpatch', version[2]) 12 | conf_data.set('VPP_ONE_DEVICE_OPTIMIZATION', one_device) 13 | conf_data.set('VPP_SHARED', shared, description: 'Compiled as shared library') 14 | 15 | configure_file(input: 'config.hpp.in', 16 | output: 'config.hpp', 17 | install_dir: 'include/vpp', 18 | configuration: conf_data) 19 | -------------------------------------------------------------------------------- /docs/concepts/rpInstance.hpp: -------------------------------------------------------------------------------- 1 | /// Vulkan RenderPass Instance, i.e. a commandbuffer recording session during 2 | /// a render pass. 3 | class RenderPassInstance : public nytl::NonCopyable { 4 | public: 5 | RenderPassInstance(vk::CommandBuffer cmdbuf, vk::RenderPass pass, vk::Framebuffer framebuffer); 6 | ~RenderPassInstance(); 7 | 8 | const vk::RenderPass& renderPass() const { return renderPass_; } 9 | const vk::CommandBuffer& vkCommandBuffer() const { return commandBuffer_; } 10 | const vk::Framebuffer& vkFramebuffer() const { return framebuffer_; } 11 | 12 | protected: 13 | vk::RenderPass renderPass_ {}; 14 | vk::CommandBuffer commandBuffer_ {}; 15 | vk::Framebuffer framebuffer_ {}; 16 | }; 17 | 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: linux 2 | dist: bionic 3 | language: python 4 | 5 | # TODO: test one_device and codegen again 6 | matrix: 7 | include: 8 | - env: CC=gcc 9 | - env: CC=clang 10 | 11 | install: 12 | - pip3 install meson ninja 13 | - sudo apt-get -y install valgrind libvulkan-dev libvulkan1 vulkan-utils 14 | 15 | script: 16 | - meson build -Dtests=true 17 | # the latest vulkan version in bionic repos is 1.1.70 18 | - meson subprojects checkout 1.1.70 vkpp 19 | - ninja -C build 20 | # yeah, we obviously can't test it, sadly. 21 | # Would need provided GPUs for that (or a CPU implementation but 22 | # I won't spend months trying to get this running on travis...) 23 | # - meson test -C build --print-errorlogs 24 | -------------------------------------------------------------------------------- /docs/examples/render.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // vpp::DefaultRenderer 4 | #include // vpp::PipelineLayout, vpp::Pipeline, ... 5 | #include // vulkan commands and structs 6 | 7 | vpp::RenderPass createRenderPass(const vpp::Device&, vk::Format); 8 | 9 | // vpp::Renderer implementation (using DefaultRenderer since we 10 | // don't need special framebuffer stuff). 11 | // Pretty much only initializes the pipeline, implements an easier resize 12 | // function for the window and implements the command buffer recording. 13 | class MyRenderer : public vpp::DefaultRenderer { 14 | public: 15 | MyRenderer(vk::RenderPass, vk::SwapchainCreateInfoKHR&, 16 | const vpp::Queue& present); 17 | 18 | void resize(const vk::Extent2D& extent); 19 | void record(const RenderBuffer& buf) override; 20 | 21 | protected: 22 | vpp::PipelineLayout pipelineLayout_; 23 | vpp::Pipeline pipeline_; 24 | vk::SwapchainCreateInfoKHR& scInfo_; 25 | }; 26 | -------------------------------------------------------------------------------- /include/vpp/vpp.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | -------------------------------------------------------------------------------- /src/vpp/meson.build: -------------------------------------------------------------------------------- 1 | vpp_sources = [ 2 | 'buffer.cpp', 3 | 'bufferOps.cpp', 4 | 'commandAllocator.cpp', 5 | 'debug.cpp', 6 | 'debugReport.cpp', 7 | 'devMemAllocator.cpp', 8 | 'descriptor.cpp', 9 | 'device.cpp', 10 | 'formats.cpp', 11 | 'handles.cpp', 12 | 'image.cpp', 13 | 'imageOps.cpp', 14 | 'memory.cpp', 15 | 'memoryMap.cpp', 16 | 'memoryResource.cpp', 17 | 'physicalDevice.cpp', 18 | 'pipeline.cpp', 19 | 'queue.cpp', 20 | 'renderer.cpp', 21 | 'resource.cpp', 22 | 'shader.cpp', 23 | 'sharedBuffer.cpp', 24 | 'submit.cpp', 25 | 'swapchain.cpp', 26 | 'trackedDescriptor.cpp', 27 | 'util/file.cpp'] 28 | 29 | vpp_args = ['-DDLG_DEFAULT_TAGS="vpp"'] 30 | if host_machine.system() == 'windows' or host_machine.system().contains('mingw') 31 | if shared 32 | vpp_args += '-DVPP_API=__declspec(dllexport)' 33 | endif 34 | endif 35 | 36 | vpp_lib = library('vpp', 37 | sources: vpp_sources, 38 | dependencies: deps, 39 | cpp_args: vpp_args, 40 | include_directories: [vpp_inc, vpp_inc_private], 41 | install: true) 42 | -------------------------------------------------------------------------------- /src/vpp/resource.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace vpp { 10 | 11 | #ifdef VPP_ONE_DEVICE_OPTIMIZATION 12 | 13 | void Resource::init(const Device& dev) { 14 | if(Device::instance() == &dev) { 15 | return; 16 | } 17 | 18 | auto msg = "vpp::Resource: invalid device instance (compiled with" 19 | "VPP_ONE_DEVICE_OPTIMIZATION)"; 20 | throw std::logic_error(msg); 21 | } 22 | 23 | #else // VPP_ONE_DEVICE_OPTIMIZATION 24 | 25 | Resource::Resource(Resource&& other) noexcept { 26 | device_ = other.device_; 27 | other.device_ = {}; 28 | } 29 | 30 | Resource& Resource::operator=(Resource&& other) noexcept { 31 | device_ = other.device_; 32 | other.device_ = {}; 33 | return *this; 34 | } 35 | 36 | #endif // VPP_ONE_DEVICE_OPTIMIZATION 37 | 38 | } // namespace vpp 39 | -------------------------------------------------------------------------------- /docs/examples/data/intro.frag.h: -------------------------------------------------------------------------------- 1 | #ifndef BINARY_INTRO_FRAG_SPV_DATA_H_INC 2 | #define BINARY_INTRO_FRAG_SPV_DATA_H_INC 3 | 4 | #include 5 | 6 | #if defined(__cplusplus) && __cplusplus >= 201103L 7 | constexpr 8 | #else 9 | const 10 | #endif 11 | 12 | uint32_t intro_frag_spv_data[] = { 13 | 119734787, 65536, 524289, 13, 0, 131089, 1, 393227, 1, 1280527431, 1685353262, 14 | 808793134, 0, 196622, 0, 1, 458767, 4, 4, 1852399981, 0, 9, 11, 196624, 4, 7, 15 | 196611, 2, 450, 589828, 1096764487, 1935622738, 1918988389, 1600484449, 16 | 1684105331, 1868526181, 1667590754, 29556, 589828, 1096764487, 1935622738, 17 | 1768186216, 1818191726, 1969712737, 1600481121, 1882206772, 7037793, 262149, 4, 18 | 1852399981, 0, 393221, 9, 1182037359, 1130848626, 1919904879, 0, 262149, 11, 19 | 1866690153, 7499628, 262215, 9, 30, 0, 262215, 11, 30, 0, 131091, 2, 196641, 3, 20 | 2, 196630, 6, 32, 262167, 7, 6, 4, 262176, 8, 3, 7, 262203, 8, 9, 3, 262176, 21 | 10, 1, 7, 262203, 10, 11, 1, 327734, 2, 4, 0, 3, 131320, 5, 262205, 7, 12, 11, 22 | 196670, 9, 12, 65789, 65592 23 | }; 24 | 25 | #endif // header guard -------------------------------------------------------------------------------- /include/vpp/util/file.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include // VPP_API 8 | #include // nytl::Span 9 | #include // std::vector 10 | #include // std::byte 11 | #include // std::string_view 12 | 13 | namespace vpp { 14 | 15 | /// Reads the file at the given filepath and returns a raw buffer with its contents. 16 | /// binary: Specifies whether the file should be read in binary mode. 17 | /// Throws directly from std::istream on error. 18 | VPP_API std::vector readFile(std::string_view path, bool binary = true); 19 | 20 | /// Writes the given buffer into the file at the given path. 21 | /// binary: Specifies whether the file should be written in binary mode. 22 | /// Throws directly from std::ostream on error. 23 | VPP_API void writeFile(std::string_view path, nytl::Span buffer, 24 | bool binary = true); 25 | 26 | } // namespace vpp 27 | -------------------------------------------------------------------------------- /include/vpp/resourceOneDev.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | namespace vpp { 11 | 12 | class VPP_API Resource { 13 | public: 14 | Resource(const Resource& other) noexcept = default; 15 | ~Resource() = default; 16 | 17 | Resource(Resource&& other) noexcept = default; 18 | Resource& operator=(Resource&& other) noexcept = default; 19 | 20 | const Device& device() const noexcept { return *Device::instance(); } 21 | vk::Instance vkInstance() const noexcept { return device().vkInstance(); } 22 | vk::Device vkDevice() const noexcept { return device().vkDevice(); } 23 | vk::PhysicalDevice vkPhysicalDevice() const noexcept { 24 | return device().vkPhysicalDevice(); 25 | } 26 | 27 | protected: 28 | Resource() = default; 29 | Resource(const Device& dev) { init(dev); } 30 | 31 | void init(const Device& dev); 32 | friend VPP_API void swap(Resource&, Resource&) noexcept {} 33 | }; 34 | 35 | } // namespace vpp 36 | -------------------------------------------------------------------------------- /docs/concepts/framebuffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace vpp { 11 | 12 | /// Idea (was previously in vpp): Framebuffer that owns its attachments. 13 | /// Easier to create and use. 14 | class ManagedFramebuffer : public vpp::Framebuffer { 15 | public: 16 | using AttachmentInfo = std::variant< 17 | // Both create an owned viewable image 18 | ViewableImage::CreateInfo, 19 | std::pair, 20 | vk::ImageView>; // static, non-owned attachment 21 | 22 | public: 23 | ManagedFramebuffer() = default; 24 | ~ManagedFramebuffer(); 25 | 26 | ManagedFramebuffer(ManagedFramebuffer&& rhs) noexcept; 27 | ManagedFramebuffer& operator=(ManagedFramebuffer rhs) noexcept; 28 | 29 | void create(const Device&, const vk::Extent2D& size, 30 | nytl::Span attachments); 31 | void init(vk::RenderPass); 32 | 33 | friend void swap(Framebuffer& a, Framebuffer& b) noexcept; 34 | 35 | protected: 36 | std::vector attachments_; 37 | }; 38 | 39 | } // namespace vpp 40 | 41 | -------------------------------------------------------------------------------- /include/vpp/config.hpp.in: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | // For more information on vpp configuration, see doc/config.md 6 | // This file (config.hpp) is automatically generated using cmake for the configuration 7 | // vpp was built with. Do not change it manually. 8 | 9 | #pragma once 10 | 11 | #define VPP_VMAJOR @vmajor@ 12 | #define VPP_VMINOR @vminor@ 13 | #define VPP_VPATCH @vpatch@ 14 | #define VPP_VERSION VPP_VMAJOR * 10000u + VPP_VMINOR * 100u + VPP_VPATCH 15 | 16 | // If this macro is enabled vpp will only allow one Device instance but nearly all objects 17 | // will consume one word less memory. For more information see resource.hpp 18 | #mesondefine VPP_ONE_DEVICE_OPTIMIZATION 19 | 20 | // Built as shared library? 21 | #mesondefine VPP_SHARED 22 | 23 | // win32 compatibility 24 | #if !defined(VPP_API) && defined(VPP_SHARED) 25 | #if defined(_WIN32) || defined(__CYGWIN__) 26 | #define VPP_API __declspec(dllimport) 27 | #elif __GNUC__ >= 4 28 | #define VPP_API __attribute__((visibility ("default"))) 29 | #endif 30 | #endif 31 | 32 | #ifndef VPP_API 33 | #define VPP_API 34 | #endif 35 | -------------------------------------------------------------------------------- /include/vpp/resourceDev.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | namespace vpp { 11 | 12 | class VPP_API Resource { 13 | public: 14 | Resource(const Resource& other) noexcept = default; 15 | ~Resource() noexcept = default; 16 | 17 | Resource(Resource&& other) noexcept; 18 | Resource& operator=(Resource&& other) noexcept; 19 | 20 | const Device& device() const noexcept { return *device_; } 21 | vk::Instance vkInstance() const noexcept { return device().vkInstance(); } 22 | vk::Device vkDevice() const noexcept { return device().vkDevice(); } 23 | vk::PhysicalDevice vkPhysicalDevice() const noexcept { 24 | return device().vkPhysicalDevice(); 25 | } 26 | 27 | protected: 28 | Resource() noexcept = default; 29 | Resource(const Device& device) noexcept : device_(&device) {} 30 | 31 | void init(const Device& dev) noexcept { device_ = &dev; } 32 | friend VPP_API inline void swap(Resource& a, Resource& b) noexcept { 33 | std::swap(a.device_, b.device_); 34 | } 35 | 36 | private: 37 | const Device* device_ {}; 38 | }; 39 | 40 | } // namespace vpp 41 | 42 | -------------------------------------------------------------------------------- /src/vpp/util/file.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace vpp { 12 | 13 | std::vector readFile(std::string_view filename, bool binary) { 14 | auto openmode = std::ios::ate; 15 | if(binary) { 16 | openmode = std::ios::ate | std::ios::binary; 17 | } 18 | 19 | std::ifstream ifs(std::string{filename}, openmode); 20 | ifs.exceptions(std::ostream::failbit | std::ostream::badbit); 21 | 22 | auto size = ifs.tellg(); 23 | ifs.seekg(0, std::ios::beg); 24 | 25 | std::vector buffer(size); 26 | auto data = reinterpret_cast(buffer.data()); 27 | ifs.read(data, size); 28 | 29 | return buffer; 30 | } 31 | 32 | void writeFile(std::string_view filename, nytl::Span buffer, 33 | bool binary) { 34 | 35 | auto openmode = binary ? std::ios::binary : std::ios::openmode{}; 36 | std::ofstream ofs(std::string{filename}, openmode); 37 | ofs.exceptions(std::ostream::failbit | std::ostream::badbit); 38 | auto data = reinterpret_cast(buffer.data()); 39 | ofs.write(data, buffer.size()); 40 | } 41 | 42 | } // namespace vpp 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /src/vpp/queue.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace vpp { 10 | 11 | // Queue 12 | void Queue::init(const Device& dev, vk::Queue queue, unsigned int fam, 13 | unsigned int id) { 14 | Resource::init(dev); 15 | queue_ = queue; 16 | family_ = fam; 17 | id_ = id; 18 | properties_ = &device().queueFamilyProperties(family_); 19 | } 20 | 21 | // QueueManager::Lock 22 | // If constructed with queue, it only locks this specific queue and 23 | // therefore only locks the shared queue mutex shared. 24 | // If only the shared mutex is given, it is constructed as total lock and therefore 25 | // locks the shared mutex totally 26 | QueueLock::QueueLock(const Device& dev) 27 | : sharedMutex_(dev.sharedQueueMutex()) { 28 | sharedMutex_.lock(); 29 | } 30 | 31 | QueueLock::QueueLock(const Device& dev, const vpp::Queue& queue) 32 | : queueMutex_(&queue.mutex()), sharedMutex_(dev.sharedQueueMutex()) { 33 | queueMutex_->lock(); 34 | sharedMutex_.lock_shared(); 35 | dlg_assertm(&queue.device() == &dev, "QueueLock: Invalid queue given"); 36 | } 37 | 38 | QueueLock::~QueueLock() { 39 | if(queueMutex_) { 40 | sharedMutex_.unlock_shared(); 41 | queueMutex_->unlock(); 42 | } else { 43 | sharedMutex_.unlock(); 44 | } 45 | } 46 | 47 | } // namespace vpp 48 | -------------------------------------------------------------------------------- /src/vpp/commandAllocator.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #include 6 | #include 7 | 8 | namespace vpp { 9 | namespace fwd { 10 | const vk::CommandBufferLevel primaryCmdBufLevel = 11 | vk::CommandBufferLevel::primary; 12 | } // namespace fwd 13 | 14 | // CommandAllocator 15 | CommandAllocator::CommandAllocator(const Device& dev) : Resource(dev) { 16 | } 17 | 18 | // important to use '==' for flags instead of '&' since flags being 19 | // not present is of equal importance 20 | CommandBuffer CommandAllocator::get(uint32_t family, 21 | vk::CommandPoolCreateFlags flags, vk::CommandBufferLevel lvl) { 22 | for(auto& pool : pools_) { 23 | if(pool.queueFamily == family && pool.flags == flags) { 24 | return pool.allocate(lvl); 25 | } 26 | } 27 | 28 | pools_.emplace_back(device(), family, flags); 29 | return pools_.back().allocate(lvl); 30 | } 31 | 32 | std::vector CommandAllocator::get(uint32_t family, 33 | unsigned int count, vk::CommandPoolCreateFlags flags, 34 | vk::CommandBufferLevel lvl) { 35 | for(auto& pool : pools_) { 36 | if(pool.queueFamily == family && pool.flags == flags) { 37 | return pool.allocate(count, lvl); 38 | } 39 | } 40 | 41 | pools_.emplace_back(device(), family, flags); 42 | return pools_.back().allocate(count, lvl); 43 | } 44 | 45 | CommandAllocator::TrCmdPool::TrCmdPool(const Device& dev, uint32_t qfamily, 46 | vk::CommandPoolCreateFlags xflags) : 47 | CommandPool(dev, qfamily, xflags), queueFamily(qfamily), flags(xflags) { 48 | } 49 | 50 | } // namespace vpp 51 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('vpp', ['c', 'cpp'], 2 | license: 'BSL', 3 | version: '0.3.0', 4 | meson_version: '>=0.46', 5 | default_options: [ 6 | 'cpp_std=c++17', 7 | 'warning_level=3', 8 | ]) 9 | 10 | # options 11 | example_glfw = get_option('example_glfw') 12 | example_ny = get_option('example_ny') 13 | tests = get_option('tests') 14 | one_device = get_option('one_device') 15 | 16 | shared = (get_option('default_library') == 'shared') 17 | 18 | cc = meson.get_compiler('cpp') 19 | 20 | # default arrguments 21 | # warnings and stuff 22 | arguments = [ 23 | '-Wno-unused-parameter', 24 | '-Wno-missing-braces', 25 | '-fvisibility=hidden' 26 | ] 27 | 28 | add_project_arguments( 29 | cc.get_supported_arguments(arguments), 30 | language: 'cpp') 31 | 32 | # dependencies 33 | dep_threads = dependency('threads') 34 | dep_dlg = dependency('dlg', fallback: ['dlg', 'dlg_dep']) 35 | dep_vkpp = dependency('vkpp', fallback: ['vkpp', 'vkpp_dep']) 36 | dep_vulkan = dependency('vulkan') 37 | 38 | deps = [ 39 | dep_threads, 40 | dep_dlg, 41 | dep_vkpp, 42 | dep_vulkan] 43 | 44 | vpp_inc = include_directories('include') 45 | vpp_inc_private = include_directories('src') 46 | 47 | subdir('include/vpp') 48 | subdir('src/vpp') 49 | 50 | # declare dependency 51 | vpp_dep = declare_dependency( 52 | include_directories: vpp_inc, 53 | dependencies: deps, 54 | link_with: vpp_lib) 55 | 56 | # examples, test 57 | # must come after dependency 58 | subdir('docs/examples') 59 | 60 | if tests 61 | subdir('docs/tests') 62 | endif 63 | 64 | # pkgconfig 65 | pkg = import('pkgconfig') 66 | pkg_dirs = ['.'] 67 | pkg.generate( 68 | vpp_lib, 69 | name: 'vpp', 70 | requires: ['vulkan'], 71 | filebase: 'vpp', 72 | subdirs: pkg_dirs, 73 | version: meson.project_version(), 74 | description: 'Vulkan utility library') 75 | -------------------------------------------------------------------------------- /docs/valgrind.supp: -------------------------------------------------------------------------------- 1 | { 2 | vulkan_one_time_malloc 3 | Memcheck:Leak 4 | match-leak-kinds: definite 5 | fun:malloc 6 | obj:* 7 | obj:* 8 | obj:* 9 | obj:* 10 | obj:/usr/lib/libvulkan.so.* 11 | obj:/usr/lib/libvulkan.so.* 12 | obj:* 13 | obj:* 14 | obj:* 15 | obj:* 16 | obj:/usr/lib/libvulkan.so.* 17 | } 18 | { 19 | vulkan_one_time_calloc 20 | Memcheck:Leak 21 | match-leak-kinds: definite 22 | fun:calloc 23 | obj:* 24 | obj:* 25 | obj:* 26 | obj:* 27 | obj:/usr/lib/libvulkan.so.* 28 | obj:/usr/lib/libvulkan.so.* 29 | obj:* 30 | obj:* 31 | obj:* 32 | obj:* 33 | obj:/usr/lib/libvulkan.so.* 34 | } 35 | { 36 | vulkan_instance_create_one_time_new 37 | Memcheck:Leak 38 | match-leak-kinds: definite 39 | fun:_Znwm 40 | obj:* 41 | obj:* 42 | obj:* 43 | obj:/usr/lib/libvulkan.so.* 44 | fun:vkCreateInstance 45 | fun:_ZN2vk14createInstanceERKNS_18InstanceCreateInfoEPKNS_19AllocationCallbacksE 46 | fun:_ZN3vpp8InstanceC1ERKN2vk18InstanceCreateInfoE 47 | fun:_Z11initGlobalsv 48 | fun:main 49 | } 50 | { 51 | dl_one_time_alloc 52 | Memcheck:Leak 53 | match-leak-kinds: definite 54 | fun:malloc 55 | obj:* 56 | obj:* 57 | obj:* 58 | obj:* 59 | fun:call_init.part.0 60 | fun:_dl_init 61 | fun:dl_open_worker 62 | fun:_dl_catch_error 63 | fun:_dl_open 64 | obj:/usr/lib/libdl-*.so 65 | fun:_dl_catch_error 66 | } 67 | { 68 | vulkan_enumerate_devices 69 | Memcheck:Leak 70 | match-leak-kinds: definite 71 | fun:malloc 72 | fun:realpath@@GLIBC_2.3 73 | obj:* 74 | obj:* 75 | obj:* 76 | obj:* 77 | obj:* 78 | obj:/usr/lib/libvulkan.so.1.1.107 79 | obj:/usr/lib/libvulkan.so.1.1.107 80 | obj:* 81 | obj:/usr/lib/libvulkan.so.1.1.107 82 | fun:vkEnumeratePhysicalDevices 83 | } 84 | -------------------------------------------------------------------------------- /include/vpp/fwd.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | #include // std::uint8_t 11 | #include // std::size_t 12 | 13 | namespace vpp { 14 | 15 | // treat them as built-in as they probably should be 16 | using std::uint8_t; 17 | using std::uint16_t; 18 | using std::uint32_t; 19 | using std::uint64_t; 20 | 21 | using std::int8_t; 22 | using std::int16_t; 23 | using std::int32_t; 24 | using std::int64_t; 25 | 26 | using std::size_t; 27 | 28 | class Resource; 29 | class Buffer; 30 | class Image; 31 | class ImageView; 32 | class Surface; 33 | class Swapchain; 34 | class ShaderProgram; 35 | class ShaderModule; 36 | class DeviceMemory; 37 | class Pipeline; 38 | class DebugCallback; 39 | class DebugMessenger; 40 | class DescriptorSet; 41 | class DescriptorPool; 42 | class DescriptorSetLayout; 43 | class Framebuffer; 44 | class QueryPool; 45 | class RenderPass; 46 | class CommandPool; 47 | class CommandBuffer; 48 | class PipelineCache; 49 | class PipelineLayout; 50 | class Fence; 51 | class Semaphore; 52 | class Event; 53 | class BufferHandle; 54 | class ImageHandle; 55 | class BufferView; 56 | class Sampler; 57 | 58 | class Instance; 59 | class Device; 60 | class Queue; 61 | class Renderer; 62 | class DefaultRenderer; 63 | class DeviceMemoryAllocator; 64 | class ViewableImage; 65 | 66 | class SharedBuffer; 67 | class SubBuffer; 68 | class CommandAllocator; 69 | class BufferAllocator; 70 | class QueueSubmitter; 71 | class BufferSpan; 72 | 73 | class TrDsLayout; 74 | class TrDsPool; 75 | class TrDs; 76 | class DescriptorAllocator; 77 | 78 | struct ViewableImageCreateInfo; 79 | 80 | } // namespace vpp 81 | -------------------------------------------------------------------------------- /src/vpp/bufferOps.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace vpp { 12 | 13 | vpp::SubBuffer fillStaging(vk::CommandBuffer cb, const BufferSpan& span, 14 | nytl::Span data) { 15 | dlg_assert(vk::DeviceSize(data.size()) <= span.size()); 16 | auto& dev = span.device(); 17 | vpp::SubBuffer stage = {dev.bufferAllocator(), vk::DeviceSize(data.size()), 18 | vk::BufferUsageBits::transferSrc, dev.hostMemoryTypes()}; 19 | auto map = stage.memoryMap(0, data.size()); 20 | std::memcpy(map.ptr(), data.data(), data.size()); 21 | 22 | vk::BufferCopy copy; 23 | copy.dstOffset = span.offset(); 24 | copy.srcOffset = stage.offset(); 25 | copy.size = data.size(); 26 | vk::cmdCopyBuffer(cb, stage.buffer(), span.buffer(), {{copy}}); 27 | 28 | return stage; 29 | } 30 | 31 | void fillDirect(vk::CommandBuffer cb, const BufferSpan& span, 32 | nytl::Span data) { 33 | dlg_assert(vk::DeviceSize(data.size()) <= span.size()); 34 | vk::cmdUpdateBuffer(cb, span.buffer(), span.offset(), data.size(), 35 | data.data()); 36 | } 37 | 38 | vpp::SubBuffer readStaging(vk::CommandBuffer cb, const BufferSpan& src) { 39 | auto& dev = src.device(); 40 | vpp::SubBuffer stage = {dev.bufferAllocator(), src.size(), 41 | vk::BufferUsageBits::transferSrc, dev.hostMemoryTypes()}; 42 | 43 | vk::BufferCopy copy; 44 | copy.dstOffset = stage.offset(); 45 | copy.srcOffset = src.offset(); 46 | copy.size = src.size(); 47 | vk::cmdCopyBuffer(cb, src.buffer(), stage.buffer(), {{copy}}); 48 | 49 | return stage; 50 | } 51 | 52 | } // namespace vpp 53 | -------------------------------------------------------------------------------- /docs/concepts/allocationCallbacks.cpp: -------------------------------------------------------------------------------- 1 | // to be inserted into device.cpp 2 | // probably not worth it though, the driver can do this better than we can 3 | // and the host-side allocator is mainly for debugging (as stated by the spec) 4 | 5 | struct VulkanAllocator 6 | { 7 | std::pmr::memory_resource& memoryResource; 8 | std::vector vulkanAllocations; 9 | vk::AllocationCallbacks allocationCallbacks; 10 | 11 | VulkanAllocator(std::pmr::memory_resource& memres) : memoryResource(memres) 12 | { 13 | allocationCallbacks.pUserData = this; 14 | allocationCallbacks.pfnAllocation = (vk::PfnAllocationFunction) &VulkanAllocator::alloc; 15 | } 16 | 17 | void* alloc(std::size_t size, std::size_t align, vk::SystemAllocationScope scope); 18 | void free(void* ptr); 19 | void* realloc(void* ptr, std::size_t size, std::size_t align, vk::SystemAllocationScope scp); 20 | void internalAlloc(size_t size, vk::InternalAllocationType type, vk::SystemAllocationScope scp); 21 | void internalFree(size_t size, vk::InternalAllocationType type, vk::SystemAllocationScope scp); 22 | 23 | static void* alloc(void* data, std::size_t size, std::size_t align, 24 | vk::SystemAllocationScope scope) 25 | { ((VulkanAllocator*)data)->alloc(size, align, scope); } 26 | 27 | static void free(void* data, void* ptr) 28 | { ((VulkanAllocator*)data)->free(ptr); } 29 | 30 | static void* realloc(void* data, void* original, std::size_t size, std::size_t align, 31 | vk::SystemAllocationScope scope) 32 | { ((VulkanAllocator*)data)->realloc(original, size, align, scope); } 33 | 34 | static void internalAlloc(void* data, size_t size, vk::InternalAllocationType type, 35 | vk::SystemAllocationScope scope) 36 | { ((VulkanAllocator*)data)->internalAlloc(size, type, scope); } 37 | 38 | static void internalFree(void* data, size_t size, vk::InternalAllocationType type, 39 | vk::SystemAllocationScope scope) 40 | { ((VulkanAllocator*)data)->internalFree(size, type, scope); } 41 | }; 42 | -------------------------------------------------------------------------------- /src/vpp/memoryResource.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #include 6 | #include 7 | 8 | namespace vpp { 9 | 10 | void MemoryResource::init(InitData& data) { 11 | dlg_assert(data.allocator && data.reservation); 12 | dlg_assert(!memory_); 13 | 14 | auto [mem, alloc] = data.allocator->alloc(data.reservation); 15 | memory_ = &mem; 16 | offset_ = alloc.offset; 17 | data.allocator = nullptr; 18 | } 19 | 20 | MemoryResource::~MemoryResource() { 21 | if(memory_) { 22 | memory_->free(offset_); 23 | } 24 | } 25 | 26 | MemoryResource::MemoryResource(DeviceMemory& mem, vk::DeviceSize offset) : 27 | memory_(&mem), offset_(offset) { 28 | } 29 | 30 | bool MemoryResource::mappable() const { 31 | return memory_ && memory_->mappable(); 32 | } 33 | 34 | MemoryMapView MemoryResource::memoryMap(vk::DeviceSize offset, 35 | vk::DeviceSize size) const { 36 | dlg_assert(size); 37 | dlg_assert(memory_); 38 | return memory_->map({offset_ + offset, size}); 39 | } 40 | 41 | void swap(MemoryResource& a, MemoryResource& b) { 42 | using std::swap; 43 | swap(a.memory_, b.memory_); 44 | swap(a.offset_, b.offset_); 45 | } 46 | 47 | // InitData 48 | MemoryResource::InitData::InitData(InitData&& rhs) noexcept { 49 | allocator = rhs.allocator; 50 | reservation = rhs.reservation; 51 | rhs.allocator = {}; 52 | rhs.reservation = {}; 53 | } 54 | 55 | MemoryResource::InitData& MemoryResource::InitData::operator=( 56 | InitData&& rhs) noexcept { 57 | this->~InitData(); 58 | 59 | allocator = rhs.allocator; 60 | reservation = rhs.reservation; 61 | rhs.allocator = {}; 62 | rhs.reservation = {}; 63 | return *this; 64 | } 65 | 66 | MemoryResource::InitData::~InitData() { 67 | if(allocator) { 68 | dlg_assert(reservation); 69 | allocator->cancel(reservation); 70 | } 71 | } 72 | 73 | } // namespace vpp 74 | -------------------------------------------------------------------------------- /docs/concepts/handle.hpp: -------------------------------------------------------------------------------- 1 | // Idea: provide typesafe handle wrapper (for vkpp) 2 | // Then, vk::nullHandle would finally be possible. 3 | // Or name it vk::nullhdl? 4 | 5 | 6 | // #1 7 | // Not yet working completely 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | 14 | namespace vk { 15 | 16 | //Handle 17 | template 18 | class Handle { 19 | private: 20 | std::uint64_t data_ {}; 21 | 22 | public: 23 | Handle() = default; 24 | Handle(const T& n) : data_((std::uint64_t)n) {} 25 | 26 | operator T() const { return (T)data_; } 27 | operator bool() const { return data_ != 0; } 28 | 29 | T native() const { return (T)data_; } 30 | std::uint64_t data() cosnt { return data_; } 31 | }; 32 | 33 | template 34 | bool operator==(const Handle& a, const Handle& b) { return a.data() == b.data(); } 35 | 36 | template 37 | bool operator!=(const Handle& a, const Handle& b) { return a.data() != b.data(); } 38 | 39 | ///NullHandle 40 | struct NullHandle { 41 | template operator Handle() const { return {}; } 42 | } nullHandle; 43 | 44 | } 45 | 46 | 47 | // #2 48 | // Not yet working completely 49 | 50 | namespace vk { 51 | 52 | //Handle 53 | template 54 | struct Handle { 55 | std::uint64_t data_ {}; // bad idea to always use 64 bits 56 | operator bool() const { return data_; } 57 | operator std::uint64_t() const { return data_; } 58 | template explicit operator O*() const { return (O*)data_; } 59 | }; 60 | 61 | 62 | template 63 | bool operator==(const Handle& a, const Handle& b) { return a.data() == b.data(); } 64 | 65 | template 66 | bool operator!=(const Handle& a, const Handle& b) { return a.data() != b.data(); } 67 | 68 | ///NullHandle 69 | struct NullHandle { 70 | template operator Handle() const { return {}; } 71 | } nullHandle; 72 | 73 | } 74 | 75 | #define VPP_DEFINE_HANDLE(name)\ 76 | struct name##_T; \ 77 | using name = vk::Handle; 78 | -------------------------------------------------------------------------------- /include/vpp/commandAllocator.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace vpp { 13 | namespace fwd { 14 | extern VPP_API const vk::CommandBufferLevel primaryCmdBufLevel; 15 | } 16 | 17 | // Owns a pool of commandPools that allow to allocate commandBuffers of all 18 | // kind. Not synchronized in any way, must always be used from only 19 | // one thread. 20 | class VPP_API CommandAllocator : public Resource { 21 | public: 22 | // Command Pool that stores its own flags and queue family. 23 | class TrCmdPool : public CommandPool { 24 | public: 25 | uint32_t queueFamily {}; 26 | vk::CommandPoolCreateFlags flags {}; 27 | 28 | public: 29 | TrCmdPool() = default; 30 | TrCmdPool(const Device&, uint32_t qfamily, vk::CommandPoolCreateFlags); 31 | ~TrCmdPool() = default; 32 | 33 | TrCmdPool(TrCmdPool&&) noexcept = default; 34 | TrCmdPool& operator=(TrCmdPool&&) noexcept = default; 35 | }; 36 | 37 | public: 38 | CommandAllocator(const Device& dev); 39 | ~CommandAllocator() = default; 40 | 41 | // Allocates a commandBuffer for the calling thread that matches 42 | // the given requirements. 43 | CommandBuffer get(uint32_t qfamily, vk::CommandPoolCreateFlags = {}, 44 | vk::CommandBufferLevel = fwd::primaryCmdBufLevel); 45 | 46 | // Allocates multiple commandBuffers for the calling thread with 47 | // the given requirements. 48 | // Might be more efficient than multiple calls to get. 49 | std::vector get(uint32_t qfamily, unsigned int count, 50 | vk::CommandPoolCreateFlags = {}, 51 | vk::CommandBufferLevel = fwd::primaryCmdBufLevel); 52 | 53 | const auto& pools() const { return pools_; } 54 | 55 | protected: 56 | std::vector pools_; 57 | }; 58 | 59 | } // namespace vpp 60 | -------------------------------------------------------------------------------- /include/vpp/util/allocation.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include // std::size_t 8 | #include // std::ceil 9 | #include // std::clamp 10 | #include // std::is_unsigned 11 | 12 | namespace vpp { 13 | 14 | /// Utility struct that represents an allocated range (offset + size). 15 | template 16 | struct BasicAllocation { 17 | Size offset {0}; 18 | Size size {0}; 19 | }; 20 | 21 | /// Returns the end of the given allocation (i.e. one-past-end address) 22 | template 23 | constexpr Size end(const BasicAllocation& a) { 24 | return a.offset + a.size; 25 | } 26 | 27 | template constexpr bool 28 | operator==(const BasicAllocation& a, const BasicAllocation& b) { 29 | return a.offset == b.offset && a.size == b.size; 30 | } 31 | 32 | template constexpr bool 33 | operator!=(const BasicAllocation& a, const BasicAllocation& b) { 34 | return a.offset != b.offset || a.size != b.size; 35 | } 36 | 37 | /// Aligns an offset to the given alignment. 38 | /// An alignment of 0 zero will not change the offset. 39 | /// An offset of 0 is treated as aligned with every possible alignment. 40 | /// Undefined if either value is negative. 41 | template 42 | constexpr auto align(A offset, B alignment) { 43 | if(offset == 0 || alignment == 0) { 44 | return offset; 45 | } 46 | 47 | auto rest = offset % alignment; 48 | return rest ? A(offset + (alignment - rest)) : A(offset); 49 | } 50 | 51 | /// Returns whether the first allocation fully contains the second one. 52 | template constexpr bool 53 | contains(const BasicAllocation& a, const BasicAllocation& b) { 54 | return (b.offset == std::clamp(b.offset, a.offset, end(a)) && 55 | end(b) == std::clamp(end(b), a.offset, end(a))); 56 | } 57 | 58 | } // namespace vpp 59 | -------------------------------------------------------------------------------- /include/vpp/memoryResource.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace vpp { 13 | 14 | /// Represents a resource that might be bound to memory span and keeps track 15 | /// of this memory. Does not support any kind of sparse bindings. 16 | class VPP_API MemoryResource { 17 | public: 18 | struct InitData { 19 | InitData() = default; 20 | InitData(InitData&&) noexcept; 21 | InitData& operator=(InitData&&) noexcept; 22 | ~InitData(); // cancels reservation 23 | 24 | DeviceMemoryAllocator* allocator {}; 25 | DeviceMemoryAllocator::ReservationID reservation {}; 26 | }; 27 | 28 | public: 29 | MemoryResource() = default; 30 | MemoryResource(DeviceMemory&, vk::DeviceSize offset); 31 | ~MemoryResource(); 32 | 33 | MemoryResource(MemoryResource&& rhs) noexcept { swap(*this, rhs); } 34 | MemoryResource& operator=(MemoryResource rhs) noexcept { 35 | swap(*this, rhs); 36 | return *this; 37 | } 38 | 39 | /// When the two-step deferred constructor was used, this function 40 | /// will allocate the memory for this resource. 41 | void init(InitData& data); 42 | 43 | /// Returns whether this resource is bound to hostVisible memory. 44 | bool mappable() const; 45 | 46 | /// Creates/Updates the memoryMap of the memory this resource is 47 | /// bound to and returns a view. Must be bound to hostVisible memory, 48 | /// i.e. mappable() must be true. 49 | MemoryMapView memoryMap(vk::DeviceSize offset, 50 | vk::DeviceSize size) const; 51 | 52 | DeviceMemory& memory() const { return *memory_; } 53 | vk::DeviceSize memoryOffset() const { return offset_; } 54 | 55 | friend VPP_API void swap(MemoryResource& a, MemoryResource& b); 56 | 57 | protected: 58 | DeviceMemory* memory_ {}; 59 | vk::DeviceSize offset_ {}; 60 | }; 61 | 62 | } // namespace vpp 63 | -------------------------------------------------------------------------------- /include/vpp/pipeline.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace vpp { 14 | 15 | /// Utility for easier pipeline creation. Sets some useful defaults, 16 | /// provides everything needed and connects it. 17 | /// Checklist for things you probably have to set yourself: 18 | /// - vertex input state stuff (all of vertexInfo), empty by default 19 | /// - [assembly info] default is triangle fan 20 | /// - [blend attachment] one attachment by default, enabled with add ops 21 | /// - optional: depthStencil, tesselation, multisample, other dynamic states 22 | class VPP_API GraphicsPipelineInfo { 23 | public: 24 | vk::PipelineVertexInputStateCreateInfo vertex; 25 | vk::PipelineInputAssemblyStateCreateInfo assembly; 26 | vk::PipelineRasterizationStateCreateInfo rasterization; 27 | vk::PipelineColorBlendStateCreateInfo blend; 28 | vk::PipelineViewportStateCreateInfo viewport; 29 | vk::PipelineDynamicStateCreateInfo dynamic; 30 | vk::PipelineMultisampleStateCreateInfo multisample; 31 | vk::PipelineTessellationStateCreateInfo tesselation; 32 | vk::PipelineDepthStencilStateCreateInfo depthStencil; 33 | 34 | vpp::ShaderProgram program; 35 | 36 | public: 37 | GraphicsPipelineInfo(); 38 | GraphicsPipelineInfo(vk::RenderPass, vk::PipelineLayout, 39 | vpp::ShaderProgram&& program, 40 | unsigned subpass = 0, 41 | vk::SampleCountBits = vk::SampleCountBits::e1); 42 | 43 | const vk::GraphicsPipelineCreateInfo& info(); 44 | 45 | void pNext(void*); 46 | void flags(vk::PipelineCreateFlags); 47 | void layout(vk::PipelineLayout); 48 | void renderPass(vk::RenderPass rp); 49 | void subpass(unsigned); 50 | void base(vk::Pipeline = {}); 51 | void base(int); 52 | 53 | private: 54 | vk::GraphicsPipelineCreateInfo info_; 55 | }; 56 | 57 | /// Saves a pipeline cache to the given filename. 58 | /// Returns whether saving was succesful. Will not throw on failure. 59 | VPP_API bool save(vk::Device dev, vk::PipelineCache cache, std::string_view filename); 60 | inline bool save(const PipelineCache& cache, std::string_view file) { 61 | return save(cache.device(), cache, file); 62 | } 63 | 64 | } // namespace vpp 65 | -------------------------------------------------------------------------------- /include/vpp/bufferOps.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace vpp { 14 | 15 | /// Fills the given buffer span by creating a temporary host visible 16 | /// staging buffer, filling that and recording a copy command on the given 17 | /// command buffer for copying the data to the given buffer span. 18 | /// Returns the host visible staging SubBuffer, must not be destroyed 19 | /// until the command buffer has finished execution. 20 | [[nodiscard]] VPP_API vpp::SubBuffer fillStaging(vk::CommandBuffer cb, 21 | const BufferSpan& dst, nytl::Span data); 22 | 23 | /// Simply uses a call to vkCmdUpdateBuffer, therefore uses no staging buffer. 24 | /// The vulkan spec states that this is less efficient than a staging buffer 25 | /// and only allowed for a data size up to 65536. Should be avoided for 26 | /// large amounts of data, see fillStaging for that. 27 | VPP_API void fillDirect(vk::CommandBuffer cb, const BufferSpan& dst, 28 | nytl::Span data); 29 | 30 | template && std::is_standard_layout_v>> 32 | [[nodiscard]] vpp::SubBuffer fillStagingObj(vk::CommandBuffer cb, 33 | const BufferSpan& span, const T& obj) { 34 | auto ptr = reinterpret_cast(&obj); 35 | auto size = sizeof(obj); 36 | return fillStaging(cb, span, {ptr, ptr + size}); 37 | } 38 | 39 | template && std::is_standard_layout_v>> 41 | void fillDirectObj(vk::CommandBuffer cb, const BufferSpan& span, const T& obj) { 42 | auto ptr = reinterpret_cast(&obj); 43 | auto size = sizeof(obj); 44 | fillDirect(cb, span, {ptr, ptr + size}); 45 | } 46 | 47 | /// Creates a host visible staging buffer, records a command for copying data 48 | /// from the given BufferSpan into the staging buffer and returns the 49 | /// staging buffer. It must not be destroyed until the given command buffer 50 | /// has finished execution. Then it can be memory mapped to read the data. 51 | [[nodiscard]] VPP_API vpp::SubBuffer readStaging(vk::CommandBuffer cb, 52 | const BufferSpan& src); 53 | 54 | } // namespace vpp 55 | -------------------------------------------------------------------------------- /docs/tests/framebuffer.cpp: -------------------------------------------------------------------------------- 1 | #include "init.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | // we create a renderpass and a framebuffer for it (as simple 8 | // as possible) 9 | TEST(framebuffer) { 10 | auto& dev = *globals.device; 11 | const auto size = vk::Extent2D {420, 693}; 12 | const auto colorFormat = vk::Format::r8g8b8a8Srgb; 13 | 14 | vpp::ViewableImageCreateInfo vic(colorFormat, vk::ImageAspectBits::color, 15 | size, vk::ImageUsageBits::colorAttachment); 16 | EXPECT(vpp::supported(dev, vic.img), true); 17 | vpp::ViewableImage color(dev.devMemAllocator(), vic); 18 | 19 | vic.img.usage = vk::ImageUsageBits::depthStencilAttachment; 20 | vic.img.format = vpp::findSupported(dev, {{ 21 | vk::Format::d32Sfloat, 22 | vk::Format::d16Unorm, 23 | vk::Format::d24UnormS8Uint, 24 | vk::Format::d32SfloatS8Uint, 25 | vk::Format::x8D24UnormPack32 26 | }}, vic.img); 27 | vic.view.format = vic.img.format; 28 | vic.view.subresourceRange = {vk::ImageAspectBits::depth, 0, 1, 0, 1}; 29 | vpp::ViewableImage depth(dev.devMemAllocator(), vic); 30 | 31 | vk::AttachmentDescription attachments[2] {{{}, 32 | colorFormat, vk::SampleCountBits::e1, 33 | vk::AttachmentLoadOp::dontCare, 34 | vk::AttachmentStoreOp::dontCare, 35 | vk::AttachmentLoadOp::dontCare, 36 | vk::AttachmentStoreOp::dontCare, 37 | vk::ImageLayout::undefined, 38 | vk::ImageLayout::colorAttachmentOptimal, 39 | }, {{}, 40 | vic.img.format, vk::SampleCountBits::e1, 41 | vk::AttachmentLoadOp::dontCare, 42 | vk::AttachmentStoreOp::dontCare, 43 | vk::AttachmentLoadOp::dontCare, 44 | vk::AttachmentStoreOp::dontCare, 45 | vk::ImageLayout::undefined, 46 | vk::ImageLayout::depthStencilAttachmentOptimal, 47 | } 48 | }; 49 | 50 | vk::AttachmentReference colorRef { 51 | 0, vk::ImageLayout::colorAttachmentOptimal 52 | }; 53 | 54 | vk::AttachmentReference depthRef { 55 | 1, vk::ImageLayout::depthStencilAttachmentOptimal 56 | }; 57 | 58 | vk::SubpassDescription subpass {{}, 59 | vk::PipelineBindPoint::graphics, 0, {}, 60 | 1, &colorRef, 0, &depthRef 61 | }; 62 | 63 | vk::RenderPassCreateInfo rpInfo; 64 | vpp::RenderPass renderPass {dev, {{}, 65 | 2, attachments, 66 | 1, &subpass, 67 | }}; 68 | 69 | auto fbAttachments = {color.vkImageView(), depth.vkImageView()}; 70 | vk::FramebufferCreateInfo info { 71 | {}, renderPass, 2, fbAttachments.begin(), size.width, size.height, 1 72 | }; 73 | auto fb = vpp::Framebuffer(dev, info); 74 | auto moved = std::move(fb); 75 | } 76 | -------------------------------------------------------------------------------- /include/vpp/init.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include // std::forward 8 | 9 | namespace vpp { 10 | 11 | /// Safe wrapper for deferred two step initialization. 12 | /// It safely ensures two-step-initiazation for local objects. 13 | /// First, create multiple resources (via the Init constructor), then 14 | /// call init on all of them. This way, all resources can be batch-allocated 15 | /// which will result in much less pools and base resources being needed. 16 | template 17 | class Init { 18 | public: 19 | /// Constructs an internal object of type T with the given arguments 20 | /// and the internally stored InitData as first argument. 21 | template 22 | Init(A&&... args) : obj_(data_, std::forward(args)...) {} 23 | 24 | /// Calls 'init' on the object to be initialized with the given arguments 25 | /// and the internally stored InitData as first argument. 26 | /// Since this finished initialization, move returns the object. 27 | template [[nodiscard]] 28 | T init(A&&... args) { 29 | obj_.init(data_, std::forward(args)...); 30 | return std::move(obj_); 31 | } 32 | 33 | /// Usually not needed 34 | T& object() { return obj_; } 35 | auto& data() { return data_; } 36 | 37 | private: 38 | using InitData = typename T::InitData; 39 | InitData data_ {}; 40 | T obj_; 41 | }; 42 | 43 | /// Like init, but initializes an object in place. 44 | /// Useful for non movable objects. 45 | template 46 | class InitObject { 47 | public: 48 | /// Constructs an internal object of type T with the given arguments 49 | /// and the internally stored InitData as first argument. 50 | template 51 | InitObject(T& ref, A&&... args) : ref_(ref) { 52 | ref_.create(data_, std::forward(args)...); 53 | } 54 | 55 | /// Calls 'init' on the object to be initialized with the given arguments 56 | /// and the internally stored InitData as first argument. 57 | /// Since this finished initialization, move returns the object. 58 | template 59 | void init(A&&... args) { 60 | ref_.init(data_, std::forward(args)...); 61 | } 62 | 63 | /// Usually not needed 64 | T& object() { return ref_; } 65 | auto& data() { return data_; } 66 | 67 | private: 68 | using InitData = typename T::InitData; 69 | InitData data_ {}; 70 | T& ref_; 71 | }; 72 | 73 | } // namespace vpp 74 | -------------------------------------------------------------------------------- /docs/tests/init.hpp: -------------------------------------------------------------------------------- 1 | #define BUGGED_NO_MAIN 2 | #include "bugged.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | class CustomDebugCallback : public vpp::DebugMessenger { 13 | public: 14 | using vpp::DebugMessenger::DebugMessenger; 15 | 16 | void call(MsgSeverity severity, MsgTypeFlags types, 17 | const Data& data) noexcept override { 18 | if(severity == MsgSeverity::error) { 19 | ++errors; 20 | } 21 | 22 | if(severity == MsgSeverity::warning) { 23 | ++warnings; 24 | } 25 | 26 | return vpp::DebugMessenger::call(severity, types, data); 27 | } 28 | 29 | unsigned int warnings {}; 30 | unsigned int errors {}; 31 | }; 32 | 33 | struct Globals { 34 | vpp::Instance instance; 35 | std::unique_ptr debugCallback; 36 | std::unique_ptr device; 37 | }; 38 | 39 | static Globals globals; 40 | 41 | void initGlobals() { 42 | constexpr const char* iniExtensions[] = { 43 | VK_KHR_SURFACE_EXTENSION_NAME, 44 | VK_EXT_DEBUG_UTILS_EXTENSION_NAME 45 | }; 46 | 47 | constexpr auto layer = "VK_LAYER_KHRONOS_validation"; 48 | 49 | vk::InstanceCreateInfo instanceInfo; 50 | instanceInfo.pApplicationInfo = nullptr; 51 | instanceInfo.enabledLayerCount = 1; 52 | instanceInfo.ppEnabledLayerNames = &layer; 53 | instanceInfo.enabledExtensionCount = sizeof(iniExtensions) / sizeof(iniExtensions[0]); 54 | instanceInfo.ppEnabledExtensionNames = iniExtensions; 55 | 56 | globals.instance = {instanceInfo}; 57 | globals.debugCallback = std::make_unique(globals.instance); 58 | globals.device = std::make_unique(globals.instance); 59 | 60 | dlg_info("Physical device info:\n\t{}", 61 | vpp::description(globals.device->vkPhysicalDevice(), "\n\t")); 62 | } 63 | 64 | unsigned dlgErrors = 0; 65 | unsigned dlgWarnings = 0; 66 | void dlgHandler(const struct dlg_origin* origin, const char* string, void* data) { 67 | if(origin->level == dlg_level_error) { 68 | ++dlgErrors; 69 | } else if(origin->level == dlg_level_warn) { 70 | ++dlgWarnings; 71 | } 72 | 73 | dlg_default_output(origin, string, data); 74 | } 75 | 76 | int main() { 77 | initGlobals(); 78 | dlg_set_handler(dlgHandler, nullptr); 79 | 80 | auto ret = bugged::Testing::run(); 81 | globals.device = {}; 82 | 83 | ret += globals.debugCallback->errors + dlgErrors; 84 | ret += globals.debugCallback->warnings + dlgWarnings; 85 | 86 | globals.debugCallback = {}; 87 | globals.instance = {}; 88 | 89 | return ret; 90 | } 91 | -------------------------------------------------------------------------------- /docs/examples/data/intro.vert.h: -------------------------------------------------------------------------------- 1 | #ifndef BINARY_INTRO_VERT_SPV_DATA_H_INC 2 | #define BINARY_INTRO_VERT_SPV_DATA_H_INC 3 | 4 | #include 5 | 6 | #if defined(__cplusplus) && __cplusplus >= 201103L 7 | constexpr 8 | #else 9 | const 10 | #endif 11 | 12 | uint32_t intro_vert_spv_data[] = { 13 | 119734787, 65536, 524289, 62, 0, 131089, 1, 393227, 1, 1280527431, 1685353262, 14 | 808793134, 0, 196622, 0, 1, 524303, 0, 4, 1852399981, 0, 13, 37, 55, 196611, 2, 15 | 450, 589828, 1096764487, 1935622738, 1918988389, 1600484449, 1684105331, 16 | 1868526181, 1667590754, 29556, 589828, 1096764487, 1935622738, 1768186216, 17 | 1818191726, 1969712737, 1600481121, 1882206772, 7037793, 262149, 4, 1852399981, 18 | 0, 393221, 11, 1348430951, 1700164197, 2019914866, 0, 393222, 11, 0, 19 | 1348430951, 1953067887, 7237481, 458758, 11, 1, 1348430951, 1953393007, 20 | 1702521171, 0, 458758, 11, 2, 1130327143, 1148217708, 1635021673, 6644590, 21 | 458758, 11, 3, 1130327143, 1147956341, 1635021673, 6644590, 196613, 13, 0, 22 | 262149, 17, 1970037078, 101, 262150, 17, 0, 7565168, 327686, 17, 1, 1869377379, 23 | 114, 393221, 37, 1449094247, 1702130277, 1684949368, 30821, 327685, 40, 24 | 1701080681, 1818386808, 101, 327685, 55, 1131705711, 1919904879, 0, 327685, 58, 25 | 1701080681, 1818386808, 101, 327752, 11, 0, 11, 0, 327752, 11, 1, 11, 1, 26 | 327752, 11, 2, 11, 3, 327752, 11, 3, 11, 4, 196679, 11, 2, 262215, 37, 11, 42, 27 | 262215, 55, 30, 0, 131091, 2, 196641, 3, 2, 196630, 6, 32, 262167, 7, 6, 4, 28 | 262165, 8, 32, 0, 262187, 8, 9, 1, 262172, 10, 6, 9, 393246, 11, 7, 6, 10, 10, 29 | 262176, 12, 3, 11, 262203, 12, 13, 3, 262165, 14, 32, 1, 262187, 14, 15, 0, 30 | 262167, 16, 6, 2, 262174, 17, 16, 7, 262187, 8, 18, 3, 262172, 19, 17, 18, 31 | 262187, 6, 20, 3208642560, 327724, 16, 21, 20, 20, 262187, 6, 22, 1063675494, 32 | 262187, 6, 23, 1050253722, 262187, 6, 24, 1065353216, 458796, 7, 25, 22, 23, 33 | 23, 24, 327724, 17, 26, 21, 25, 262187, 6, 27, 0, 262187, 6, 28, 1061158912, 34 | 327724, 16, 29, 27, 28, 458796, 7, 30, 23, 22, 23, 24, 327724, 17, 31, 29, 30, 35 | 327724, 16, 32, 28, 20, 458796, 7, 33, 23, 23, 22, 24, 327724, 17, 34, 32, 33, 36 | 393260, 19, 35, 26, 31, 34, 262176, 36, 1, 14, 262203, 36, 37, 1, 262176, 39, 37 | 7, 19, 262176, 41, 7, 16, 262176, 47, 3, 7, 262187, 6, 49, 3212836864, 262176, 38 | 50, 3, 6, 262203, 47, 55, 3, 262187, 14, 57, 1, 262176, 59, 7, 7, 327734, 2, 4, 39 | 0, 3, 131320, 5, 262203, 39, 40, 7, 262203, 39, 58, 7, 262205, 14, 38, 37, 40 | 196670, 40, 35, 393281, 41, 42, 40, 38, 15, 262205, 16, 43, 42, 327761, 6, 44, 41 | 43, 0, 327761, 6, 45, 43, 1, 458832, 7, 46, 44, 45, 27, 24, 327745, 47, 48, 13, 42 | 15, 196670, 48, 46, 393281, 50, 51, 13, 15, 9, 262205, 6, 52, 51, 327813, 6, 43 | 53, 52, 49, 393281, 50, 54, 13, 15, 9, 196670, 54, 53, 262205, 14, 56, 37, 44 | 196670, 58, 35, 393281, 59, 60, 58, 56, 57, 262205, 7, 61, 60, 196670, 55, 61, 45 | 65789, 65592 46 | }; 47 | 48 | #endif // header guard -------------------------------------------------------------------------------- /src/vpp/buffer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include // std::move 11 | 12 | namespace vpp { 13 | 14 | // Buffer 15 | Buffer::Buffer(DeviceMemoryAllocator& alloc, const vk::BufferCreateInfo& info, 16 | unsigned int memBits) : 17 | Buffer(alloc, vk::createBuffer(alloc.device(), info), memBits) { 18 | } 19 | 20 | Buffer::Buffer(DeviceMemoryAllocator& alloc, vk::Buffer buffer, 21 | unsigned int memBits) { 22 | InitData data; 23 | *this = {data, alloc, buffer, memBits}; 24 | init(data); 25 | } 26 | 27 | Buffer::Buffer(DeviceMemory& mem, const vk::BufferCreateInfo& info) : 28 | BufferHandle(mem.device(), vk::createBuffer(mem.device(), info)) { 29 | auto reqs = vk::getBufferMemoryRequirements(mem.device(), vkHandle()); 30 | dlg_assertm(reqs.memoryTypeBits & (1 << mem.type()), "Invalid memory type"); 31 | auto alloc = mem.alloc(reqs.size, reqs.alignment, AllocationType::linear); 32 | if(alloc.size == 0) { 33 | throw std::runtime_error("Failed to alloc from memory"); 34 | } 35 | 36 | MemoryResource::memory_ = &mem; 37 | MemoryResource::offset_ = alloc.offset; 38 | } 39 | 40 | Buffer::Buffer(DeviceMemory& mem, vk::Buffer buffer, vk::DeviceSize memOffset) : 41 | BufferHandle(mem.device(), buffer), 42 | MemoryResource(mem, memOffset) { 43 | } 44 | 45 | Buffer::Buffer(InitData& data, DeviceMemoryAllocator& alloc, vk::Buffer buffer, 46 | unsigned int memBits) : BufferHandle(alloc.device(), buffer) { 47 | dlg_assert(buffer); 48 | dlg_assert(memBits); 49 | 50 | auto reqs = vk::getBufferMemoryRequirements(alloc.device(), vkHandle()); 51 | reqs.memoryTypeBits &= memBits; 52 | dlg_assertm(reqs.memoryTypeBits, "No memory type bits left"); 53 | dlg_assert(reqs.size > 0); 54 | 55 | data.allocator = &alloc; 56 | data.reservation = data.allocator->reserve(AllocationType::linear, reqs); 57 | } 58 | 59 | Buffer::Buffer(InitData& data, DeviceMemoryAllocator& alloc, 60 | const vk::BufferCreateInfo& info, unsigned int memBits) : 61 | Buffer(data, alloc, vk::createBuffer(alloc.device(), info), memBits) { 62 | } 63 | 64 | void Buffer::init(InitData& data) { 65 | MemoryResource::init(data); 66 | vk::bindBufferMemory(device(), vkHandle(), memory(), memoryOffset()); 67 | } 68 | 69 | // BufferSpan 70 | BufferSpan::BufferSpan(const SubBuffer& b) : allocation_(b.allocation()) { 71 | if(size()) { 72 | buffer_ = &b.buffer(); 73 | } 74 | } 75 | 76 | BufferSpan::BufferSpan(const Buffer& b, vk::DeviceSize size, 77 | vk::DeviceSize offset) : buffer_(&b), allocation_{offset, size} { 78 | dlg_assert(size == 0 || b); 79 | } 80 | 81 | MemoryMapView BufferSpan::memoryMap() const { 82 | return buffer().memoryMap(offset(), size()); 83 | } 84 | 85 | } // namespace vpp 86 | -------------------------------------------------------------------------------- /include/vpp/resource.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include // vpp::Device 9 | #include // std::swap 10 | 11 | /// Resource definition. 12 | /// When VPP_ONE_DEVICE_OPTIMIZATION is defined, the Resource class stores 13 | /// no device member but uses Device::instance instead. That means that 14 | /// resources don't have to store the extra word for the device but also 15 | /// that applications can only use one logcial vulkan device at a time (which 16 | /// almost all do anyways). 17 | /// Both implementations provide the same public and protected interface. 18 | #ifdef VPP_ONE_DEVICE_OPTIMIZATION 19 | #include // vpp::Resource 20 | #else 21 | #include // vpp::Resource 22 | #endif 23 | 24 | namespace vpp { 25 | 26 | /// Utility template base class that makes RAII wrappers easier. 27 | /// Note that move constructor and assignment operator can be defined using the 28 | /// swap member function if there are no additional data members. They cannot be 29 | /// implemented here instead of '= delete' and then simply defaulted in derived 30 | /// classes since the destructor of the derived classes needs to trigger in the 31 | /// case of operator=(ResourceHandle&&). 32 | template 33 | class ResourceHandle : public Resource { 34 | public: 35 | const Handle& vkHandle() const noexcept { return handle_; } 36 | operator const Handle&() const noexcept { return vkHandle(); } 37 | 38 | Handle release() { 39 | auto ret = handle_; 40 | handle_ = {}; 41 | return ret; 42 | } 43 | 44 | protected: 45 | ResourceHandle() = default; 46 | ResourceHandle(const Device& dev, const Handle& h = {}) : 47 | Resource(dev), handle_(h) {} 48 | ~ResourceHandle() = default; 49 | 50 | ResourceHandle(ResourceHandle&& other) noexcept = delete; 51 | ResourceHandle& operator=(ResourceHandle&& other) noexcept = delete; 52 | 53 | friend void swap(ResourceHandle& a, 54 | ResourceHandle& b) noexcept { 55 | using std::swap; 56 | swap(static_cast(a), static_cast(b)); 57 | swap(a.handle_, b.handle_); 58 | } 59 | 60 | protected: 61 | Handle handle_ {}; 62 | }; 63 | 64 | /// Can be used for non owned resources. Does not store any additional data. 65 | /// Derives from the given type and simply calls its protected release function 66 | /// (which should release the owned resource) before calling its destructor 67 | /// which then cannot destroy the not owned resource. 68 | template 69 | class NonOwned : public T { 70 | public: 71 | using T::T; 72 | ~NonOwned() { T::release(); } 73 | 74 | NonOwned(NonOwned&& other) = default; 75 | NonOwned& operator=(NonOwned&& other) { 76 | T::release(); 77 | T::operator=(std::move(other)); 78 | return *this; 79 | } 80 | }; 81 | 82 | } // namespace vpp 83 | -------------------------------------------------------------------------------- /include/vpp/queue.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | #include // std::mutex 11 | #include // std::shared_mutex 12 | 13 | namespace vpp { 14 | 15 | /// Represents a vulkan device queue. 16 | /// Cannot be created or destroyed, must be received by the device class. 17 | /// Provides synchronization mechanisms. 18 | class VPP_API Queue : public Resource { 19 | public: 20 | /// Return the queueFamily of this queue 21 | unsigned int family() const noexcept { return family_; } 22 | 23 | /// Returns the id of this queue which is unique under all queues with the 24 | /// same family. E.g. if there are two queues of family A and one queue of 25 | /// family B, the queues of family A wiill have the ids {0, 1} while the 26 | /// queue of family B will have the id 0. 27 | /// Gives every Queue object (for one device) a unique identification if used 28 | /// together with the family. 29 | unsigned int id() const noexcept { return id_; } 30 | 31 | /// Returns the properties of the queue family of this queue. 32 | const auto& properties() const noexcept { return *properties_; } 33 | 34 | /// The queue must be locked before performing any operations 35 | /// (such as presenting or sparse binding) on the queue. 36 | /// Note that locking the queues mutex is not enough for submitting 37 | /// command buffers to the queue, since while submitting, no operation 38 | /// on any other queue is allowed. 39 | /// Prefer to use the vpp::QueueLock class over using the plain mutex. 40 | std::mutex& mutex() const noexcept { return mutex_; } 41 | 42 | vk::Queue vkHandle() const noexcept { return queue_; } 43 | operator vk::Queue() const noexcept { return queue_; } 44 | 45 | protected: 46 | Queue() = default; 47 | ~Queue() = default; 48 | 49 | Queue(Queue&&) = delete; 50 | Queue& operator=(Queue&&) = delete; 51 | 52 | void init(const Device&, vk::Queue, unsigned int fam, unsigned int id); 53 | 54 | private: 55 | vk::Queue queue_; 56 | const vk::QueueFamilyProperties* properties_; 57 | unsigned int family_; 58 | unsigned int id_; 59 | mutable std::mutex mutex_; 60 | }; 61 | 62 | /// Acquires ownership over a single or all queues. 63 | /// Used for queue synchronization across threads. 64 | /// The constructor without a specific queue will lock all queues, 65 | /// which is e.g. needed when submitting command buffes. 66 | /// Otherwise just ownership over the given queue is claimed. 67 | /// RAII lock class, i.e. the lock is bound the objects lifetime. 68 | struct VPP_API QueueLock { 69 | QueueLock(const Device& dev); 70 | QueueLock(const Device& dev, const vpp::Queue& queue); 71 | ~QueueLock(); 72 | 73 | QueueLock(QueueLock&&) = delete; 74 | QueueLock& operator=(QueueLock&&) = delete; 75 | 76 | private: 77 | std::mutex* queueMutex_ {}; 78 | std::shared_mutex& sharedMutex_; 79 | }; 80 | 81 | } // namespace vpp 82 | -------------------------------------------------------------------------------- /src/vpp/shader.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace vpp { 13 | 14 | vk::ShaderModule loadShaderModule(vk::Device dev, std::string_view filename) { 15 | auto code = readFile(filename, true); 16 | if(code.size() % 4) { 17 | std::string msg = "vpp::loadShaderModule: invalid spirv shader file '"; 18 | msg += filename; 19 | msg += "', filesize isn't a multiple of 4"; 20 | throw std::runtime_error(msg); 21 | } 22 | 23 | auto ptr = reinterpret_cast(code.data()); 24 | return loadShaderModule(dev, {ptr, ptr + (code.size() / 4)}); 25 | } 26 | 27 | vk::ShaderModule loadShaderModule(vk::Device dev, 28 | nytl::Span code) { 29 | vk::ShaderModuleCreateInfo info; 30 | info.codeSize = code.size() * 4; 31 | info.pCode = code.data(); 32 | return vk::createShaderModule(dev, info); 33 | } 34 | 35 | // ShaderModule 36 | ShaderModule::ShaderModule(const Device& dev, vk::ShaderModule module) : 37 | ResourceHandle(dev, module) { 38 | } 39 | ShaderModule::ShaderModule(const Device& dev, std::string_view filename) : 40 | ResourceHandle(dev, loadShaderModule(dev, filename)) { 41 | } 42 | 43 | ShaderModule::ShaderModule(const Device& dev, 44 | nytl::Span code) : 45 | ResourceHandle(dev, loadShaderModule(dev, code)) { 46 | } 47 | 48 | ShaderModule::~ShaderModule() { 49 | if(vkHandle()) { 50 | vk::destroyShaderModule(device(), vkHandle()); 51 | } 52 | } 53 | 54 | vk::ObjectType ShaderModule::vkObjectType() { 55 | return vk::ObjectType::shaderModule; 56 | } 57 | 58 | // ShaderProgram 59 | ShaderProgram::ShaderProgram(nytl::Span infos) { 60 | stages_.reserve(infos.size()); 61 | for(auto& i : infos) { 62 | stage(i); 63 | } 64 | } 65 | 66 | void ShaderProgram::stage(vk::ShaderModule module, vk::ShaderStageBits stage) { 67 | for(auto& s : stages_) { 68 | if(s.stage == stage) { 69 | s.module = module; 70 | return; 71 | } 72 | } 73 | 74 | stages_.push_back({{}, stage, module, u8"main"}); 75 | } 76 | 77 | void ShaderProgram::stage(const vk::PipelineShaderStageCreateInfo& info) { 78 | for(auto& s : stages_) { 79 | if(s.stage == info.stage) { 80 | s = info; 81 | return; 82 | } 83 | } 84 | 85 | stages_.push_back(info); 86 | } 87 | 88 | void ShaderProgram::stage(const StageInfo& info) { 89 | for(auto& s : stages_) { 90 | if(s.stage == info.stage) { 91 | s.pName = info.entry; 92 | s.flags = info.flags; 93 | s.module = info.module; 94 | s.pSpecializationInfo = info.specialization; 95 | return; 96 | } 97 | } 98 | 99 | stages_.push_back({info.flags, info.stage, info.module, info.entry, 100 | info.specialization}); 101 | } 102 | 103 | vk::PipelineShaderStageCreateInfo* ShaderProgram::stage(vk::ShaderStageBits stage) { 104 | for(auto& s : stages_) { 105 | if(s.stage == stage) { 106 | return &s; 107 | } 108 | } 109 | 110 | return nullptr; 111 | } 112 | 113 | const vk::PipelineShaderStageCreateInfo* ShaderProgram::stage( 114 | vk::ShaderStageBits stage) const { 115 | for(auto& s : stages_) { 116 | if(s.stage == stage) { 117 | return &s; 118 | } 119 | } 120 | 121 | return nullptr; 122 | } 123 | 124 | } // namespace vpp 125 | -------------------------------------------------------------------------------- /include/vpp/debugReport.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include // std::vector 12 | #include // std::uint64_t 13 | 14 | namespace vpp { 15 | 16 | /// Vulkan DebugCallback base class. 17 | /// Requires VK_EXT_debug_report to be enabled in instance. 18 | /// Prefer to vpp::DebugMesssenger via VK_EXT_debug_utils, that extension 19 | /// was added later as a replacement for VK_EXT_debug_report. 20 | /// By default this will just dump the received information for the given flags 21 | /// to the standard output. Custom implementations can derive from this class 22 | /// and override the call member to handle debug callbacks in a custom way. 23 | /// Making DebugCallback virtual is reasonable regarding performance 24 | /// since no DebugCallback should be created when using a release build. 25 | /// NonMovable since it registers a pointer to itself as callback user data. 26 | class VPP_API DebugCallback { 27 | public: 28 | struct CallbackInfo { 29 | vk::DebugReportFlagsEXT flags; 30 | vk::DebugReportObjectTypeEXT objectType; 31 | uint64_t srcObject; 32 | size_t location; 33 | int32_t messageCode; 34 | const char* layer; 35 | const char* message; 36 | }; 37 | public: 38 | /// Returns error | warning | performanceWarning. 39 | /// Used as default debug report flags. 40 | /// Additional options would be e.g. information or debug. 41 | static vk::DebugReportFlagsEXT defaultFlags(); 42 | 43 | /// Returns all possible bits. 44 | static vk::DebugReportFlagsEXT allBits(); 45 | 46 | /// The default flags for which to return a validation error, 47 | /// i.e. make the vulkan call fail. Returns only the error flag. 48 | static vk::DebugReportFlagsEXT defaultErrorFlags(); 49 | 50 | public: 51 | [[deprecated("Prefer vpp::DebugMessenger and VK_EXT_debug_utils")]] 52 | DebugCallback(vk::Instance instance, 53 | vk::DebugReportFlagsEXT flags = defaultFlags(), bool verbose = false, 54 | vk::DebugReportFlagsEXT errorFlags = defaultErrorFlags()); 55 | virtual ~DebugCallback(); 56 | 57 | DebugCallback(DebugCallback&&) = delete; 58 | DebugCallback& operator=(DebugCallback&&) = delete; 59 | 60 | vk::Instance vkInstance() const { return instance_; } 61 | vk::DebugReportCallbackEXT vkCallback() const { return debugCallback_; } 62 | 63 | /// This function is called from within the debug callback. It is expected 64 | /// to handle (e.g. simply output) the debug information in some way. 65 | /// Custom debug callbacks can override this function. Note that this 66 | /// function is not allowed to throw any exceptions since it is a callback 67 | /// from potential non C++ code. If this function returns false, the 68 | /// vulkan api call that triggered it will return a valiation failed error 69 | /// code. Note that this function might be called from multiple threads 70 | /// and therefore must be threadsafe (reason why it is marked const). The 71 | /// default implementation always returns true when the error flag is error 72 | /// and false otherwise. 73 | virtual bool call(const CallbackInfo& info) noexcept; 74 | 75 | protected: 76 | vk::Instance instance_ {}; 77 | vk::DebugReportCallbackEXT debugCallback_ {}; 78 | vk::DebugReportFlagsEXT errorFlags_ {}; 79 | bool verbose_ {}; 80 | }; 81 | 82 | // NOTE: could add functions for naming, tagging and debug markers again. 83 | // They were broken and therefore removed in commit 012252c on 7.12.2019 84 | // that fixed them for debug utils since the debug callback extension 85 | // is deprecated anyways. 86 | 87 | } // namespace vpp 88 | 89 | -------------------------------------------------------------------------------- /include/vpp/physicalDevice.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | #include // std::vector 11 | #include // std::pair 12 | #include // std::string 13 | #include // std::array 14 | 15 | namespace vpp { 16 | 17 | // Tries to select the best physical device from the given span by querying its 18 | // properties. Will choose the one with the best properties like device type, 19 | // biggest memory and needed queue families. 20 | // Can be used if an application has no special needs and just wants 21 | // to choose the device that is usual the best. 22 | [[nodiscard]] VPP_API vk::PhysicalDevice choose(nytl::Span); 23 | 24 | // Chooses the best physical device to render on the given surface. 25 | // Usually tries to select the best/most powerful physical device. 26 | // Returns a null handle if the given span is empty or no physical device 27 | // supports rendering on the surface. 28 | // The given surface and physical devices must have been retrieved from the 29 | // given instance which is needed for querying the surface function pointers. 30 | [[nodiscard]] VPP_API vk::PhysicalDevice choose(nytl::Span, vk::SurfaceKHR); 31 | 32 | // Returns the name of the given enum value. 33 | [[nodiscard]] VPP_API const char* name(vk::PhysicalDeviceType); 34 | 35 | 36 | // Returns the version (major, minor, patch) of the given driverVersion (from 37 | // vk::PhysicalDeviceProperties) 38 | [[nodiscard]] VPP_API std::array apiVersion(uint32_t driverVersion); 39 | 40 | // Returns a (by default multiline) string identifying the given physical device. 41 | // Useful as debug mechanism, users usually only need the name. 42 | [[nodiscard]] VPP_API std::string description(vk::PhysicalDevice, const char* sep = "\n"); 43 | 44 | // Specifies in which way a queue family should be selected. 45 | // When 'none' is used, the first valid queue family is returned. 46 | // When 'highestCount' is used, the valid queue family with the highest queue 47 | // count is returned. 48 | // When 'mimImageGranularity' is used, the valid queue family with the smallest 49 | // minimal image transfer granularity is returned. Note that this correctly 50 | // treats the granularity (0,0,0) as the worst, i.e. the greatest. 51 | enum class OptimizeQueueFamily { 52 | none, 53 | highestCount, 54 | minImageGranularity 55 | }; 56 | 57 | // Returns a queue family on the given physical device that fulfills the given 58 | // queue bits. The optimize parameter can be used to select the best matching 59 | // queue for the callers needs. 60 | // If there is no such queue family returns -1. 61 | [[nodiscard]] VPP_API int findQueueFamily(vk::PhysicalDevice, vk::QueueFlags, 62 | OptimizeQueueFamily optimize = {}); 63 | 64 | // Returns a queue family that can be used ot present on the given surface. 65 | // The additional queue bits and optimize parameters can be used to optimize 66 | // the returned queue if multiple are available, or try to find a queue family 67 | // that additionally has the given bits set. 68 | // The physical device and instance must have been If there is no such queue 69 | // family returns -1. 70 | [[nodiscard]] VPP_API int findQueueFamily(vk::PhysicalDevice, vk::SurfaceKHR, 71 | vk::QueueFlags = {}, OptimizeQueueFamily optimize = {}); 72 | 73 | // Returns the queue families of the given physical device that are supported 74 | // to present on the given surface (queried with the generic present support 75 | // functions). 76 | [[nodiscard]] VPP_API std::vector supportedQueueFamilies( 77 | vk::SurfaceKHR, vk::PhysicalDevice); 78 | 79 | } // namespace vpp 80 | -------------------------------------------------------------------------------- /docs/tests/address.cpp: -------------------------------------------------------------------------------- 1 | #include "bugged.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | TEST(align) { 8 | EXPECT(vpp::align(1023, 4), 1024); 9 | EXPECT(vpp::align(1023u, 2u), 1024u); 10 | EXPECT(vpp::align(1u, 1024u), 1024u); 11 | EXPECT(vpp::align(0u, 2u), 0u); 12 | EXPECT(vpp::align(233u, 0u), 233u); 13 | EXPECT(vpp::align(933u, 1u), 933u); 14 | EXPECT(vpp::align(933u, 2u), 934u); 15 | } 16 | 17 | TEST(contains) { 18 | using Allocation = vpp::BasicAllocation; 19 | Allocation a {0, 1024}; 20 | Allocation b {0, 10}; 21 | 22 | EXPECT(contains(a, b), true); 23 | EXPECT(contains(b, a), false); 24 | 25 | b = {1, 1023}; 26 | EXPECT(contains(a, b), true); 27 | EXPECT(contains(b, a), false); 28 | EXPECT(a == b, false); 29 | 30 | b = {0, 1024}; 31 | EXPECT(contains(a, b), true); 32 | EXPECT(contains(b, a), true); 33 | EXPECT(a == b, true); 34 | EXPECT(a != b, false); 35 | 36 | b = {0, 1025}; 37 | EXPECT(contains(a, b), false); 38 | EXPECT(contains(b, a), true); 39 | EXPECT(a == b, false); 40 | EXPECT(b == b, true); 41 | 42 | b = {10, 1200}; 43 | EXPECT(contains(a, b), false); 44 | EXPECT(contains(b, a), false); 45 | EXPECT(a != b, true); 46 | EXPECT(b != b, false); 47 | 48 | b = {10, 20}; 49 | EXPECT(contains(a, b), true); 50 | EXPECT(contains(b, a), false); 51 | 52 | b = {0, 0}; 53 | EXPECT(contains(a, b), true); 54 | EXPECT(contains(b, a), false); 55 | 56 | b = {1024, 0}; 57 | EXPECT(contains(a, b), true); 58 | EXPECT(contains(b, a), false); 59 | 60 | b = {10, 0}; 61 | EXPECT(contains(a, b), true); 62 | EXPECT(contains(b, a), false); 63 | 64 | b = {1025, 10}; 65 | EXPECT(contains(a, b), false); 66 | EXPECT(contains(b, a), false); 67 | 68 | b = {1025, 0}; 69 | EXPECT(contains(a, b), false); 70 | EXPECT(contains(b, a), false); 71 | } 72 | 73 | // negative size is undefined behavior, no need to test that 74 | TEST(sContains) { 75 | using Allocation = vpp::BasicAllocation; 76 | Allocation a {-50, 100}; 77 | Allocation b {-10, 10}; 78 | 79 | EXPECT(contains(a, b), true); 80 | EXPECT(contains(b, a), false); 81 | EXPECT(a == b, false); 82 | EXPECT(a == a, true); 83 | EXPECT(b != a, true); 84 | } 85 | 86 | // tbh, not sure how to properly test it. 87 | TEST(texelAddress) { 88 | vk::SubresourceLayout layout {}; 89 | 90 | auto texelSize = 1u; 91 | layout.offset = 100u; 92 | layout.rowPitch = 15u; 93 | layout.depthPitch = 120u; 94 | layout.arrayPitch = 1010u; 95 | 96 | EXPECT(vpp::texelAddress(layout, texelSize, 0, 0, 0, 0), layout.offset); 97 | EXPECT(vpp::texelAddress(layout, texelSize, 1, 0, 0, 0), layout.offset + texelSize); 98 | EXPECT(vpp::texelAddress(layout, texelSize, 1, 1, 0, 0), 99 | layout.offset + layout.rowPitch + texelSize); 100 | EXPECT(vpp::texelAddress(layout, texelSize, 1, 2, 0, 0), 101 | layout.offset + 2 * layout.rowPitch + texelSize); 102 | EXPECT(vpp::texelAddress(layout, texelSize, 1, 2, 3, 0), 103 | layout.offset + 3 * layout.depthPitch + 2 * layout.rowPitch + texelSize); 104 | EXPECT(vpp::texelAddress(layout, texelSize, 1, 2, 3, 4), 105 | layout.offset + 4 * layout.arrayPitch + 3 * layout.depthPitch + 106 | 2 * layout.rowPitch + texelSize); 107 | } 108 | 109 | TEST(imageBufferOffset) { 110 | EXPECT(vpp::tightTexelNumber({1u, 1u, 1u}, 2, 0, 0), 0u); 111 | EXPECT(vpp::tightTexelNumber({1u, 1u, 1u}, 2, 0, 1), 1u); 112 | EXPECT(vpp::tightTexelNumber({5u, 1u, 1u}, 2, 0, 1), 5u); 113 | EXPECT(vpp::tightTexelNumber({5u, 5u, 1u}, 2, 0, 1), 25u); 114 | EXPECT(vpp::tightTexelNumber({5u, 5u, 5u}, 2, 0, 1), 125u); 115 | 116 | EXPECT(vpp::tightTexelNumber({5u, 5u, 5u}, 2, 1, 0), 250u); 117 | EXPECT(vpp::tightTexelNumber({5u, 5u, 5u}, 2, 1, 1), 250u + 2 * 2 * 2); 118 | 119 | EXPECT(vpp::tightLayerTexelNumber({10u, 10u, 10u}, 1, 1, 1), 111u); 120 | } 121 | -------------------------------------------------------------------------------- /include/vpp/formats.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace vpp { 13 | 14 | /// Various helper for choosing dynamic formats as well as default 15 | /// ViewableImageCreateInfo constructors. 16 | /// When choosing formats for your images or texelBuffer, the 17 | /// best way (except for highly variable areas like depth/stencil) 18 | /// is usually to use a format that is guaranteed to be supported 19 | /// in the way you need it by the vulkan standard (section 31.3.3). 20 | 21 | /// Returns the size of the given format in bits. 22 | /// E.g. vk::Format::r8g8b8a8* will return 32, since it has 4 * 8 = 32 bits 23 | /// For compressed formats this function will return the size of one block. 24 | [[nodiscard]] VPP_API unsigned int formatSizeBits(vk::Format); 25 | 26 | /// Returns the size in bytes of the given format. 27 | /// E.g. vk::Format::r8g8b8a8* will return 4, since it has 4 * 8 bits = 4 bytes. 28 | /// For compressed formats this function will return the size of one block. 29 | [[nodiscard]] VPP_API unsigned int formatSize(vk::Format); 30 | 31 | /// Returns the size of one compressed block of a compressed vulkan format. 32 | /// If the given format is not a compressed format, {1, 1} is returned. 33 | /// For vk::Format::undefined, {0, 0} is returned 34 | [[nodiscard]] VPP_API vk::Extent2D blockSize(vk::Format); 35 | 36 | /// Returns whether the given FormatFeatureFlags support the given usage. 37 | [[nodiscard]] VPP_API bool supportedUsage(vk::FormatFeatureFlags, vk::ImageUsageFlags); 38 | [[nodiscard]] VPP_API bool supportedUsage(vk::FormatFeatureFlags, vk::BufferUsageFlags); 39 | 40 | /// Returns whether the format of the given ImageCreateInfo supports the 41 | /// other parameters. 42 | [[nodiscard]] VPP_API bool supported(const Device&, const vk::ImageCreateInfo&, 43 | vk::FormatFeatureFlags additional = {}); 44 | 45 | /// Returns whether the given format is valid for the given use case. 46 | [[nodiscard]] VPP_API bool supported(const Device&, vk::Format, vk::BufferUsageFlags, 47 | vk::FormatFeatureFlags additional = {}); 48 | 49 | /// Selects the first format from the list that supports the given 50 | /// use case. Returns vk::Format::undefined if none is supported. 51 | [[nodiscard]] VPP_API vk::Format findSupported(const Device&, nytl::Span, 52 | const vk::ImageCreateInfo&, vk::FormatFeatureFlags additional = {}); 53 | [[nodiscard]] VPP_API vk::Format findSupported(const Device&, nytl::Span, 54 | vk::BufferUsageFlags, vk::FormatFeatureFlags additional = {}); 55 | 56 | /// Returns the number of mipmap levels needed for a full mipmap 57 | /// chain for an image with the given extent. 58 | [[nodiscard]] VPP_API unsigned mipmapLevels(const vk::Extent2D& extent); 59 | [[nodiscard]] VPP_API unsigned mipmapLevels(const vk::Extent3D& extent); 60 | 61 | /// Returns the size of an image with given size at the given mip level. 62 | /// Returns {1, 1, 1} if the mip level does not exist (i.e. too high). 63 | [[nodiscard]] VPP_API vk::Extent3D mipSize(vk::Extent3D size, unsigned l); 64 | 65 | /// Combines vk::ImageCreateInfo and vk::ImageViewCreatInfo and 66 | /// offers default initializers. 67 | struct VPP_API ViewableImageCreateInfo { 68 | vk::ImageCreateInfo img {}; // info to create the image 69 | vk::ImageViewCreateInfo view {}; // info to create the view 70 | 71 | ViewableImageCreateInfo() = default; 72 | 73 | /// 2D default constructor, 1 layer, 1 sample 74 | ViewableImageCreateInfo(vk::Format format, 75 | vk::ImageAspectBits aspect, vk::Extent2D size, 76 | vk::ImageUsageFlags usage, 77 | vk::ImageTiling tiling = vk::ImageTiling::optimal, 78 | uint32_t mipLevels = 1u); 79 | }; 80 | 81 | } // namespace vpp 82 | -------------------------------------------------------------------------------- /include/vpp/swapchain.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace vpp { 16 | 17 | /// Preferences for creating a swapchain. 18 | /// Can be used to query a valid SwapchainCreateInfo. 19 | struct VPP_API SwapchainPreferences { 20 | enum class ErrorAction { 21 | none, /// Simply chooses another setting. 22 | output, /// Choses another setting and prints a warning 23 | exception /// Throws a std::runtime_error 24 | }; 25 | 26 | SwapchainPreferences(); 27 | 28 | ErrorAction errorAction {}; 29 | bool preferCurrentExtent {true}; // ignore the size parameter if possible 30 | bool preferSrgb {true}; // choose srgb over unorm formats 31 | unsigned minImageCount {2}; 32 | 33 | vk::Format format; // = vk::Format::b8g8r8a8, depends on preferSrgb. 34 | vk::PresentModeKHR presentMode; // = vk::PresentModeKHR::mailbox; 35 | vk::CompositeAlphaBitsKHR alpha; // = vk::CompositeAlphaBitsKHR::opaque; 36 | vk::SurfaceTransformBitsKHR transform; // = vk::SurfaceTransformBitsKHR::identity; 37 | vk::ImageUsageFlags usage; // = vk::ImageUsageBits::colorAttachment; 38 | }; 39 | 40 | /// Parses the given SwapchainPreferences for the given device and surface 41 | /// into a valid SwapchainCreateInfo. This has to be done only once, 42 | /// the returned info can be reused when a swapchain is resized. 43 | VPP_API vk::SwapchainCreateInfoKHR swapchainCreateInfo(const vpp::Device&, 44 | vk::SurfaceKHR, const vk::Extent2D& size, 45 | const SwapchainPreferences& prefs = {}); 46 | 47 | /// Updates the imageExtent member of the given swapchain info 48 | /// given on the capabilities of the surface returned by the surface 49 | /// of the create info. Tries to use the requested width and height as well 50 | /// as possible. Must only be called with a swapchain create info 51 | /// that already has the surface member set. 52 | VPP_API void updateImageExtent(vk::PhysicalDevice, vk::SwapchainCreateInfoKHR& sci, 53 | vk::Extent2D desired); 54 | 55 | /// Swapchain wrapper that (together with SwapchainSettings and its 56 | /// implementations) manages surface property querying and swapchain setup. 57 | /// Does not remember/store any additional information about itself. 58 | class VPP_API Swapchain : public ResourceHandle { 59 | public: 60 | Swapchain() = default; 61 | Swapchain(const Device& device, const vk::SwapchainCreateInfoKHR&); 62 | Swapchain(const Device& dev, vk::SwapchainKHR swapchain); 63 | ~Swapchain(); 64 | 65 | Swapchain(Swapchain&& rhs) noexcept { swap(*this, rhs); } 66 | Swapchain& operator=(Swapchain rhs) noexcept { 67 | swap(*this, rhs); 68 | return *this; 69 | } 70 | 71 | /// Resizes the swapchain to the given size. 72 | /// Should be called if the native window of the underlying surface 73 | /// handle changes it size to make sure the swapchain fills the 74 | /// whole window. 75 | /// Will automatically update the given size (or currentExtent if valid 76 | /// and prefCurrentExtent true) and the oldSwapchain member of the 77 | /// given info. 78 | /// Will return the really used size. 79 | void resize(const vk::Extent2D& size, vk::SwapchainCreateInfoKHR&); 80 | 81 | /// Wrapper for vkAcquireNextImageKHR, will simply forward the result. 82 | vk::Result acquire(std::uint32_t& id, vk::Semaphore sem, 83 | vk::Fence fence = {}, std::uint64_t timeout = UINT64_MAX) const; 84 | 85 | /// Wrapper for vkQueuePresentKHR, will simply forward the result. 86 | vk::Result present(const Queue& queue, unsigned int image, 87 | nytl::Span wait = {}) const; 88 | 89 | /// Wrapper around vkGetSwapchainImagesKHR 90 | std::vector images() const; 91 | 92 | static vk::ObjectType vkObjectType(); 93 | }; 94 | 95 | } // namespace vpp 96 | -------------------------------------------------------------------------------- /include/vpp/submit.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | namespace vpp { 16 | 17 | /// Bundles multiple SubmitInfos into few queueSubmit calls. 18 | /// Can be used to track the submit state using an id. 19 | /// Is bound to a fixed queue and not synchronized in any way but 20 | /// will respect queue locks. 21 | class VPP_API QueueSubmitter { 22 | public: 23 | QueueSubmitter() = default; 24 | QueueSubmitter(const Queue& queue); 25 | ~QueueSubmitter(); 26 | 27 | QueueSubmitter(QueueSubmitter&&) = default; 28 | QueueSubmitter& operator=(QueueSubmitter&&) = default; 29 | 30 | /// Adds a submission. 31 | /// Returns the id associated with this submission. The id 32 | /// will usually not be unique. 33 | /// All values referenced by the given SubmitInfo must 34 | /// stay valid until the returned id was submitted. 35 | /// It will not be possible to remove this submitInfo. 36 | /// If the given second argument is not null, sets it to the specific of 37 | /// this submission. Until the returned id is not submitted, 38 | /// pendingInfos()[specificID] can be used to refer to (and change) 39 | /// this submit info (since submissions cannot be deleted). 40 | uint64_t add(const vk::SubmitInfo& info, unsigned* specificID = {}); 41 | 42 | /// Shortcut overload for above. 43 | /// Note that the reference must stay valid until this batch is 44 | /// submitted. If called with a vpp::CommandBuffer (and therefore 45 | /// the implicit conversion to const vk::CommandBuffer& is used), 46 | /// the vpp::CommandBuffer must not be moved until then. 47 | uint64_t add(const vk::CommandBuffer& cb, unsigned* specificID = {}); 48 | 49 | /// Submits all pending submissions. 50 | /// Returns the number of submissions. 51 | unsigned int submit(); 52 | 53 | /// Makes sure the given id is submitted to the device. 54 | /// The id must have been returned by add. 55 | void submit(uint64_t); 56 | 57 | /// Returns whether the given id was already submitted. 58 | /// Also returns true if it has already completed. 59 | /// The id must have been returned by add. 60 | bool submitted(uint64_t) const; 61 | 62 | /// Returns if the submission associated with the given id 63 | /// has completed on the device. 64 | /// The id must have been returned by add. 65 | bool completed(uint64_t) const; 66 | 67 | /// Waits for the submission associated with the given id to complete. 68 | /// Will submit it if it hasn't been yet submitted. 69 | /// Returns whether the id is now completed (e.g. if false is 70 | /// returned, the timeout triggered the return). 71 | bool wait(uint64_t id, uint64_t timeout = UINT64_MAX); 72 | 73 | /// Returns the number of pending submissions. 74 | unsigned int pending() const; 75 | 76 | /// Can (but does not have to) be called at any time to 77 | /// check all possibly signaled fences. Will be automatically 78 | /// called on submit. 79 | void update(); 80 | 81 | /// Returns the current id. 82 | /// Could be used to observe its state. 83 | uint64_t current() const { return id_; } 84 | 85 | const auto& pendingInfos() const { return pending_; } 86 | auto& pendingInfos() { return pending_; } 87 | 88 | const auto& queue() const { return *queue_; } 89 | const Device& device() const { return queue().device(); } 90 | 91 | protected: 92 | std::vector pending_; 93 | const vpp::Queue* queue_ {}; 94 | 95 | struct NamedFence { 96 | uint64_t id; 97 | vpp::Fence fence; 98 | }; 99 | 100 | mutable std::deque fences_; 101 | mutable std::vector unusedFences_; 102 | uint64_t id_ {1u}; 103 | bool wrapped_ {}; 104 | }; 105 | 106 | } // namespace vpp 107 | -------------------------------------------------------------------------------- /docs/tests/memory.cpp: -------------------------------------------------------------------------------- 1 | #include "init.hpp" 2 | 3 | TEST(memory) { 4 | auto size = 1024u; 5 | auto& dev = *globals.device; 6 | uint32_t type = dev.memoryType(vk::MemoryPropertyBits::hostVisible); 7 | vpp::DeviceMemory memory(dev, {size, type}); 8 | 9 | EXPECT(memory.mappable(), true); 10 | EXPECT(memory.mapped(), nullptr); 11 | 12 | EXPECT(memory.size(), size); 13 | EXPECT(memory.largestFreeSegment(), size); 14 | EXPECT(memory.totalFree(), size); 15 | EXPECT(memory.allocations().empty(), true); 16 | 17 | auto allocSize = 600u; 18 | auto alloc1 = memory.alloc(allocSize, 16, vpp::AllocationType::linear); 19 | EXPECT(alloc1.offset, 0u); 20 | EXPECT(alloc1.size, allocSize); 21 | 22 | EXPECT(memory.largestFreeSegment(), size - allocSize); 23 | EXPECT(memory.totalFree(), size - allocSize); 24 | EXPECT(memory.allocations().size(), 1u); 25 | 26 | EXPECT(memory.alloc(allocSize, 32, vpp::AllocationType::linear).size, 0u); 27 | EXPECT(memory.alloc(100, 1024, vpp::AllocationType::linear).size, 0u); 28 | EXPECT(memory.allocatable(5, 512, vpp::AllocationType::optimal).size, 0u); 29 | EXPECT(memory.allocatable(500, 0, vpp::AllocationType::optimal).size, 0u); 30 | 31 | allocSize = 424u; 32 | auto alloc2 = memory.allocatable(allocSize, 4, vpp::AllocationType::linear); 33 | EXPECT(alloc2.offset, 600u); 34 | EXPECT(alloc2.size, allocSize); 35 | 36 | memory.allocSpecified({alloc2.offset, alloc2.size}, 37 | vpp::AllocationType::linear); 38 | 39 | EXPECT(memory.largestFreeSegment(), 0u); 40 | EXPECT(memory.totalFree(), 0u); 41 | EXPECT(memory.allocations().size(), 2u); 42 | 43 | memory.free(alloc1); 44 | 45 | EXPECT(memory.largestFreeSegment(), size - allocSize); 46 | EXPECT(memory.totalFree(), size - allocSize); 47 | EXPECT(memory.allocations().size(), 1u); 48 | 49 | memory.free(alloc2); 50 | 51 | EXPECT(memory.largestFreeSegment(), size); 52 | EXPECT(memory.totalFree(), size); 53 | EXPECT(memory.allocations().empty(), true); 54 | } 55 | 56 | TEST(map) { 57 | auto size = 1024u; 58 | auto& dev = *globals.device; 59 | uint32_t type = dev.memoryType(vk::MemoryPropertyBits::hostVisible); 60 | vpp::DeviceMemory memory(dev, {size, type}); 61 | 62 | EXPECT(memory.mappable(), true); 63 | auto map = memory.map({0u, 1024u}); 64 | auto map2 = std::move(map); 65 | map = std::move(map2); 66 | 67 | EXPECT(map.offset(), 0u); 68 | EXPECT(map.size(), 1024u); 69 | EXPECT(&map.memory(), &memory); 70 | EXPECT(map.ptr() != nullptr, true); 71 | EXPECT(memory.mapped() != nullptr, true); 72 | 73 | // this will have no effect, just return another view 74 | map2 = memory.map({0, 256}); 75 | EXPECT(map2.offset(), 0u); 76 | EXPECT(map2.size(), 256u); 77 | EXPECT(map2.ptr(), map.ptr()); 78 | EXPECT(map2.memoryMap().valid(), true); 79 | 80 | // just another view 81 | auto map3 = memory.map({256, 256}); 82 | EXPECT(map3.ptr(), map.ptr() + 256); 83 | 84 | map = std::move(map3); 85 | } 86 | 87 | TEST(remap) { 88 | auto size = 1024u; 89 | auto& dev = *globals.device; 90 | uint32_t type = dev.memoryType(vk::MemoryPropertyBits::hostVisible); 91 | vpp::DeviceMemory memory(dev, {size, type}); 92 | 93 | EXPECT(memory.mappable(), true); 94 | auto map = memory.map({100u, 200}); 95 | EXPECT(map.offset(), 100u); 96 | EXPECT(memory.mapped() != nullptr, true); 97 | EXPECT(memory.mapped()->valid(), true); 98 | EXPECT(memory.mapped()->offset(), 100u); 99 | EXPECT(memory.mapped()->size(), 200u); 100 | EXPECT(map.size(), 200u); 101 | EXPECT(&map.memory(), &memory); 102 | EXPECT(map.ptr() != nullptr, true); 103 | 104 | // remap the memory 105 | { 106 | auto map2 = memory.map({0u, 16u}); 107 | EXPECT(map2.offset(), 0u); 108 | EXPECT(map2.size(), 16u); 109 | EXPECT(map2.memoryMap().valid(), true); 110 | EXPECT(memory.mapped(), &map2.memoryMap()); 111 | EXPECT(memory.mapped()->valid(), true); 112 | EXPECT(memory.mapped()->offset(), 0u); 113 | EXPECT(memory.mapped()->size(), 300u); 114 | EXPECT(map2.ptr(), memory.mapped()->ptr()); 115 | EXPECT(map.ptr(), memory.mapped()->ptr() + 100u); 116 | } 117 | 118 | EXPECT(map.ptr() != nullptr, true); 119 | } -------------------------------------------------------------------------------- /include/vpp/shader.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include // vpp::ResourceHandle 9 | #include // nytl::Span 10 | #include // std::string_view 11 | #include // std::vector 12 | 13 | namespace vpp { 14 | 15 | // RAII wrapper around a vulkan ShaderModule. 16 | // Can be created from a file holding the spirv binary or directly from the 17 | // spirv code. 18 | class VPP_API ShaderModule : public ResourceHandle { 19 | public: 20 | ShaderModule() = default; 21 | ShaderModule(const Device& dev, vk::ShaderModule); 22 | 23 | // Throws std::runtime_error if the given file cannot be read or has 24 | // an invalid size. 25 | ShaderModule(const Device& dev, std::string_view file); 26 | ShaderModule(const Device& dev, nytl::Span bytes); 27 | ~ShaderModule(); 28 | 29 | ShaderModule(ShaderModule&& rhs) noexcept { swap(*this, rhs); } 30 | ShaderModule& operator=(ShaderModule&& rhs) noexcept { 31 | swap(*this, rhs); 32 | return *this; 33 | } 34 | 35 | static vk::ObjectType vkObjectType(); 36 | }; 37 | 38 | // ShaderProgram with multiple stages for graphic pipelines. 39 | // Can be used to group multiple shader modules together to full programs. 40 | // Does not own the references shader modules, therefore they have to remain valid 41 | // at least until the ShaderProgram is destructed. 42 | // Note that this class is not a Resource class since it only logically groups 43 | // shader stages togehter and not manages them in any way. 44 | class VPP_API ShaderProgram { 45 | public: 46 | // Describes a single shader stage of a program. 47 | // Lighter version of vk::PipelineShaderStageCreateInfo with sane defaults. 48 | // Module and stage must always be set. 49 | struct StageInfo { 50 | vk::ShaderModule module; 51 | vk::ShaderStageBits stage; 52 | const vk::SpecializationInfo* specialization = nullptr; 53 | const char* entry = u8"main"; 54 | vk::PipelineShaderStageCreateFlags flags = {}; 55 | }; 56 | 57 | public: 58 | ShaderProgram(nytl::Span stages = {}); 59 | ~ShaderProgram() = default; 60 | 61 | ShaderProgram(const ShaderProgram& rhs) = default; 62 | ShaderProgram& operator=(const ShaderProgram& rhs) = default; 63 | 64 | // Returns the given shader stage if there is any, nullptr otherwise. 65 | // If not null, the returned pointer is guaranteed to be valid until the object 66 | // is destructed or a new shader stage is added. 67 | vk::PipelineShaderStageCreateInfo* stage(vk::ShaderStageBits stage); 68 | const vk::PipelineShaderStageCreateInfo* stage(vk::ShaderStageBits stage) const; 69 | 70 | // Changes or adds a new shader stage. 71 | // The passed shader modules must be valid until the ShaderProgram is destructed. 72 | // Note that this might invalidate returned stages as well as previously used 73 | // vkStageInfos data poitners. 74 | void stage(vk::ShaderModule, vk::ShaderStageBits stage); 75 | void stage(const vk::PipelineShaderStageCreateInfo&); 76 | void stage(const StageInfo&); 77 | 78 | // Returns a vector with the stage infos that can be used for pipeline creation. 79 | const std::vector& vkStageInfos() const { 80 | return stages_; 81 | } 82 | 83 | protected: 84 | std::vector stages_; 85 | }; 86 | 87 | // Can be used to create a shader module from a spirv binary file. 88 | // Note that the code size must be a multiple of 4 bytes. 89 | // Throws std::runtime_error if the file cannot be read or has an invalid size. 90 | // Note that the returned object is a plain handle and must be manually destroyed. 91 | // If possible, prefer to use the wrapper class ShaderModule. 92 | [[nodiscard]] VPP_API vk::ShaderModule loadShaderModule(vk::Device dev, 93 | std::string_view filename); 94 | 95 | // Can be used to create a shader module form the raw spirv code. 96 | // Note that the returned object is a plain handle and must be manually destroyed. 97 | // If possible, prefer to use the wrapper class ShaderModule. 98 | [[nodiscard]] VPP_API vk::ShaderModule loadShaderModule(vk::Device dev, 99 | nytl::Span code); 100 | 101 | } // namespace vpp 102 | -------------------------------------------------------------------------------- /src/vpp/pipeline.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace vpp { 12 | 13 | // GraphicsPipelineInfo 14 | GraphicsPipelineInfo::GraphicsPipelineInfo() { 15 | static const auto dynStates = { 16 | vk::DynamicState::viewport, 17 | vk::DynamicState::scissor}; 18 | 19 | static const vk::PipelineColorBlendAttachmentState blendAttachment = { 20 | true, // enable blending 21 | 22 | // color 23 | vk::BlendFactor::srcAlpha, // src 24 | vk::BlendFactor::oneMinusSrcAlpha, //dst 25 | vk::BlendOp::add, 26 | 27 | // alpha 28 | // Rationale behind using the dst alpha is that there 29 | // is no use in storing the src alpha somewhere, as 30 | // we've already processed it via the color blending above. 31 | vk::BlendFactor::zero, // src 32 | vk::BlendFactor::one, // dst 33 | vk::BlendOp::add, 34 | 35 | // color write mask 36 | vk::ColorComponentBits::r | 37 | vk::ColorComponentBits::g | 38 | vk::ColorComponentBits::b | 39 | vk::ColorComponentBits::a, 40 | }; 41 | 42 | assembly.topology = vk::PrimitiveTopology::triangleFan; 43 | 44 | rasterization.polygonMode = vk::PolygonMode::fill; 45 | rasterization.cullMode = vk::CullModeBits::none; 46 | rasterization.frontFace = vk::FrontFace::counterClockwise; 47 | rasterization.depthClampEnable = false; 48 | rasterization.rasterizerDiscardEnable = false; 49 | rasterization.depthBiasEnable = false; 50 | rasterization.lineWidth = 1.f; 51 | 52 | multisample.rasterizationSamples = vk::SampleCountBits::e1; 53 | info_.subpass = 0u; 54 | 55 | blend.attachmentCount = 1; 56 | blend.pAttachments = &blendAttachment; 57 | 58 | viewport.scissorCount = 1; 59 | viewport.viewportCount = 1; 60 | 61 | dynamic.dynamicStateCount = dynStates.size(); 62 | dynamic.pDynamicStates = dynStates.begin(); 63 | } 64 | 65 | GraphicsPipelineInfo::GraphicsPipelineInfo(vk::RenderPass renderPass, 66 | vk::PipelineLayout layout, ShaderProgram&& program, unsigned subpass, 67 | vk::SampleCountBits samples) : GraphicsPipelineInfo() { 68 | this->program = std::move(program); 69 | info_.layout = layout; 70 | info_.renderPass = renderPass; 71 | info_.subpass = subpass; 72 | multisample.rasterizationSamples = samples; 73 | } 74 | 75 | const vk::GraphicsPipelineCreateInfo& GraphicsPipelineInfo::info() { 76 | info_.pStages = program.vkStageInfos().data(); 77 | info_.stageCount = program.vkStageInfos().size(); 78 | info_.pDynamicState = &dynamic; 79 | info_.pMultisampleState = &multisample; 80 | info_.pViewportState = &viewport; 81 | info_.pColorBlendState = &blend; 82 | info_.pRasterizationState = &rasterization; 83 | info_.pVertexInputState = &vertex; 84 | info_.pInputAssemblyState = &assembly; 85 | info_.pDepthStencilState = &depthStencil; 86 | info_.pTessellationState = &tesselation; 87 | return info_; 88 | } 89 | 90 | void GraphicsPipelineInfo::pNext(void* p) { 91 | info_.pNext = p; 92 | } 93 | void GraphicsPipelineInfo::flags(vk::PipelineCreateFlags flags) { 94 | info_.flags = flags; 95 | } 96 | void GraphicsPipelineInfo::layout(vk::PipelineLayout layout) { 97 | info_.layout = layout; 98 | } 99 | void GraphicsPipelineInfo::renderPass(vk::RenderPass rp) { 100 | info_.renderPass = rp; 101 | } 102 | void GraphicsPipelineInfo::subpass(unsigned s) { 103 | info_.subpass = s; 104 | } 105 | void GraphicsPipelineInfo::base(vk::Pipeline b) { 106 | info_.basePipelineIndex = -1; 107 | info_.basePipelineHandle = b; 108 | if(b) { 109 | info_.flags |= vk::PipelineCreateBits::derivative; 110 | } 111 | } 112 | void GraphicsPipelineInfo::base(int i) { 113 | info_.basePipelineIndex = i; 114 | info_.basePipelineHandle = {}; 115 | if(i != -1) { 116 | info_.flags |= vk::PipelineCreateBits::derivative; 117 | } 118 | } 119 | 120 | // util 121 | bool save(vk::Device dev, vk::PipelineCache cache, std::string_view file) { 122 | auto data = vk::getPipelineCacheData(dev, cache); 123 | auto ptr = reinterpret_cast(data.data()); 124 | 125 | try { 126 | writeFile(file, {ptr, ptr + data.size()}); 127 | } catch(const std::exception& err) { 128 | dlg_warn("vpp::save(PipelineCache): {}", err.what()); 129 | return false; 130 | } 131 | 132 | return true; 133 | } 134 | 135 | } // namespace vpp 136 | -------------------------------------------------------------------------------- /docs/tests/objects.cpp: -------------------------------------------------------------------------------- 1 | #include "init.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | // test just instanciates all vpp objects 13 | // also used as bootstrap for all classes that currently have no own test 14 | 15 | TEST(mem) { 16 | auto& dev = *globals.device; 17 | 18 | auto rawMem = vk::allocateMemory(dev, {2048, 0u}); 19 | auto memHandle1 = vpp::DeviceMemoryHandle(dev, {256u, 0u}); 20 | auto memHandle2 = vpp::DeviceMemoryHandle(dev, rawMem); 21 | auto memHandle3 = std::move(memHandle2); 22 | 23 | auto rawMem2 = vk::allocateMemory(dev, {14u, 0u}); 24 | auto mem1 = vpp::DeviceMemory(dev, {256u, 0u}); 25 | auto mem2 = vpp::DeviceMemory(dev, rawMem2, 14u, 0u); 26 | 27 | auto allocator = vpp::DeviceMemoryAllocator(dev); 28 | vk::BufferCreateInfo bufInfo; 29 | bufInfo.size = 256u; 30 | bufInfo.usage = vk::BufferUsageBits::storageBuffer; 31 | auto buf = vpp::Buffer(mem1, bufInfo); 32 | auto buf2 = std::move(buf); 33 | } 34 | 35 | TEST(cmd) { 36 | auto& dev = *globals.device; 37 | 38 | auto commandPool = vpp::CommandPool{dev, 0u}; 39 | auto cmdBuf1 = vpp::CommandBuffer(commandPool); 40 | auto cmdBuf2 = vpp::CommandBuffer(commandPool, vk::CommandBufferLevel::secondary); 41 | 42 | auto cmdBufsRaw = vk::allocateCommandBuffers(dev, {commandPool, 43 | vk::CommandBufferLevel::primary, 2u}); 44 | auto cmdBuf3 = vpp::CommandBuffer(commandPool, cmdBufsRaw[0]); 45 | auto cmdBuf4 = vpp::CommandBuffer(dev, commandPool, cmdBufsRaw[1]); 46 | 47 | auto cmdAllocator = vpp::CommandAllocator(dev); 48 | auto cmdBuf5 = cmdAllocator.get(0); 49 | auto cmdBufs6 = cmdAllocator.get(0, 3); 50 | auto cmdBuf7 = std::move(cmdBuf5); 51 | } 52 | 53 | // returns the index of the first set bit of an uint 54 | // returns -1 if no bit is set 55 | int firstBitSet(uint32_t bits) 56 | { 57 | for(auto i = 0u; i < 32; ++i) { 58 | if(bits & (1 << i)) { 59 | return i; 60 | } 61 | } 62 | 63 | return -1; 64 | } 65 | 66 | TEST(buf) { 67 | auto& dev = *globals.device; 68 | vpp::BufferHandle buf1(dev, {{}, 1024, vk::BufferUsageBits::uniformBuffer}); 69 | 70 | auto rawBuf = vk::createBuffer(dev, {{}, 2341, vk::BufferUsageBits::vertexBuffer}); 71 | auto rawBuf2 = vk::createBuffer(dev, {{}, 12, vk::BufferUsageBits::vertexBuffer}); 72 | vpp::BufferHandle buf2(dev, rawBuf); 73 | 74 | // None of this should actually destroy we buffer, we use it below again 75 | vpp::NonOwned buf2b(dev, rawBuf2); 76 | vpp::NonOwned buf2c(std::move(buf2b)); 77 | buf2c = {}; 78 | 79 | auto hvBits = dev.memoryTypeBits(vk::MemoryPropertyBits::hostVisible); 80 | auto dlBits = dev.memoryTypeBits(vk::MemoryPropertyBits::deviceLocal); 81 | 82 | auto info = vk::BufferCreateInfo {{}, 123, 83 | vk::BufferUsageBits::storageBuffer | 84 | vk::BufferUsageBits::indexBuffer | 85 | vk::BufferUsageBits::transferDst | 86 | vk::BufferUsageBits::uniformTexelBuffer}; 87 | vpp::Buffer buf3(dev.devMemAllocator(), info); 88 | vpp::Buffer buf4(dev.devMemAllocator(), info, hvBits); 89 | 90 | vpp::Buffer::InitData initData5; 91 | vpp::Buffer::InitData initData5b; 92 | 93 | vpp::DeviceMemoryAllocator allocator(dev); 94 | vpp::Buffer buf5(initData5, allocator, info, dlBits); 95 | auto rawBuf3b = vk::createBuffer(dev, info); 96 | vpp::Buffer buf5b(initData5b, allocator, rawBuf3b, dlBits); 97 | vpp::Buffer buf6(allocator, info, dlBits); 98 | buf5.init(initData5); 99 | buf5b.init(initData5b); 100 | EXPECT(allocator.memories().size(), 1u); 101 | 102 | vpp::Buffer buf7(dev.devMemAllocator(), rawBuf2); 103 | 104 | auto rawBuf3 = vk::createBuffer(dev, {{}, 123, vk::BufferUsageBits::uniformBuffer}); 105 | auto reqs = vk::getBufferMemoryRequirements(dev, rawBuf3); 106 | unsigned int type = firstBitSet(reqs.memoryTypeBits); 107 | vpp::DeviceMemory mem(dev, {12000, type}); 108 | auto memAlloc = mem.alloc(reqs.size, reqs.alignment, vpp::AllocationType::linear); 109 | vk::bindBufferMemory(dev, rawBuf3, mem, memAlloc.offset); 110 | vpp::Buffer buf8(mem, rawBuf3, memAlloc.offset); 111 | 112 | vpp::Buffer buf9(mem, info); 113 | auto buf10 = std::move(buf9); 114 | } 115 | 116 | TEST(sync) { 117 | auto& dev = *globals.device; 118 | vpp::Fence fence(dev); 119 | vpp::Semaphore semaphore(dev); 120 | vpp::Event event(dev); 121 | } 122 | 123 | // TODO: add remaining objects 124 | -------------------------------------------------------------------------------- /src/vpp/image.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace vpp { 12 | 13 | // Image 14 | Image::Image(DeviceMemoryAllocator& alloc, const vk::ImageCreateInfo& info, 15 | unsigned int memBits) : Image(alloc, 16 | vk::createImage(alloc.device(), info), info.tiling, memBits) { 17 | } 18 | 19 | Image::Image(DeviceMemoryAllocator& alloc, vk::Image image, 20 | vk::ImageTiling tiling, unsigned int memBits) { 21 | InitData data; 22 | *this = {data, alloc, image, tiling, memBits}; 23 | init(data); 24 | } 25 | 26 | Image::Image(DeviceMemory& mem, const vk::ImageCreateInfo& info) : 27 | ImageHandle(mem.device(), vk::createImage(mem.device(), info)) { 28 | auto reqs = vk::getImageMemoryRequirements(mem.device(), vkHandle()); 29 | dlg_assertm(reqs.memoryTypeBits & (1 << mem.type()), "Invalid memory type"); 30 | 31 | auto allocType = info.tiling == vk::ImageTiling::linear ? 32 | AllocationType::linear : AllocationType::optimal; 33 | auto alloc = mem.alloc(reqs.size, reqs.alignment, allocType); 34 | if(alloc.size == 0) { 35 | throw std::runtime_error("Failed to alloc from memory"); 36 | } 37 | 38 | memory_ = &mem; 39 | offset_ = alloc.offset; 40 | } 41 | 42 | Image::Image(DeviceMemory& mem, vk::Image image, vk::DeviceSize memOffset) : 43 | ImageHandle(mem.device(), image), 44 | MemoryResource(mem, memOffset) { 45 | } 46 | 47 | Image::Image(InitData& data, DeviceMemoryAllocator& alloc, vk::Image image, 48 | vk::ImageTiling tiling, unsigned int memBits) : 49 | ImageHandle(alloc.device(), image) { 50 | dlg_assert(image); 51 | dlg_assert(memBits); 52 | 53 | auto reqs = vk::getImageMemoryRequirements(alloc.device(), vkHandle()); 54 | reqs.memoryTypeBits &= memBits; 55 | dlg_assertm(reqs.memoryTypeBits, "Image: No memory type bits left"); 56 | dlg_assert(reqs.size > 0); 57 | auto type = tiling == vk::ImageTiling::linear ? 58 | AllocationType::linear : AllocationType::optimal; 59 | 60 | data.allocator = &alloc; 61 | data.reservation = data.allocator->reserve(type, reqs); 62 | } 63 | 64 | Image::Image(InitData& data, DeviceMemoryAllocator& alloc, 65 | const vk::ImageCreateInfo& info, unsigned int mbits) : Image(data, alloc, 66 | vk::createImage(alloc.device(), info), info.tiling, mbits) { 67 | } 68 | 69 | void Image::init(InitData& data) { 70 | MemoryResource::init(data); 71 | vk::bindImageMemory(device(), vkHandle(), memory(), memoryOffset()); 72 | } 73 | 74 | // ViewableImage 75 | ViewableImage::ViewableImage(DeviceMemoryAllocator& alloc, 76 | const vk::ImageCreateInfo& imgInfo, const vk::ImageViewCreateInfo& viewInfo, 77 | unsigned int memBits) : 78 | ViewableImage(Image{alloc, imgInfo, memBits}, viewInfo) { 79 | } 80 | 81 | ViewableImage::ViewableImage(DeviceMemoryAllocator& alloc, 82 | const ViewableImageCreateInfo& info, unsigned int memBits) : 83 | ViewableImage(alloc, info.img, info.view, memBits) { 84 | } 85 | 86 | ViewableImage::ViewableImage(Image&& img, vk::ImageViewCreateInfo ivi) : 87 | image_(std::move(img)) { 88 | dlg_assert(image_.vkHandle()); 89 | ivi.image = image_; 90 | imageView_ = {device(), ivi}; 91 | } 92 | 93 | ViewableImage::ViewableImage(Image&& image, ImageView&& view) : 94 | image_(std::move(image)), imageView_(std::move(view)) { 95 | } 96 | 97 | ViewableImage::ViewableImage(InitData& data, DeviceMemoryAllocator& alloc, 98 | const vk::ImageCreateInfo& info, unsigned int memBits) : 99 | image_(data, alloc, info, memBits) { 100 | } 101 | 102 | ViewableImage::ViewableImage(InitData&, Image&& img) : image_(std::move(img)) { 103 | } 104 | 105 | void ViewableImage::init(InitData& data, vk::ImageViewCreateInfo ivi) { 106 | dlg_assert(image_.vkHandle()); 107 | dlg_assert(!imageView_.vkHandle()); 108 | 109 | image_.init(data); 110 | ivi.image = image_; 111 | imageView_ = {device(), ivi}; 112 | } 113 | 114 | inline namespace debug { 115 | 116 | vk::Result nameHandle(const ViewableImage& vi, std::string name) { 117 | auto pos = name.length(); 118 | name += ".image"; 119 | if(auto res = nameHandle(vi.image(), name.c_str()); 120 | res != vk::Result::success) { 121 | return res; 122 | } 123 | 124 | name.erase(pos); 125 | name += ".view"; 126 | return nameHandle(vi.imageView(), name.c_str()); 127 | } 128 | 129 | } // namespace debug 130 | } // namespace vpp 131 | -------------------------------------------------------------------------------- /include/vpp/buffer.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include // vpp::BufferHandle 9 | #include // vpp::MemoryResource 10 | 11 | namespace vpp { 12 | 13 | /// Combines Buffer and MemoryResource, i.e. a buffer that knows 14 | /// its bound memory. Does not support sparse memory bindings. 15 | class VPP_API Buffer : public BufferHandle, public MemoryResource { 16 | public: 17 | Buffer() = default; 18 | 19 | /// The various types of constructors: 20 | /// - Transfer ownership vs create 21 | /// * (1,3,5): BufferCreateInfo parameter: create a new buffer 22 | /// * (2,4,6): vk::Buffer parameter: transfer ownerhip of existent buffer 23 | /// Note that for (2,6), the buffer must NOT already be bound to memory 24 | /// - Allocate mechanism. You can either use/pass 25 | /// * (1,2): using a DeviceMemoryAllocator, if none is given using 26 | /// the default threadlocal allocator of the given device. 27 | /// Guarantees that the memory is allocated on a memory type 28 | /// contained in memBits (e.g. to assure it's allocated 29 | /// on hostVisible memory). 30 | /// * (3): allocate on a specific DeviceMemory object. 31 | /// Will throw std::runtime_error if the DeviceMemory fails 32 | /// to allocate enough memory. The DeviceMemory must 33 | /// be allocated on a type that is supported for the 34 | /// created buffer (the vulkan spec gives some guarantess there). 35 | /// * (4): Will pass ownership of the allocated memory span which must 36 | /// be bound to the buffer. 37 | /// - Deferred? See the vpp doc for deferred initialization 38 | /// * (1,2,3) bind the buffer immediately to memory. For (1,2) this 39 | /// means to immediately request memory, which might result 40 | /// in a vkAllocateMemory call 41 | /// * (5, 6) does not bind the buffer to memory, only when 42 | /// ensureMemory is called. Already issues a reserving request 43 | /// to the DeviceMemoryAllocator, might result in less 44 | /// memory allocations made if multiple resources are created deferred. 45 | Buffer(DeviceMemoryAllocator&, const vk::BufferCreateInfo&, 46 | unsigned int memBits = ~0u); 47 | Buffer(DeviceMemoryAllocator&, vk::Buffer, unsigned int memBits = ~0u); 48 | 49 | Buffer(DeviceMemory&, const vk::BufferCreateInfo&); 50 | Buffer(DeviceMemory&, vk::Buffer, vk::DeviceSize memOffset); 51 | 52 | /// Creates the buffer without any bound memory. 53 | /// You have to call the ensureMemory function later on to 54 | /// make sure memory is bound to the buffer. 55 | Buffer(InitData&, DeviceMemoryAllocator&, const vk::BufferCreateInfo&, 56 | unsigned int memBits = ~0u); 57 | Buffer(InitData&, DeviceMemoryAllocator&, vk::Buffer, 58 | unsigned int memBits = ~0u); 59 | 60 | Buffer(Buffer&& rhs) noexcept = default; 61 | Buffer& operator=(Buffer&& rhs) noexcept = default; 62 | 63 | /// When the two-step deferred constructor was used, this function 64 | /// will allocate the memory for this resource. 65 | /// Otherwise undefined behaviour. 66 | void init(InitData& data); 67 | }; 68 | 69 | /// Continous non-owned device buffer span. 70 | class VPP_API BufferSpan { 71 | public: 72 | BufferSpan() = default; 73 | BufferSpan(const SubBuffer&); 74 | BufferSpan(const Buffer&, vk::DeviceSize size, vk::DeviceSize offset = 0u); 75 | 76 | MemoryMapView memoryMap() const; 77 | 78 | const auto& buffer() const { return *buffer_; } 79 | auto offset() const { return allocation_.offset; } 80 | auto end() const { return vpp::end(allocation_); } 81 | auto size() const { return allocation_.size; } 82 | const auto& allocation() const { return allocation_; } 83 | 84 | const auto& device() const { return buffer_->device(); } 85 | auto vkDevice() const { return device().vkDevice(); } 86 | auto vkInstance() const { return device().vkInstance(); } 87 | auto vkPhysicalDevice() const { return device().vkPhysicalDevice(); } 88 | bool valid() const { return buffer_ && size(); } 89 | 90 | protected: 91 | const Buffer* buffer_ {}; 92 | BasicAllocation allocation_ {}; 93 | }; 94 | 95 | inline bool operator==(const BufferSpan& a, const BufferSpan& b) { 96 | return (!a.valid() && !b.valid()) || (a.valid() && b.valid() && 97 | a.buffer().vkHandle() == b.buffer().vkHandle() && 98 | a.allocation() == b.allocation()); 99 | } 100 | 101 | } // namespace vpp 102 | -------------------------------------------------------------------------------- /docs/tests/submit.cpp: -------------------------------------------------------------------------------- 1 | #include "init.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | TEST(submit) { 11 | auto& dev = *globals.device; 12 | auto& queue = *dev.queue(vk::QueueBits::graphics); 13 | vpp::QueueSubmitter submitter(queue); 14 | 15 | // Basic tests, assuring the information/submit/add/wait 16 | // funcitonality works 17 | 18 | EXPECT(submitter.current(), 1u); 19 | EXPECT(submitter.pending(), 0u); 20 | EXPECT(submitter.submitted(1u), false); 21 | EXPECT(submitter.completed(1u), false); 22 | 23 | vpp::Event event = {dev}; 24 | 25 | auto cmdBuf = dev.commandAllocator().get(queue.family()); 26 | vk::beginCommandBuffer(cmdBuf, {}); 27 | vk::cmdSetEvent(cmdBuf, event, vk::PipelineStageBits::allCommands); 28 | vk::endCommandBuffer(cmdBuf); 29 | 30 | EXPECT(submitter.add({{}, {}, {}, 1, &cmdBuf.vkHandle(), {}, {}}), 1u); 31 | EXPECT(submitter.current(), 1u); 32 | EXPECT(submitter.pending(), 1u); 33 | EXPECT(submitter.submitted(1u), false); 34 | EXPECT(submitter.completed(1u), false); 35 | 36 | submitter.submit(1u); 37 | EXPECT(submitter.current(), 2u); 38 | EXPECT(submitter.pending(), 0u); 39 | EXPECT(submitter.submitted(1u), true); 40 | 41 | submitter.wait(1u); 42 | EXPECT(submitter.pending(), 0u); 43 | EXPECT(submitter.submitted(1u), true); 44 | EXPECT(submitter.completed(1u), true); 45 | EXPECT(vk::getEventStatus(dev, event), vk::Result::eventSet); 46 | 47 | submitter.update(); 48 | EXPECT(submitter.current(), 2u); 49 | EXPECT(submitter.pending(), 0u); 50 | EXPECT(submitter.submitted(1u), true); 51 | EXPECT(submitter.completed(1u), true); 52 | } 53 | 54 | TEST(wait) { 55 | auto& dev = *globals.device; 56 | auto& submitter = dev.queueSubmitter(); 57 | auto& queue = submitter.queue(); 58 | 59 | // we record 2 commands buffer: 60 | // - cmdBuf1 waits on event1 to become signaled 61 | // - cmdBuf2 just signals event2 62 | // - there is a semaphore dependency to execute cmdBuf2 after 63 | // cmdBuf1 has completed 64 | // - after submitting, we can start everything (leading finally 65 | // to the settings on event2 by setting event1 from the device) 66 | // - we test that QueueSubmitter submits everything the way and order 67 | // we expect it to, allowing such things 68 | 69 | vpp::Event event1 = {dev}; 70 | vpp::Event event2 = {dev}; 71 | 72 | auto cmdBuf1 = dev.commandAllocator().get(queue.family()); 73 | vk::beginCommandBuffer(cmdBuf1, {}); 74 | vk::cmdWaitEvents(cmdBuf1, {{event1.vkHandle()}}, vk::PipelineStageBits::host, 75 | vk::PipelineStageBits::topOfPipe, {}, {}, {}); 76 | vk::endCommandBuffer(cmdBuf1); 77 | 78 | vpp::Semaphore semaphore = {dev}; 79 | 80 | auto submitInfo = vk::SubmitInfo { 81 | {}, {}, {}, 82 | 1, &cmdBuf1.vkHandle(), 83 | 1, &semaphore.vkHandle() 84 | }; 85 | 86 | auto id = submitter.add(submitInfo); 87 | 88 | auto cmdBuf2 = dev.commandAllocator().get(queue.family()); 89 | vk::beginCommandBuffer(cmdBuf2, {}); 90 | vk::cmdSetEvent(cmdBuf2, event2, vk::PipelineStageBits::allCommands); 91 | vk::endCommandBuffer(cmdBuf2); 92 | 93 | const vk::PipelineStageFlags stage = vk::PipelineStageBits::topOfPipe; 94 | submitInfo = { 95 | 1, &semaphore.vkHandle(), &stage, 96 | 1, &cmdBuf2.vkHandle() 97 | }; 98 | 99 | EXPECT(submitter.add(submitInfo), id); 100 | EXPECT(submitter.current(), id); 101 | EXPECT(submitter.submitted(id), false); 102 | EXPECT(submitter.completed(id), false); 103 | 104 | EXPECT(vk::getEventStatus(dev, event1), vk::Result::eventReset); 105 | EXPECT(vk::getEventStatus(dev, event2), vk::Result::eventReset); 106 | 107 | submitter.submit(id); 108 | EXPECT(submitter.current(), id + 1); 109 | EXPECT(submitter.submitted(id), true); 110 | EXPECT(submitter.completed(id), false); 111 | 112 | EXPECT(vk::getEventStatus(dev, event1), vk::Result::eventReset); 113 | EXPECT(vk::getEventStatus(dev, event2), vk::Result::eventReset); 114 | 115 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 116 | 117 | EXPECT(vk::getEventStatus(dev, event1), vk::Result::eventReset); 118 | EXPECT(vk::getEventStatus(dev, event2), vk::Result::eventReset); 119 | 120 | vk::setEvent(dev, event1); 121 | EXPECT(vk::getEventStatus(dev, event1), vk::Result::eventSet); 122 | 123 | EXPECT(submitter.submitted(id), true); 124 | submitter.wait(id); 125 | EXPECT(submitter.completed(id), true); 126 | 127 | EXPECT(vk::getEventStatus(dev, event1), vk::Result::eventSet); 128 | EXPECT(vk::getEventStatus(dev, event2), vk::Result::eventSet); 129 | } 130 | -------------------------------------------------------------------------------- /docs/tests/allocator.cpp: -------------------------------------------------------------------------------- 1 | #include "init.hpp" 2 | #include "bugged.hpp" 3 | 4 | #include 5 | 6 | TEST(memory) { 7 | auto& dev = *globals.device; 8 | auto& alloc = dev.devMemAllocator(); 9 | EXPECT(alloc.memories().empty(), true); 10 | 11 | // allocate some stuff 12 | { 13 | std::array initData; 14 | 15 | vk::BufferCreateInfo bufInfo; 16 | bufInfo.size = 1024; 17 | bufInfo.usage = vk::BufferUsageBits::storageBuffer; 18 | auto buffer = vpp::Buffer(initData[0], alloc, bufInfo); 19 | vpp::Buffer buffer1(initData[1], alloc, bufInfo); 20 | vpp::Buffer buffer2(initData[2], alloc, bufInfo); 21 | vpp::Buffer buffer3(initData[3], alloc, bufInfo); 22 | vpp::Buffer buffer4(initData[4], alloc, bufInfo); 23 | EXPECT(alloc.memories().empty(), true); 24 | 25 | buffer.init(initData[0]); 26 | buffer1.init(initData[1]); 27 | buffer2.init(initData[2]); 28 | buffer3.init(initData[3]); 29 | buffer4.init(initData[4]); 30 | 31 | EXPECT(alloc.memories().size(), 1u); 32 | 33 | EXPECT(&buffer1.memory(), alloc.memories()[0].get()); 34 | EXPECT(&buffer2.memory(), alloc.memories()[0].get()); 35 | EXPECT(&buffer3.memory(), alloc.memories()[0].get()); 36 | } 37 | 38 | EXPECT(alloc.memories().size(), 1u); 39 | 40 | // XXX: might fail due to alignment requirements etc (allocater 41 | // allocated larger memory) 42 | EXPECT(alloc.memories()[0]->totalFree(), 1024u * 5); 43 | 44 | // make sure same memory is used again 45 | { 46 | vk::BufferCreateInfo bufInfo; 47 | bufInfo.size = 1024; 48 | bufInfo.usage = vk::BufferUsageBits::storageBuffer; 49 | auto buffer = vpp::Buffer(alloc, bufInfo); 50 | EXPECT(alloc.memories().size(), 1u); 51 | EXPECT(alloc.memories()[0]->totalFree(), 1024u * 4); 52 | 53 | vpp::Buffer::InitData initData; 54 | vpp::Buffer buffer1(initData, alloc, bufInfo); 55 | EXPECT(alloc.memories()[0]->totalFree(), 1024u * 4); 56 | 57 | buffer1.init(initData); 58 | EXPECT(alloc.memories()[0]->totalFree(), 1024u * 3); 59 | } 60 | } 61 | 62 | 63 | TEST(custom) { 64 | auto& dev = *globals.device; 65 | auto alloc = vpp::DeviceMemoryAllocator(dev); 66 | EXPECT(alloc.memories().empty(), true); 67 | 68 | // allocate some stuff 69 | { 70 | vk::BufferCreateInfo bufInfo; 71 | bufInfo.size = 1024; 72 | bufInfo.usage = vk::BufferUsageBits::storageBuffer; 73 | 74 | std::array initData; 75 | auto buffer = vpp::Buffer(initData[0], alloc, bufInfo, ~0u); 76 | vpp::Buffer buffer1(initData[1], alloc, bufInfo, ~0u); 77 | vpp::Buffer buffer2(initData[2], alloc, bufInfo, ~0u); 78 | vpp::Buffer buffer3(initData[3], alloc, bufInfo, ~0u); 79 | EXPECT(initData[3].allocator, &alloc); 80 | 81 | vpp::Buffer buffer4(initData[4], alloc, bufInfo, ~0u); 82 | EXPECT(alloc.memories().empty(), true); 83 | EXPECT(initData[4].allocator, &alloc); 84 | 85 | alloc.alloc(); 86 | buffer.init(initData[0]); 87 | buffer1.init(initData[1]); 88 | buffer2.init(initData[2]); 89 | buffer3.init(initData[3]); 90 | buffer4.init(initData[4]); 91 | 92 | EXPECT(alloc.memories().size(), 1u); 93 | EXPECT(&buffer.memory(), alloc.memories()[0].get()); 94 | EXPECT(&buffer1.memory(), alloc.memories()[0].get()); 95 | EXPECT(&buffer2.memory(), alloc.memories()[0].get()); 96 | EXPECT(&buffer3.memory(), alloc.memories()[0].get()); 97 | EXPECT(&buffer4.memory(), alloc.memories()[0].get()); 98 | } 99 | 100 | EXPECT(alloc.memories().size(), 1u); 101 | 102 | // XXX: might fail due to alignment requirements etc (allocater 103 | // allocated larger memory) 104 | EXPECT(alloc.memories()[0]->totalFree(), 1024u * 5); 105 | 106 | // make sure same memory is used again 107 | { 108 | vk::BufferCreateInfo bufInfo; 109 | bufInfo.size = 1024; 110 | bufInfo.usage = vk::BufferUsageBits::storageBuffer; 111 | auto buffer = vpp::Buffer(alloc, bufInfo, ~0u); 112 | EXPECT(alloc.memories().size(), 1u); 113 | EXPECT(alloc.memories()[0]->totalFree(), 1024u * 4); 114 | 115 | vpp::Buffer::InitData data; 116 | vpp::Buffer buffer1(data, alloc, bufInfo, ~0u); 117 | EXPECT(alloc.memories()[0]->totalFree(), 1024u * 4); 118 | 119 | buffer1.init(data); 120 | EXPECT(alloc.memories()[0]->totalFree(), 1024u * 3); 121 | } 122 | } 123 | 124 | TEST(cancel) { 125 | auto& dev = *globals.device; 126 | auto& alloc = dev.devMemAllocator(); 127 | 128 | vk::BufferCreateInfo bufInfo; 129 | bufInfo.size = 1024; 130 | bufInfo.usage = vk::BufferUsageBits::storageBuffer | 131 | vk::BufferUsageBits::uniformBuffer; 132 | 133 | vpp::Buffer::InitData data; 134 | vpp::Buffer buffer(data, alloc, bufInfo); 135 | buffer = {}; // destroy, never finish initialization 136 | } 137 | -------------------------------------------------------------------------------- /src/vpp/submit.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace vpp { 12 | 13 | QueueSubmitter::QueueSubmitter(const Queue& queue) : queue_(&queue) { 14 | } 15 | 16 | QueueSubmitter::~QueueSubmitter() { 17 | // make sure all fences have completed before destroying them 18 | std::vector fences; 19 | for(auto& fence : fences_) { 20 | auto status = vk::getFenceStatus(device(), fence.fence); 21 | if(status != vk::Result::success) { 22 | fences.push_back(fence.fence); 23 | } 24 | } 25 | 26 | if(!fences.empty()) { 27 | vk::waitForFences(device(), fences, true, UINT64_MAX); 28 | } 29 | } 30 | 31 | uint64_t QueueSubmitter::add(const vk::SubmitInfo& info, unsigned int* sid) { 32 | dlg_assert(queue_); 33 | if(sid) { 34 | *sid = pending_.size(); 35 | } 36 | 37 | pending_.push_back(info); 38 | return id_; 39 | } 40 | 41 | uint64_t QueueSubmitter::add(const vk::CommandBuffer& cb, unsigned* sid) { 42 | vk::SubmitInfo info; 43 | info.commandBufferCount = 1u; 44 | info.pCommandBuffers = &cb; 45 | return add(info, sid); 46 | } 47 | 48 | void QueueSubmitter::submit(uint64_t id) { 49 | dlg_assert(queue_); 50 | dlg_assert(id != 0); 51 | dlg_assert(wrapped_ || id <= id_); 52 | 53 | if(id == id_) { 54 | submit(); 55 | } 56 | } 57 | 58 | bool QueueSubmitter::submitted(uint64_t id) const { 59 | dlg_assert(queue_); 60 | dlg_assert(id != 0); 61 | dlg_assert(wrapped_ || id <= id_); 62 | 63 | return id != id_; 64 | } 65 | 66 | bool QueueSubmitter::completed(uint64_t id) const { 67 | dlg_assert(queue_); 68 | dlg_assert(id != 0); 69 | dlg_assert(wrapped_ || id <= id_); 70 | 71 | if(id == id_) { 72 | return false; 73 | } 74 | 75 | auto it = std::find_if(fences_.begin(), fences_.end(), 76 | [&](auto& fence) { return fence.id == id; }); 77 | if(it == fences_.end()) { 78 | return true; 79 | } 80 | 81 | auto status = vk::getFenceStatus(device(), it->fence); 82 | if(status == vk::Result::success) { 83 | vk::resetFences(device(), {{it->fence.vkHandle()}}); 84 | unusedFences_.emplace_back(std::move(it->fence)); 85 | fences_.erase(it); 86 | return true; 87 | } 88 | 89 | return false; 90 | } 91 | 92 | unsigned int QueueSubmitter::submit() { 93 | // this function has a special focus on exception safety 94 | dlg_assert(queue_); 95 | if(pending_.empty()) { 96 | return 0u; 97 | } 98 | 99 | update(); 100 | 101 | // get fence 102 | vpp::Fence fence; 103 | if(!unusedFences_.empty()) { 104 | fence = std::move(unusedFences_.back()); 105 | unusedFences_.pop_back(); 106 | } else { 107 | fence = {device()}; 108 | } 109 | 110 | // move (clear) pending & increase id 111 | auto pending = std::move(pending_); 112 | auto id = id_; 113 | if(id_ == UINT64_MAX) { 114 | id_ = 1; 115 | wrapped_= true; 116 | } else { 117 | ++id_; 118 | } 119 | 120 | // submit 121 | { 122 | // lock all queues, submit must finish without other queue operation 123 | // Note that vk::queueSubmit might throw 124 | QueueLock lock(device()); 125 | vk::queueSubmit(queue(), pending, fence); 126 | } 127 | 128 | // set fence 129 | auto& entry = fences_.emplace_back(); 130 | entry.id = id; 131 | entry.fence = std::move(fence); 132 | return pending.size(); 133 | } 134 | 135 | bool QueueSubmitter::wait(uint64_t id, uint64_t timeout) { 136 | dlg_assert(queue_); 137 | dlg_assert(id != 0); 138 | dlg_assert(wrapped_ || id <= id_); 139 | 140 | submit(id); 141 | 142 | auto it = std::find_if(fences_.begin(), fences_.end(), 143 | [&](auto& fence) { return fence.id == id; }); 144 | if(it == fences_.end()) { 145 | return true; 146 | } 147 | 148 | auto fh = it->fence.vkHandle(); 149 | dlg_assert(fh); 150 | auto res = vk::waitForFences(device(), {{fh}}, false, timeout); 151 | return res == vk::Result::success; 152 | } 153 | 154 | unsigned int QueueSubmitter::pending() const { 155 | dlg_assert(queue_); 156 | return pending_.size(); 157 | } 158 | 159 | void QueueSubmitter::update() { 160 | dlg_assert(queue_); 161 | std::vector reset; 162 | for(auto it = fences_.begin(); it != fences_.end();) { 163 | auto status = vk::getFenceStatus(device(), it->fence); 164 | if(status == vk::Result::success) { 165 | reset.push_back(it->fence); 166 | unusedFences_.emplace_back(std::move(it->fence)); 167 | it = fences_.erase(it); 168 | } else { 169 | ++it; 170 | } 171 | } 172 | 173 | if(!reset.empty()) { 174 | vk::resetFences(device(), reset); 175 | } 176 | } 177 | 178 | } // namespace vpp 179 | -------------------------------------------------------------------------------- /docs/concepts/sparse.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | const vpp::Device& device(); 5 | 6 | auto fence = vk::createFence(device(), {}); 7 | std::vector infos; 8 | 9 | //binding a sparse buffer to a single memory object 10 | vk::SparseMemoryBind bind; 11 | bind.resourceOffset = 0; 12 | bind.size = memReqs.size; //the size must be at least memReqs.alignment 13 | bind.memory = memory; 14 | bind.flags = {}; 15 | 16 | vk::SparseBufferMemoryBindInfo bufferBind; 17 | bufferBind.buffer = buffer; 18 | bufferBind.bindCount = 1; 19 | bufferBind.pBinds = &bind; 20 | 21 | infos.emplace_back(); 22 | infos.back().bufferBindCount = 1; 23 | infos.back().pBufferBinds = &bufferBind; 24 | 25 | //binding an image 26 | //difference between opaque and partially resident images 27 | //sparse image binding is way more complex since one has to care about metadata bindings, 28 | //differentiate between opaque or normal bindings and query/deal with additional requirements 29 | //and device features since some stuff might not be enabled 30 | auto reqs = vk::getImageSparseMemoryRequirements(device(), image); 31 | 32 | vk::SparseMemoryBind bind; 33 | bind.resourceOffset = 0; 34 | bind.size = memReqs.size; 35 | bind.memory = memory; 36 | bind.flags = {}; 37 | 38 | 39 | //submitting the sparse binding operations to the device 40 | queue.mutex().lock(); 41 | vk::queueBindSparse(queue, infos, fence); 42 | queue.mutex().unlock(); 43 | 44 | //wait for the fence 45 | 46 | 47 | 48 | //the idea for implementing sparse memory entries is to make the MemoryEntry class virtual and 49 | //then provide multiple implementations (e.g. default, sparse, sparseResidency, sparseAliasing) 50 | //The down-side of this would be that it would result in one extra word per memory resource for 51 | //the virtual table, the need to allocate the memory entry implementation on the heap instead of 52 | //the stack and the fact that sparse memory entries cannot be implemented without huuge host-side 53 | //memory needs. 54 | //The question is if sparse memory resources can be handled in any safe way, so that they would 55 | //not need that much resources on the host side. If there is such a way, leave memor entry as it 56 | //is and make vpp::MemoryResource a class for non-sparse memory resources and instead provide 57 | //helpers for the alternative handling of sparse resources. 58 | //probably there isnt such a way. one has to keep track of memory bindings. 59 | 60 | class SparseMemoryEntry : public Resource 61 | { 62 | public: 63 | ///Ensures that the given memory resource range is bound to a device memory object. 64 | ///This function will request the needed memory from the associated DeviceMemoryAllocator. 65 | WorkPtr ensureBound(const Allocation& allocation); 66 | 67 | WorkPtr bind(const Allocation& allocation, ) 68 | 69 | protected: 70 | MemoryResourceType type_; //buffer, image, opaque image 71 | vk::DeviceSize size_; 72 | }; 73 | 74 | class ManagedSparseMemoryEntry : public SparseMemoryEntry 75 | { 76 | protected: 77 | DeviceMemoryAllocator& allocator_; 78 | std::vector binds_; 79 | }; 80 | 81 | 82 | class MemoryEntry 83 | { 84 | public: 85 | virtual ~MemoryEntry(); 86 | virtual void ensureBound(vk::DeviceSize offset = 0, vk::DeviceSize size = 0) = 0; 87 | virtual vk::DeviceSize size() const = 0; 88 | }; 89 | 90 | class SparseBinder 91 | { 92 | public: 93 | void add(/* some bind info params*/); 94 | }; 95 | 96 | 97 | 98 | 99 | class MemoryEntry 100 | { 101 | protected: 102 | DeviceMemoryAllocator* allocator_; 103 | unsigned int id_; 104 | }; 105 | 106 | class MemoryAllocator; 107 | class SparseMemoryAllocator : public MemoryAllocator; 108 | 109 | 110 | // attempt #2 111 | // sparse header 112 | // TODO 113 | struct OpaqueMemoryBind { 114 | DeviceMemory* memory; 115 | Allocation allocation; 116 | vk::DeviceSize resourceOffset; 117 | }; 118 | 119 | template 120 | struct ImageMemoryBindT { 121 | MemoryEntry entry; 122 | Extent3D offset; 123 | Extent3D size; 124 | }; 125 | using ImageMemoryBind = ImageMemoryBindT; 126 | 127 | class SparseOpaqueMemoryEntry { 128 | protected: 129 | std::vector binds_; 130 | }; 131 | 132 | class SparseImageMemoryEntry { 133 | protected: 134 | std::vector> binds_; 135 | }; 136 | 137 | class SparseImage : public vpp::ImageHandle { 138 | public: 139 | 140 | protected: 141 | SparseImageMemoryEntry entry_; 142 | }; 143 | 144 | class SparseOpaqueImage : public vpp::ImageHandle { 145 | public: 146 | protected: 147 | SparseOpaqueMemoryEntry entry_; 148 | }; 149 | 150 | class SparseBuffer : public vpp::BufferHandle { 151 | public: 152 | 153 | protected: 154 | SparseOpaqueMemoryEntry entry_; 155 | }; 156 | -------------------------------------------------------------------------------- /docs/tests/image.cpp: -------------------------------------------------------------------------------- 1 | #include "init.hpp" 2 | #include "bugged.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | TEST(image) { 12 | auto& dev = *globals.device; 13 | auto size = vk::Extent3D {20u, 20u, 1u}; 14 | 15 | vk::ImageCreateInfo imgInfo = { 16 | {}, 17 | vk::ImageType::e2d, 18 | vk::Format::b8g8r8a8Unorm, 19 | size, 20 | 1, 1, 21 | vk::SampleCountBits::e1, 22 | vk::ImageTiling::optimal, 23 | vk::ImageUsageBits::transferDst | vk::ImageUsageBits::transferSrc, 24 | vk::SharingMode::exclusive, 25 | 0, nullptr, vk::ImageLayout::undefined 26 | }; 27 | 28 | vpp::Image img1 = {dev.devMemAllocator(), imgInfo}; 29 | auto moved = std::move(img1); 30 | img1 = std::move(moved); 31 | 32 | imgInfo.usage = vk::ImageUsageBits::sampled; 33 | imgInfo.format = vk::Format::r8Uint; 34 | imgInfo.tiling = vk::ImageTiling::linear; 35 | imgInfo.initialLayout = vk::ImageLayout::preinitialized; 36 | auto bits = dev.memoryTypeBits(vk::MemoryPropertyBits::hostVisible); 37 | vpp::Image img2 = {dev.devMemAllocator(), imgInfo, bits}; 38 | 39 | std::vector data(4 * size.width * size.height, std::byte{0xffu}); 40 | data[0] = std::byte {0x00}; 41 | data[1] = std::byte {0x01}; 42 | data[2] = std::byte {0x02}; 43 | data[3] = std::byte {0x03}; 44 | 45 | auto& qs = dev.queueSubmitter(); 46 | auto& queue = qs.queue(); 47 | { 48 | auto cb = dev.commandAllocator().get(queue.family()); 49 | vk::beginCommandBuffer(cb, {}); 50 | 51 | vk::ImageMemoryBarrier barrier; 52 | barrier.image = img1; 53 | barrier.oldLayout = vk::ImageLayout::undefined; 54 | barrier.newLayout = vk::ImageLayout::transferDstOptimal; 55 | barrier.dstAccessMask = vk::AccessBits::transferWrite; 56 | barrier.subresourceRange = {vk::ImageAspectBits::color, 0, 1, 0, 1}; 57 | vk::cmdPipelineBarrier(cb, vk::PipelineStageBits::topOfPipe, 58 | vk::PipelineStageBits::transfer, {}, {}, {}, {{barrier}}); 59 | 60 | auto stage = vpp::fillStaging(cb, img1, vk::Format::r8g8b8a8Unorm, 61 | vk::ImageLayout::transferDstOptimal, size, data, 62 | {vk::ImageAspectBits::color}); 63 | 64 | vk::endCommandBuffer(cb); 65 | qs.wait(qs.add(cb)); 66 | } 67 | 68 | vpp::fillMap(img2, vk::Format::r8Unorm, size, 69 | {data.data(), size.width * size.height}, 70 | {vk::ImageAspectBits::color}); 71 | 72 | { 73 | auto cb = dev.commandAllocator().get(queue.family()); 74 | vk::beginCommandBuffer(cb, {}); 75 | 76 | vk::ImageMemoryBarrier barrier; 77 | barrier.image = img1; 78 | barrier.oldLayout = vk::ImageLayout::transferDstOptimal; 79 | barrier.newLayout = vk::ImageLayout::transferSrcOptimal; 80 | barrier.srcAccessMask = vk::AccessBits::transferWrite; 81 | barrier.dstAccessMask = vk::AccessBits::transferRead; 82 | barrier.subresourceRange = {vk::ImageAspectBits::color, 0, 1, 0, 1}; 83 | vk::cmdPipelineBarrier(cb, vk::PipelineStageBits::transfer, 84 | vk::PipelineStageBits::transfer, {}, {}, {}, {{barrier}}); 85 | 86 | auto stage = vpp::retrieveStaging(cb, img1, vk::Format::r8g8b8a8Unorm, 87 | vk::ImageLayout::transferSrcOptimal, size, 88 | {vk::ImageAspectBits::color}); 89 | 90 | vk::endCommandBuffer(cb); 91 | qs.wait(qs.add(cb)); 92 | 93 | auto map = stage.memoryMap(); 94 | auto data = map.span(); 95 | 96 | EXPECT(data.size(), size.width * size.height * 4u); 97 | EXPECT((unsigned int) data[0], 0x00u); 98 | EXPECT((unsigned int) data[1], 0x01u); 99 | EXPECT((unsigned int) data[2], 0x02u); 100 | EXPECT((unsigned int) data[3], 0x03u); 101 | for(auto i = 4u; i < data.size(); ++i) { 102 | EXPECT((unsigned int) data[i], 0xffu); 103 | } 104 | } 105 | 106 | auto data2 = vpp::retrieveMap(img2, vk::Format::r8Unorm, size, 107 | {vk::ImageAspectBits::color}); 108 | EXPECT(data2.size(), size.width * size.height * 1u); 109 | EXPECT((unsigned int) data2[0], 0x00u); 110 | EXPECT((unsigned int) data2[1], 0x01u); 111 | EXPECT((unsigned int) data2[2], 0x02u); 112 | EXPECT((unsigned int) data2[3], 0x03u); 113 | for(auto i = 4u; i < data2.size(); ++i) { 114 | EXPECT((unsigned int) data2[i], 0xffu); 115 | } 116 | } 117 | 118 | TEST(moving) { 119 | auto& dev = *globals.device; 120 | auto size = vk::Extent3D {20u, 20u, 1u}; 121 | 122 | vk::ImageCreateInfo imgInfo = { 123 | {}, 124 | vk::ImageType::e2d, 125 | vk::Format::b8g8r8a8Unorm, 126 | size, 127 | 1, 1, 128 | vk::SampleCountBits::e1, 129 | vk::ImageTiling::optimal, 130 | vk::ImageUsageBits::transferDst | vk::ImageUsageBits::transferSrc, 131 | vk::SharingMode::exclusive, 132 | 0, nullptr, vk::ImageLayout::undefined 133 | }; 134 | 135 | vpp::Image img1 = {dev.devMemAllocator(), imgInfo}; 136 | img1 = {}; 137 | } 138 | -------------------------------------------------------------------------------- /docs/examples/intro_glfw.cpp: -------------------------------------------------------------------------------- 1 | // Encapsulates render logic, window library independent 2 | #include "render.hpp" 3 | 4 | #include // vpp::DefaultRenderer 5 | #include // vpp::Instance, vpp::RenderPass, ... 6 | #include // vpp::DebugMessenger 7 | #include // vpp::Device 8 | #include // vulkan commands 9 | 10 | #include 11 | 12 | #define GLFW_INCLUDE_VULKAN 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | int main(int, char**) { 19 | if(!::glfwInit()) { 20 | throw std::runtime_error("Failed to init glfw"); 21 | } 22 | 23 | uint32_t count; 24 | const char** extensions = ::glfwGetRequiredInstanceExtensions(&count); 25 | std::vector iniExtensions {extensions, extensions + count}; 26 | iniExtensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); 27 | iniExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); 28 | 29 | // enables all default layers 30 | constexpr const char* layers[] = { 31 | "VK_LAYER_KHRONOS_validation", 32 | // "VK_LAYER_RENDERDOC_Capture", 33 | }; 34 | 35 | // basic application info 36 | // we use vulkan api version 1.0 37 | vk::ApplicationInfo appInfo ("vpp-intro", 1, "vpp", 1, VK_API_VERSION_1_0); 38 | 39 | vk::InstanceCreateInfo instanceInfo; 40 | instanceInfo.pApplicationInfo = &appInfo; 41 | instanceInfo.enabledExtensionCount = iniExtensions.size(); 42 | instanceInfo.ppEnabledExtensionNames = iniExtensions.data(); 43 | instanceInfo.enabledLayerCount = sizeof(layers) / sizeof(const char*); 44 | instanceInfo.ppEnabledLayerNames = layers; 45 | 46 | vpp::Instance instance(instanceInfo); 47 | 48 | // create a debug messenger for our instance and the default layers. 49 | // the default implementation will just output the debug messages 50 | vpp::DebugMessenger debugMessenger(instance); 51 | 52 | // create the ny window and vukan surface 53 | // init glfw window 54 | vk::Extent2D size {800u, 500u}; 55 | ::glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 56 | GLFWwindow* window = ::glfwCreateWindow(size.width, size.height, 57 | "vpp glfw example", NULL, NULL); 58 | if(!window) { 59 | const char* error; 60 | ::glfwGetError(&error); 61 | std::string msg = "Failed to create glfw window: "; 62 | msg += error; 63 | throw std::runtime_error(msg); 64 | } 65 | 66 | // avoiding reinterpret_cast due to aliasing warnings 67 | VkInstance vkini; 68 | auto handle = instance.vkHandle(); 69 | std::memcpy(&vkini, &handle, sizeof(vkini)); 70 | 71 | VkSurfaceKHR vkSurf {}; 72 | VkResult err = ::glfwCreateWindowSurface(vkini, window, NULL, &vkSurf); 73 | if(err) { 74 | auto str = std::string("Failed to create vulkan surface: "); 75 | str += vk::name(static_cast(err)); 76 | throw std::runtime_error(str); 77 | } 78 | 79 | vk::SurfaceKHR surface {}; 80 | std::memcpy(&surface, &vkSurf, sizeof(surface)); 81 | 82 | // now (if everything went correctly) we have the window (and a 83 | // vulkan surface) and can create the device and renderer. 84 | const vpp::Queue* present; 85 | vpp::Device device(instance, surface, present); 86 | dlg_assert(present); 87 | 88 | // we can construct everything for our renderer 89 | // swapchain info and renderpass 90 | // here we could try to use vsync or alpha/transform settings 91 | auto scInfo = vpp::swapchainCreateInfo(device, surface, size); 92 | dlg_info("size: {}, {}", scInfo.imageExtent.width, scInfo.imageExtent.height); 93 | auto renderPass = createRenderPass(device, scInfo.imageFormat); 94 | vpp::nameHandle(renderPass, "main:renderPass"); 95 | 96 | // our renderer 97 | { 98 | MyRenderer renderer(renderPass, scInfo, *present); 99 | 100 | // setup events 101 | ::glfwSetWindowUserPointer(window, &renderer); 102 | auto sizeCallback = [](auto* window, int width, int height) { 103 | auto ptr = ::glfwGetWindowUserPointer(window); 104 | auto& renderer = *static_cast(ptr); 105 | renderer.resize({unsigned(width), unsigned(height)}); 106 | }; 107 | 108 | ::glfwSetFramebufferSizeCallback(window, sizeCallback); 109 | 110 | // ... and start the main loop. 111 | // We have a classicaly dumb game loop here that just renders as fast 112 | // as it can. See the ny example for only rendering when it's needed, 113 | // glfw doesn't support any draw events. 114 | dlg_info("Entering main loop"); 115 | while(!::glfwWindowShouldClose(window)) { 116 | // render and wait, will block cpu 117 | // you probably rather want to use renderer.render, then do 118 | // work on the cpu and then wait for the rendering to 119 | // complete in a real application 120 | renderer.renderStall(); 121 | ::glfwWaitEvents(); 122 | } 123 | 124 | // wait for rendering to finish 125 | vk::deviceWaitIdle(device); 126 | } 127 | 128 | // needed, but must be done *after* destroying the swapchain 129 | vk::destroySurfaceKHR(instance, surface); 130 | 131 | dlg_info("Returning from main with grace"); 132 | } 133 | -------------------------------------------------------------------------------- /include/vpp/memory.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include // vpp::DeviceMemoryHandle 9 | #include // vpp::ResourceHandle 10 | #include // vpp::MemoryMap 11 | #include // vpp::Allocation 12 | #include // std::vector 13 | 14 | namespace vpp { 15 | 16 | /// Specifies the different types of allocation on a memory object. 17 | enum class AllocationType { 18 | none = 0, 19 | linear = 1, 20 | optimal = 2, 21 | }; 22 | 23 | /// DeviceMemory class that keeps track of its allocated and freed areas. 24 | /// Makes it easy to reuse memory as well as bind multiple memoryRequestors to 25 | /// one allocation. 26 | class VPP_API DeviceMemory : public DeviceMemoryHandle { 27 | public: 28 | using Allocation = BasicAllocation; 29 | struct AllocationEntry { 30 | Allocation allocation; 31 | AllocationType type; 32 | }; 33 | 34 | public: 35 | DeviceMemory(const Device&, const vk::MemoryAllocateInfo&); 36 | DeviceMemory(const Device&, vk::DeviceMemory, vk::DeviceSize size, 37 | unsigned int memoryType); 38 | ~DeviceMemory(); 39 | 40 | /// DeviceMemory is not movable to allow memory resources to keep references 41 | /// to the DeviceMemory object (to free it on destruction). 42 | DeviceMemory(DeviceMemory&&) noexcept = delete; 43 | DeviceMemory& operator=(DeviceMemory&&) noexcept = delete; 44 | 45 | /// Tries to allocate a memory part that matches the given size and 46 | /// alignment requirements. Will return an empty (size == 0) allocation 47 | /// if there is not enough free space left. 48 | /// One can test if there is enough space for the needed allocation with the 49 | /// allocatable() member function (but then prefer to use 50 | /// allocSpecified for better performance). 51 | /// Alignment must be a power of 2, size not 0 and the given AllocationType 52 | /// valid must represent the type the allocation will be used for. 53 | Allocation alloc(vk::DeviceSize size, vk::DeviceSize aligment, 54 | AllocationType type); 55 | 56 | /// Allocates the specified memory part. 57 | /// Does not check for matched requirements or if the specified space 58 | /// is already occupied, so this function has to be used with care. 59 | /// This function can be useful if the possibility of a given allocation 60 | /// was checked before with a call to the allocatable function (then 61 | /// the returned range can safely be allocated if no other allocation 62 | /// was made in between). 63 | /// It might also be useful if one wants to partly manage the memory 64 | /// reservation itself externally and can therefore ensure that 65 | /// the given range can be allocated (like e.g. the first allocation). 66 | void allocSpecified(Allocation, AllocationType); 67 | 68 | /// Frees the given allocation. Undefined behavior if the 69 | /// allocation is invalid (i.e. not allocated on this Memory). 70 | void free(const Allocation& alloc); 71 | void free(vk::DeviceSize offset); 72 | 73 | /// Tests if an allocation for the given requirements can be made. 74 | /// Will return an empty (size = 0) allocation if it is not possible. 75 | /// Otherwise returns the allocation which could be made, which could then 76 | /// (before any other allocate call) safely be allocated with a call to 77 | /// allocSpecified. The call itself does not change the state of this 78 | /// object, i.e. does not NOT reserve memory or something like it. 79 | /// Alignment must be a power of 2, size not 0 and the given AllocationType 80 | /// valid must represent the type the allocation would be used for. 81 | /// Usually this function is not needed. 82 | Allocation allocatable(vk::DeviceSize size, vk::DeviceSize aligment, 83 | AllocationType) const; 84 | 85 | /// Maps the specified memory range. 86 | /// Undefined behavior if the memory is not mappable, the caller must 87 | /// check this using mappable() 88 | MemoryMapView map(const Allocation& allocation); 89 | 90 | /// Returns the the biggest (continuously) allocatable block. 91 | /// This does not mean that an allocation of this size can be made, 92 | /// since there are also alignment or granularity requirements which 93 | /// will effectively "shrink" this block. 94 | vk::DeviceSize largestFreeSegment() const noexcept; 95 | 96 | /// Returns the total amount of free bytes. 97 | vk::DeviceSize totalFree() const noexcept; 98 | 99 | /// Returns the total size this DeviceMemory object has. 100 | vk::DeviceSize size() const noexcept { return size_; } 101 | 102 | /// Returns the currently mapped range, or nullptr if there is none. 103 | MemoryMap* mapped() noexcept; 104 | const MemoryMap* mapped() const noexcept; 105 | 106 | vk::MemoryPropertyFlags properties() const noexcept; 107 | bool mappable() const noexcept; 108 | 109 | unsigned int type() const noexcept { return type_; } 110 | const auto& allocations() const noexcept { return allocations_; } 111 | 112 | protected: 113 | std::vector allocations_ {}; 114 | vk::DeviceSize size_ {}; 115 | unsigned int type_ {}; 116 | MemoryMap memoryMap_ {}; // the current memory map, may be invalid 117 | }; 118 | 119 | } // namespace vpp 120 | -------------------------------------------------------------------------------- /src/vpp/memoryMap.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // NOTE: vulkan 1.0 does not have any requirements onto the memoryMap 11 | // offset, minMemoryMapAlignment (despite its name) is a guarantee 12 | // the implementation gives for the returned data pointer. 13 | // Only noted here since i pretty much panic every few months seeing 14 | // minMemoryMapAlignment somewhere. 15 | 16 | namespace vpp { 17 | 18 | // MemoryMap 19 | MemoryMap::~MemoryMap() { 20 | try { 21 | unmap(); 22 | } catch(const std::exception& error) { 23 | dlg_warnt(("~MemoryMap"), "unmap(): {}", error.what()); 24 | } 25 | } 26 | 27 | void MemoryMap::map(const DeviceMemory& memory, const Allocation& alloc) { 28 | unmap(); 29 | 30 | dlg_assertm(memory.properties() & vk::MemoryPropertyBits::hostVisible, 31 | "Trying to map unmappable memory"); 32 | memory_ = &memory; 33 | allocation_ = alloc; 34 | ptr_ = vk::mapMemory(device(), vkMemory(), offset(), size(), {}); 35 | flush(); 36 | } 37 | 38 | void MemoryMap::remap(const Allocation& allocation) { 39 | // new allocation extent 40 | auto nbeg = std::min(allocation.offset, allocation_.offset); 41 | auto nsize = std::max(end(allocation), end(allocation_)) - nbeg; 42 | 43 | // if new extent lay inside old do nothing 44 | if(offset() <= nbeg && offset() + size() >= nbeg + nsize) { 45 | return; 46 | } 47 | 48 | // else remap the memory 49 | vk::unmapMemory(device(), vkMemory()); 50 | allocation_ = {nbeg, nsize}; 51 | 52 | ptr_ = vk::mapMemory(device(), vkMemory(), offset(), size(), {}); 53 | invalidate(); 54 | } 55 | 56 | vk::MappedMemoryRange MemoryMap::mappedMemoryRange() const noexcept { 57 | return {vkMemory(), offset(), size()}; 58 | } 59 | 60 | const vk::DeviceMemory& MemoryMap::vkMemory() const noexcept { 61 | return memory_->vkHandle(); 62 | } 63 | 64 | void MemoryMap::flush(const Allocation& range) const { 65 | if(coherent()) { 66 | return; 67 | } 68 | 69 | auto o = range.offset + offset(); 70 | auto s = range.size == vk::wholeSize ? size() : range.size; 71 | dlg_assert(s <= size()); 72 | 73 | vk::flushMappedMemoryRanges(device(), 1, {vkMemory(), o, s}); 74 | } 75 | 76 | void MemoryMap::invalidate(const Allocation& range) const { 77 | if(coherent()) { 78 | return; 79 | } 80 | 81 | auto o = range.offset + offset(); 82 | auto s = range.size == vk::wholeSize ? size() : range.size; 83 | dlg_assert(s <= size()); 84 | 85 | vk::invalidateMappedMemoryRanges(device(), 1, {vkMemory(), o, s}); 86 | } 87 | 88 | bool MemoryMap::coherent() const noexcept { 89 | return memory().properties() & vk::MemoryPropertyBits::hostCoherent; 90 | } 91 | 92 | void MemoryMap::unmap() { 93 | dlg_assertm(views_ == 0, "unmap: still views for this map"); 94 | 95 | if(memory_ && vkMemory() && ptr() && size()) { 96 | flush(); 97 | vk::unmapMemory(memory().device(), vkMemory()); 98 | } 99 | 100 | memory_ = nullptr; 101 | allocation_ = {}; 102 | ptr_ = nullptr; 103 | views_ = 0; 104 | } 105 | 106 | void MemoryMap::ref() noexcept { 107 | views_++; 108 | } 109 | 110 | void MemoryMap::unref() noexcept { 111 | dlg_assertm(views_ > 0, "unref: refcount already zero"); 112 | views_--; 113 | 114 | if(views_ == 0) { 115 | try { 116 | unmap(); 117 | } catch(const std::exception& error) { 118 | dlg_warnt(("MemoryMap::unref"), "unmap: ", error.what()); 119 | } 120 | } 121 | } 122 | 123 | const Device& MemoryMap::device() const noexcept { 124 | return memory_->device(); 125 | } 126 | 127 | // MemoryMapView 128 | MemoryMapView::MemoryMapView(MemoryMap& map, const Allocation& allocation) 129 | : memoryMap_(&map), allocation_(allocation) { 130 | dlg_assert(map.valid()); 131 | dlg_assert(contains(map.allocation(), allocation)); 132 | dlg_assertm(allocation.size > 0, "MemoryMapView: invalid allocation"); 133 | memoryMap_->ref(); 134 | } 135 | 136 | MemoryMapView::~MemoryMapView() { 137 | // NOTE: theortically, we could now reduce the mapped range but 138 | // this would alter the data pointer and is therefore not possible. 139 | // Having memory mapped that is used on the device seems to be ok 140 | // per spec though, as long as it's not accessed on the host at 141 | // the same time (and when the view is destroyed, it won't be). 142 | if(memoryMap_) { 143 | memoryMap_->unref(); 144 | } 145 | } 146 | 147 | void MemoryMapView::swap(MemoryMapView& lhs) noexcept { 148 | using std::swap; 149 | 150 | swap(memoryMap_, lhs.memoryMap_); 151 | swap(allocation_, lhs.allocation_); 152 | } 153 | 154 | vk::MappedMemoryRange MemoryMapView::mappedMemoryRange() const noexcept { 155 | return {vkMemory(), offset(), size()}; 156 | } 157 | 158 | void MemoryMapView::flush() const { 159 | if(coherent()) { 160 | return; 161 | } 162 | 163 | auto range = mappedMemoryRange(); 164 | vk::flushMappedMemoryRanges(device(), 1, range); 165 | } 166 | 167 | void MemoryMapView::invalidate() const { 168 | if(coherent()) { 169 | return; 170 | } 171 | 172 | auto range = mappedMemoryRange(); 173 | vk::invalidateMappedMemoryRanges(device(), 1, range); 174 | } 175 | 176 | std::byte* MemoryMapView::ptr() const noexcept { 177 | return memoryMap().ptr() + allocation().offset - memoryMap().offset(); 178 | } 179 | 180 | bool MemoryMapView::coherent() const noexcept { 181 | return memory().properties() & vk::MemoryPropertyBits::hostCoherent; 182 | } 183 | 184 | } // namespace vpp 185 | -------------------------------------------------------------------------------- /docs/examples/render.cpp: -------------------------------------------------------------------------------- 1 | #include "render.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "data/intro.frag.h" 8 | #include "data/intro.vert.h" 9 | 10 | // initializing the rendering resources 11 | vpp::Pipeline createGraphicsPipeline(const vpp::Device&, vk::RenderPass, 12 | vk::PipelineLayout); 13 | 14 | // MyRenderer 15 | MyRenderer::MyRenderer(vk::RenderPass rp, vk::SwapchainCreateInfoKHR& scInfo, 16 | const vpp::Queue& present) : vpp::DefaultRenderer(), scInfo_(scInfo) { 17 | // pipeline 18 | pipelineLayout_ = {present.device(), vk::PipelineLayoutCreateInfo {}}; 19 | vpp::nameHandle(pipelineLayout_, "MyRenderer:pipelineLayout"); 20 | 21 | pipeline_ = createGraphicsPipeline(present.device(), rp, pipelineLayout_); 22 | vpp::nameHandle(pipeline_, "MyRenderer:pipeline"); 23 | 24 | init(rp, scInfo, present); 25 | vpp::nameHandle(swapchain(), "MyRenderer:swapchain"); 26 | } 27 | 28 | void MyRenderer::resize(const vk::Extent2D& size) { 29 | vpp::DefaultRenderer::recreate(size, scInfo_); 30 | } 31 | 32 | void MyRenderer::record(const RenderBuffer& buf) { 33 | static const auto clearValue = vk::ClearValue {{0.f, 0.f, 0.f, 1.f}}; 34 | const auto width = scInfo_.imageExtent.width; 35 | const auto height = scInfo_.imageExtent.height; 36 | 37 | auto cmdBuf = buf.commandBuffer; 38 | 39 | vk::beginCommandBuffer(cmdBuf, {}); 40 | vk::cmdBeginRenderPass(cmdBuf, { 41 | renderPass(), 42 | buf.framebuffer, 43 | {0u, 0u, width, height}, 44 | 1, 45 | &clearValue 46 | }, {}); 47 | 48 | vk::Viewport vp {0.f, 0.f, (float) width, (float) height, 0.f, 1.f}; 49 | vk::cmdSetViewport(cmdBuf, 0, 1, vp); 50 | vk::cmdSetScissor(cmdBuf, 0, 1, {0, 0, width, height}); 51 | 52 | #ifdef RENDERDOC 53 | vpp::insertDebugMarker(device(), cmdBuf, "finish setup"); 54 | vpp::beginDebugRegion(device(), cmdBuf, "render triangle", {1, 0.5, 0.5, 1}); 55 | #endif 56 | 57 | vk::cmdBindPipeline(cmdBuf, vk::PipelineBindPoint::graphics, pipeline_); 58 | vk::cmdDraw(cmdBuf, 3, 1, 0, 0); 59 | 60 | #ifdef RENDERDOC 61 | vpp::endDebugRegion(device(), cmdBuf); 62 | #endif 63 | 64 | vk::cmdEndRenderPass(cmdBuf); 65 | vk::endCommandBuffer(cmdBuf); 66 | } 67 | 68 | // resources 69 | vpp::Pipeline createGraphicsPipeline(const vpp::Device& dev, vk::RenderPass rp, 70 | vk::PipelineLayout layout) { 71 | // first load the shader modules and create the shader program for our pipeline 72 | // if the shaders cannot be found/compiled, this will throw (and end the application) 73 | vpp::ShaderModule vertexShader(dev, intro_vert_spv_data); 74 | vpp::ShaderModule fragmentShader(dev, intro_frag_spv_data); 75 | 76 | vpp::nameHandle(vertexShader, "triangleVertexShader"); 77 | vpp::nameHandle(fragmentShader, "triangleFragmentShader"); 78 | 79 | vpp::GraphicsPipelineInfo pipeInfo(rp, layout, {{{ 80 | {vertexShader, vk::ShaderStageBits::vertex}, 81 | {fragmentShader, vk::ShaderStageBits::fragment} 82 | }}}); 83 | 84 | pipeInfo.assembly.topology = vk::PrimitiveTopology::triangleList; 85 | 86 | /* NOTE: if we used a vertex buffer 87 | constexpr auto stride = (2 + 4) * sizeof(float); // pos (vec2), color(vec4) 88 | vk::VertexInputBindingDescription bufferBinding {0, 89 | stride, vk::VertexInputRate::vertex}; 90 | 91 | // vertex position, color attributes 92 | vk::VertexInputAttributeDescription attributes[2]; 93 | attributes[0].format = vk::Format::r32g32Sfloat; 94 | 95 | attributes[1].location = 1; 96 | attributes[1].format = vk::Format::r32g32b32a32Sfloat; 97 | attributes[1].offset = 2 * sizeof(float); 98 | 99 | pipeInfo.vertex.vertexBindingDescriptionCount = 1; 100 | pipeInfo.vertex.pVertexBindingDescriptions = &bufferBinding; 101 | pipeInfo.vertex.vertexAttributeDescriptionCount = 2; 102 | pipeInfo.vertex.pVertexAttributeDescriptions = attributes; 103 | */ 104 | 105 | // we also use the vpp::PipelienCache in this case 106 | // we try to load it from an already existent cache 107 | constexpr auto cacheName = "graphicsPipelineCache.bin"; 108 | vpp::PipelineCache cache {dev, cacheName}; 109 | 110 | vk::Pipeline vkPipeline; 111 | vk::createGraphicsPipelines(dev, cache, 1, pipeInfo.info(), nullptr, 112 | vkPipeline); 113 | 114 | // save the cache to the file we tried to load it from 115 | vpp::save(cache, cacheName); 116 | return {dev, vkPipeline}; 117 | } 118 | 119 | vpp::RenderPass createRenderPass(const vpp::Device& dev, vk::Format format) { 120 | vk::AttachmentDescription attachment {}; 121 | 122 | // color from swapchain 123 | attachment.format = format; 124 | attachment.samples = vk::SampleCountBits::e1; 125 | attachment.loadOp = vk::AttachmentLoadOp::clear; 126 | attachment.storeOp = vk::AttachmentStoreOp::store; 127 | attachment.stencilLoadOp = vk::AttachmentLoadOp::dontCare; 128 | attachment.stencilStoreOp = vk::AttachmentStoreOp::dontCare; 129 | attachment.initialLayout = vk::ImageLayout::undefined; 130 | attachment.finalLayout = vk::ImageLayout::presentSrcKHR; 131 | 132 | vk::AttachmentReference colorReference; 133 | colorReference.attachment = 0; 134 | colorReference.layout = vk::ImageLayout::colorAttachmentOptimal; 135 | 136 | // only subpass 137 | vk::SubpassDescription subpass; 138 | subpass.pipelineBindPoint = vk::PipelineBindPoint::graphics; 139 | subpass.colorAttachmentCount = 1; 140 | subpass.pColorAttachments = &colorReference; 141 | 142 | vk::RenderPassCreateInfo renderPassInfo; 143 | renderPassInfo.attachmentCount = 1; 144 | renderPassInfo.pAttachments = &attachment; 145 | renderPassInfo.subpassCount = 1; 146 | renderPassInfo.pSubpasses = &subpass; 147 | 148 | return {dev, renderPassInfo}; 149 | } 150 | -------------------------------------------------------------------------------- /docs/tests/pipeline.cpp: -------------------------------------------------------------------------------- 1 | #include "init.hpp" 2 | #include "bugged.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | uint32_t dummy_vert_spv[] = { 10 | 119734787, 65536, 524289, 62, 0, 131089, 1, 393227, 1, 1280527431, 1685353262, 11 | 808793134, 0, 196622, 0, 1, 524303, 0, 4, 1852399981, 0, 13, 37, 55, 196611, 2, 12 | 450, 589828, 1096764487, 1935622738, 1918988389, 1600484449, 1684105331, 13 | 1868526181, 1667590754, 29556, 589828, 1096764487, 1935622738, 1768186216, 14 | 1818191726, 1969712737, 1600481121, 1882206772, 7037793, 262149, 4, 1852399981, 15 | 0, 393221, 11, 1348430951, 1700164197, 2019914866, 0, 393222, 11, 0, 16 | 1348430951, 1953067887, 7237481, 458758, 11, 1, 1348430951, 1953393007, 17 | 1702521171, 0, 458758, 11, 2, 1130327143, 1148217708, 1635021673, 6644590, 18 | 458758, 11, 3, 1130327143, 1147956341, 1635021673, 6644590, 196613, 13, 0, 19 | 262149, 17, 1970037078, 101, 262150, 17, 0, 7565168, 327686, 17, 1, 1869377379, 20 | 114, 393221, 37, 1449094247, 1702130277, 1684949368, 30821, 327685, 40, 21 | 1701080681, 1818386808, 101, 327685, 55, 1131705711, 1919904879, 0, 327685, 58, 22 | 1701080681, 1818386808, 101, 327752, 11, 0, 11, 0, 327752, 11, 1, 11, 1, 23 | 327752, 11, 2, 11, 3, 327752, 11, 3, 11, 4, 196679, 11, 2, 262215, 37, 11, 42, 24 | 262215, 55, 30, 0, 131091, 2, 196641, 3, 2, 196630, 6, 32, 262167, 7, 6, 4, 25 | 262165, 8, 32, 0, 262187, 8, 9, 1, 262172, 10, 6, 9, 393246, 11, 7, 6, 10, 10, 26 | 262176, 12, 3, 11, 262203, 12, 13, 3, 262165, 14, 32, 1, 262187, 14, 15, 0, 27 | 262167, 16, 6, 2, 262174, 17, 16, 7, 262187, 8, 18, 3, 262172, 19, 17, 18, 28 | 262187, 6, 20, 3208642560, 327724, 16, 21, 20, 20, 262187, 6, 22, 1063675494, 29 | 262187, 6, 23, 1050253722, 262187, 6, 24, 1065353216, 458796, 7, 25, 22, 23, 30 | 23, 24, 327724, 17, 26, 21, 25, 262187, 6, 27, 0, 262187, 6, 28, 1061158912, 31 | 327724, 16, 29, 27, 28, 458796, 7, 30, 23, 22, 23, 24, 327724, 17, 31, 29, 30, 32 | 327724, 16, 32, 28, 20, 458796, 7, 33, 23, 23, 22, 24, 327724, 17, 34, 32, 33, 33 | 393260, 19, 35, 26, 31, 34, 262176, 36, 1, 14, 262203, 36, 37, 1, 262176, 39, 34 | 7, 19, 262176, 41, 7, 16, 262176, 47, 3, 7, 262187, 6, 49, 3212836864, 262176, 35 | 50, 3, 6, 262203, 47, 55, 3, 262187, 14, 57, 1, 262176, 59, 7, 7, 327734, 2, 4, 36 | 0, 3, 131320, 5, 262203, 39, 40, 7, 262203, 39, 58, 7, 262205, 14, 38, 37, 37 | 196670, 40, 35, 393281, 41, 42, 40, 38, 15, 262205, 16, 43, 42, 327761, 6, 44, 38 | 43, 0, 327761, 6, 45, 43, 1, 458832, 7, 46, 44, 45, 27, 24, 327745, 47, 48, 13, 39 | 15, 196670, 48, 46, 393281, 50, 51, 13, 15, 9, 262205, 6, 52, 51, 327813, 6, 40 | 53, 52, 49, 393281, 50, 54, 13, 15, 9, 196670, 54, 53, 262205, 14, 56, 37, 41 | 196670, 58, 35, 393281, 59, 60, 58, 56, 57, 262205, 7, 61, 60, 196670, 55, 61, 42 | 65789, 65592 43 | }; 44 | 45 | uint32_t dummy_frag_spv[] = { 46 | 119734787, 65536, 524289, 13, 0, 131089, 1, 393227, 1, 1280527431, 1685353262, 47 | 808793134, 0, 196622, 0, 1, 458767, 4, 4, 1852399981, 0, 9, 11, 196624, 4, 7, 48 | 196611, 2, 450, 589828, 1096764487, 1935622738, 1918988389, 1600484449, 49 | 1684105331, 1868526181, 1667590754, 29556, 589828, 1096764487, 1935622738, 50 | 1768186216, 1818191726, 1969712737, 1600481121, 1882206772, 7037793, 262149, 4, 51 | 1852399981, 0, 393221, 9, 1182037359, 1130848626, 1919904879, 0, 262149, 11, 52 | 1866690153, 7499628, 262215, 9, 30, 0, 262215, 11, 30, 0, 131091, 2, 196641, 3, 53 | 2, 196630, 6, 32, 262167, 7, 6, 4, 262176, 8, 3, 7, 262203, 8, 9, 3, 262176, 54 | 10, 1, 7, 262203, 10, 11, 1, 327734, 2, 4, 0, 3, 131320, 5, 262205, 7, 12, 11, 55 | 196670, 9, 12, 65789, 65592 56 | }; 57 | 58 | vpp::RenderPass createRenderPass(const vpp::Device& dev, vk::Format format) { 59 | vk::AttachmentDescription attachment {}; 60 | 61 | attachment.format = format; 62 | attachment.samples = vk::SampleCountBits::e1; 63 | attachment.loadOp = vk::AttachmentLoadOp::clear; 64 | attachment.storeOp = vk::AttachmentStoreOp::store; 65 | attachment.stencilLoadOp = vk::AttachmentLoadOp::dontCare; 66 | attachment.stencilStoreOp = vk::AttachmentStoreOp::dontCare; 67 | attachment.initialLayout = vk::ImageLayout::undefined; 68 | attachment.finalLayout = vk::ImageLayout::presentSrcKHR; 69 | 70 | vk::AttachmentReference colorReference; 71 | colorReference.attachment = 0; 72 | colorReference.layout = vk::ImageLayout::colorAttachmentOptimal; 73 | 74 | vk::SubpassDescription subpass; 75 | subpass.pipelineBindPoint = vk::PipelineBindPoint::graphics; 76 | subpass.colorAttachmentCount = 1; 77 | subpass.pColorAttachments = &colorReference; 78 | 79 | vk::RenderPassCreateInfo renderPassInfo; 80 | renderPassInfo.attachmentCount = 1; 81 | renderPassInfo.pAttachments = &attachment; 82 | renderPassInfo.subpassCount = 1; 83 | renderPassInfo.pSubpasses = &subpass; 84 | 85 | return {dev, renderPassInfo}; 86 | } 87 | 88 | TEST(defaultCreate) { 89 | auto& dev = *globals.device; 90 | 91 | auto rp = createRenderPass(dev, vk::Format::b8g8r8a8Unorm); 92 | auto layout = vpp::PipelineLayout(dev, vk::PipelineLayoutCreateInfo {}); 93 | 94 | auto vert = vpp::ShaderModule(dev, dummy_vert_spv); 95 | auto frag = vpp::ShaderModule(dev, dummy_frag_spv); 96 | 97 | auto info = vpp::GraphicsPipelineInfo(rp, layout, {{{ 98 | {vert, vk::ShaderStageBits::vertex}, 99 | {frag, vk::ShaderStageBits::fragment} 100 | }}}); 101 | 102 | constexpr auto cacheName = "graphicsPipelineCache.bin"; 103 | vpp::PipelineCache cache(dev, cacheName); 104 | 105 | vk::Pipeline vkPipeline; 106 | vk::createGraphicsPipelines(dev, cache, 1, info.info(), nullptr, 107 | vkPipeline); 108 | 109 | vpp::save(cache, cacheName); 110 | auto raii = vpp::Pipeline(dev, vkPipeline); 111 | } 112 | 113 | -------------------------------------------------------------------------------- /docs/tests/trackedDescriptor.cpp: -------------------------------------------------------------------------------- 1 | #include "init.hpp" 2 | #include "bugged.hpp" 3 | 4 | #include 5 | #include 6 | 7 | TEST(basic) { 8 | auto& dev = *globals.device; 9 | 10 | // layout 11 | auto bindings1 = std::array { 12 | vpp::descriptorBinding(vk::DescriptorType::uniformBuffer), 13 | vpp::descriptorBinding(vk::DescriptorType::uniformBuffer), 14 | vpp::descriptorBinding(vk::DescriptorType::combinedImageSampler, 15 | vk::ShaderStageBits::fragment, nullptr, 10, 4), 16 | }; 17 | 18 | auto bindings2 = { 19 | vpp::descriptorBinding(vk::DescriptorType::storageBuffer, 20 | vk::ShaderStageBits::compute, nullptr, 3), 21 | }; 22 | 23 | auto layout1 = vpp::TrDsLayout(dev, bindings1); 24 | auto layout2 = vpp::TrDsLayout(dev, bindings2); 25 | 26 | EXPECT(layout1.poolSizes().size(), 2u); 27 | EXPECT(layout1.poolSizes()[0].descriptorCount, 2u); 28 | EXPECT(layout1.poolSizes()[0].type, vk::DescriptorType::uniformBuffer); 29 | EXPECT(layout1.poolSizes()[1].descriptorCount, 10u); 30 | EXPECT(layout1.poolSizes()[1].type, vk::DescriptorType::combinedImageSampler); 31 | 32 | EXPECT(layout2.poolSizes().size(), 1u); 33 | EXPECT(layout2.poolSizes()[0].descriptorCount, 3u); 34 | EXPECT(layout2.poolSizes()[0].type, vk::DescriptorType::storageBuffer); 35 | 36 | // pool 37 | auto pool = vpp::TrDsPool(dev, 3, {{ 38 | {vk::DescriptorType::uniformBuffer, 4}, 39 | {vk::DescriptorType::storageBuffer, 3}, 40 | {vk::DescriptorType::combinedImageSampler, 20} 41 | }}); 42 | 43 | EXPECT(pool.remainingSets(), 3u); 44 | EXPECT(pool.remaining().size(), 3u); 45 | EXPECT(pool.remaining()[0].type, vk::DescriptorType::uniformBuffer); 46 | EXPECT(pool.remaining()[0].descriptorCount, 4u); 47 | EXPECT(pool.remaining()[1].descriptorCount, 3u); 48 | EXPECT(pool.remaining()[1].type, vk::DescriptorType::storageBuffer); 49 | EXPECT(pool.remaining()[2].descriptorCount, 20u); 50 | EXPECT(pool.remaining()[2].type, vk::DescriptorType::combinedImageSampler); 51 | 52 | // sets 53 | auto set1 = vpp::TrDs(pool, layout1); 54 | 55 | EXPECT(pool.remainingSets(), 2u); 56 | EXPECT(pool.remaining().size(), 3u); 57 | EXPECT(pool.remaining()[0].descriptorCount, 2u); 58 | EXPECT(pool.remaining()[1].descriptorCount, 3u); 59 | EXPECT(pool.remaining()[2].descriptorCount, 10u); 60 | 61 | auto set2 = vpp::TrDs(pool, layout1); 62 | 63 | EXPECT(pool.remainingSets(), 1u); 64 | EXPECT(pool.remaining().size(), 3u); 65 | EXPECT(pool.remaining()[0].descriptorCount, 0u); 66 | EXPECT(pool.remaining()[1].descriptorCount, 3u); 67 | EXPECT(pool.remaining()[2].descriptorCount, 0u); 68 | 69 | auto set3 = vpp::TrDs(pool, layout2); 70 | 71 | EXPECT(pool.remainingSets(), 0u); 72 | EXPECT(pool.remaining().size(), 3u); 73 | EXPECT(pool.remaining()[0].descriptorCount, 0u); 74 | EXPECT(pool.remaining()[1].descriptorCount, 0u); 75 | EXPECT(pool.remaining()[2].descriptorCount, 0u); 76 | 77 | EXPECT(&set1.pool(), &pool); 78 | EXPECT(&set2.pool(), &pool); 79 | EXPECT(&set3.pool(), &pool); 80 | 81 | EXPECT(&set1.layout(), &layout1); 82 | EXPECT(&set2.layout(), &layout1); 83 | EXPECT(&set3.layout(), &layout2); 84 | } 85 | 86 | TEST(allocator) { 87 | auto& dev = *globals.device; 88 | auto alloc = vpp::DescriptorAllocator(dev); 89 | 90 | auto bindings = { 91 | vpp::descriptorBinding(vk::DescriptorType::storageBuffer), 92 | vpp::descriptorBinding(vk::DescriptorType::combinedImageSampler) 93 | }; 94 | 95 | auto layout = vpp::TrDsLayout(dev, bindings); 96 | 97 | // Check that with correct reserves, we don't use more than one pool 98 | for(auto i = 0u; i < 10; ++i) { 99 | alloc.reserve(layout); 100 | } 101 | 102 | vpp::TrDs::InitData data; 103 | auto defered = vpp::TrDs(data, alloc, layout); 104 | EXPECT(defered.vkHandle(), vk::DescriptorSet {}); 105 | 106 | auto set0 = vpp::TrDs(alloc, layout); 107 | EXPECT(&set0.layout(), &layout); 108 | 109 | std::vector sets; 110 | for(auto i = 0u; i < 10; ++i) { 111 | sets.push_back({alloc, layout}); 112 | EXPECT(sets.back().vkHandle() != vk::DescriptorSet {}, true); 113 | EXPECT(&sets.back().layout(), &layout); 114 | EXPECT(&sets.back().pool(), &set0.pool()); 115 | } 116 | 117 | EXPECT(alloc.pools().size(), 1u); 118 | 119 | defered.init(data); 120 | EXPECT(defered.vkHandle() != vk::DescriptorSet {}, true); 121 | EXPECT(&defered.pool(), &set0.pool()); 122 | EXPECT(&defered.layout(), &layout); 123 | 124 | EXPECT(alloc.pools().size(), 1u); 125 | 126 | // check that resources are correctly freed and reused 127 | sets.clear(); 128 | for(auto i = 0u; i < 100; ++i) { 129 | auto set = vpp::TrDs(alloc, layout); 130 | EXPECT(set.vkHandle() != vk::DescriptorSet {}, true); 131 | EXPECT(&set.layout(), &layout); 132 | EXPECT(&set.pool(), &set0.pool()); 133 | EXPECT(alloc.pools().size(), 1u); 134 | } 135 | } 136 | 137 | TEST(devAllocator) { 138 | auto& dev = *globals.device; 139 | auto& alloc = dev.descriptorAllocator(); 140 | 141 | auto bindings = { 142 | vpp::descriptorBinding(vk::DescriptorType::storageBuffer), 143 | vpp::descriptorBinding(vk::DescriptorType::combinedImageSampler) 144 | }; 145 | 146 | auto layout = vpp::TrDsLayout(dev, bindings); 147 | 148 | alloc.reserve(layout, 1); 149 | 150 | vpp::TrDs::InitData data; 151 | auto ds2 = vpp::TrDs(data, alloc, layout); 152 | 153 | // destroy without finishing intialization 154 | for(auto i = 0u; i < 1000; ++i) { 155 | vpp::TrDs::InitData ldata; 156 | auto tmp = vpp::TrDs(ldata, alloc, layout); 157 | } 158 | 159 | for(auto i = 0u; i < 1000; ++i) { 160 | vpp::TrDs::InitData ldata; 161 | auto tmp = vpp::TrDs(ldata, alloc, layout); 162 | tmp = {}; 163 | } 164 | 165 | auto ds = vpp::TrDs(alloc, layout); 166 | ds2.init(data); 167 | 168 | EXPECT(alloc.pools().size(), 1u); 169 | EXPECT(alloc.pools()[0].remainingSets() < 1000u, true); 170 | } 171 | -------------------------------------------------------------------------------- /include/vpp/descriptor.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | namespace vpp { 16 | namespace fwd { 17 | extern VPP_API const vk::ShaderStageFlags allShaderStages; 18 | } // namespace fwd 19 | 20 | /// Allows convenient descriptorSet updates. 21 | /// Does not perform any checking and has the overhead of internally 22 | /// allocating memory. 23 | class VPP_API DescriptorSetUpdate { 24 | public: 25 | using BufferInfos = std::vector; 26 | using BufferViewInfos = std::vector; 27 | using ImageInfos = std::vector; 28 | 29 | public: 30 | DescriptorSetUpdate() = default; 31 | DescriptorSetUpdate(const DescriptorSet& set); 32 | ~DescriptorSetUpdate(); 33 | 34 | DescriptorSetUpdate(DescriptorSetUpdate&) = delete; 35 | DescriptorSetUpdate& operator=(DescriptorSetUpdate&) = default; 36 | 37 | DescriptorSetUpdate(DescriptorSetUpdate&&) noexcept = default; 38 | DescriptorSetUpdate& operator=(DescriptorSetUpdate&&) noexcept = default; 39 | 40 | /// When the range member of any buffer info is 0 (default constructed), it will 41 | /// be automatically set to vk::wholeSize. 42 | void uniform(BufferInfos, int binding = -1, unsigned int elem = 0); 43 | void uniform(nytl::Span, int binding = -1, unsigned elem = 0); 44 | void uniform(BufferSpan span, int binding = -1, unsigned elem = 0); 45 | 46 | void storage(BufferInfos, int binding = -1, unsigned int elem = 0); 47 | void storage(nytl::Span, int binding = -1, unsigned elem = 0); 48 | void storage(BufferSpan, int binding = -1, unsigned elem = 0); 49 | 50 | void uniformDynamic(nytl::Span, int binding = -1, unsigned elem = 0); 51 | void uniformDynamic(BufferInfos, int binding = -1, unsigned int elem = 0); 52 | void storageDynamic(BufferInfos, int binding = -1, unsigned int elem = 0); 53 | void storageDynamic(nytl::Span, int binding = -1, unsigned elem = 0); 54 | 55 | void sampler(ImageInfos, int binding = -1, unsigned int elem = 0); 56 | void image(ImageInfos, int binding = -1, unsigned int elem = 0); 57 | void image(vk::ImageView, vk::ImageLayout, int binding = -1, unsigned elem = 0); 58 | void storage(ImageInfos, int binding = -1, unsigned int elem = 0); 59 | void storage(vk::ImageView, vk::ImageLayout, int binding = -1, unsigned elem = 0); 60 | void storage(vk::ImageView, int binding = -1, unsigned elem = 0); 61 | void imageSampler(ImageInfos, int binding = -1, unsigned int elem = 0); 62 | void imageSampler(vk::ImageView, vk::Sampler = {}, int binding = -1, unsigned elem = 0); 63 | void imageSampler(vk::ImageView, vk::ImageLayout, vk::Sampler = {}, int binding = -1, unsigned elem = 0); 64 | void inputAttachment(ImageInfos, int binding = -1, unsigned int elem = 0); 65 | 66 | void uniformView(BufferViewInfos, int binding = -1, unsigned int elem = 0); 67 | void storageView(BufferViewInfos, int binding = -1, unsigned int elem = 0); 68 | 69 | void copy(const vk::CopyDescriptorSet& copySet); 70 | 71 | /// Appplies all queued updates. 72 | /// If never called, will be automatically triggered on destruction. 73 | void apply(); 74 | 75 | /// Resets all queued updates without executing them. 76 | void reset(); 77 | 78 | /// Skips the given number of descriptors. 79 | void skip(unsigned int count = 1) { currentBinding_ += count; }; 80 | 81 | const auto& device() const { return set_->device(); } 82 | auto vkDevice() const { return device().vkDevice(); } 83 | auto vkInstance() const { return device().vkInstance(); } 84 | auto vkPhysicalDevice() const { return device().vkPhysicalDevice(); } 85 | auto currentBinding() const { return currentBinding_; } 86 | 87 | const auto& writes() const { return writes_; } 88 | const auto& copies() const { return copies_; } 89 | 90 | protected: 91 | std::vector writes_; 92 | std::vector copies_; 93 | 94 | // double vector to avoid reference (in writes_) invalidation 95 | // some values must be stored continuesly, so deque doesnt work 96 | std::vector> buffers_; 97 | std::vector> views_; 98 | std::vector> images_; 99 | 100 | unsigned int currentBinding_ = 0; 101 | const DescriptorSet* set_ {}; 102 | }; 103 | 104 | /// Applies multiple descriptor set updates. 105 | /// May be a bit more efficient than updating them individually. 106 | VPP_API void apply(nytl::Span>); 107 | VPP_API void apply(nytl::Span); 108 | 109 | /// Alternative vk::DescriptorSetLayoutBinding constructor. 110 | /// When passed to the DescriptorSetLayout constructor, will automatically 111 | /// update binding number without spaces if it is autoDescriptorBinding (-1). 112 | constexpr auto autoDescriptorBinding = std::uint32_t(0xFFFFFFFF); 113 | VPP_API vk::DescriptorSetLayoutBinding descriptorBinding(vk::DescriptorType type, 114 | vk::ShaderStageFlags stages = fwd::allShaderStages, 115 | const vk::Sampler* samplers = nullptr, 116 | std::uint32_t count = 1, std::uint32_t binding = autoDescriptorBinding); 117 | 118 | /// Less intuitive overload, kept for legacy reason. 119 | [[deprecated("Use more intuitive sampler-first overload")]] 120 | VPP_API vk::DescriptorSetLayoutBinding descriptorBinding(vk::DescriptorType type, 121 | vk::ShaderStageFlags stages, std::uint32_t binding, 122 | std::uint32_t count = 1, const vk::Sampler* samplers = nullptr); 123 | 124 | } // namespace vpp 125 | -------------------------------------------------------------------------------- /include/vpp/memoryMap.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include // vpp::Allocation 10 | #include // Span 11 | 12 | namespace vpp { 13 | 14 | // Represents a mapped range of a vulkan DeviceMemory. 15 | // There must never be more than one MemoryMap object for on DeviceMemory object. 16 | // The MemoryMap class is usually never used directly, but rather accessed 17 | // through a MemoryMapView. 18 | // Instances of this class cannot be created manually but must be indirectly 19 | // retrieved by a DeviceMemory object. 20 | // This class is not threadsafe. 21 | class VPP_API MemoryMap { 22 | public: 23 | using Allocation = BasicAllocation; 24 | 25 | public: 26 | /// MemoryMap is NonMovable to allow views to keep references 27 | /// to the MemoryMap object (e.g. unref it on destruction). 28 | MemoryMap(MemoryMap&&) noexcept = delete; 29 | MemoryMap& operator=(MemoryMap&&) noexcept = delete; 30 | 31 | /// Assures that the range given by allocation is included in the map. 32 | /// Might remap the mapped range. If the memory is currently not 33 | /// mapped, will map it. 34 | void remap(const Allocation& allocation); 35 | 36 | /// Calls flushMappedMemoryRanges, see the vulkan spec. 37 | /// If memory is coherent, this function will have no effect (it 38 | /// will check everytime). 39 | void flush(const Allocation& = {0u, vk::wholeSize}) const; 40 | 41 | /// Calls invalidateMappedMemoryRanges, see the vulkan spec. 42 | /// If memory is coherent, this function will have no effect (it 43 | /// will check everytime). 44 | void invalidate(const Allocation& = {0u, vk::wholeSize}) const; 45 | 46 | /// Returns whether this object is valid. 47 | /// If it is not, any operations on it may result in undefined behavior. 48 | bool valid() const noexcept { return ptr_; } 49 | 50 | /// Note that pointer (and span) may change when the memory is remapped. 51 | /// So don't store them over a time where a remap might happen. 52 | std::byte* ptr() const noexcept { return static_cast(ptr_); } 53 | nytl::Span span() const noexcept { return {ptr(), ptr() + size()}; } 54 | nytl::Span cspan() const noexcept { 55 | return {ptr(), ptr() + size()}; 56 | } 57 | 58 | const vk::DeviceMemory& vkMemory() const noexcept; 59 | const Allocation& allocation() const noexcept { return allocation_; } 60 | vk::DeviceSize offset() const noexcept { return allocation().offset; } 61 | vk::DeviceSize size() const noexcept { return allocation().size; } 62 | const DeviceMemory& memory() const noexcept { return *memory_; } 63 | bool coherent() const noexcept; 64 | 65 | vk::MappedMemoryRange mappedMemoryRange() const noexcept; 66 | const Device& device() const noexcept; 67 | 68 | protected: 69 | friend class MemoryMapView; 70 | friend class DeviceMemory; 71 | 72 | MemoryMap() = default; 73 | ~MemoryMap(); 74 | 75 | void map(const DeviceMemory& memory, const Allocation& alloc); 76 | 77 | void ref() noexcept; 78 | void unref() noexcept; 79 | void unmap(); 80 | 81 | protected: 82 | const DeviceMemory* memory_ {nullptr}; 83 | Allocation allocation_ {}; 84 | unsigned int views_ {}; 85 | void* ptr_ {nullptr}; 86 | }; 87 | 88 | // A view into a mapped memory range. 89 | // Makes it possible to write/read from multiple allocations on a mapped memory. 90 | // Objects are always retrieved by a DeviceMemory object. 91 | // This class is not threadsafe. 92 | class VPP_API MemoryMapView { 93 | public: 94 | using Allocation = BasicAllocation; 95 | 96 | public: 97 | MemoryMapView() noexcept = default; 98 | ~MemoryMapView(); 99 | 100 | MemoryMapView(MemoryMapView&& lhs) noexcept { swap(lhs); } 101 | MemoryMapView& operator=(MemoryMapView lhs) noexcept { 102 | swap(lhs); 103 | return *this; 104 | } 105 | 106 | /// Calls flushMappedMemoryRanges, see the vulkan spec. 107 | /// If memory is coherent, this function will have no effect (it 108 | /// will check everytime). 109 | void flush() const; 110 | 111 | /// Calls invalidateMappedMemoryRanges, see the vulkan spec. 112 | /// If memory is coherent, this function will have no effect (it 113 | /// will check everytime). 114 | void invalidate() const; 115 | 116 | /// Returns whether the view is valid. 117 | bool valid() const noexcept { return memoryMap_; } 118 | 119 | /// Note that pointer (and span) may change when the memory is remapped. 120 | /// So don't store them over a time where a remap might happen. 121 | std::byte* ptr() const noexcept; 122 | nytl::Span span() const noexcept { return {ptr(), ptr() + size()}; } 123 | nytl::Span cspan() const noexcept { 124 | return {ptr(), ptr() + size()}; 125 | } 126 | 127 | MemoryMap& memoryMap() const noexcept { return *memoryMap_; } 128 | const DeviceMemory& memory() const noexcept { return memoryMap().memory(); } 129 | vk::DeviceMemory vkMemory() const noexcept { return memoryMap().vkMemory(); } 130 | const Allocation& allocation() const noexcept { return allocation_; } 131 | vk::DeviceSize offset() const noexcept { return allocation().offset; } 132 | vk::DeviceSize size() const noexcept { return allocation().size; } 133 | bool coherent() const noexcept; 134 | 135 | vk::MappedMemoryRange mappedMemoryRange() const noexcept; 136 | 137 | const Device& device() const noexcept { return memoryMap_->device(); } 138 | void swap(MemoryMapView& lhs) noexcept; 139 | 140 | protected: 141 | friend class DeviceMemory; 142 | MemoryMapView(MemoryMap& map, const Allocation& range); 143 | 144 | protected: 145 | MemoryMap* memoryMap_ {}; 146 | Allocation allocation_ {}; 147 | }; 148 | 149 | } // namespace vpp 150 | -------------------------------------------------------------------------------- /include/vpp/image.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include // vpp::ImageHandle, vpp::ImageView 9 | #include // vpp::MemoryResource 10 | 11 | namespace vpp { 12 | 13 | class VPP_API Image : public ImageHandle, public MemoryResource { 14 | public: 15 | Image() = default; 16 | 17 | /// The various types of constructors: 18 | /// - Transfer ownership vs create 19 | /// * (1,3,5,7): ImageCreateInfo parameter: create a new image 20 | /// * (2,4,6,8): vk::Image parameter: transfer ownerhip of existent image 21 | /// - Allocate mechanism. You can either use/pass 22 | /// * (1,2): using a DeviceMemoryAllocator. 23 | /// Guarantees that the memory is allocated on a memory type 24 | /// contained in memBits (e.g. to assure it's allocated 25 | /// on hostVisible memory). 26 | /// * (3): allocate on a specific DeviceMemory object. 27 | /// Will throw std::runtime_error if the DeviceMemory fails 28 | /// to allocate enough memory. The DeviceMemory must 29 | /// be allocated on a type that is supported for the 30 | /// created image (the vulkan spec gives some guarantess there). 31 | /// * (4): Will pass ownership of the allocated memory span which must 32 | /// be bound to the image. 33 | /// - Deferred? See the vpp doc for deferred initialization 34 | /// * (1-6) bind the image immediately to memory. For (1,2) this 35 | /// means to immediately allocate memory, which might result 36 | /// in a vkAllocateMemory call 37 | /// * (7,8) does not bind the image to memory, only when 38 | /// ensureMemory is called. Already issues a reserving request 39 | /// to the DeviceMemoryAllocator, might result in less 40 | /// memory allocations made if multiple resources are created deferred. 41 | /// For constructors that receive an already existent image but 42 | /// allocate and bind the memory for you, you have to pass the images 43 | /// tiling to allow the constructor to keep any granularity 44 | /// requirements. 45 | Image(vpp::DeviceMemoryAllocator&, const vk::ImageCreateInfo&, 46 | unsigned int memBits = ~0u); 47 | Image(vpp::DeviceMemoryAllocator&, vk::Image, vk::ImageTiling, 48 | unsigned int memBits = ~0u); 49 | 50 | Image(DeviceMemory&, const vk::ImageCreateInfo&); 51 | Image(DeviceMemory&, vk::Image, vk::DeviceSize memOffset); 52 | 53 | /// Creates the image without any bound memory. 54 | /// You have to call the ensureMemory function later on to 55 | /// make sure memory is bound to the image. 56 | Image(InitData&, DeviceMemoryAllocator&, const vk::ImageCreateInfo&, 57 | unsigned int memBits = ~0u); 58 | Image(InitData&, DeviceMemoryAllocator&, vk::Image, vk::ImageTiling, 59 | unsigned int memBits = ~0u); 60 | 61 | Image(Image&& rhs) noexcept = default; 62 | Image& operator=(Image&& rhs) noexcept = default; 63 | 64 | /// When the two-step deferred constructor was used, this function 65 | /// will allocate the memory for this resource. 66 | /// Otherwise undefined behaviour. 67 | void init(InitData& data); 68 | }; 69 | 70 | /// Combines a vulkan image and an imageView for it. 71 | /// Can be e.g. used for textures or framebuffer attachments. 72 | /// See also ViewableImageCreateInfo for default initializers. 73 | class VPP_API ViewableImage { 74 | public: 75 | /// NOTE: we could require the ImageViewCreateInfo in the deferred 76 | /// constructor as well and store it in InitData. 77 | using InitData = Image::InitData; 78 | 79 | public: 80 | ViewableImage() = default; 81 | 82 | /// Constructs new (or transfers ownership of existent) image and 83 | /// view. Immediately binds memory to the image. 84 | /// The image member can be left empty (nullHandle) in all 85 | /// passed ImageViewCreateInfos, it will be set automatically. 86 | ViewableImage(DeviceMemoryAllocator&, const vk::ImageCreateInfo&, 87 | const vk::ImageViewCreateInfo&, unsigned int memBits = ~0u); 88 | ViewableImage(DeviceMemoryAllocator&, const ViewableImageCreateInfo&, 89 | unsigned int memBits = ~0u); 90 | ViewableImage(Image&&, vk::ImageViewCreateInfo); 91 | ViewableImage(Image&&, ImageView&&); 92 | 93 | /// Creates (or transfer ownership of) an image but doesn't bind 94 | /// memory to it or create the view. You have to call 95 | /// init with the ViewCreateInfo before it can be used in any way. 96 | ViewableImage(InitData&, DeviceMemoryAllocator&, 97 | const vk::ImageCreateInfo&, unsigned int memBits = ~0u); 98 | ViewableImage(InitData&, Image&&); 99 | 100 | ~ViewableImage() = default; 101 | 102 | ViewableImage(ViewableImage&&) noexcept = default; 103 | ViewableImage& operator=(ViewableImage&&) noexcept = default; 104 | 105 | /// If the Viewable image was constructed with a deferred constructor, 106 | /// this will finish the initialization process. 107 | /// Will assure that the image has bound memory and create the image view. 108 | /// Will automatically set the image of the CreateImageViewInfo. 109 | void init(InitData&, vk::ImageViewCreateInfo); 110 | 111 | const auto& image() const { return image_; } 112 | const auto& imageView() const { return imageView_; } 113 | const vk::ImageView& vkImageView() const { return imageView_; } 114 | const vk::Image& vkImage() const { return image_; } 115 | 116 | const Device& device() const { return image_.device(); } 117 | auto vkDevice() const { return device().vkDevice(); } 118 | auto vkInstance() const { return device().vkInstance(); } 119 | auto vkPhysicalDevice() const { return device().vkPhysicalDevice(); } 120 | 121 | std::pair split() { 122 | return {std::move(image_), std::move(imageView_)}; 123 | } 124 | 125 | protected: 126 | Image image_; 127 | ImageView imageView_; 128 | }; 129 | 130 | } // namespace vpp 131 | -------------------------------------------------------------------------------- /include/vpp/trackedDescriptor.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace vpp { 14 | 15 | /// Tracked DescriptorSetLayout, knows all its bindings. 16 | /// Mainly useful when one wants to simply create a descriptor set from 17 | /// a layout, see TrDs and TrDsPool/TrDsAllocator. 18 | class VPP_API TrDsLayout : public DescriptorSetLayout { 19 | public: 20 | TrDsLayout(); // = default 21 | TrDsLayout(const Device&, const vk::DescriptorSetLayoutCreateInfo&); 22 | TrDsLayout(const Device&, nytl::Span); 23 | TrDsLayout(const Device&, std::initializer_list); 24 | ~TrDsLayout(); // = default 25 | 26 | // Moving a TrDsLayout would be a problem for all TrDs's that 27 | // reference it. 28 | TrDsLayout(TrDsLayout&&) = delete; 29 | TrDsLayout& operator=(TrDsLayout&&) = delete; 30 | 31 | // Calling this on an already initialized TrDsLayout works, 32 | // but will trigger UB when there TrDs's referencing it. 33 | void init(const Device&, const vk::DescriptorSetLayoutCreateInfo&); 34 | void init(const Device&, nytl::Span); 35 | void init(const Device&, std::initializer_list); 36 | 37 | // Returns the types and counts of binding types required by this 38 | // layout. Note that this does not preserve all the information 39 | // about the dsLayout itself, just the information needed for 40 | // allocating a TrDs from it. 41 | const auto& poolSizes() const { return poolSizes_; } 42 | 43 | protected: 44 | void init(nytl::Span); 45 | std::vector poolSizes_; 46 | }; 47 | 48 | /// Tracked DescriptorPool, knows how many resources it has left. 49 | /// Always created with the freeDescriptorSet flag since otherwise it makes 50 | /// no sense tracking the resources. 51 | /// Not movable since it is referenced by TrDs objects. 52 | class VPP_API TrDsPool : public DescriptorPool { 53 | public: 54 | TrDsPool(); // = default; 55 | TrDsPool(const Device&, vk::DescriptorPoolCreateInfo); 56 | TrDsPool(const Device&, unsigned maxSets, 57 | nytl::Span sizes, 58 | vk::DescriptorPoolCreateFlags = {}); 59 | ~TrDsPool(); // = default; 60 | 61 | TrDsPool(TrDsPool&&) = delete; 62 | TrDsPool& operator=(TrDsPool&&) = delete; 63 | 64 | const auto& remaining() const { return remaining_; } 65 | const auto& remainingSets() const { return remainingSets_; } 66 | 67 | protected: 68 | friend class TrDs; 69 | std::vector remaining_; // types remaining 70 | unsigned int remainingSets_ {}; // how many sets to allocate are left 71 | }; 72 | 73 | /// Tracked DescriptorSet, knows its associated pool and layout. 74 | /// Will allocate/free the needed descriptors from the tracked pool it is 75 | /// created with. 76 | class VPP_API TrDs : public DescriptorSet { 77 | public: 78 | struct InitData { 79 | InitData() = default; 80 | InitData(InitData&&) noexcept; 81 | InitData& operator=(InitData&&) noexcept; 82 | ~InitData(); 83 | 84 | const TrDsLayout* layout {}; // required for canceln a reservation 85 | DescriptorAllocator* allocator {}; 86 | size_t reservation {}; // number of pools in alloc at reserve time + 1 87 | }; 88 | 89 | public: 90 | TrDs() = default; 91 | 92 | /// Directly allocates a descriptor set with the given layout from 93 | /// the given pool. 94 | TrDs(TrDsPool&, const TrDsLayout&); 95 | 96 | /// Allocates a descriptor set with given alyout from the given allocator. 97 | TrDs(DescriptorAllocator&, const TrDsLayout&); 98 | 99 | /// Reserves the resources needed to allocate a descriptor set with the 100 | /// given layout from the given allocator. 101 | /// Only a call later to init() will really create the descriptor set. 102 | TrDs(InitData&, DescriptorAllocator&, const TrDsLayout&); 103 | ~TrDs(); 104 | 105 | TrDs(TrDs&& rhs) noexcept { swap(*this, rhs); } 106 | TrDs& operator=(TrDs rhs) noexcept { 107 | swap(*this, rhs); 108 | return *this; 109 | } 110 | 111 | /// When this object was constructed with a deferred constructor, 112 | /// this will finish object initialization. 113 | /// Otherwise undefined behaviour. 114 | void init(InitData&); 115 | 116 | TrDsPool& pool() const { return *pool_; } 117 | const TrDsLayout& layout() const { return *layout_; } 118 | friend VPP_API void swap(TrDs& a, TrDs& b) noexcept; 119 | 120 | protected: 121 | TrDsPool* pool_ {}; 122 | const TrDsLayout* layout_ {}; 123 | }; 124 | 125 | /// Dynamically allocates tracked descriptors. 126 | /// Will create descriptor pools on the fly but try to create as few 127 | /// as possible. Can't be moved since TrDs objects might reference it. 128 | class VPP_API DescriptorAllocator : public vpp::Resource { 129 | public: 130 | DescriptorAllocator(); // = default; 131 | DescriptorAllocator(const vpp::Device&, vk::DescriptorPoolCreateFlags = {}); 132 | ~DescriptorAllocator(); // = default 133 | 134 | DescriptorAllocator(DescriptorAllocator&&) = delete; 135 | DescriptorAllocator& operator=(DescriptorAllocator&&) = delete; 136 | 137 | void init(const vpp::Device&, vk::DescriptorPoolCreateFlags = {}); 138 | void reserve(nytl::Span, unsigned count = 1); 139 | void reserve(const TrDsLayout&, unsigned count = 1); 140 | void unreserve(const TrDsLayout&); 141 | void clearReservations(); 142 | TrDs alloc(const TrDsLayout&); 143 | 144 | const auto& pools() const { return pools_; } 145 | const auto& pending() const { return pending_; } 146 | 147 | // NOTE: we could implement something like 'mergePools', comparable 148 | // to the merge operations in DeviceMemory- and BufferAllocator here, 149 | // if ever needed. 150 | 151 | private: 152 | struct { 153 | std::vector types; 154 | unsigned int count {}; 155 | } pending_; 156 | 157 | // all owned descriptor pools 158 | std::deque pools_; 159 | vk::DescriptorPoolCreateFlags flags_ {}; 160 | }; 161 | 162 | } // namespace vpp 163 | -------------------------------------------------------------------------------- /src/vpp/physicalDevice.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace vpp { 10 | 11 | vk::PhysicalDevice choose(nytl::Span phdevs) { 12 | vk::PhysicalDevice best = {}; 13 | auto bestScore = 0u; 14 | 15 | // TODO: better querying! consider memory size and stuff 16 | for(auto& phdev : phdevs) { 17 | auto props = vk::getPhysicalDeviceProperties(phdev); 18 | auto score = 0u; 19 | 20 | if(props.deviceType == vk::PhysicalDeviceType::discreteGpu) { 21 | score = 5; 22 | } else if(props.deviceType == vk::PhysicalDeviceType::integratedGpu) { 23 | score = 4; 24 | } else if(props.deviceType == vk::PhysicalDeviceType::virtualGpu) { 25 | score = 3; 26 | } else if(props.deviceType == vk::PhysicalDeviceType::cpu) { 27 | score = 2; 28 | } else { 29 | score = 1u; 30 | } 31 | 32 | if(score > bestScore) { 33 | bestScore = score; 34 | best = phdev; 35 | } 36 | } 37 | 38 | return best; 39 | } 40 | 41 | vk::PhysicalDevice choose(nytl::Span phdevs, 42 | vk::SurfaceKHR surface) { 43 | std::vector supported; 44 | supported.reserve(phdevs.size()); 45 | 46 | // first check for all passed physical devices if it has any 47 | // queue that can present on the passed device 48 | // if so, insert it into the supported vector 49 | for(auto phdev : phdevs) { 50 | if(findQueueFamily(phdev, surface) != -1) { 51 | supported.push_back(phdev); 52 | } 53 | } 54 | 55 | // default choose by usability from the phdevs that support presenting 56 | return choose(supported); 57 | } 58 | 59 | const char* name(vk::PhysicalDeviceType type) { 60 | switch(type) { 61 | case vk::PhysicalDeviceType::discreteGpu: 62 | return "discreteGpu"; 63 | case vk::PhysicalDeviceType::integratedGpu: 64 | return "integratedGpu"; 65 | case vk::PhysicalDeviceType::virtualGpu: 66 | return "virtualGpu"; 67 | case vk::PhysicalDeviceType::other: 68 | return "other"; 69 | default: 70 | return ""; 71 | } 72 | } 73 | 74 | std::array apiVersion(uint32_t v) { 75 | return { 76 | VK_VERSION_MAJOR(v), 77 | VK_VERSION_MINOR(v), 78 | VK_VERSION_PATCH(v) 79 | }; 80 | } 81 | 82 | std::string description(vk::PhysicalDevice phdev, const char* sep) { 83 | std::string ret; 84 | auto props = vk::getPhysicalDeviceProperties(phdev); 85 | 86 | ret += "Name: "; 87 | ret += props.deviceName.data(); 88 | ret += sep; 89 | 90 | ret += "Type: "; 91 | ret += name(props.deviceType); 92 | ret += sep; 93 | 94 | auto v = apiVersion(props.apiVersion); 95 | ret += "Api version: "; 96 | ret += std::to_string(v[0]); 97 | ret += "."; 98 | ret += std::to_string(v[1]); 99 | ret += "."; 100 | ret += std::to_string(v[2]); 101 | ret += " (" + std::to_string(props.apiVersion) + ")"; 102 | ret += sep; 103 | 104 | ret += "Device ID: "; 105 | ret += std::to_string(props.deviceID); 106 | ret += sep; 107 | 108 | ret += "Vendor ID: "; 109 | ret += std::to_string(props.vendorID); 110 | ret += sep; 111 | 112 | ret += "Driver version: "; 113 | ret += std::to_string(props.driverVersion); 114 | 115 | return ret; 116 | } 117 | 118 | int findQueueFamily(vk::PhysicalDevice phdev, vk::QueueFlags flags, 119 | OptimizeQueueFamily optimize) { 120 | auto queueProps = vk::getPhysicalDeviceQueueFamilyProperties(phdev); 121 | 122 | auto highestCount = 0u; 123 | auto minGranSum = 0u; 124 | int best = -1; 125 | 126 | // iterator though all families, check if flags are supported 127 | // if so, check if better than current optimum value 128 | for(auto i = 0u; i < queueProps.size(); ++i) { 129 | if((queueProps[i].queueFlags & flags) != flags) continue; 130 | if(optimize == OptimizeQueueFamily::none) return i; 131 | 132 | if(optimize == OptimizeQueueFamily::highestCount) { 133 | auto count = queueProps[i].queueCount; 134 | if(count > highestCount) { 135 | highestCount = count; 136 | best = i; 137 | } 138 | } else if(optimize == OptimizeQueueFamily::minImageGranularity) { 139 | auto gran = queueProps[i].minImageTransferGranularity; 140 | auto granSum = gran.width + gran.height + gran.depth; 141 | if(granSum != 0 && (granSum < minGranSum || minGranSum == 0)) { 142 | minGranSum = granSum; 143 | best = i; 144 | } 145 | } 146 | } 147 | 148 | return best; 149 | } 150 | 151 | int findQueueFamily(vk::PhysicalDevice phdev, 152 | vk::SurfaceKHR surface, vk::QueueFlags flags, 153 | OptimizeQueueFamily optimize) { 154 | if(!vk::dispatch.vkGetPhysicalDeviceSurfaceSupportKHR) { 155 | return -1; 156 | } 157 | 158 | auto queueProps = vk::getPhysicalDeviceQueueFamilyProperties(phdev); 159 | 160 | auto highestCount = 0u; 161 | auto minGranSum = 0u; 162 | int best = -1; 163 | 164 | // iterate though all families, check if flags and surface are supported 165 | // if so, check if better than current optimum value 166 | for(auto i = 0u; i < queueProps.size(); ++i) { 167 | if((queueProps[i].queueFlags & flags) != flags) { 168 | continue; 169 | } 170 | 171 | if(!vk::getPhysicalDeviceSurfaceSupportKHR(phdev, i, surface)) { 172 | continue; 173 | } 174 | 175 | if(optimize == OptimizeQueueFamily::none) { 176 | return i; 177 | } 178 | 179 | if(optimize == OptimizeQueueFamily::highestCount) { 180 | auto count = queueProps[i].queueCount; 181 | if(count > highestCount) { 182 | highestCount = count; 183 | best = i; 184 | } 185 | } else if(optimize == OptimizeQueueFamily::minImageGranularity) { 186 | auto gran = queueProps[i].minImageTransferGranularity; 187 | auto granSum = gran.width + gran.height + gran.depth; 188 | if(granSum != 0 && (granSum < minGranSum || minGranSum == 0)) { 189 | minGranSum = granSum; 190 | best = i; 191 | } 192 | } 193 | } 194 | 195 | return best; 196 | } 197 | 198 | std::vector supportedQueueFamilies(vk::SurfaceKHR surf, 199 | vk::PhysicalDevice phdev) { 200 | std::uint32_t count; 201 | vk::getPhysicalDeviceQueueFamilyProperties(phdev, count, nullptr); 202 | std::vector ret; 203 | for(auto i = 0u; i < count; ++i) { 204 | if(vk::getPhysicalDeviceSurfaceSupportKHR(phdev, i, surf)) { 205 | ret.push_back(i); 206 | } 207 | } 208 | 209 | return ret; 210 | } 211 | 212 | } // namespace vpp 213 | -------------------------------------------------------------------------------- /src/vpp/debugReport.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Jan Kelling 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace vpp { 10 | namespace { 11 | 12 | VkBool32 VKAPI_PTR defaultMessageCallback(VkDebugReportFlagsEXT flags, 13 | VkDebugReportObjectTypeEXT objType, 14 | uint64_t srcObject, 15 | size_t location, 16 | int32_t msgCode, 17 | const char* pLayerPrefix, 18 | const char* pMsg, 19 | void* pUserData) { 20 | 21 | if(!pUserData) { 22 | dlg_error("DebugCallback called with nullptr user data"); 23 | return true; 24 | } 25 | 26 | auto callback = static_cast(pUserData); 27 | auto vflags = static_cast(flags); 28 | auto vobjType = static_cast(objType); 29 | return callback->call({vflags, vobjType, srcObject, location, msgCode, 30 | pLayerPrefix, pMsg}); 31 | } 32 | 33 | std::string to_string(vk::DebugReportFlagsEXT flags) { 34 | static constexpr struct { 35 | vk::DebugReportBitsEXT bit; 36 | const char* name; 37 | } names[] = { 38 | {vk::DebugReportBitsEXT::information, "information"}, 39 | {vk::DebugReportBitsEXT::warning, "warning"}, 40 | {vk::DebugReportBitsEXT::performanceWarning, "performanceWarning"}, 41 | {vk::DebugReportBitsEXT::error, "error"}, 42 | {vk::DebugReportBitsEXT::debug, "debug"}, 43 | }; 44 | 45 | std::string ret; 46 | unsigned int count = 0u; 47 | for(auto name : names) { 48 | if(flags & name.bit) { 49 | ++count; 50 | ret += name.name; 51 | ret += ", "; 52 | } 53 | } 54 | 55 | if(ret.empty()) { 56 | ret = ""; 57 | } else if(ret.size() > 3) { 58 | ret = ret.substr(0, ret.size() - 2); 59 | } 60 | 61 | if(count > 1) { 62 | ret = "{" + ret + "}"; 63 | } 64 | 65 | return ret; 66 | } 67 | 68 | std::string to_string(vk::DebugReportObjectTypeEXT type) { 69 | switch(type) { 70 | case vk::DebugReportObjectTypeEXT::unknown: return "unknown"; 71 | case vk::DebugReportObjectTypeEXT::instance: return "instance"; 72 | case vk::DebugReportObjectTypeEXT::physicalDevice: return "physicalDevice"; 73 | case vk::DebugReportObjectTypeEXT::device: return "device"; 74 | case vk::DebugReportObjectTypeEXT::queue: return "queue"; 75 | case vk::DebugReportObjectTypeEXT::semaphore: return "semaphore"; 76 | case vk::DebugReportObjectTypeEXT::commandBuffer: return "commandBuffer"; 77 | case vk::DebugReportObjectTypeEXT::fence: return "fence"; 78 | case vk::DebugReportObjectTypeEXT::deviceMemory: return "deviceMemory"; 79 | case vk::DebugReportObjectTypeEXT::buffer: return "buffer"; 80 | case vk::DebugReportObjectTypeEXT::image: return "image"; 81 | case vk::DebugReportObjectTypeEXT::event: return "event"; 82 | case vk::DebugReportObjectTypeEXT::queryPool: return "queryPool"; 83 | case vk::DebugReportObjectTypeEXT::bufferView: return "bufferView"; 84 | case vk::DebugReportObjectTypeEXT::imageView: return "imageView"; 85 | case vk::DebugReportObjectTypeEXT::shaderModule: return "shaderModule"; 86 | case vk::DebugReportObjectTypeEXT::pipelineCache: return "pipelineCache"; 87 | case vk::DebugReportObjectTypeEXT::pipelineLayout: return "pipelineLayout"; 88 | case vk::DebugReportObjectTypeEXT::renderPass: return "renderPass"; 89 | case vk::DebugReportObjectTypeEXT::pipeline: return "pipeline"; 90 | case vk::DebugReportObjectTypeEXT::descriptorSetLayout: return "descriptorSetLayout"; 91 | case vk::DebugReportObjectTypeEXT::sampler: return "sampler"; 92 | case vk::DebugReportObjectTypeEXT::descriptorPool: return "descriptorPool"; 93 | case vk::DebugReportObjectTypeEXT::descriptorSet: return "descriptorSet"; 94 | case vk::DebugReportObjectTypeEXT::framebuffer: return "framebuffer"; 95 | case vk::DebugReportObjectTypeEXT::commandPool: return "commandPool"; 96 | case vk::DebugReportObjectTypeEXT::surfaceKHR: return "surfaceKHR"; 97 | case vk::DebugReportObjectTypeEXT::swapchainKHR: return "swapchainKHR"; 98 | default: return ""; 99 | } 100 | } 101 | 102 | } // anonymous util namespace 103 | 104 | // DebugCallback 105 | vk::DebugReportFlagsEXT DebugCallback::defaultFlags() { 106 | return vk::DebugReportBitsEXT::warning | 107 | vk::DebugReportBitsEXT::error | 108 | vk::DebugReportBitsEXT::performanceWarning; 109 | } 110 | 111 | vk::DebugReportFlagsEXT DebugCallback::defaultErrorFlags() { 112 | return vk::DebugReportBitsEXT::error; 113 | } 114 | vk::DebugReportFlagsEXT DebugCallback::allBits() { 115 | return vk::DebugReportBitsEXT::warning | 116 | vk::DebugReportBitsEXT::error | 117 | vk::DebugReportBitsEXT::performanceWarning | 118 | vk::DebugReportBitsEXT::debug | 119 | vk::DebugReportBitsEXT::information; 120 | } 121 | 122 | DebugCallback::DebugCallback(vk::Instance instance, vk::DebugReportFlagsEXT flags, 123 | bool verbose, vk::DebugReportFlagsEXT error) : 124 | instance_(instance), errorFlags_(error), verbose_(verbose) { 125 | 126 | if(!vk::dispatch.vkCreateDebugReportCallbackEXT) { 127 | auto msg = "vpp::DebugCallback: VK_EXT_debug_report not enabled"; 128 | throw std::runtime_error(msg); 129 | } 130 | 131 | vk::DebugReportCallbackCreateInfoEXT createInfo(flags, &defaultMessageCallback, this); 132 | debugCallback_ = vk::createDebugReportCallbackEXT(vkInstance(), createInfo); 133 | } 134 | 135 | DebugCallback::~DebugCallback() { 136 | if(vkCallback()) { 137 | vk::destroyDebugReportCallbackEXT(vkInstance(), vkCallback()); 138 | } 139 | } 140 | 141 | bool DebugCallback::call(const CallbackInfo& info) noexcept { 142 | std::string verbose; 143 | if(verbose_) { 144 | verbose = "\n\tflags: " + to_string(info.flags) + "\n\t"; 145 | verbose += "objType: " + to_string(info.objectType) + "\n\t"; 146 | verbose += "srcObject: " + std::to_string(info.srcObject) + "\n\t"; 147 | verbose += "location: " + std::to_string(info.location) + "\n\t"; 148 | verbose += "code: " + std::to_string(info.messageCode) + "\n\t"; 149 | verbose += "layer: "; 150 | verbose += info.layer; 151 | } 152 | 153 | dlg_tags("DebugCallback"); 154 | if(info.flags & vk::DebugReportBitsEXT::error) { 155 | dlg_error("{}{}", info.message, verbose); 156 | } else if(info.flags & vk::DebugReportBitsEXT::warning) { 157 | dlg_warn("{}{}", info.message, verbose); 158 | } else if(info.flags & vk::DebugReportBitsEXT::information) { 159 | dlg_info("{}{}", info.message, verbose); 160 | } else if(info.flags & vk::DebugReportBitsEXT::performanceWarning) { 161 | dlg_info("{}{}", info.message, verbose); 162 | } else if(info.flags & vk::DebugReportBitsEXT::debug) { 163 | dlg_debug("{}{}", info.message, verbose); 164 | } 165 | 166 | return (info.flags & errorFlags_); 167 | } 168 | 169 | } // namespace vpp 170 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | vpp 2 | === 3 | 4 | A vulkan abstraction and utility library written in modern C++. 5 | 6 | Does not aim to be a framework or full-sized graphics engine, but rather focuses on 7 | providing some useful features that make programming vulkan applications in 8 | C++ more convenient and less repetitive while not introducing unreasonable overhead. 9 | It does not force you to use shared pointers for all your resources, 10 | automagically synchronizes pools or implicitly submits to the device. 11 | Think of vpp more like an independently usable set of utilities. 12 | 13 | __Things vpp provides:__ 14 | 15 | - C++ vulkan api with all its advantages 16 | - while still keeping the compile time and LOC amount sane 17 | - RAII classes for all handles 18 | - you only pay for what you use: no shared_ptr lock-in, no virtual abstraction 19 | - memory management utilities 20 | - automatic yet efficient memory allocation 21 | - you can still allocate/manage your own memory if you need to 22 | - e.g. vpp currently does not provide any sparse memory utility 23 | - idioms for efficient vulkan use 24 | - Deferred initialization: allows to allocate as few resources as possible 25 | and share them 26 | - SharedBuffer and BufferAllocator 27 | - sharing DeviceMemory, DeviceMemoryAllocator (grouping allocations) 28 | - batching command buffer submissions via QueueSubmitter 29 | - some utility for retrieving/filling buffers and images 30 | - query valid image usage combinations and format support 31 | - default image/imageView creation initializers 32 | - various initialization helpers (querying properties) 33 | - Swapchain 34 | - Image and ImageView default createInfos 35 | - physical device querying 36 | - vpp::Renderer: implementing swapchain renderbuffer handling and 37 | submission/presentation synchronization 38 | 39 | __Things vpp does not provide:__ 40 | 41 | - full renderer/graphical implementations 42 | - vpp will not provide something like a forward/deferred renderer 43 | imlpementation, specific render techniques or shaders 44 | - no non-vulkan concepts like camera, transform, matrix or vector 45 | - high-cost or non-generic abstractions like Texture/Model 46 | - a Window abstraction (just use sdl/glfw or whatever you want) 47 | - vpp does not implement any platform-specific bits 48 | - examples for learning vulkan 49 | 50 | You can build vpp using meson. Linux (android as well) and Windows are 51 | supported and tested. Needs a solid C++17 compiler, msvc does 52 | currently not implement enough of the standard correctly. 53 | MinGW-w64 works on windows. 54 | 55 | Any contributions (feature requests, issues and ideas as well) are appreciated. 56 | 57 | # Examples 58 | 59 | __[This example](docs/examples/intro_ny.cpp)__ implements the hello-world-triangle 60 | using a cross-platform window abstraction and vpp. 61 | Below a couple of idiomatic usages of the rather high-level interfaces. 62 | You could also check out the [tests](docs/tests) to see basic usage 63 | of those classes with expected results. 64 | For more information on any of them, just check out the usually well-documented 65 | [header files](include/vpp). 66 | 67 | #### Initialization 68 | 69 | ```cpp 70 | // Create instance and debugCallback 71 | vk::InstanceCreateInfo iniInfo = { ... }; 72 | vpp::Instance instance(iniInfo); 73 | vpp::DebugMessenger debugMessenger(instance); // debug_utils extension must be enabled 74 | 75 | auto surface = /* use your favorite window abstraction to create a surface */ 76 | 77 | // Let vpp choose a default physical device, present, graphics and compute queue 78 | // Note that while this usually works and is convenient at the beginning, 79 | // you can still just choose your own physical device or pass 80 | // your own DeviceCreateInfo with chosen queue families. 81 | const vpp::Queue* presentQueue; 82 | vpp::Device device(instance, surface, present); 83 | ``` 84 | 85 | #### Submission batches 86 | 87 | ```cpp 88 | // vpp::QueueSubmitter allows to bundle multiple queue submissions 89 | // and then track the submission state. 90 | vpp::QueueSubmitter submitter(queue); 91 | auto id = submitter.add({submitInfo1, submitInfo2, submitInfo3}); 92 | submitter.submit(id); 93 | submitter.wait(id); 94 | ``` 95 | 96 | #### Deferred initialization 97 | 98 | ```cpp 99 | // Create initializers for a couple of memory resources. 100 | // They will already reserve as much memory as they need. 101 | // The api can also be used manually (via vpp::Buffer/Image constructors 102 | // and the init method), without the vpp::Init helper 103 | vpp::Init initBuf1(device, bufferCreateInfo1); 104 | vpp::Init initBuf2(device, bufferCreateInfo2); 105 | vpp::Init iniImg1(device, imgCreateInfo1); 106 | 107 | // Finish deferred initialization for all resources 108 | // If posssible (due to vulkan MemoryRequirements) will allocate all resources 109 | // on a single device memory (respecting alignments etc) 110 | buffer1 = initBuf1.init(); 111 | buffer2 = initBuf2.init(); 112 | image1 = initImg1.init(); 113 | ``` 114 | 115 | #### SharedBuffer and BufferAllocator 116 | 117 | ```cpp 118 | // Create a bufferAllocator (we could also use the device's default allocator) 119 | // and allocate a bunch of mappable uniform buffer objects. 120 | // Since we reserve the needed storage, only one buffer will be created. 121 | // Alternatively, we could have directly created one vpp::SharedBuffer. 122 | vpp::BufferAllocator bufAlloc(device); 123 | 124 | auto hostBits = device.hostMemoryTypes(); // memory types that are hostVisible 125 | auto usage = vk::BufferUsageBits::uniformBuffer; 126 | auto uboSize = 64u; 127 | 128 | bufAlloc.reserve(1024, usage, hostBits); // reserve enough for all ubos 129 | 130 | vpp::SubBuffer ubo1(bufAlloc, 64u, usage, hostBits); 131 | vpp::SubBuffer ubo2(bufAlloc, 64u, usage, hostBits); 132 | vpp::SubBuffer ubo3(bufAlloc, 64u, usage, hostBits); 133 | ``` 134 | 135 | #### Image filling/retrieving 136 | 137 | ```cpp 138 | // Some helpers to fill or read images. 139 | auto subres = vk::ImageSubresource{vk::AspectBits::color}; 140 | vpp::fillMap(commandBuffer, img1, img1Size, dataToWrite, subres); 141 | 142 | // Creates a stage buffer and copies from/to it via the given 143 | // commandBuffer. The stage buffer must be kept alive until the 144 | // command buffer has finished. 145 | auto stageBuffer = vpp::fillStaging(commandBuffer, img2, 146 | img2Format, img2Layout, img2Size, dataToWrite, subres); 147 | auto stageBuffer = vpp::retrieveStaging(commandBuffer, img3, 148 | img3Format, img3Layout, img3Size, subres); 149 | ``` 150 | 151 | #### Testing 152 | 153 | To run all tests with valgrind leak checks, execute (in build dir): 154 | 155 | ``` 156 | meson test \ 157 | --print-errorlogs \ 158 | --wrapper 'valgrind 159 | --leak-check=full 160 | --error-exitcode=1 161 | --suppressions=../docs/valgrind.supp' 162 | ``` 163 | --------------------------------------------------------------------------------