├── docs ├── requirements.txt ├── Makefile ├── make.bat ├── topics │ ├── context.rst │ ├── allocators.rst │ ├── rendergraph.rst │ └── commandbuffer.rst ├── _static │ └── breathe.css ├── CMakeLists.txt ├── index.rst └── conf.py ├── .gitignore ├── vuk_logo.png ├── examples ├── doge.png ├── stbi.cpp ├── suzy.glb ├── ubo_test_tex.vert.spv ├── the_sky_is_on_fire_1k.hdr ├── triangle_depthshaded_tex.frag.spv ├── ubo.glsl ├── triangle.frag ├── rtt.frag ├── fullscreen.frag ├── invert.comp ├── ubo_test.vert ├── triangle_depthshaded.frag ├── fullscreen.vert ├── triangle.vert ├── triangle_depthshaded_tex.frag ├── triangle_tex_bindless.frag ├── cubemap.vert ├── stupidsort.comp ├── triangle_tinted_tex.frag ├── scrambled_draw.frag ├── ubo_test_tex.vert ├── baby_renderer.vert ├── equirectangular_to_cubemap.frag ├── bindless.vert ├── deferred.frag ├── deferred.vert ├── glfw.hpp ├── rt.rmiss ├── deferred_resolve.frag ├── deferred_reflective.frag ├── rt.rchit ├── blelloch_add.comp ├── imgui_frag.hpp ├── rt.rgen ├── fxaa.frag ├── blelloch_scan.comp ├── CMakeLists.txt ├── imgui_vert.hpp ├── 01_triangle.cpp ├── example_runner_single.cpp ├── utils.hpp └── 04_texture.cpp ├── include └── vuk │ ├── Util.hpp │ ├── VulkanPFNOptional.hpp │ ├── Query.hpp │ ├── RenderGraphReflection.hpp │ ├── SampledImage.hpp │ ├── RelSpan.hpp │ ├── Config.hpp │ ├── MapProxy.hpp │ ├── Name.hpp │ ├── Hash.hpp │ ├── vuk_fwd.hpp │ ├── SourceLocation.hpp │ ├── Bitset.hpp │ ├── Swapchain.hpp │ ├── VulkanPFNRequired.hpp │ ├── ShortAlloc.hpp │ ├── ImageAttachment.hpp │ ├── Exception.hpp │ ├── Buffer.hpp │ ├── Program.hpp │ ├── resources │ ├── DeviceLinearResource.hpp │ ├── DeviceNestedResource.hpp │ └── DeviceVkResource.hpp │ └── ShaderSource.hpp ├── src ├── CreateInfo.hpp ├── ToIntegral.hpp ├── tests │ ├── Test.cpp │ ├── rg_errors.cpp │ ├── buffer_ops.cpp │ └── frame_allocator.cpp ├── ShadercIncluder.hpp ├── BufferAllocator.hpp ├── Descriptor.cpp ├── ContextImpl.hpp ├── Name.cpp └── RenderPass.hpp ├── benchmarks ├── blit.frag ├── fullscreen.vert ├── dependent_texture_fetch.frag ├── dependent_texture_fetch_explicit_lod.frag ├── nondependent_texture_fetch_explicit_lod.frag ├── nondependent_texture_fetch.frag ├── CMakeLists.txt ├── draw_overhead.cpp └── bench_runner.hpp ├── imgui.frag ├── cmake └── FindSphinx.cmake ├── imgui.vert ├── LICENSE ├── .gitmodules ├── .clang-format ├── README.md └── .github └── workflows └── cmake.yml /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | breathe -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .vs 3 | .spv -------------------------------------------------------------------------------- /vuk_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martty/vuk/HEAD/vuk_logo.png -------------------------------------------------------------------------------- /examples/doge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martty/vuk/HEAD/examples/doge.png -------------------------------------------------------------------------------- /examples/stbi.cpp: -------------------------------------------------------------------------------- 1 | #define STB_IMAGE_IMPLEMENTATION 2 | #include "stb_image.h" 3 | -------------------------------------------------------------------------------- /examples/suzy.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martty/vuk/HEAD/examples/suzy.glb -------------------------------------------------------------------------------- /examples/ubo_test_tex.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martty/vuk/HEAD/examples/ubo_test_tex.vert.spv -------------------------------------------------------------------------------- /examples/the_sky_is_on_fire_1k.hdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martty/vuk/HEAD/examples/the_sky_is_on_fire_1k.hdr -------------------------------------------------------------------------------- /examples/triangle_depthshaded_tex.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martty/vuk/HEAD/examples/triangle_depthshaded_tex.frag.spv -------------------------------------------------------------------------------- /examples/ubo.glsl: -------------------------------------------------------------------------------- 1 | layout(binding = 0) uniform VP { 2 | mat4 view; 3 | mat4 projection; 4 | }; 5 | 6 | layout(binding = 1) uniform Model { 7 | mat4 model; 8 | }; -------------------------------------------------------------------------------- /include/vuk/Util.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "vuk/Image.hpp" 6 | 7 | namespace vuk { 8 | std::string_view image_view_type_to_sv(ImageViewType) noexcept; 9 | } -------------------------------------------------------------------------------- /src/CreateInfo.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace vuk { 4 | template 5 | struct create_info; 6 | 7 | template 8 | using create_info_t = typename create_info::type; 9 | } // namespace vuk 10 | -------------------------------------------------------------------------------- /src/ToIntegral.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | template 6 | inline constexpr auto to_integral(E e) -> typename std::underlying_type::type { 7 | return static_cast::type>(e); 8 | } -------------------------------------------------------------------------------- /examples/triangle.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(fragment) 3 | 4 | layout (location = 0) in vec3 inColor; 5 | 6 | layout (location = 0) out vec4 outFragColor; 7 | 8 | void main() 9 | { 10 | outFragColor = vec4(inColor, 1.0); 11 | } -------------------------------------------------------------------------------- /benchmarks/blit.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(fragment) 3 | 4 | layout (location = 0) in vec2 uv; 5 | layout (location = 0) out vec4 FragmentColor; 6 | layout(binding = 0) uniform sampler2D image; 7 | 8 | void main() { 9 | FragmentColor = textureLod(image, uv, 0); 10 | } -------------------------------------------------------------------------------- /examples/rtt.frag: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #pragma shader_stage(fragment) 3 | 4 | layout(location = 0) out vec4 fColor; 5 | 6 | layout(set=0, binding=0) uniform sampler2D sTexture; 7 | 8 | layout(location = 0) in vec2 UV; 9 | 10 | void main() 11 | { 12 | fColor = vec4(texture(sTexture, UV.st).rgb, 1); 13 | } 14 | -------------------------------------------------------------------------------- /examples/fullscreen.frag: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #pragma shader_stage(fragment) 3 | 4 | layout(location = 0) out vec4 fColor; 5 | 6 | layout(set=0, binding=0) uniform sampler2D sTexture; 7 | 8 | layout(location = 0) in vec2 UV; 9 | 10 | void main() 11 | { 12 | fColor = vec4(texture(sTexture, UV.st).xxx, 1); 13 | } 14 | -------------------------------------------------------------------------------- /src/tests/Test.cpp: -------------------------------------------------------------------------------- 1 | #ifdef VUK_BUILD_TESTS 2 | #define DOCTEST_CONFIG_IMPLEMENT 3 | #endif 4 | #include 5 | 6 | #ifdef VUK_TEST_RUNNER 7 | #include "TestContext.hpp" 8 | namespace vuk { 9 | TestContext test_context; 10 | } 11 | int main(int argc, char** argv) { 12 | return doctest::Context(argc, argv).run(); 13 | } 14 | #endif -------------------------------------------------------------------------------- /imgui.frag: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #pragma shader_stage(fragment) 3 | 4 | layout(location = 0) out vec4 fColor; 5 | 6 | layout(set=0, binding=0) uniform sampler2D sTexture; 7 | 8 | layout(location = 0) in struct { 9 | vec4 Color; 10 | vec2 UV; 11 | } In; 12 | 13 | void main() 14 | { 15 | fColor = In.Color * texture(sTexture, In.UV.st); 16 | } 17 | -------------------------------------------------------------------------------- /examples/invert.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(compute) 3 | 4 | layout (binding = 0) uniform sampler2D tex_in; 5 | layout (binding = 1, rgba8) uniform image2D tex_out; 6 | 7 | layout (local_size_x = 8, local_size_y = 8) in; 8 | 9 | void main() { 10 | vec4 val = texelFetch(tex_in, ivec2(gl_GlobalInvocationID.xy), 0); 11 | imageStore(tex_out, ivec2(gl_GlobalInvocationID.xy), 1.0 - val); 12 | } -------------------------------------------------------------------------------- /cmake/FindSphinx.cmake: -------------------------------------------------------------------------------- 1 | #Look for an executable called sphinx-build 2 | find_program(SPHINX_EXECUTABLE 3 | NAMES sphinx-build 4 | DOC "Path to sphinx-build executable") 5 | 6 | include(FindPackageHandleStandardArgs) 7 | 8 | #Handle standard arguments to find_package like REQUIRED and QUIET 9 | find_package_handle_standard_args(Sphinx 10 | "Failed to find sphinx-build executable" 11 | SPHINX_EXECUTABLE) -------------------------------------------------------------------------------- /examples/ubo_test.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(vertex) 3 | #include "ubo.glsl" 4 | 5 | layout(location = 0) in vec3 ipos; 6 | 7 | out gl_PerVertex 8 | { 9 | vec4 gl_Position; 10 | }; 11 | 12 | layout (location = 0) out vec3 color; 13 | 14 | #ifdef SCALE 15 | const float scale = SCALE; 16 | #else 17 | const float scale = 1.0; 18 | #endif 19 | 20 | void main() { 21 | color = vec3(1,1,1); 22 | gl_Position = projection * view * model * vec4(scale * ipos, 1.0); 23 | } 24 | -------------------------------------------------------------------------------- /examples/triangle_depthshaded.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(fragment) 3 | 4 | layout (location = 0) in vec3 inColor; 5 | 6 | layout (constant_id = 0) const float col_r = 1.0; 7 | layout (constant_id = 1) const float col_g = 1.0; 8 | layout (constant_id = 2) const float col_b = 1.0; 9 | 10 | layout (location = 0) out vec4 outFragColor; 11 | 12 | void main() { 13 | vec3 tint = vec3(col_r, col_g, col_b); 14 | outFragColor = vec4(tint * inColor * sqrt(1 - gl_FragCoord.z), 1.0); 15 | } -------------------------------------------------------------------------------- /examples/fullscreen.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(vertex) 3 | 4 | out gl_PerVertex 5 | { 6 | vec4 gl_Position; 7 | }; 8 | 9 | layout (location = 0) out vec2 uv; 10 | 11 | void main() { 12 | if(gl_VertexIndex == 0){ 13 | gl_Position = vec4(-1, -1, 0.0, 1.0); 14 | uv = vec2(0, 0); 15 | } else if (gl_VertexIndex == 1){ 16 | gl_Position = vec4(-1, 3, 0.0, 1.0); 17 | uv = vec2(0, 2); 18 | } else { 19 | gl_Position = vec4(3, -1, 0.0, 1.0); 20 | uv = vec2(2, 0); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /benchmarks/fullscreen.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(vertex) 3 | 4 | out gl_PerVertex 5 | { 6 | vec4 gl_Position; 7 | }; 8 | 9 | layout (location = 0) out vec2 uv; 10 | 11 | void main() { 12 | if(gl_VertexIndex % 3 == 0){ 13 | gl_Position = vec4(-1, -1, 0.0, 1.0); 14 | uv = vec2(0, 0); 15 | } else if (gl_VertexIndex % 3 == 1){ 16 | gl_Position = vec4(-1, 3, 0.0, 1.0); 17 | uv = vec2(0, 2); 18 | } else { 19 | gl_Position = vec4(3, -1, 0.0, 1.0); 20 | uv = vec2(2, 0); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/triangle.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(vertex) 3 | 4 | out gl_PerVertex 5 | { 6 | vec4 gl_Position; 7 | }; 8 | 9 | layout (location = 0) out vec3 color; 10 | 11 | void main() { 12 | if(gl_VertexIndex == 0){ 13 | gl_Position = vec4(0.0, -0.3, 0.0, 1.0); 14 | color = vec3(0, 1, 0); 15 | } else if (gl_VertexIndex == 1){ 16 | gl_Position = vec4(-0.3, 0.3, 0.0, 1.0); 17 | color = vec3(1, 0, 0); 18 | } else { 19 | gl_Position = vec4(0.3, 0.3, 0.0, 1.0); 20 | color = vec3(0, 0, 1); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/triangle_depthshaded_tex.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(fragment) 3 | 4 | layout (location = 0) in vec3 inColor; 5 | layout (location = 1) in vec2 inUV; 6 | 7 | layout (location = 0) out vec4 outFragColor; 8 | 9 | layout(binding = 2) uniform sampler2D tex; 10 | 11 | void main() 12 | { 13 | vec3 col = texture(tex, vec2(inUV.x, inUV.y)).xyz; 14 | if(any(greaterThan(col, vec3(0.8)))) discard; 15 | outFragColor = vec4(inColor * pow(1 - gl_FragCoord.z, 1/3) * texture(tex, vec2(inUV.x, inUV.y)).xyz, 1.0); 16 | } -------------------------------------------------------------------------------- /examples/triangle_tex_bindless.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(fragment) 3 | #extension GL_EXT_nonuniform_qualifier : require 4 | 5 | layout (location = 1) in vec2 inUV; 6 | layout (location = 0) flat in uint base_instance; 7 | 8 | layout (location = 0) out vec4 outFragColor; 9 | 10 | layout(set = 1, binding = 0) uniform sampler2D[] tex; 11 | 12 | void main() 13 | { 14 | vec3 col = texture(tex[base_instance], vec2(inUV.x, inUV.y)).xyz; 15 | if(any(greaterThan(col, vec3(0.8)))) discard; 16 | outFragColor = vec4(col, 1.0); 17 | } -------------------------------------------------------------------------------- /examples/cubemap.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(vertex) 3 | 4 | #extension GL_ARB_shader_viewport_layer_array : require 5 | 6 | layout (location = 0) in vec3 in_pos; 7 | 8 | layout (location = 0) out vec3 out_pos; 9 | 10 | layout (binding = 0) uniform Projection { 11 | mat4 projection; 12 | }; 13 | 14 | layout (binding = 1) uniform View { 15 | mat4 view[6]; 16 | }; 17 | 18 | void main() 19 | { 20 | gl_Layer = gl_InstanceIndex; 21 | out_pos = in_pos; 22 | gl_Position = projection * view[gl_InstanceIndex] * vec4(out_pos, 1.0); 23 | } -------------------------------------------------------------------------------- /examples/stupidsort.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(compute) 3 | 4 | layout (std430, binding = 0) buffer coherent BufferIn { 5 | uint[] data_in; 6 | }; 7 | 8 | layout (constant_id = 0) const uint CHANGE_COUNT = 1; 9 | 10 | layout (local_size_x = 1) in; 11 | 12 | void main() { 13 | int n_changes = 0; 14 | for(int i = 0; i < data_in.length(); i++){ 15 | if(data_in[i - 1] > data_in[i]){ 16 | uint tmp = data_in[i]; 17 | data_in[i] = data_in[i-1]; 18 | data_in[i-1] = tmp; 19 | if(n_changes++ > CHANGE_COUNT) 20 | return; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /examples/triangle_tinted_tex.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(fragment) 3 | 4 | layout (location = 0) in vec3 inColor; 5 | layout (location = 1) in vec2 inUV; 6 | 7 | layout (location = 0) out vec4 outFragColor; 8 | 9 | layout(binding = 2) uniform sampler2D tex; 10 | layout(binding = 3) uniform Material { 11 | vec4 tint; 12 | }; 13 | 14 | void main() 15 | { 16 | vec3 col = texture(tex, vec2(inUV.x, inUV.y)).xyz; 17 | if(any(greaterThan(col, vec3(0.8)))) discard; 18 | vec3 tinted = col + (tint.xyz - col) * tint.a; 19 | outFragColor = vec4(inColor * tinted, 1.0); 20 | } -------------------------------------------------------------------------------- /imgui.vert: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #pragma shader_stage(vertex) 3 | layout(location = 0) in vec2 aPos; 4 | layout(location = 1) in vec2 aUV; 5 | layout(location = 2) in vec4 aColor; 6 | 7 | layout(push_constant) uniform uPushConstant { 8 | vec2 uScale; 9 | vec2 uTranslate; 10 | } pc; 11 | 12 | out gl_PerVertex { 13 | vec4 gl_Position; 14 | }; 15 | 16 | layout(location = 0) out struct { 17 | vec4 Color; 18 | vec2 UV; 19 | } Out; 20 | 21 | void main() 22 | { 23 | Out.Color = aColor; 24 | Out.UV = aUV; 25 | gl_Position = vec4(aPos * pc.uScale + pc.uTranslate, 0, 1); 26 | } 27 | -------------------------------------------------------------------------------- /examples/scrambled_draw.frag: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #pragma shader_stage(fragment) 3 | 4 | layout(location = 0) out vec4 fColor; 5 | 6 | layout(binding = 0) uniform sampler2D sTexture; 7 | layout(std430, binding = 1) buffer readonly scramble { 8 | uint indices[]; 9 | }; 10 | 11 | layout(location = 0) in vec2 UV; 12 | 13 | void main() { 14 | uvec2 size = textureSize(sTexture, 0); 15 | uvec2 coords = uvec2(size * UV.st); 16 | uint remap = indices[coords.x + coords.y * size.x]; 17 | ivec2 newcoords = ivec2(remap % size.x, remap / size.y); 18 | fColor = vec4(texelFetch(sTexture, newcoords, 0).rgb, 1); 19 | } 20 | -------------------------------------------------------------------------------- /examples/ubo_test_tex.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(vertex) 3 | 4 | layout(location = 0) in vec3 ipos; 5 | layout(location = 1) in vec2 iuv; 6 | 7 | layout(binding = 0) uniform VP { 8 | mat4 view; 9 | mat4 projection; 10 | }; 11 | 12 | layout(binding = 1) uniform Model { 13 | mat4 model; 14 | }; 15 | 16 | out gl_PerVertex 17 | { 18 | vec4 gl_Position; 19 | }; 20 | 21 | layout (location = 0) out vec3 color; 22 | layout (location = 1) out vec2 oUV; 23 | 24 | void main() { 25 | color = vec3(1,1,1); 26 | oUV = iuv; 27 | gl_Position = projection * view * model * vec4(ipos, 1.0); 28 | } 29 | -------------------------------------------------------------------------------- /examples/baby_renderer.vert: -------------------------------------------------------------------------------- 1 | #version 460 2 | #pragma shader_stage(vertex) 3 | 4 | layout(location = 0) in vec3 ipos; 5 | layout(location = 1) in vec2 iuv; 6 | 7 | layout(binding = 0) uniform VP { 8 | mat4 view; 9 | mat4 projection; 10 | }; 11 | 12 | layout(binding = 1) buffer readonly Model { 13 | mat4 model[]; 14 | }; 15 | 16 | out gl_PerVertex 17 | { 18 | vec4 gl_Position; 19 | }; 20 | 21 | layout (location = 0) out vec3 color; 22 | layout (location = 1) out vec2 oUV; 23 | 24 | void main() { 25 | color = vec3(1,1,1); 26 | oUV = iuv; 27 | gl_Position = projection * view * model[gl_BaseInstance] * vec4(ipos, 1.0); 28 | } 29 | -------------------------------------------------------------------------------- /examples/equirectangular_to_cubemap.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(fragment) 3 | 4 | layout (location = 0) in vec3 in_pos; 5 | 6 | layout (location = 0) out vec4 out_color; 7 | 8 | layout (binding = 2) uniform sampler2D equirectangular_map; 9 | 10 | const vec2 inv_atan = vec2(0.1591, 0.3183); 11 | 12 | vec2 sample_spherical_map(vec3 v) { 13 | vec2 uv = vec2(atan(v.z, v.x), asin(v.y)); 14 | uv *= inv_atan; 15 | uv += 0.5; 16 | return uv; 17 | } 18 | 19 | void main() { 20 | vec2 uv = sample_spherical_map(normalize(in_pos)); 21 | vec3 color = texture(equirectangular_map, uv).rgb; 22 | 23 | out_color = vec4(color, 1.0); 24 | } -------------------------------------------------------------------------------- /examples/bindless.vert: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | #pragma shader_stage(vertex) 3 | 4 | layout(location = 0) in vec3 ipos; 5 | layout(location = 1) in vec2 iuv; 6 | 7 | layout(binding = 0) uniform VP { 8 | mat4 view; 9 | mat4 projection; 10 | }; 11 | 12 | layout(binding = 1) uniform Model { 13 | mat4 model; 14 | }; 15 | 16 | out gl_PerVertex 17 | { 18 | vec4 gl_Position; 19 | }; 20 | 21 | layout (location = 0) out uint base_instance; 22 | layout (location = 1) out vec2 oUV; 23 | 24 | void main() { 25 | base_instance = gl_BaseInstance; 26 | oUV = iuv; 27 | gl_Position = projection * view * model * vec4(ipos + gl_BaseInstance * vec3(2, 0, 0) - vec3(2, 0, 0), 1.0); 28 | } 29 | -------------------------------------------------------------------------------- /examples/deferred.frag: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #pragma shader_stage(fragment) 3 | 4 | layout (location = 0) out vec4 gPosition; 5 | layout (location = 1) out vec4 gNormal; 6 | layout (location = 2) out vec4 gAlbedoSpec; 7 | 8 | layout (location = 0) in vec3 FragPos; 9 | layout (location = 1) in vec3 Normal; 10 | layout (location = 2) in vec2 TexCoords; 11 | 12 | void main() 13 | { 14 | // store the fragment position vector in the first gbuffer texture 15 | gPosition = vec4(FragPos, 1); 16 | // also store the per-fragment normals into the gbuffer 17 | gNormal = vec4(normalize(Normal), 1); 18 | // and the diffuse per-fragment color 19 | gAlbedoSpec = vec4(1,0,0.5,1); 20 | } -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /examples/deferred.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(vertex) 3 | 4 | layout(location = 0) in vec3 ipos; 5 | layout(location = 1) in vec3 inormal; 6 | layout(location = 2) in vec2 iuv; 7 | 8 | layout(binding = 0) uniform VP { 9 | mat4 view; 10 | mat4 projection; 11 | }; 12 | 13 | layout(binding = 1) uniform Model { 14 | mat4 model; 15 | }; 16 | 17 | out gl_PerVertex 18 | { 19 | vec4 gl_Position; 20 | }; 21 | 22 | layout (location = 0) out vec3 position; 23 | layout (location = 1) out vec3 normal; 24 | layout (location = 2) out vec2 uv; 25 | 26 | void main() { 27 | position = vec3(model * vec4(ipos, 1.0)); 28 | uv = iuv; 29 | normal = vec3(model * vec4(inormal, 0.0)); 30 | gl_Position = projection * view * model * vec4(ipos, 1.0); 31 | } 32 | -------------------------------------------------------------------------------- /include/vuk/VulkanPFNOptional.hpp: -------------------------------------------------------------------------------- 1 | // OPTIONAL 2 | // VK_KHR_swapchain 3 | VUK_X(vkAcquireNextImageKHR) 4 | VUK_X(vkQueuePresentKHR) 5 | VUK_X(vkDestroySwapchainKHR) 6 | 7 | // VK_KHR_debug_utils 8 | VUK_Y(vkSetDebugUtilsObjectNameEXT) 9 | VUK_Y(vkCmdBeginDebugUtilsLabelEXT) 10 | VUK_Y(vkCmdEndDebugUtilsLabelEXT) 11 | 12 | // VK_KHR_ray_tracing 13 | VUK_X(vkCmdBuildAccelerationStructuresKHR) 14 | VUK_X(vkGetAccelerationStructureBuildSizesKHR) 15 | VUK_X(vkCmdTraceRaysKHR) 16 | VUK_X(vkCreateAccelerationStructureKHR) 17 | VUK_X(vkDestroyAccelerationStructureKHR) 18 | VUK_X(vkGetRayTracingShaderGroupHandlesKHR) 19 | VUK_X(vkCreateRayTracingPipelinesKHR) 20 | 21 | // VK_EXT_calibrated_timestamps 22 | VUK_X(vkGetCalibratedTimestampsEXT) 23 | VUK_Y(vkGetPhysicalDeviceCalibrateableTimeDomainsEXT) -------------------------------------------------------------------------------- /benchmarks/dependent_texture_fetch.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(fragment) 3 | 4 | const float offset[5] = float[](0.0, 1.0, 2.0, 3.0, 4.0); 5 | const float weight[5] = float[](0.2270270270, 0.1945945946, 0.1216216216, 6 | 0.0540540541, 0.0162162162); 7 | 8 | layout (location = 0) in vec2 uv; 9 | layout (location = 0) out vec4 FragmentColor; 10 | layout(binding = 0) uniform sampler2D image; 11 | 12 | void main() { 13 | vec2 size = textureSize(image, 0); 14 | 15 | FragmentColor = texture(image, uv) * weight[0]; 16 | for (int i=1; i<5; i++) { 17 | FragmentColor += 18 | texture(image, (uv * size + vec2(0.0, offset[i])) / size) 19 | * weight[i]; 20 | FragmentColor += 21 | texture(image, (uv * size - vec2(0.0, offset[i])) / size) 22 | * weight[i]; 23 | } 24 | } -------------------------------------------------------------------------------- /include/vuk/Query.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace vuk { 6 | /// @brief Handle to a query result 7 | struct Query { 8 | uint64_t id; 9 | 10 | constexpr bool operator==(const Query& other) const noexcept { 11 | return id == other.id; 12 | } 13 | }; 14 | 15 | struct TimestampQueryPool { 16 | static constexpr uint32_t num_queries = 32; 17 | 18 | VkQueryPool pool; 19 | Query queries[num_queries]; 20 | uint8_t count = 0; 21 | }; 22 | 23 | struct TimestampQuery { 24 | VkQueryPool pool; 25 | uint32_t id; 26 | }; 27 | 28 | struct TimestampQueryCreateInfo { 29 | TimestampQueryPool* pool = nullptr; 30 | Query query; 31 | }; 32 | } // namespace vuk 33 | 34 | namespace std { 35 | template<> 36 | struct hash { 37 | size_t operator()(vuk::Query const& s) const { 38 | return hash()(s.id); 39 | } 40 | }; 41 | } // namespace std -------------------------------------------------------------------------------- /include/vuk/RenderGraphReflection.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "vuk/RelSpan.hpp" 4 | #include "vuk/RenderGraph.hpp" 5 | #include 6 | 7 | // struct describing use chains 8 | namespace vuk { 9 | struct ChainAccess { 10 | int32_t pass; 11 | int32_t resource = -1; 12 | }; 13 | 14 | struct ChainLink { 15 | ChainLink* source = nullptr; // in subchains, this denotes the end of the undiverged chain 16 | ChainLink* prev = nullptr; // if this came from a previous undef, we link them together 17 | std::optional def; 18 | RelSpan reads; 19 | Resource::Type type; 20 | std::optional undef; 21 | ChainLink* next = nullptr; // if this links to a def, we link them together 22 | ChainLink* destination = nullptr; // in subchains, this denotes the start of the converged chain 23 | RelSpan child_chains; 24 | }; 25 | 26 | } // namespace vuk -------------------------------------------------------------------------------- /benchmarks/dependent_texture_fetch_explicit_lod.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(fragment) 3 | 4 | const float offset[5] = float[](0.0 / 112, 1.0 / 112, 2.0 / 112, 3.0 / 112, 4.0 / 112); 5 | const float weight[5] = float[](0.2270270270, 0.1945945946, 0.1216216216, 6 | 0.0540540541, 0.0162162162); 7 | 8 | layout (location = 0) in vec2 uv; 9 | layout (location = 0) out vec4 FragmentColor; 10 | layout(binding = 0) uniform sampler2D image; 11 | 12 | layout(push_constant) uniform PC { 13 | float image_size_rcp; 14 | }; 15 | 16 | void main() { 17 | FragmentColor = textureLod(image, uv, 0) * weight[0]; 18 | float offset = 0; 19 | for (int i=1; i<5; i++) { 20 | offset += image_size_rcp; 21 | FragmentColor += textureLod(image, uv + vec2(0.0, offset), 0) * weight[i]; 22 | FragmentColor += textureLod(image, uv - vec2(0.0, offset), 0) * weight[i]; 23 | } 24 | } -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /examples/glfw.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "vuk/Config.hpp" 3 | #include 4 | 5 | inline GLFWwindow* create_window_glfw(const char* title, bool resize = true) { 6 | glfwInit(); 7 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 8 | if (!resize) 9 | glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); 10 | 11 | return glfwCreateWindow(1024, 1024, title, NULL, NULL); 12 | } 13 | 14 | inline void destroy_window_glfw(GLFWwindow* window) { 15 | glfwDestroyWindow(window); 16 | glfwTerminate(); 17 | } 18 | 19 | inline VkSurfaceKHR create_surface_glfw(VkInstance instance, GLFWwindow* window) { 20 | VkSurfaceKHR surface = nullptr; 21 | VkResult err = glfwCreateWindowSurface(instance, window, NULL, &surface); 22 | if (err) { 23 | const char* error_msg; 24 | int ret = glfwGetError(&error_msg); 25 | if (ret != 0) { 26 | throw error_msg; 27 | } 28 | surface = nullptr; 29 | } 30 | return surface; 31 | } 32 | -------------------------------------------------------------------------------- /src/tests/rg_errors.cpp: -------------------------------------------------------------------------------- 1 | #include "TestContext.hpp" 2 | #include "vuk/AllocatorHelpers.hpp" 3 | #include "vuk/Partials.hpp" 4 | #include 5 | 6 | using namespace vuk; 7 | 8 | TEST_CASE("error: unattached resource") { 9 | REQUIRE(test_context.prepare()); 10 | 11 | std::shared_ptr rg = std::make_shared("uatt"); 12 | rg->add_pass({ 13 | .resources = { "nonexistent_image"_image >> vuk::eColorWrite }, 14 | }); 15 | 16 | Compiler compiler; 17 | REQUIRE_THROWS(compiler.compile(std::span{ &rg, 1 }, {})); 18 | } 19 | 20 | TEST_CASE("error: cbuf references unknown resource") { 21 | REQUIRE(test_context.prepare()); 22 | 23 | std::shared_ptr rg = std::make_shared("uatt"); 24 | rg->add_pass({ .execute = [](vuk::CommandBuffer& cbuf) { 25 | cbuf.bind_buffer(0, 0, "foo"); 26 | } }); 27 | 28 | Compiler compiler; 29 | auto ex = compiler.link(std::span{ &rg, 1 }, {}); 30 | REQUIRE((bool)ex); 31 | REQUIRE_THROWS(ex->execute(*test_context.allocator, {})); 32 | } -------------------------------------------------------------------------------- /docs/topics/context.rst: -------------------------------------------------------------------------------- 1 | Context 2 | ======= 3 | The Context represents the base object of the runtime, encapsulating the knowledge about the GPU (similar to a VkDevice). 4 | Use this class to manage pipelines and other cached objects, add/remove swapchains, manage persistent descriptor sets, submit work to device and retrieve query results. 5 | 6 | 7 | .. doxygenstruct:: vuk::ContextCreateParameters 8 | :members: 9 | 10 | .. doxygenclass:: vuk::Context 11 | :members: 12 | 13 | 14 | .. doxygenstruct:: vuk::Query 15 | 16 | Submitting work 17 | =============== 18 | While submitting work to the device can be performed by the user, it is usually sufficient to use a utility function that takes care of translating a RenderGraph into device execution. Note that these functions are used internally when using :cpp:class:`vuk::Future`s, and as such Futures can be used to manage submission in a more high-level fashion. 19 | 20 | .. doxygenfunction:: vuk::execute_submit_and_present_to_one 21 | 22 | .. doxygenfunction:: vuk::execute_submit_and_wait 23 | 24 | .. doxygenfunction:: vuk::link_execute_submit 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Marcell Kiss 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /examples/rt.rmiss: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION 17 | * SPDX-License-Identifier: Apache-2.0 18 | */ 19 | 20 | #version 460 21 | #pragma shader_stage(miss) 22 | #extension GL_EXT_ray_tracing : require 23 | #extension GL_EXT_shader_explicit_arithmetic_types_int64 : require 24 | 25 | layout(location = 0) rayPayloadInEXT vec3 prd; 26 | 27 | void main() { 28 | prd = vec3(0.3f, 0.5f, 0.3f); 29 | } -------------------------------------------------------------------------------- /benchmarks/nondependent_texture_fetch_explicit_lod.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(fragment) 3 | 4 | const float weight[5] = float[](0.2270270270, 0.1945945946, 0.1216216216, 5 | 0.0540540541, 0.0162162162); 6 | 7 | layout (location = 0) in vec2 uv; 8 | layout (location = 0) out vec4 FragmentColor; 9 | layout(binding = 0) uniform sampler2D image; 10 | 11 | void main() { 12 | FragmentColor = textureLod(image, uv, 0) * weight[0]; 13 | FragmentColor += textureLodOffset(image, uv, 0, ivec2(0, 1)) * weight[1]; 14 | FragmentColor += textureLodOffset(image, uv, 0, ivec2(0,-1)) * weight[1]; 15 | FragmentColor += textureLodOffset(image, uv, 0, ivec2(0, 2)) * weight[2]; 16 | FragmentColor += textureLodOffset(image, uv, 0, ivec2(0,-2)) * weight[2]; 17 | FragmentColor += textureLodOffset(image, uv, 0, ivec2(0, 3)) * weight[3]; 18 | FragmentColor += textureLodOffset(image, uv, 0, ivec2(0,-3)) * weight[3]; 19 | FragmentColor += textureLodOffset(image, uv, 0, ivec2(0, 4)) * weight[4]; 20 | FragmentColor += textureLodOffset(image, uv, 0, ivec2(0,-4)) * weight[4]; 21 | 22 | } -------------------------------------------------------------------------------- /benchmarks/nondependent_texture_fetch.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(fragment) 3 | 4 | const float weight[5] = float[](0.2270270270, 0.1945945946, 0.1216216216, 5 | 0.0540540541, 0.0162162162); 6 | 7 | layout (location = 0) in vec2 uv; 8 | layout (location = 0) out vec4 FragmentColor; 9 | layout(binding = 0) uniform sampler2D image; 10 | 11 | void main() { 12 | vec2 size = textureSize(image, 0); 13 | 14 | FragmentColor = textureLod(image, uv, 0) * weight[0]; 15 | 16 | FragmentColor += textureLodOffset(image, uv, 0, ivec2(0, 1)) * weight[1]; 17 | FragmentColor += textureLodOffset(image, uv, ivec2(0,-1)) * weight[1]; 18 | FragmentColor += textureLodOffset(image, uv, ivec2(0, 2)) * weight[2]; 19 | FragmentColor += textureLodOffset(image, uv, ivec2(0,-2)) * weight[2]; 20 | FragmentColor += textureLodOffset(image, uv, ivec2(0, 3)) * weight[3]; 21 | FragmentColor += textureLodOffset(image, uv, ivec2(0,-3)) * weight[3]; 22 | FragmentColor += textureLodOffset(image, uv, ivec2(0, 4)) * weight[4]; 23 | FragmentColor += textureLodOffset(image, uv, ivec2(0,-4)) * weight[4]; 24 | 25 | } -------------------------------------------------------------------------------- /examples/deferred_resolve.frag: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #pragma shader_stage(fragment) 3 | layout(location = 0) out vec4 FragColor; 4 | 5 | layout(location = 0) in vec2 TexCoords; 6 | 7 | layout(binding = 0) uniform sampler2D gPosition; 8 | layout(binding = 1) uniform sampler2D gNormal; 9 | layout(binding = 2) uniform sampler2D gAlbedoSpec; 10 | 11 | struct Light { 12 | vec3 Position; 13 | vec3 Color; 14 | }; 15 | 16 | const Light l = Light(vec3(1,1,1), vec3(0.5, 0.7, 0.9)); 17 | 18 | layout(binding = 3) uniform V { 19 | vec3 viewPos; 20 | }; 21 | 22 | void main() 23 | { 24 | // retrieve data from G-buffer 25 | vec3 FragPos = texture(gPosition, TexCoords).rgb; 26 | vec3 Normal = texture(gNormal, TexCoords).rgb; 27 | vec3 Albedo = texture(gAlbedoSpec, TexCoords).rgb; 28 | 29 | // then calculate lighting as usual 30 | vec3 lighting = Albedo * 0.1; // hard-coded ambient component 31 | vec3 viewDir = normalize(viewPos - FragPos); 32 | // diffuse 33 | vec3 lightDir = normalize(l.Position - FragPos); 34 | vec3 diffuse = max(dot(Normal, lightDir), 0.0) * Albedo * l.Color; 35 | lighting += diffuse; 36 | 37 | FragColor = vec4(lighting, 1.0); 38 | } 39 | -------------------------------------------------------------------------------- /docs/_static/breathe.css: -------------------------------------------------------------------------------- 1 | 2 | /* -- breathe specific styles ----------------------------------------------- */ 3 | 4 | /* So enum value descriptions are displayed inline to the item */ 5 | .breatheenumvalues li tt + p { 6 | display: inline; 7 | } 8 | 9 | /* So parameter descriptions are displayed inline to the item */ 10 | .breatheparameterlist li tt + p { 11 | display: inline; 12 | } 13 | 14 | .container .breathe-sectiondef { 15 | width: inherit; 16 | } 17 | 18 | .descname { 19 | padding-left: inherit !important; 20 | } 21 | 22 | span+code.descname { 23 | padding-left: 0 !important; 24 | } 25 | 26 | dt>code.sig-prename { 27 | margin-right: -6px !important; 28 | padding-left: 6px !important; 29 | } 30 | 31 | dt>em { 32 | padding-left: 4px !important; 33 | } 34 | 35 | dt>em.property { 36 | padding-left: 0 !important; 37 | } 38 | 39 | .property { 40 | font-style: normal; 41 | padding-right: 2px !important; 42 | } 43 | 44 | dt>a:first-child.reference { 45 | padding-right: 4px !important; 46 | } 47 | 48 | .github-btn { 49 | border: 0; 50 | overflow: hidden; 51 | } 52 | 53 | .jumbotron { 54 | background-size: 100% 4px; 55 | background-repeat: repeat-y; 56 | color: white; 57 | text-align: center; 58 | } 59 | -------------------------------------------------------------------------------- /include/vuk/SampledImage.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "vuk/Image.hpp" 4 | #include "vuk/Types.hpp" 5 | #include 6 | 7 | namespace vuk { 8 | // high level type around binding a sampled image with a sampler 9 | struct SampledImage { 10 | struct Global { 11 | vuk::ImageView iv; 12 | vuk::SamplerCreateInfo sci = {}; 13 | vuk::ImageLayout image_layout; 14 | }; 15 | 16 | struct RenderGraphAttachment { 17 | NameReference reference; 18 | vuk::SamplerCreateInfo sci = {}; 19 | std::optional ivci = {}; 20 | vuk::ImageLayout image_layout; 21 | }; 22 | 23 | union { 24 | Global global = {}; 25 | RenderGraphAttachment rg_attachment; 26 | }; 27 | bool is_global; 28 | 29 | SampledImage(Global g) : global(g), is_global(true) {} 30 | SampledImage(RenderGraphAttachment g) : rg_attachment(g), is_global(false) {} 31 | 32 | SampledImage(const SampledImage& o) { 33 | *this = o; 34 | } 35 | 36 | SampledImage& operator=(const SampledImage& o) { 37 | if (o.is_global) { 38 | global = {}; 39 | global = o.global; 40 | } else { 41 | rg_attachment = {}; 42 | rg_attachment = o.rg_attachment; 43 | } 44 | is_global = o.is_global; 45 | return *this; 46 | } 47 | }; 48 | } // namespace vuk 49 | -------------------------------------------------------------------------------- /docs/topics/allocators.rst: -------------------------------------------------------------------------------- 1 | Allocators 2 | ========== 3 | 4 | Management of GPU resources is an important part of any renderer. vuk provides an API that lets you plug in your allocation schemes, complementing built-in general purpose schemes that get you started and give good performance out of the box. 5 | 6 | Overview 7 | -------- 8 | 9 | .. doxygenclass:: vuk::Allocator 10 | 11 | .. doxygenstruct:: vuk::DeviceResource 12 | 13 | To facilitate ownership, a RAII wrapper type is provided, that wraps an Allocator and a payload: 14 | 15 | .. doxygenclass:: vuk::Unique 16 | 17 | Built-in resources 18 | ------------------ 19 | 20 | .. doxygenstruct:: vuk::DeviceNestedResource 21 | 22 | .. doxygenstruct:: vuk::DeviceVkResource 23 | 24 | .. doxygenstruct:: vuk::DeviceFrameResource 25 | 26 | .. doxygenstruct:: vuk::DeviceSuperFrameResource 27 | 28 | Helpers 29 | ------- 30 | Allocator provides functions that can perform bulk allocation (to reduce overhead for repeated calls) and return resources directly. However, usually it is more convenient to allocate a single resource and immediately put it into a RAII wrapper to prevent forgetting to deallocate it. 31 | 32 | .. doxygenfile:: include/vuk/AllocatorHelpers.hpp 33 | 34 | 35 | Reference 36 | --------- 37 | 38 | .. doxygenclass:: vuk::Allocator 39 | :members: 40 | -------------------------------------------------------------------------------- /examples/deferred_reflective.frag: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #pragma shader_stage(fragment) 3 | 4 | layout (location = 0) out vec4 gPosition; 5 | layout (location = 1) out vec4 gNormal; 6 | layout (location = 2) out vec4 gAlbedoSpec; 7 | 8 | layout (location = 0) in vec3 FragPos; 9 | layout (location = 1) in vec3 Normal; 10 | layout (location = 2) in vec2 TexCoords; 11 | 12 | layout (binding = 2) uniform samplerCube env_map; 13 | 14 | layout (push_constant) uniform PushConstants { 15 | vec3 camPos; 16 | }; 17 | 18 | vec3 fresnel_schlick(float cosTheta, vec3 F0) { 19 | return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); 20 | } 21 | 22 | void main() 23 | { 24 | // store the fragment position vector in the first gbuffer texture 25 | gPosition = vec4(FragPos, 1); 26 | // also store the per-fragment normals into the gbuffer 27 | gNormal = vec4(normalize(Normal), 1); 28 | // and the diffuse per-fragment color 29 | vec3 V = normalize(FragPos - camPos); 30 | vec3 R = reflect(V, gNormal.xyz); 31 | vec3 environ = texture(env_map, R).xyz; 32 | vec3 inColor = vec3(1,0.5,0.5); 33 | vec3 F0 = inColor; 34 | 35 | vec3 F = fresnel_schlick(max(dot(gNormal.xyz, V), 0.0), F0); 36 | vec3 hdr_color = environ * inColor; 37 | vec3 ldr_color = hdr_color / (1+hdr_color); 38 | gAlbedoSpec = vec4(environ, 1.0);//vec4(inColor * sqrt(1 - gl_FragCoord.z), 1.0); 39 | } -------------------------------------------------------------------------------- /examples/rt.rchit: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION 17 | * SPDX-License-Identifier: Apache-2.0 18 | */ 19 | 20 | #version 460 21 | #pragma shader_stage(closest) 22 | #extension GL_EXT_ray_tracing : require 23 | #extension GL_EXT_nonuniform_qualifier : enable 24 | #extension GL_EXT_scalar_block_layout : enable 25 | 26 | hitAttributeEXT vec2 attribs; 27 | 28 | #extension GL_EXT_shader_explicit_arithmetic_types_int64 : require 29 | #extension GL_EXT_buffer_reference2 : require 30 | 31 | layout(location = 0) rayPayloadInEXT vec3 prd; 32 | 33 | void main() { 34 | const vec3 barycentrics = vec3(1.0 - attribs.x - attribs.y, attribs.x, attribs.y); 35 | 36 | prd = barycentrics; 37 | } -------------------------------------------------------------------------------- /include/vuk/RelSpan.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace vuk { 7 | template 8 | struct RelSpan { 9 | size_t offset0 = 0; 10 | size_t offset1 = 0; 11 | 12 | constexpr size_t size() const noexcept { 13 | return offset1 - offset0; 14 | } 15 | 16 | constexpr std::span to_span(T* base) const noexcept { 17 | return std::span{ base + offset0, base + offset1 }; 18 | } 19 | 20 | constexpr std::span to_span(std::vector& base) const noexcept { 21 | return std::span{ base.data() + offset0, base.data() + offset1 }; 22 | } 23 | 24 | constexpr std::span to_span(const std::vector& base) const noexcept { 25 | return std::span{ base.data() + offset0, base.data() + offset1 }; 26 | } 27 | 28 | void append(std::vector& base, T value) { 29 | // easy case: we have space at the end of the vector 30 | if (offset1 == base.size()) { 31 | base.push_back(std::move(value)); 32 | offset1++; 33 | return; 34 | } 35 | // non-easy case: copy the span to the end and extend 36 | auto new_offset0 = base.size(); 37 | base.resize(base.size() + offset1 - offset0 + 1); 38 | std::copy(base.begin() + offset0, base.begin() + offset1, base.begin() + new_offset0); 39 | base.back() = std::move(value); 40 | offset0 = new_offset0; 41 | offset1 = base.size(); 42 | } 43 | }; 44 | } // namespace vuk -------------------------------------------------------------------------------- /examples/blelloch_add.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(compute) 3 | 4 | #extension GL_EXT_shared_memory_block : require 5 | #extension GL_EXT_buffer_reference : require 6 | #extension GL_EXT_buffer_reference2 : require 7 | #extension GL_EXT_shader_explicit_arithmetic_types_int64 : require 8 | 9 | layout(buffer_reference, std430, buffer_reference_align = 4) buffer bufUI { 10 | uint x; 11 | }; 12 | 13 | layout (push_constant) uniform PC { 14 | bufUI source; 15 | bufUI dst; 16 | bufUI tmp; 17 | uint count; 18 | }; 19 | 20 | layout (local_size_x = 64) in; 21 | 22 | // https://developer.nvidia.com/gpugems/gpugems3/part-vi-gpu-computing/chapter-39-parallel-prefix-sum-scan-cuda 23 | const uint array_size = 128; 24 | 25 | uint linear_index(uvec3 id){ 26 | uvec3 mp = gl_NumWorkGroups * gl_WorkGroupSize; 27 | return id.z * mp.y * mp.x + id.y * mp.x + id.x * 1; 28 | } 29 | 30 | void add_uint(uint n, uint64_t src, uint64_t dst, uint64_t tmp) { 31 | uint gid = linear_index(gl_GlobalInvocationID); 32 | uint thid = gl_LocalInvocationIndex; 33 | bufUI g_idata_ui = bufUI(src); 34 | bufUI g_odata_ui = bufUI(dst); 35 | bufUI temp = bufUI(tmp); 36 | uint a1 = g_idata_ui[2*gid + 128].x; // we don't process the first block 37 | uint a2 = g_idata_ui[2*gid+1 + 128].x; 38 | 39 | g_odata_ui[2*gid + 128].x = a1 + temp[gl_WorkGroupID.x + 1].x; // write results to device memory 40 | g_odata_ui[2*gid+1 + 128].x = a2 + temp[gl_WorkGroupID.x + 1].x; 41 | } 42 | 43 | void main() { 44 | add_uint(count, uint64_t(dst), uint64_t(dst), uint64_t(tmp) + 4 + array_size * 4); 45 | } -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ext/SPIRV-Cross"] 2 | path = ext/SPIRV-Cross 3 | url = https://github.com/KhronosGroup/SPIRV-Cross 4 | [submodule "ext/plf_colony"] 5 | path = ext/plf_colony 6 | url = https://github.com/mattreecebentley/plf_colony 7 | [submodule "ext/VulkanMemoryAllocator"] 8 | path = ext/VulkanMemoryAllocator 9 | url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator 10 | [submodule "ext/glm"] 11 | path = ext/glm 12 | url = https://github.com/g-truc/glm 13 | [submodule "ext/stb"] 14 | path = ext/stb 15 | url = https://github.com/nothings/stb 16 | [submodule "ext/imgui"] 17 | path = ext/imgui 18 | url = https://github.com/ocornut/imgui 19 | [submodule "ext/Mustache"] 20 | path = ext/Mustache 21 | url = https://github.com/kainjow/Mustache 22 | [submodule "ext/Catch2"] 23 | path = ext/Catch2 24 | url = https://github.com/catchorg/Catch2 25 | [submodule "ext/ImGuiColorTextEdit"] 26 | path = ext/ImGuiColorTextEdit 27 | url = https://github.com/martty/ImGuiColorTextEdit 28 | [submodule "ext/ImGui-Addons"] 29 | path = ext/ImGui-Addons 30 | url = https://github.com/gallickgunner/ImGui-Addons 31 | [submodule "ext/tinygltf"] 32 | path = ext/tinygltf 33 | url = https://github.com/syoyo/tinygltf 34 | [submodule "ext/concurrentqueue"] 35 | path = ext/concurrentqueue 36 | url = https://github.com/cameron314/concurrentqueue 37 | [submodule "ext/robin-hood-hashing"] 38 | path = ext/robin-hood-hashing 39 | url = https://github.com/martinus/robin-hood-hashing 40 | [submodule "ext/doctest"] 41 | path = ext/doctest 42 | url = https://github.com/doctest/doctest 43 | [submodule "ext/fmt"] 44 | path = ext/fmt 45 | url = https://github.com/fmtlib/fmt 46 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AlignAfterOpenBracket: Align 3 | AlignConsecutiveMacros: 'true' 4 | AlignConsecutiveAssignments: 'false' 5 | AlignConsecutiveDeclarations: 'false' 6 | AlignEscapedNewlines: Right 7 | AlignOperands: 'true' 8 | AlignTrailingComments: 'true' 9 | AllowAllConstructorInitializersOnNextLine: 'false' 10 | AllowAllParametersOfDeclarationOnNextLine: 'false' 11 | AllowShortBlocksOnASingleLine: 'false' 12 | AllowShortFunctionsOnASingleLine: Empty 13 | AllowShortIfStatementsOnASingleLine: Never 14 | AllowShortLambdasOnASingleLine: Inline 15 | AllowShortLoopsOnASingleLine: 'false' 16 | AlwaysBreakAfterDefinitionReturnType: None 17 | AlwaysBreakAfterReturnType: None 18 | AlwaysBreakBeforeMultilineStrings: 'false' 19 | AlwaysBreakTemplateDeclarations: 'Yes' 20 | BinPackArguments: 'false' 21 | BinPackParameters: 'false' 22 | BreakBeforeBraces: Attach 23 | BreakConstructorInitializers: AfterColon 24 | ColumnLimit: 160 25 | CompactNamespaces: 'false' 26 | ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' 27 | Cpp11BracedListStyle: 'false' 28 | DerivePointerAlignment: 'false' 29 | IndentCaseLabels: 'false' 30 | KeepEmptyLinesAtTheStartOfBlocks: 'false' 31 | Language: Cpp 32 | NamespaceIndentation: All 33 | PointerAlignment: Left 34 | ReflowComments: 'true' 35 | SortIncludes: 'true' 36 | SpaceAfterCStyleCast: 'false' 37 | SpaceAfterLogicalNot: 'false' 38 | SpaceAfterTemplateKeyword: 'false' 39 | SpaceBeforeAssignmentOperators: 'true' 40 | SpaceBeforeCpp11BracedList: 'false' 41 | SpaceBeforeCtorInitializerColon: 'true' 42 | SpaceBeforeInheritanceColon: 'true' 43 | SpaceBeforeParens: ControlStatements 44 | SpaceBeforeRangeBasedForLoopColon: 'true' 45 | SpaceInEmptyParentheses: 'false' 46 | SpacesInAngles: 'false' 47 | SpacesInParentheses: 'false' 48 | SpacesInSquareBrackets: 'false' 49 | TabWidth: '2' 50 | UseTab: ForIndentation 51 | 52 | ... 53 | -------------------------------------------------------------------------------- /include/vuk/Config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef VUK_CUSTOM_VULKAN_HEADER 4 | #include 5 | #else 6 | #include VUK_CUSTOM_VULKAN_HEADER 7 | #endif 8 | 9 | // number of sets that can be bound to the command buffer 10 | #ifndef VUK_MAX_SETS 11 | #define VUK_MAX_SETS 8u 12 | #endif 13 | 14 | // number of bindings (individual descriptor) per set for non-persistent descriptorsets 15 | #ifndef VUK_MAX_BINDINGS 16 | #define VUK_MAX_BINDINGS 16u 17 | #endif 18 | 19 | // number of attributes that can be bound to the command buffer 20 | #ifndef VUK_MAX_ATTRIBUTES 21 | #define VUK_MAX_ATTRIBUTES 8u 22 | #endif 23 | 24 | // number of color attachments supported 25 | #ifndef VUK_MAX_COLOR_ATTACHMENTS 26 | #define VUK_MAX_COLOR_ATTACHMENTS 8u 27 | #endif 28 | 29 | // size of the push constant buffer 30 | #ifndef VUK_MAX_PUSHCONSTANT_SIZE 31 | #define VUK_MAX_PUSHCONSTANT_SIZE 128u 32 | #endif 33 | 34 | // number of individual push constant ranges that can be bound to the command buffer 35 | #ifndef VUK_MAX_PUSHCONSTANT_RANGES 36 | #define VUK_MAX_PUSHCONSTANT_RANGES 8u 37 | #endif 38 | 39 | // number of specialization constants that can be set per pipeline 40 | #ifndef VUK_MAX_SPECIALIZATIONCONSTANT_RANGES 41 | #define VUK_MAX_SPECIALIZATIONCONSTANT_RANGES 64u 42 | #endif 43 | 44 | // number of bytes specialization constants can take up for pipelines 45 | #ifndef VUK_MAX_SPECIALIZATIONCONSTANT_SIZE 46 | #define VUK_MAX_SPECIALIZATIONCONSTANT_SIZE 32u 47 | #endif 48 | 49 | // number of viewports that can be set on the command buffer 50 | #ifndef VUK_MAX_VIEWPORTS 51 | #define VUK_MAX_VIEWPORTS 1u 52 | #endif 53 | 54 | // number of scissors that can be set on the command buffer 55 | #ifndef VUK_MAX_SCISSORS 56 | #define VUK_MAX_SCISSORS 1u 57 | #endif 58 | 59 | #ifndef VUK_DISABLE_EXCEPTIONS 60 | #define VUK_USE_EXCEPTIONS 1 61 | #else 62 | #define VUK_USE_EXCEPTIONS 0 63 | #endif -------------------------------------------------------------------------------- /include/vuk/MapProxy.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace vuk { 6 | // generic iterator for both const_iterator and iterator. 7 | template 8 | class ConstMapIterator { 9 | public: 10 | using difference_type = std::ptrdiff_t; 11 | using value_type = std::pair; 12 | using reference = value_type; 13 | using iterator_category = std::forward_iterator_tag; 14 | 15 | ConstMapIterator(void* iter) : _iter(iter) {} 16 | ConstMapIterator(ConstMapIterator const&) noexcept; 17 | ConstMapIterator& operator=(ConstMapIterator const& other) noexcept { 18 | ConstMapIterator tmp(other); 19 | swap(tmp); 20 | return *this; 21 | } 22 | 23 | ~ConstMapIterator(); 24 | 25 | void swap(ConstMapIterator& other) noexcept { 26 | using std::swap; 27 | swap(_iter, other._iter); 28 | } 29 | 30 | ConstMapIterator& operator++() noexcept; 31 | 32 | ConstMapIterator operator++(int) const noexcept { 33 | ConstMapIterator tmp = *this; 34 | ++(*this); 35 | return tmp; 36 | } 37 | 38 | reference operator*() noexcept; 39 | 40 | bool operator==(ConstMapIterator const& o) const noexcept; 41 | bool operator!=(ConstMapIterator const& o) const noexcept { 42 | return !(*this == o); 43 | } 44 | 45 | private: 46 | void* _iter; 47 | }; 48 | 49 | template 50 | struct MapProxy { 51 | using const_iterator = ConstMapIterator; 52 | 53 | MapProxy(void* map) : _map(map) {} 54 | 55 | const_iterator begin() const noexcept { 56 | return cbegin(); 57 | } 58 | const_iterator end() const noexcept { 59 | return cend(); 60 | } 61 | 62 | const_iterator cbegin() const noexcept; 63 | const_iterator cend() const noexcept; 64 | 65 | size_t size() const noexcept; 66 | const_iterator find(Key) const noexcept; 67 | 68 | private: 69 | void* _map; 70 | }; 71 | } // namespace vuk -------------------------------------------------------------------------------- /include/vuk/Name.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "vuk/Hash.hpp" 4 | #include 5 | 6 | namespace vuk { 7 | class Name { 8 | public: 9 | Name() = default; 10 | 11 | Name(decltype(nullptr)); 12 | Name(const char* str) noexcept; 13 | Name(std::string_view str) noexcept; 14 | 15 | std::string_view to_sv() const noexcept; 16 | const char* c_str() const noexcept { 17 | return id; 18 | } 19 | 20 | Name append(std::string_view other) const noexcept; 21 | 22 | bool is_invalid() const noexcept; 23 | 24 | friend bool operator==(Name a, Name b) noexcept { 25 | return a.id == b.id; 26 | } 27 | 28 | friend bool operator!=(Name a, Name b) noexcept { 29 | return a.id != b.id; 30 | } 31 | 32 | friend bool operator<(Name a, Name b) noexcept { 33 | return (uintptr_t)a.id < (uintptr_t)b.id; 34 | } 35 | 36 | private: 37 | static constexpr const char invalid_value[] = "UNNAMED"; 38 | const char* id = invalid_value; 39 | 40 | friend struct std::hash; 41 | }; 42 | 43 | struct QualifiedName { 44 | Name prefix; 45 | Name name; 46 | 47 | constexpr QualifiedName() = default; 48 | constexpr QualifiedName(Name prefix, Name name) : prefix(prefix), name(name) {} 49 | 50 | bool operator==(const QualifiedName&) const noexcept = default; 51 | bool is_invalid() const noexcept { 52 | return name.is_invalid(); 53 | } 54 | }; 55 | 56 | // a stable Name that can refer to an arbitrary subgraph Name 57 | struct NameReference { 58 | struct RenderGraph* rg = nullptr; 59 | QualifiedName name; 60 | 61 | static constexpr NameReference direct(Name n) { 62 | return NameReference{ nullptr, { Name{}, n } }; 63 | } 64 | }; 65 | } // namespace vuk 66 | 67 | namespace std { 68 | template<> 69 | struct hash { 70 | size_t operator()(vuk::Name const& s) const; 71 | }; 72 | 73 | template<> 74 | struct hash { 75 | size_t operator()(vuk::QualifiedName const& s) const; 76 | }; 77 | } // namespace std 78 | -------------------------------------------------------------------------------- /include/vuk/Hash.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | // https://gist.github.com/filsinger/1255697/21762ea83a2d3c17561c8e6a29f44249a4626f9e 6 | 7 | namespace hash { 8 | template 9 | struct fnv_internal; 10 | template 11 | struct fnv1a_tpl; 12 | 13 | template<> 14 | struct fnv_internal { 15 | constexpr static uint32_t default_offset_basis = 0x811C9DC5; 16 | constexpr static uint32_t prime = 0x01000193; 17 | }; 18 | 19 | template<> 20 | struct fnv1a_tpl : public fnv_internal { 21 | constexpr static inline uint32_t hash(char const* const aString, const uint32_t val = default_offset_basis) { 22 | return (aString[0] == '\0') ? val : hash(&aString[1], (val ^ uint32_t(aString[0])) * prime); 23 | } 24 | 25 | constexpr static inline uint32_t hash(char const* const aString, const size_t aStrlen, const uint32_t val) { 26 | return (aStrlen == 0) ? val : hash(aString + 1, aStrlen - 1, (val ^ uint32_t(aString[0])) * prime); 27 | } 28 | }; 29 | 30 | using fnv1a = fnv1a_tpl; 31 | } // namespace hash 32 | 33 | inline constexpr uint32_t operator"" _fnv1a(const char* aString, const size_t aStrlen) { 34 | typedef hash::fnv1a_tpl hash_type; 35 | return hash_type::hash(aString, aStrlen, hash_type::default_offset_basis); 36 | } 37 | 38 | template 39 | inline void hash_combine(size_t& seed, const T& v) noexcept { 40 | std::hash hasher; 41 | seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); 42 | } 43 | 44 | inline constexpr void hash_combine_direct(uint32_t& seed, uint32_t v) noexcept { 45 | seed ^= v + 0x9e3779b9 + (seed << 6) + (seed >> 2); 46 | } 47 | 48 | #define FWD(x) (static_cast(x)) 49 | 50 | template 51 | inline void hash_combine(size_t& seed, const T& v, Rest&&... rest) noexcept { 52 | std::hash hasher; 53 | seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); 54 | hash_combine(seed, FWD(rest)...); 55 | } 56 | 57 | #undef FWD -------------------------------------------------------------------------------- /src/tests/buffer_ops.cpp: -------------------------------------------------------------------------------- 1 | #include "TestContext.hpp" 2 | #include "vuk/AllocatorHelpers.hpp" 3 | #include "vuk/Partials.hpp" 4 | #include 5 | 6 | using namespace vuk; 7 | 8 | TEST_CASE("test text_context preparation") { 9 | REQUIRE(test_context.prepare()); 10 | } 11 | 12 | constexpr bool operator==(const std::span& lhs, const std::span& rhs) { 13 | return std::equal(begin(lhs), end(lhs), begin(rhs), end(rhs)); 14 | } 15 | 16 | constexpr bool operator==(const std::span& lhs, const std::span& rhs) { 17 | return std::equal(begin(lhs), end(lhs), begin(rhs), end(rhs)); 18 | } 19 | 20 | constexpr bool operator==(const std::span& lhs, const std::span& rhs) { 21 | return std::equal(begin(lhs), end(lhs), begin(rhs), end(rhs)); 22 | } 23 | 24 | TEST_CASE("test buffer harness") { 25 | REQUIRE(test_context.prepare()); 26 | auto data = { 1u, 2u, 3u }; 27 | auto [buf, fut] = create_buffer(*test_context.allocator, MemoryUsage::eCPUtoGPU, vuk::DomainFlagBits::eTransferOnTransfer, std::span(data)); 28 | auto res = fut.get(*test_context.allocator, test_context.compiler); 29 | CHECK(std::span((uint32_t*)res->mapped_ptr, 3) == std::span(data)); 30 | } 31 | 32 | TEST_CASE("test buffer upload/download") { 33 | REQUIRE(test_context.prepare()); 34 | { 35 | auto data = { 1u, 2u, 3u }; 36 | auto [buf, fut] = create_buffer(*test_context.allocator, MemoryUsage::eGPUonly, DomainFlagBits::eAny, std::span(data)); 37 | 38 | auto res = download_buffer(fut).get(*test_context.allocator, test_context.compiler); 39 | CHECK(std::span((uint32_t*)res->mapped_ptr, 3) == std::span(data)); 40 | } 41 | { 42 | auto data = { 1u, 2u, 3u, 4u, 5u }; 43 | auto [buf, fut] = create_buffer(*test_context.allocator, MemoryUsage::eGPUonly, DomainFlagBits::eAny, std::span(data)); 44 | 45 | auto res = download_buffer(fut).get(*test_context.allocator, test_context.compiler); 46 | CHECK(std::span((uint32_t*)res->mapped_ptr, 5) == std::span(data)); 47 | } 48 | } -------------------------------------------------------------------------------- /docs/topics/rendergraph.rst: -------------------------------------------------------------------------------- 1 | Rendergraph 2 | =========== 3 | 4 | .. doxygenstruct:: vuk::Resource 5 | :members: 6 | 7 | .. doxygenstruct:: vuk::RenderGraph 8 | :members: 9 | 10 | .. doxygenstruct:: vuk::ExecutableRenderGraph 11 | :members: 12 | 13 | Futures 14 | ======= 15 | vuk Futures allow you to reason about computation of resources that happened in the past, or will happen in the future. In general the limitation of RenderGraphs are that they don't know the state of the resources produces by previous computation, or the state the resources should be left in for future computation, so these states must be provided manually (this is error-prone). Instead you can encapsulate the computation and its result into a Future, which can then serve as an input to other RenderGraphs. 16 | 17 | Futures can be constructed from a RenderGraph and a named Resource that is considered to be the output. A Future can optionally own the RenderGraph - but in all cases a Future must outlive the RenderGraph it references. 18 | 19 | You can submit Futures manually, which will compile, execute and submit the RenderGraph it references. In this case when you use this Future as input to another RenderGraph it will wait for the result on the device. If a Future has not yet been submitted, the contained RenderGraph is simply appended as a subgraph (i.e. inlined). 20 | 21 | It is also possible to wait for the result to be produced to be available on the host - but this forces a CPU-GPU sync and should be used sparingly. 22 | 23 | .. doxygenclass:: vuk::Future 24 | :members: 25 | 26 | Composing render graphs 27 | ======================= 28 | Futures make easy to compose complex operations and effects out of RenderGraph building blocks, linked by Futures. These building blocks are termed partials, and vuk provides some built-in. Such partials are functions that take a number of Futures as input, and produce a Future as output. 29 | 30 | The built-in partials can be found below. Built on these, there are some convenience functions that couple resource allocation with initial data (`create_XXX()`). 31 | 32 | .. doxygenfile:: include/vuk/Partials.hpp -------------------------------------------------------------------------------- /include/vuk/vuk_fwd.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "vuk/Name.hpp" 4 | 5 | namespace vuk { 6 | class Context; 7 | class Allocator; 8 | 9 | class CommandBuffer; 10 | 11 | struct Swapchain; 12 | using SwapchainRef = Swapchain*; 13 | 14 | class LegacyGPUAllocator; 15 | 16 | struct ShaderSource; 17 | 18 | // 0b00111 -> 3 19 | inline uint32_t num_leading_ones(uint32_t mask) noexcept { 20 | #ifdef __has_builtin 21 | #if __has_builtin(__builtin_clz) 22 | return (31 ^ __builtin_clz(mask)) + 1; 23 | #else 24 | #error "__builtin_clz not available" 25 | #endif 26 | #else 27 | unsigned long lz; 28 | if (!_BitScanReverse(&lz, mask)) 29 | return 0; 30 | return lz + 1; 31 | #endif 32 | } 33 | 34 | // return a/b rounded to infinity 35 | constexpr uint64_t idivceil(uint64_t a, uint64_t b) noexcept { 36 | return (a + b - 1) / b; 37 | } 38 | 39 | struct Exception; 40 | struct ShaderCompilationException; 41 | struct RenderGraphException; 42 | struct AllocateException; 43 | struct PresentException; 44 | struct VkException; 45 | 46 | template 47 | struct Result; 48 | 49 | template 50 | class Unique; 51 | 52 | struct FramebufferCreateInfo; 53 | 54 | struct BufferCreateInfo; 55 | 56 | struct Buffer; 57 | 58 | struct Query; 59 | struct TimestampQuery; 60 | struct TimestampQueryPool; 61 | struct TimestampQueryCreateInfo; 62 | 63 | struct CommandBufferAllocationCreateInfo; 64 | struct CommandBufferAllocation; 65 | 66 | struct SetBinding; 67 | struct DescriptorSet; 68 | struct PersistentDescriptorSetCreateInfo; 69 | struct PersistentDescriptorSet; 70 | 71 | struct ShaderModule; 72 | struct PipelineBaseCreateInfo; 73 | struct PipelineBaseInfo; 74 | struct Program; 75 | 76 | struct GraphicsPipelineInfo; 77 | struct GraphicsPipelineInstanceCreateInfo; 78 | struct ComputePipelineInfo; 79 | struct ComputePipelineInstanceCreateInfo; 80 | struct RayTracingPipelineInfo; 81 | struct RayTracingPipelineInstanceCreateInfo; 82 | 83 | struct RenderPassCreateInfo; 84 | 85 | struct FutureBase; 86 | class Future; 87 | 88 | struct Compiler; 89 | } // namespace vuk 90 | -------------------------------------------------------------------------------- /src/ShadercIncluder.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace vuk { 8 | /// @brief This default includer will look in the current working directory of the app and relative to the includer file to resolve includes 9 | class ShadercDefaultIncluder : public shaderc::CompileOptions::IncluderInterface { 10 | struct IncludeData { 11 | std::string source; 12 | std::string content; 13 | }; 14 | 15 | std::filesystem::path base_path = std::filesystem::current_path(); 16 | 17 | public: 18 | // Handles shaderc_include_resolver_fn callbacks. 19 | shaderc_include_result* GetInclude(const char* requested_source, shaderc_include_type type, const char* requesting_source, size_t include_depth) override { 20 | auto data = new IncludeData; 21 | auto path = base_path / requested_source; 22 | auto alternative_path = std::filesystem::absolute(std::filesystem::path(requesting_source).remove_filename() / requested_source); 23 | std::ostringstream buf; 24 | if (std::ifstream input(path); input) { 25 | buf << input.rdbuf(); 26 | data->content = buf.str(); 27 | data->source = path.string(); 28 | } else if (input = std::ifstream(alternative_path); input) { 29 | buf << input.rdbuf(); 30 | data->content = buf.str(); 31 | data->source = alternative_path.string(); 32 | } else { 33 | data->content = fmt::format("file could not be read (tried: {}; {})", path.string().c_str(), alternative_path.string().c_str()); 34 | } 35 | 36 | shaderc_include_result* result = new shaderc_include_result; 37 | result->user_data = data; 38 | result->source_name = data->source.c_str(); 39 | result->source_name_length = data->source.size(); 40 | result->content = data->content.c_str(); 41 | result->content_length = data->content.size(); 42 | 43 | return result; 44 | } 45 | 46 | // Handles shaderc_include_result_release_fn callbacks. 47 | void ReleaseInclude(shaderc_include_result* data) override { 48 | delete static_cast(data->user_data); 49 | delete data; 50 | } 51 | }; 52 | } // namespace vuk -------------------------------------------------------------------------------- /include/vuk/SourceLocation.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace vuk { 6 | /// @cond INTERNAL 7 | #ifndef __cpp_consteval 8 | struct source_location { 9 | uint_least32_t line_{}; 10 | uint_least32_t column_{}; 11 | const char* file = ""; 12 | const char* function = ""; 13 | 14 | [[nodiscard]] constexpr source_location() noexcept = default; 15 | 16 | [[nodiscard]] static source_location current(const uint_least32_t line_ = __builtin_LINE(), 17 | const uint_least32_t column_ = __builtin_COLUMN(), 18 | const char* const file_ = __builtin_FILE(), 19 | const char* const function_ = __builtin_FUNCTION()) noexcept { 20 | source_location result; 21 | result.line_ = line_; 22 | result.column_ = column_; 23 | result.file = file_; 24 | result.function = function_; 25 | return result; 26 | } 27 | 28 | [[nodiscard]] constexpr uint_least32_t line() const noexcept { 29 | return line_; 30 | } 31 | 32 | [[nodiscard]] constexpr uint_least32_t column() const noexcept { 33 | return line_; 34 | } 35 | 36 | [[nodiscard]] constexpr const char* file_name() const noexcept { 37 | return file; 38 | } 39 | 40 | [[nodiscard]] constexpr const char* function_name() const noexcept { 41 | return function; 42 | } 43 | }; 44 | 45 | struct SourceLocationAtFrame { 46 | source_location location; 47 | uint64_t absolute_frame; 48 | }; 49 | #else 50 | struct SourceLocationAtFrame { 51 | std::source_location location; 52 | uint64_t absolute_frame; 53 | }; 54 | 55 | using source_location = std::source_location; 56 | #endif 57 | } // namespace vuk 58 | 59 | /// @cond INTERNAL 60 | #define VUK_HERE_AND_NOW() \ 61 | SourceLocationAtFrame { \ 62 | vuk::source_location::current(), (uint64_t)-1LL \ 63 | } 64 | /// @endcond -------------------------------------------------------------------------------- /examples/imgui_frag.hpp: -------------------------------------------------------------------------------- 1 | // 1112.2.0 2 | #pragma once 3 | const uint32_t imgui_frag[] = { 4 | 0x07230203,0x00010000,0x0008000b,0x0000001e,0x00000000,0x00020011,0x00000001,0x0006000b, 5 | 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, 6 | 0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x00000009,0x0000000d,0x00030010, 7 | 0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, 8 | 0x00000000,0x00040005,0x00000009,0x6c6f4366,0x0000726f,0x00030005,0x0000000b,0x00000000, 9 | 0x00050006,0x0000000b,0x00000000,0x6f6c6f43,0x00000072,0x00040006,0x0000000b,0x00000001, 10 | 0x00005655,0x00030005,0x0000000d,0x00006e49,0x00050005,0x00000016,0x78655473,0x65727574, 11 | 0x00000000,0x00040047,0x00000009,0x0000001e,0x00000000,0x00040047,0x0000000d,0x0000001e, 12 | 0x00000000,0x00040047,0x00000016,0x00000022,0x00000000,0x00040047,0x00000016,0x00000021, 13 | 0x00000000,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,0x00000006, 14 | 0x00000020,0x00040017,0x00000007,0x00000006,0x00000004,0x00040020,0x00000008,0x00000003, 15 | 0x00000007,0x0004003b,0x00000008,0x00000009,0x00000003,0x00040017,0x0000000a,0x00000006, 16 | 0x00000002,0x0004001e,0x0000000b,0x00000007,0x0000000a,0x00040020,0x0000000c,0x00000001, 17 | 0x0000000b,0x0004003b,0x0000000c,0x0000000d,0x00000001,0x00040015,0x0000000e,0x00000020, 18 | 0x00000001,0x0004002b,0x0000000e,0x0000000f,0x00000000,0x00040020,0x00000010,0x00000001, 19 | 0x00000007,0x00090019,0x00000013,0x00000006,0x00000001,0x00000000,0x00000000,0x00000000, 20 | 0x00000001,0x00000000,0x0003001b,0x00000014,0x00000013,0x00040020,0x00000015,0x00000000, 21 | 0x00000014,0x0004003b,0x00000015,0x00000016,0x00000000,0x0004002b,0x0000000e,0x00000018, 22 | 0x00000001,0x00040020,0x00000019,0x00000001,0x0000000a,0x00050036,0x00000002,0x00000004, 23 | 0x00000000,0x00000003,0x000200f8,0x00000005,0x00050041,0x00000010,0x00000011,0x0000000d, 24 | 0x0000000f,0x0004003d,0x00000007,0x00000012,0x00000011,0x0004003d,0x00000014,0x00000017, 25 | 0x00000016,0x00050041,0x00000019,0x0000001a,0x0000000d,0x00000018,0x0004003d,0x0000000a, 26 | 0x0000001b,0x0000001a,0x00050057,0x00000007,0x0000001c,0x00000017,0x0000001b,0x00050085, 27 | 0x00000007,0x0000001d,0x00000012,0x0000001c,0x0003003e,0x00000009,0x0000001d,0x000100fd, 28 | 0x00010038 29 | }; 30 | -------------------------------------------------------------------------------- /benchmarks/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7) 2 | project(vuk-benchmarks) 3 | 4 | FetchContent_Declare( 5 | vk-bootstrap 6 | GIT_REPOSITORY https://github.com/charles-lunarg/vk-bootstrap 7 | GIT_TAG 8e61b2d81c3f5f84339735085ff5651f71bbe1e7 8 | ) 9 | FetchContent_MakeAvailable(vk-bootstrap) 10 | 11 | set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE) 12 | set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE) 13 | set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) 14 | FetchContent_Declare( 15 | glfw 16 | GIT_REPOSITORY https://github.com/glfw/glfw 17 | GIT_TAG 3.3.2 18 | ) 19 | FetchContent_MakeAvailable(glfw) 20 | 21 | FetchContent_Declare( 22 | glm 23 | GIT_REPOSITORY https://github.com/g-truc/glm 24 | GIT_TAG 0.9.9.8 25 | ) 26 | FetchContent_MakeAvailable(glm) 27 | 28 | FetchContent_Declare( 29 | volk 30 | GIT_REPOSITORY https://github.com/zeux/volk 31 | GIT_TAG 1.2.170 32 | ) 33 | FetchContent_MakeAvailable(volk) 34 | 35 | SET(imgui_sources ../ext/imgui/imgui.cpp ../ext/imgui/imgui_draw.cpp ../ext/imgui/imgui_demo.cpp ../ext/imgui/imgui_widgets.cpp ../ext/imgui/imgui_tables.cpp ../ext/imgui/backends/imgui_impl_glfw.cpp) 36 | 37 | file(RELATIVE_PATH binary_to_source ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}) 38 | 39 | function(ADD_BENCH name) 40 | set(FULL_NAME "vuk_bench_${name}") 41 | add_executable(${FULL_NAME}) 42 | target_sources(${FULL_NAME} PRIVATE "${name}.cpp" bench_runner.cpp ../examples/imgui.cpp ../examples/stbi.cpp ${imgui_sources}) 43 | target_include_directories(${FULL_NAME} SYSTEM PRIVATE ../ext/stb ../ext/imgui) 44 | target_compile_definitions(${FULL_NAME} PRIVATE GLM_FORCE_SIZE_FUNC GLM_FORCE_EXPLICIT_CTOR GLM_ENABLE_EXPERIMENTAL GLM_FORCE_RADIANS GLM_FORCE_DEPTH_ZERO_TO_ONE) 45 | target_compile_definitions(${FULL_NAME} PUBLIC VUK_EX_PATH_TO_ROOT="${binary_to_source}") 46 | target_link_libraries(${FULL_NAME} PRIVATE vuk) 47 | target_link_libraries(${FULL_NAME} PRIVATE vk-bootstrap glfw glm) 48 | set_target_properties(${FULL_NAME} 49 | PROPERTIES 50 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" 51 | ) 52 | if(VUK_COMPILER_CLANGPP OR VUK_COMPILER_GPP) 53 | target_compile_options(${FULL_NAME} PRIVATE -std=c++20 -fno-char8_t) 54 | elseif(MSVC) 55 | target_compile_options(${FULL_NAME} PRIVATE /std:c++20 /permissive- /Zc:char8_t-) 56 | endif() 57 | endfunction(ADD_BENCH) 58 | 59 | ADD_BENCH(dependent_texture_fetches) 60 | -------------------------------------------------------------------------------- /examples/rt.rgen: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION 17 | * SPDX-License-Identifier: Apache-2.0 18 | */ 19 | 20 | #version 460 21 | #pragma shader_stage(raygen) 22 | #extension GL_EXT_ray_tracing : require 23 | #extension GL_EXT_shader_explicit_arithmetic_types_int64 : require 24 | 25 | layout(location = 0) rayPayloadEXT vec3 prd; 26 | 27 | layout(binding = 0) uniform accelerationStructureEXT topLevelAS; 28 | layout(binding = 1, rgba32f) uniform image2D image; 29 | layout(binding = 2) uniform VP { 30 | mat4 viewInverse; 31 | mat4 projInverse; 32 | }; 33 | 34 | 35 | void main() { 36 | const vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + vec2(0.5); 37 | const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); 38 | vec2 d = inUV * 2.0 - 1.0; 39 | 40 | vec4 origin = viewInverse * vec4(0, 0, 0, 1); 41 | vec4 target = projInverse * vec4(d.x, d.y, 1, 1); 42 | vec4 direction = viewInverse * vec4(normalize(target.xyz), 0); 43 | 44 | uint rayFlags = gl_RayFlagsOpaqueEXT; 45 | float tMin = 0.001; 46 | float tMax = 10000.0; 47 | 48 | traceRayEXT(topLevelAS, // acceleration structure 49 | rayFlags, // rayFlags 50 | 0xFF, // cullMask 51 | 0, // sbtRecordOffset 52 | 0, // sbtRecordStride 53 | 0, // missIndex 54 | origin.xyz, // ray origin 55 | tMin, // ray min range 56 | direction.xyz, // ray direction 57 | tMax, // ray max range 58 | 0 // payload (location = 0) 59 | 60 | ); 61 | 62 | imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(prd, 1.0)); 63 | } -------------------------------------------------------------------------------- /include/vuk/Bitset.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "vuk_fwd.hpp" 4 | 5 | #include 6 | #include 7 | 8 | namespace vuk { 9 | template 10 | struct Bitset { 11 | static constexpr uint64_t bitmask(uint64_t const onecount) { 12 | return static_cast(-(onecount != 0)) & (static_cast(-1) >> (8*sizeof(uint64_t) - onecount)); 13 | } 14 | 15 | static constexpr uint64_t n_bits = sizeof(uint64_t) * 8; 16 | static constexpr uint64_t n_words = idivceil(Count, n_bits); 17 | static constexpr uint64_t remainder = Count - n_bits * (Count / n_bits); 18 | static constexpr uint64_t last_word_mask = remainder > 0 ? bitmask(remainder) : 0; 19 | uint64_t words[n_words]; 20 | 21 | Bitset& set(uint64_t pos, bool value = true) noexcept { 22 | auto word = pos / n_bits; 23 | if (value) { 24 | words[word] |= 1ULL << (pos - n_bits * word); 25 | } else { 26 | words[word] &= ~(1ULL << (pos - n_bits * word)); 27 | } 28 | return *this; 29 | } 30 | 31 | uint64_t to_ulong() const noexcept{ 32 | static_assert(n_words == 1); 33 | return words[0]; 34 | } 35 | 36 | uint64_t count() const noexcept { 37 | uint64_t accum = 0; 38 | for (uint64_t i = 0; i < (Count / n_bits); i++) { 39 | accum += std::popcount(words[i]); 40 | } 41 | if constexpr (remainder > 0) { 42 | accum += std::popcount(words[n_words - 1] & last_word_mask); 43 | } 44 | return accum; 45 | } 46 | 47 | bool test(uint64_t pos) const noexcept { 48 | auto word = pos / n_bits; 49 | return words[word] & 1ULL << (pos - n_bits * word); 50 | } 51 | 52 | void reset() noexcept { 53 | for (uint64_t i = 0; i < n_words; i++) { 54 | words[i] = 0; 55 | } 56 | } 57 | 58 | bool operator==(const Bitset& other) const noexcept { 59 | for (uint64_t i = 0; i < (Count / n_bits); i++) { 60 | if (words[i] != other.words[i]) 61 | return false; 62 | } 63 | if constexpr (remainder > 0) { 64 | return (words[n_words - 1] & last_word_mask) == (other.words[n_words - 1] & last_word_mask); 65 | } 66 | return true; 67 | } 68 | 69 | Bitset operator|(const Bitset& other) const noexcept { 70 | Bitset out; 71 | for (uint64_t i = 0; i < (Count / n_bits); i++) { 72 | out.words[i] = words[i] | other.words[i]; 73 | } 74 | if constexpr (remainder > 0) { 75 | out.words[n_words - 1] = (words[n_words - 1] & last_word_mask) | (other.words[n_words - 1] & last_word_mask); 76 | } 77 | return out; 78 | } 79 | 80 | 81 | }; 82 | } // namespace vuk -------------------------------------------------------------------------------- /src/BufferAllocator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "vuk/Buffer.hpp" 4 | #include "vuk/Config.hpp" 5 | #include "vuk/SourceLocation.hpp" 6 | #include "vuk/Types.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace vuk { 18 | struct DeviceResource; 19 | 20 | struct LinearSegment { 21 | Buffer buffer; 22 | size_t num_blocks; 23 | uint64_t base_address = 0; 24 | }; 25 | 26 | struct BufferLinearAllocator { 27 | DeviceResource* upstream; 28 | std::mutex mutex; 29 | std::atomic current_buffer = -1; 30 | std::atomic needle = 0; 31 | MemoryUsage mem_usage; 32 | BufferUsageFlags usage; 33 | // TODO: convert to deque 34 | std::array available_allocations; // up to 4 GB of allocations with the default block_size 35 | std::array used_allocations; // up to 4 GB of allocations with the default block_size 36 | size_t available_allocation_count = 0; 37 | size_t used_allocation_count = 0; 38 | 39 | size_t block_size; 40 | 41 | BufferLinearAllocator(DeviceResource& upstream, MemoryUsage mem_usage, BufferUsageFlags buf_usage, size_t block_size = 1024 * 1024 * 16) : 42 | upstream(&upstream), 43 | mem_usage(mem_usage), 44 | usage(buf_usage), 45 | block_size(block_size) {} 46 | ~BufferLinearAllocator(); 47 | 48 | Result grow(size_t num_blocks, SourceLocationAtFrame source); 49 | Result allocate_buffer(size_t size, size_t alignment, SourceLocationAtFrame source); 50 | // trim the amount of memory to the currently used amount 51 | void trim(); 52 | // return all resources to available 53 | void reset(); 54 | // explicitly release resources 55 | void free(); 56 | }; 57 | 58 | struct BufferBlock { 59 | Buffer buffer = {}; 60 | size_t allocation_count = 0; 61 | }; 62 | 63 | struct SubAllocation { 64 | size_t block_index; 65 | VmaVirtualAllocation allocation; 66 | }; 67 | 68 | struct BufferSubAllocator { 69 | DeviceResource* upstream; 70 | MemoryUsage mem_usage; 71 | BufferUsageFlags usage; 72 | std::vector blocks; 73 | VmaVirtualBlock virtual_alloc; 74 | std::mutex mutex; 75 | size_t block_size; 76 | 77 | BufferSubAllocator(DeviceResource& upstream, MemoryUsage mem_usage, BufferUsageFlags buf_usage, size_t block_size); 78 | ~BufferSubAllocator(); 79 | 80 | Result allocate_buffer(size_t size, size_t alignment, SourceLocationAtFrame source); 81 | void deallocate_buffer(const Buffer& buf); 82 | }; 83 | }; // namespace vuk 84 | -------------------------------------------------------------------------------- /docs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Doxygen REQUIRED) 2 | 3 | # Find all the public headers 4 | get_target_property(VUK_PUBLIC_HEADER_DIR vuk INTERFACE_INCLUDE_DIRECTORIES) 5 | file(GLOB_RECURSE VUK_PUBLIC_HEADERS ${VUK_PUBLIC_HEADER_DIR}/*.hpp) 6 | 7 | #This will be the main output of our command 8 | set(DOXYGEN_INDEX_FILE ${CMAKE_CURRENT_SOURCE_DIR}/html/index.html) 9 | 10 | set(DOXYGEN_INPUT_DIR ${PROJECT_SOURCE_DIR}/include/vuk\ ${PROJECT_SOURCE_DIR}/include/vuk/resources) 11 | set(DOXYGEN_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/doxygen) 12 | set(DOXYGEN_INDEX_FILE ${DOXYGEN_OUTPUT_DIR}/html/index.html) 13 | set(DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) 14 | set(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) 15 | 16 | #Replace variables inside @@ with the current values 17 | configure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY) 18 | 19 | file(MAKE_DIRECTORY ${DOXYGEN_OUTPUT_DIR}) #Doxygen won't create this for us 20 | add_custom_command(OUTPUT ${DOXYGEN_INDEX_FILE} 21 | DEPENDS ${VUK_PUBLIC_HEADERS} 22 | COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT} 23 | MAIN_DEPENDENCY ${DOXYFILE_OUT} ${DOXYFILE_IN} 24 | COMMENT "Generating docs") 25 | 26 | add_custom_target(Doxygen ALL DEPENDS ${DOXYGEN_INDEX_FILE}) 27 | 28 | find_package(Sphinx REQUIRED) 29 | 30 | set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}) 31 | set(SPHINX_BUILD ${CMAKE_CURRENT_BINARY_DIR}/sphinx) 32 | set(SPHINX_INDEX_FILE ${SPHINX_BUILD}/index.html) 33 | 34 | # Only regenerate Sphinx when: 35 | # - Doxygen has rerun 36 | # - Our doc files have been updated 37 | # - The Sphinx config has been updated 38 | add_custom_command(OUTPUT ${SPHINX_INDEX_FILE} 39 | COMMAND 40 | ${SPHINX_EXECUTABLE} -b html 41 | # Tell Breathe where to find the Doxygen output 42 | -Dbreathe_projects.vuk=${DOXYGEN_OUTPUT_DIR}/xml 43 | ${SPHINX_SOURCE} ${SPHINX_BUILD} 44 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 45 | DEPENDS 46 | # Other docs files you want to track should go here (or in some variable) 47 | ${CMAKE_CURRENT_SOURCE_DIR}/index.rst 48 | ${CMAKE_CURRENT_SOURCE_DIR}/topics/context.rst 49 | ${CMAKE_CURRENT_SOURCE_DIR}/topics/allocators.rst 50 | ${CMAKE_CURRENT_SOURCE_DIR}/topics/rendergraph.rst 51 | ${CMAKE_CURRENT_SOURCE_DIR}/topics/commandbuffer.rst 52 | ${DOXYGEN_INDEX_FILE} 53 | MAIN_DEPENDENCY ${SPHINX_SOURCE}/conf.py 54 | COMMENT "Generating documentation with Sphinx") 55 | 56 | # Nice named target so we can run the job easily 57 | add_custom_target(Sphinx ALL DEPENDS ${SPHINX_INDEX_FILE}) -------------------------------------------------------------------------------- /examples/fxaa.frag: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #pragma shader_stage(fragment) 3 | layout(location = 0) out vec4 FragColor; 4 | 5 | layout(location = 0) in vec2 uv; 6 | 7 | layout(binding = 0) uniform sampler2D Color; 8 | layout (constant_id = 0) const float res_x = 1024; 9 | layout (constant_id = 1) const float res_y = 1024; 10 | 11 | // based on: https://www.shadertoy.com/view/ls3GWS 12 | // FXAA code from: http://www.geeks3d.com/20110405/fxaa-fast-approximate-anti-aliasing-demo-glsl-opengl-test-radeon-geforce/3/ 13 | 14 | #define FXAA_SPAN_MAX 8.0 15 | #define FXAA_REDUCE_MUL (1.0/FXAA_SPAN_MAX) 16 | #define FXAA_REDUCE_MIN (1.0/128.0) 17 | #define FXAA_SUBPIX_SHIFT (1.0/4.0) 18 | 19 | vec3 FxaaPixelShader( vec4 uv, sampler2D tex, vec2 rcpFrame) { 20 | 21 | vec3 rgbNW = textureLod(tex, uv.zw, 0.0).xyz; 22 | vec3 rgbNE = textureLod(tex, uv.zw + vec2(1,0)*rcpFrame.xy, 0.0).xyz; 23 | vec3 rgbSW = textureLod(tex, uv.zw + vec2(0,1)*rcpFrame.xy, 0.0).xyz; 24 | vec3 rgbSE = textureLod(tex, uv.zw + vec2(1,1)*rcpFrame.xy, 0.0).xyz; 25 | vec3 rgbM = textureLod(tex, uv.xy, 0.0).xyz; 26 | 27 | vec3 luma = vec3(0.299, 0.587, 0.114); 28 | float lumaNW = dot(rgbNW, luma); 29 | float lumaNE = dot(rgbNE, luma); 30 | float lumaSW = dot(rgbSW, luma); 31 | float lumaSE = dot(rgbSE, luma); 32 | float lumaM = dot(rgbM, luma); 33 | 34 | float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); 35 | float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); 36 | 37 | vec2 dir; 38 | dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); 39 | dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE)); 40 | 41 | float dirReduce = max( 42 | (lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), 43 | FXAA_REDUCE_MIN); 44 | float rcpDirMin = 1.0/(min(abs(dir.x), abs(dir.y)) + dirReduce); 45 | 46 | dir = min(vec2( FXAA_SPAN_MAX, FXAA_SPAN_MAX), 47 | max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), 48 | dir * rcpDirMin)) * rcpFrame.xy; 49 | 50 | vec3 rgbA = (1.0/2.0) * ( 51 | textureLod(tex, uv.xy + dir * (1.0/3.0 - 0.5), 0.0).xyz + 52 | textureLod(tex, uv.xy + dir * (2.0/3.0 - 0.5), 0.0).xyz); 53 | vec3 rgbB = rgbA * (1.0/2.0) + (1.0/4.0) * ( 54 | textureLod(tex, uv.xy + dir * (0.0/3.0 - 0.5), 0.0).xyz + 55 | textureLod(tex, uv.xy + dir * (3.0/3.0 - 0.5), 0.0).xyz); 56 | 57 | float lumaB = dot(rgbB, luma); 58 | 59 | if((lumaB < lumaMin) || (lumaB > lumaMax)) return rgbA; 60 | 61 | return rgbB; 62 | } 63 | 64 | void main() 65 | { 66 | vec2 resolution = vec2(res_x, res_y); 67 | vec2 rcpFrame = 1./resolution.xy; 68 | 69 | vec3 col; 70 | 71 | vec4 uv = vec4( uv, uv - (rcpFrame * (0.5 + FXAA_SUBPIX_SHIFT))); 72 | col = FxaaPixelShader( uv, Color, rcpFrame ); 73 | FragColor = vec4(col, 1.0); 74 | } 75 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. vuk documentation master file, created by 2 | sphinx-quickstart on Thu Dec 3 19:06:20 2020. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to vuk's documentation! 7 | =============================== 8 | 9 | Quickstart 10 | ========== 11 | 1. Grab the vuk repository 12 | 2. Compile the examples 13 | 3. Run the example browser and get a feel for the library:: 14 | 15 | git clone http://github.com/martty/vuk 16 | cd vuk 17 | git submodule init 18 | git submodule update --recursive 19 | mkdir build 20 | cd build 21 | mkdir debug 22 | cd debug 23 | cmake ../.. -G Ninja 24 | cmake --build . 25 | ./vuk_all_examples 26 | 27 | (if building with a multi-config generator, do not make the `debug` folder) 28 | 29 | .. toctree:: 30 | :maxdepth: 2 31 | :caption: Topics: 32 | 33 | topics/context 34 | topics/allocators 35 | topics/rendergraph 36 | topics/commandbuffer 37 | 38 | 39 | Background 40 | ========== 41 | vuk was initially conceived based on the rendergraph articles of themaister (https://themaister.net/blog/2017/08/15/render-graphs-and-vulkan-a-deep-dive/). In essence the idea is to describe work undertaken during a frame in advance in a high level manner, then the library takes care of low-level details, such as insertion of synchronization (barriers) and managing resource states (image layouts). This over time evolved to a somewhat complete Vulkan runtime - you can use the facilities afforded by vuk's runtime without even using the rendergraph part. The runtime presents a more easily approachable interface to Vulkan, abstracting over common pain points of pipeline management, state setting and descriptors. The rendergraph part has grown to become more powerful than simple 'autosync' abstraction - it allows expressing complex dependencies via `vuk::Future` and allows powerful optimisation opportunities for the backend (even if those are to be implemented). 42 | 43 | Alltogether vuk presents a vision of GPU development that embraces compilation - the idea that knowledge about optimisation of programs can be encoded into to tools (compilers) and this way can be insitutionalised, which allows a broader range of programs and programmers to take advantage of these. The future developments will focus on this backend(Vulkan, DX12, etc.)-agnostic form of representing graphics programs and their optimisation. 44 | 45 | As such vuk is in active development, and will change in API and behaviour as we better understand the shape of the problem. With that being said, vuk is already usable to base projects off of - with the occasional refactoring. For support or feedback, please join the Discord server or use Github issues - we would be very happy to hear your thoughts! 46 | 47 | Indices and tables 48 | ================== 49 | 50 | * :ref:`genindex` 51 | -------------------------------------------------------------------------------- /include/vuk/Swapchain.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "vuk/Config.hpp" 4 | #include "vuk/Types.hpp" 5 | 6 | #include 7 | 8 | namespace vuk { 9 | enum class ColorSpaceKHR { 10 | eSrgbNonlinear = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, 11 | eDisplayP3NonlinearEXT = VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT, 12 | eExtendedSrgbLinearEXT = VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT, 13 | eDisplayP3LinearEXT = VK_COLOR_SPACE_DISPLAY_P3_LINEAR_EXT, 14 | eDciP3NonlinearEXT = VK_COLOR_SPACE_DCI_P3_NONLINEAR_EXT, 15 | eBt709LinearEXT = VK_COLOR_SPACE_BT709_LINEAR_EXT, 16 | eBt709NonlinearEXT = VK_COLOR_SPACE_BT709_NONLINEAR_EXT, 17 | eBt2020LinearEXT = VK_COLOR_SPACE_BT2020_LINEAR_EXT, 18 | eHdr10St2084EXT = VK_COLOR_SPACE_HDR10_ST2084_EXT, 19 | eDolbyvisionEXT = VK_COLOR_SPACE_DOLBYVISION_EXT, 20 | eHdr10HlgEXT = VK_COLOR_SPACE_HDR10_HLG_EXT, 21 | eAdobergbLinearEXT = VK_COLOR_SPACE_ADOBERGB_LINEAR_EXT, 22 | eAdobergbNonlinearEXT = VK_COLOR_SPACE_ADOBERGB_NONLINEAR_EXT, 23 | ePassThroughEXT = VK_COLOR_SPACE_PASS_THROUGH_EXT, 24 | eExtendedSrgbNonlinearEXT = VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT, 25 | eDisplayNativeAMD = VK_COLOR_SPACE_DISPLAY_NATIVE_AMD, 26 | eVkColorspaceSrgbNonlinear = VK_COLORSPACE_SRGB_NONLINEAR_KHR, 27 | eDciP3LinearEXT = VK_COLOR_SPACE_DCI_P3_LINEAR_EXT 28 | }; 29 | 30 | struct SurfaceFormatKHR { 31 | Format format = Format::eUndefined; 32 | ColorSpaceKHR colorSpace = ColorSpaceKHR::eSrgbNonlinear; 33 | 34 | operator VkSurfaceFormatKHR const&() const noexcept { 35 | return *reinterpret_cast(this); 36 | } 37 | 38 | operator VkSurfaceFormatKHR&() noexcept { 39 | return *reinterpret_cast(this); 40 | } 41 | bool operator==(SurfaceFormatKHR const& rhs) const noexcept { 42 | return (format == rhs.format) && (colorSpace == rhs.colorSpace); 43 | } 44 | 45 | bool operator!=(SurfaceFormatKHR const& rhs) const noexcept { 46 | return !operator==(rhs); 47 | } 48 | }; 49 | static_assert(sizeof(SurfaceFormatKHR) == sizeof(VkSurfaceFormatKHR), "struct and wrapper have different size!"); 50 | static_assert(std::is_standard_layout::value, "struct wrapper is not a standard layout!"); 51 | 52 | enum class PresentModeKHR { 53 | eImmediate = VK_PRESENT_MODE_IMMEDIATE_KHR, 54 | eMailbox = VK_PRESENT_MODE_MAILBOX_KHR, 55 | eFifo = VK_PRESENT_MODE_FIFO_KHR, 56 | eFifoRelaxed = VK_PRESENT_MODE_FIFO_RELAXED_KHR, 57 | eSharedDemandRefresh = VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR, 58 | eSharedContinuousRefresh = VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR 59 | }; 60 | 61 | struct Swapchain { 62 | VkSwapchainKHR swapchain; 63 | VkSurfaceKHR surface; 64 | 65 | vuk::Format format; 66 | vuk::Extent2D extent = { 0, 0 }; 67 | std::vector images; 68 | std::vector image_views; 69 | }; 70 | 71 | using SwapchainRef = Swapchain*; 72 | } // namespace vuk -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![vuk logo](https://github.com/martty/vuk/blob/master/vuk_logo.png) 2 | 3 | ### **vuk** - A rendergraph-based abstraction for Vulkan 4 | 5 | [![Discord Server](https://img.shields.io/discord/939539624039186432?style=for-the-badge)](https://discord.gg/UNkJMHgUmZ) 6 | [![Documentation](https://img.shields.io/readthedocs/vuk?style=for-the-badge)](https://vuk.readthedocs.io) 7 | [![CI](https://img.shields.io/github/actions/workflow/status/martty/vuk/cmake.yml?branch=master&style=for-the-badge)](https://github.com/martty/vuk/actions/workflows/cmake.yml) 8 | 9 | ### Quick Start 10 | 1. Grab the vuk repository 11 | 2. Compile the examples 12 | 3. Run the example browser and get a feel for the library 13 | ``` 14 | git clone http://github.com/martty/vuk 15 | cd vuk 16 | git submodule init 17 | git submodule update --recursive 18 | mkdir build 19 | cd build 20 | mkdir debug 21 | cd debug 22 | cmake ../.. -G Ninja -DVUK_BUILD_EXAMPLES=ON -DVUK_USE_DXC=OFF 23 | cmake --build . 24 | ./vuk_all_examples 25 | ``` 26 | (if building with a multi-config generator, do not make the `debug` folder) 27 | 28 | ### Overview of using **vuk** 29 | 1. Initialize your window(s) and Vulkan device 30 | 2. Create a `vuk::Context` object 31 | 3. Each frame: 32 | 1. Each frame, prepare high level description of your rendering, in the form of `vuk::Pass` 33 | 2. Bind concrete resources as inputs and outputs 34 | 3. Bind managed resources (temporary resources used by the rendergraph) 35 | 4. Record the execution your rendergraph into a command buffer 36 | 5. Submit and present 37 | 38 | ### What does **vuk** do 39 | - [x] Automatically deduces renderpasses, subpasses and framebuffers 40 | - [x] with all the synchronization handled for you 41 | - [x] including buffers 42 | - [x] images 43 | - [x] and rendertargets. 44 | - [x] for multiple queues 45 | - [ ] using fine grained synchronization when possible (events) 46 | - [x] Automatically transitions images into proper layouts 47 | - [x] for renderpasses 48 | - [x] and commands outside of renderpasses (eg. blitting). 49 | - [x] Automates pipeline creation with 50 | - [x] optionally compiling your shaders at runtime using shaderc 51 | - [x] pipeline layouts and 52 | - [x] descriptor set layouts 53 | - [x] by reflecting your shaders 54 | - [x] and deducing parameters based on renderpass and framebuffer. 55 | - [x] Automates resource binding with hashmaps, reducing descriptor set allocations and updates. 56 | - [x] Handles temporary allocations for a frame 57 | - [x] Handles long-term allocations with RAII handles 58 | - [x] Comes with lots of sugar to simplify common operations, but still exposing the full Vulkan interface: 59 | - [x] Matching viewport/scissor dimensions to attachment sizes 60 | - [x] Simplified vertex format specification 61 | - [x] Blend presets 62 | - [x] Directly writable mapped UBOs 63 | - [x] Automatic management of multisampling 64 | - [x] Helps debugging by naming the internal resources 65 | - [x] dear imgui integration code 66 | -------------------------------------------------------------------------------- /examples/blelloch_scan.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | #pragma shader_stage(compute) 3 | 4 | #extension GL_EXT_shared_memory_block : require 5 | #extension GL_EXT_buffer_reference : require 6 | #extension GL_EXT_buffer_reference2 : require 7 | #extension GL_EXT_shader_explicit_arithmetic_types_int64 : require 8 | #extension GL_KHR_shader_subgroup_basic : require 9 | 10 | layout(buffer_reference, std430, buffer_reference_align = 4) buffer bufUI { 11 | uint x; 12 | }; 13 | 14 | layout (push_constant) uniform PC { 15 | uint64_t source; 16 | uint64_t dst; 17 | uint64_t tmp; 18 | uint count; 19 | }; 20 | 21 | layout (local_size_x = 64) in; 22 | 23 | // https://developer.nvidia.com/gpugems/gpugems3/part-vi-gpu-computing/chapter-39-parallel-prefix-sum-scan-cuda 24 | const uint array_size = 128; 25 | shared TempUI { 26 | uint temp_ui[array_size]; 27 | }; 28 | 29 | uint linear_index(uvec3 id){ 30 | uvec3 mp = gl_NumWorkGroups * gl_WorkGroupSize; 31 | return id.z * mp.y * mp.x + id.y * mp.x + id.x * 1; 32 | } 33 | 34 | void scan_uint(uint n, uint64_t src, uint64_t dst, uint64_t tmp, uint gid) { 35 | uint thid = gl_LocalInvocationIndex; 36 | int offset = 1; 37 | bufUI g_idata_ui = bufUI(src); 38 | bufUI g_odata_ui = bufUI(dst); 39 | temp_ui[2*thid] = g_idata_ui[2*gid].x; // load input into shared memory 40 | temp_ui[2*thid+1] = g_idata_ui[2*gid+1].x; 41 | 42 | for (uint d = n>>1; d > 0; d >>= 1) { // build sum in place up the tree 43 | barrier(); 44 | if (thid < d) { 45 | uint ai = offset*(2*thid+1)-1; 46 | uint bi = offset*(2*thid+2)-1; 47 | temp_ui[bi] += temp_ui[ai]; 48 | } 49 | offset *= 2; 50 | } 51 | 52 | if (thid == 0) { 53 | if(tmp > 0) { 54 | bufUI g_temp = bufUI(tmp); 55 | g_temp[gl_WorkGroupID.x].x = temp_ui[n - 1]; 56 | } 57 | temp_ui[n - 1] = 0; // clear the last element 58 | } 59 | 60 | for (int d = 1; d < n; d *= 2){ // traverse down tree & build scan 61 | offset >>= 1; 62 | barrier(); 63 | if (thid < d) { 64 | uint ai = offset*(2*thid+1)-1; 65 | uint bi = offset*(2*thid+2)-1; 66 | uint t = temp_ui[ai]; 67 | temp_ui[ai] = temp_ui[bi]; 68 | temp_ui[bi] += t; 69 | } 70 | } 71 | barrier(); 72 | 73 | g_odata_ui[2*gid].x = temp_ui[2*thid]; // write results to device memory 74 | g_odata_ui[2*gid+1].x = temp_ui[2*thid+1]; 75 | } 76 | 77 | shared TempF { 78 | float temp_f[array_size]; 79 | }; 80 | 81 | void main() { 82 | bufUI wg_counter = bufUI(tmp); 83 | atomicExchange(wg_counter.x, 0); 84 | barrier(); 85 | uint gid = linear_index(gl_GlobalInvocationID); 86 | scan_uint(array_size, source, dst, tmp + 4, gid); 87 | if(gl_LocalInvocationIndex == 0){ 88 | if(atomicAdd(wg_counter.x, 1) != (gl_NumWorkGroups.x - 1)) 89 | return; 90 | } 91 | atomicExchange(wg_counter.x, 77); 92 | // surviving WG : needs loop 93 | scan_uint(count / array_size, tmp + 4, tmp + 4 + array_size * 4, 0, gl_LocalInvocationIndex); 94 | } -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | env: 6 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 7 | BUILD_TYPE: Release 8 | 9 | jobs: 10 | linux-build: 11 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 12 | # You can convert this to a matrix build if you need cross-platform coverage. 13 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | config: 20 | - { 21 | name: "Ubuntu GCC-11 Release", 22 | cc: "gcc-11", cxx: "g++-11" 23 | } 24 | - { 25 | name: "Ubuntu Clang-11 Release", 26 | cc: "clang-11", cxx: "clang++-11" 27 | } 28 | 29 | steps: 30 | - uses: actions/checkout@v3 31 | with: 32 | submodules: recursive 33 | 34 | - run: | 35 | sudo apt-get update 36 | sudo apt-get install -y xorg-dev gcc-11 g++-11 clang-11 37 | 38 | - name: Setup Vulkan SDK 39 | uses: sjcobb2022/setup-vulkan-sdk@c2612401009bbce8002630e838bf91cc67f8b3c3 40 | with: 41 | vulkan-query-version: 1.3.204.0 42 | vulkan-components: Vulkan-Headers, Vulkan-Loader, shaderc 43 | vulkan-use-cache: true 44 | 45 | - name: Configure CMake 46 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 47 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 48 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DVUK_BUILD_EXAMPLES=ON -DVUK_USE_DXC=OFF -DCMAKE_CXX_COMPILER=${{matrix.config.cxx}} -DCMAKE_C_COMPILER=${{matrix.config.cc}} 49 | 50 | - name: Build 51 | # Build your program with the given configuration 52 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 53 | 54 | windows-build: 55 | runs-on: windows-latest 56 | steps: 57 | - uses: actions/checkout@v3 58 | with: 59 | submodules: recursive 60 | 61 | - name: Setup Vulkan SDK 62 | uses: sjcobb2022/setup-vulkan-sdk@c2612401009bbce8002630e838bf91cc67f8b3c3 63 | with: 64 | vulkan-query-version: 1.3.204.0 65 | vulkan-components: Vulkan-Headers, Vulkan-Loader, shaderc 66 | vulkan-use-cache: true 67 | 68 | - name: Configure CMake 69 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 70 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 71 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DVUK_BUILD_EXAMPLES=ON -DVUK_USE_DXC=OFF 72 | 73 | - name: Build 74 | # Build your program with the given configuration 75 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 76 | -------------------------------------------------------------------------------- /include/vuk/VulkanPFNRequired.hpp: -------------------------------------------------------------------------------- 1 | // REQUIRED 2 | // 1.0 3 | VUK_X(vkCmdBindDescriptorSets) 4 | VUK_X(vkCmdBindIndexBuffer) 5 | VUK_X(vkCmdBindPipeline) 6 | VUK_X(vkCmdBindVertexBuffers) 7 | VUK_X(vkCmdBlitImage) 8 | VUK_X(vkCmdCopyImage) 9 | VUK_X(vkCmdClearColorImage) 10 | VUK_X(vkCmdClearDepthStencilImage) 11 | VUK_X(vkCmdCopyBuffer) 12 | VUK_X(vkCmdCopyBufferToImage) 13 | VUK_X(vkCmdCopyImageToBuffer) 14 | VUK_X(vkCmdFillBuffer) 15 | VUK_X(vkCmdUpdateBuffer) 16 | VUK_X(vkCmdResolveImage) 17 | VUK_X(vkCmdPipelineBarrier) 18 | VUK_X(vkCmdWriteTimestamp) 19 | VUK_X(vkCmdDraw) 20 | VUK_X(vkCmdDrawIndexed) 21 | VUK_X(vkCmdDrawIndexedIndirect) 22 | VUK_X(vkCmdDispatch) 23 | VUK_X(vkCmdDispatchIndirect) 24 | VUK_X(vkCmdPushConstants) 25 | VUK_X(vkCmdSetViewport) 26 | VUK_X(vkCmdSetScissor) 27 | VUK_X(vkCmdSetLineWidth) 28 | VUK_X(vkCmdSetDepthBias) 29 | VUK_X(vkCmdSetBlendConstants) 30 | VUK_X(vkCmdSetDepthBounds) 31 | 32 | VUK_Y(vkGetPhysicalDeviceProperties) 33 | 34 | VUK_X(vkCreateFramebuffer) 35 | VUK_X(vkDestroyFramebuffer) 36 | 37 | VUK_X(vkCreateCommandPool) 38 | VUK_X(vkResetCommandPool) 39 | VUK_X(vkDestroyCommandPool) 40 | 41 | VUK_X(vkAllocateCommandBuffers) 42 | VUK_X(vkBeginCommandBuffer) 43 | VUK_X(vkEndCommandBuffer) 44 | VUK_X(vkFreeCommandBuffers) 45 | 46 | VUK_X(vkCreateDescriptorPool) 47 | VUK_X(vkResetDescriptorPool) 48 | VUK_X(vkDestroyDescriptorPool) 49 | 50 | VUK_X(vkAllocateDescriptorSets) 51 | VUK_X(vkUpdateDescriptorSets) 52 | 53 | VUK_X(vkCreateGraphicsPipelines) 54 | VUK_X(vkCreateComputePipelines) 55 | VUK_X(vkDestroyPipeline) 56 | 57 | VUK_X(vkCreateQueryPool) 58 | VUK_X(vkGetQueryPoolResults) 59 | VUK_X(vkDestroyQueryPool) 60 | 61 | VUK_X(vkCreatePipelineCache) 62 | VUK_X(vkGetPipelineCacheData) 63 | VUK_X(vkDestroyPipelineCache) 64 | 65 | VUK_X(vkCreateRenderPass) 66 | VUK_X(vkCmdBeginRenderPass) 67 | VUK_X(vkCmdNextSubpass) 68 | VUK_X(vkCmdEndRenderPass) 69 | VUK_X(vkDestroyRenderPass) 70 | 71 | VUK_X(vkCreateSampler) 72 | VUK_X(vkDestroySampler) 73 | 74 | VUK_X(vkCreateShaderModule) 75 | VUK_X(vkDestroyShaderModule) 76 | 77 | VUK_X(vkCreateImageView) 78 | VUK_X(vkDestroyImageView) 79 | 80 | VUK_X(vkCreateDescriptorSetLayout) 81 | VUK_X(vkDestroyDescriptorSetLayout) 82 | 83 | VUK_X(vkCreatePipelineLayout) 84 | VUK_X(vkDestroyPipelineLayout) 85 | 86 | VUK_X(vkCreateFence) 87 | VUK_X(vkWaitForFences) 88 | VUK_X(vkDestroyFence) 89 | 90 | VUK_X(vkCreateSemaphore) 91 | VUK_X(vkWaitSemaphores) 92 | VUK_X(vkDestroySemaphore) 93 | 94 | VUK_X(vkQueueSubmit) 95 | VUK_X(vkDeviceWaitIdle) 96 | 97 | VUK_Y(vkGetPhysicalDeviceMemoryProperties) 98 | VUK_X(vkAllocateMemory) 99 | VUK_X(vkFreeMemory) 100 | VUK_X(vkMapMemory) 101 | VUK_X(vkUnmapMemory) 102 | VUK_X(vkFlushMappedMemoryRanges) 103 | VUK_X(vkInvalidateMappedMemoryRanges) 104 | VUK_X(vkBindBufferMemory) 105 | VUK_X(vkBindImageMemory) 106 | VUK_X(vkGetBufferMemoryRequirements) 107 | VUK_X(vkGetImageMemoryRequirements) 108 | VUK_X(vkCreateBuffer) 109 | VUK_X(vkDestroyBuffer) 110 | VUK_X(vkCreateImage) 111 | VUK_X(vkDestroyImage) 112 | 113 | // 1.1 114 | VUK_Y(vkGetPhysicalDeviceProperties2) 115 | 116 | // 1.2 117 | VUK_X(vkGetBufferDeviceAddress) 118 | VUK_X(vkCmdDrawIndexedIndirectCount) 119 | VUK_X(vkResetQueryPool) 120 | 121 | // sync2 or 1.3 122 | VUK_X(vkCmdPipelineBarrier2KHR) 123 | VUK_X(vkQueueSubmit2KHR) -------------------------------------------------------------------------------- /src/Descriptor.cpp: -------------------------------------------------------------------------------- 1 | #include "vuk/Descriptor.hpp" 2 | #include "vuk/Context.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace vuk { 9 | struct DescriptorPoolImpl { 10 | std::mutex grow_mutex; 11 | std::vector pools; 12 | uint32_t sets_allocated = 0; 13 | moodycamel::ConcurrentQueue free_sets{ 1024 }; 14 | }; 15 | 16 | DescriptorPool::DescriptorPool() : impl(new DescriptorPoolImpl) {} 17 | DescriptorPool::~DescriptorPool() { 18 | delete impl; 19 | } 20 | 21 | DescriptorPool::DescriptorPool(DescriptorPool&& o) noexcept { 22 | if (impl) { 23 | delete impl; 24 | } 25 | impl = o.impl; 26 | o.impl = nullptr; 27 | } 28 | 29 | void DescriptorPool::grow(Context& ctx, vuk::DescriptorSetLayoutAllocInfo layout_alloc_info) { 30 | if (!impl->grow_mutex.try_lock()) 31 | return; 32 | VkDescriptorPoolCreateInfo dpci{ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; 33 | dpci.maxSets = impl->sets_allocated == 0 ? 1 : impl->sets_allocated * 2; 34 | std::array descriptor_counts = {}; 35 | size_t count = ctx.vkCmdBuildAccelerationStructuresKHR ? descriptor_counts.size() : descriptor_counts.size() - 1; 36 | uint32_t used_idx = 0; 37 | for (size_t i = 0; i < count; i++) { 38 | if (layout_alloc_info.descriptor_counts[i] > 0) { 39 | auto& d = descriptor_counts[used_idx]; 40 | d.type = i == 11 ? VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR : VkDescriptorType(i); 41 | d.descriptorCount = layout_alloc_info.descriptor_counts[i] * dpci.maxSets; 42 | used_idx++; 43 | } 44 | } 45 | dpci.pPoolSizes = descriptor_counts.data(); 46 | dpci.poolSizeCount = used_idx; 47 | VkDescriptorPool pool; 48 | ctx.vkCreateDescriptorPool(ctx.device, &dpci, nullptr, &pool); 49 | impl->pools.emplace_back(pool); 50 | 51 | VkDescriptorSetAllocateInfo dsai{ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO }; 52 | dsai.descriptorPool = impl->pools.back(); 53 | dsai.descriptorSetCount = dpci.maxSets; 54 | std::vector layouts(dpci.maxSets, layout_alloc_info.layout); 55 | dsai.pSetLayouts = layouts.data(); 56 | // allocate all the descriptorsets 57 | std::vector sets(dsai.descriptorSetCount); 58 | ctx.vkAllocateDescriptorSets(ctx.device, &dsai, sets.data()); 59 | impl->free_sets.enqueue_bulk(sets.data(), sets.size()); 60 | impl->sets_allocated = dpci.maxSets; 61 | 62 | impl->grow_mutex.unlock(); 63 | } 64 | 65 | VkDescriptorSet DescriptorPool::acquire(Context& ctx, vuk::DescriptorSetLayoutAllocInfo layout_alloc_info) { 66 | VkDescriptorSet ds; 67 | while (!impl->free_sets.try_dequeue(ds)) { 68 | grow(ctx, layout_alloc_info); 69 | } 70 | return ds; 71 | } 72 | 73 | void DescriptorPool::release(VkDescriptorSet ds) { 74 | impl->free_sets.enqueue(ds); 75 | } 76 | 77 | void DescriptorPool::destroy(Context& ctx, VkDevice device) const { 78 | for (auto& p : impl->pools) { 79 | ctx.vkDestroyDescriptorPool(device, p, nullptr); 80 | } 81 | } 82 | 83 | SetBinding SetBinding::finalize(Bitset used_mask) { 84 | SetBinding final; 85 | final.used = used_mask; 86 | final.layout_info = layout_info; 87 | uint32_t mask = (uint32_t)used_mask.to_ulong(); 88 | for (size_t i = 0; i < VUK_MAX_BINDINGS; i++) { 89 | if ((mask & (1 << i)) == 0) { 90 | continue; 91 | } else { 92 | final.bindings[i] = bindings[i]; 93 | } 94 | } 95 | return final; 96 | } 97 | } // namespace vuk -------------------------------------------------------------------------------- /src/ContextImpl.hpp: -------------------------------------------------------------------------------- 1 | #include "Cache.hpp" 2 | #include "RenderPass.hpp" 3 | #include "vuk/Allocator.hpp" 4 | #include "vuk/Context.hpp" 5 | #include "vuk/PipelineInstance.hpp" 6 | #include "vuk/Query.hpp" 7 | #include "vuk/resources/DeviceVkResource.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace vuk { 18 | struct ContextImpl { 19 | template 20 | struct FN { 21 | static T create_fn(void* ctx, const create_info_t& ci) { 22 | return reinterpret_cast(ctx)->create(ci); 23 | } 24 | 25 | static void destroy_fn(void* ctx, const T& v) { 26 | return reinterpret_cast(ctx)->destroy(v); 27 | } 28 | }; 29 | 30 | VkDevice device; 31 | 32 | std::unique_ptr device_vk_resource; 33 | Allocator direct_allocator; 34 | 35 | Cache pipelinebase_cache; 36 | Cache pool_cache; 37 | Cache sampler_cache; 38 | Cache shader_modules; 39 | Cache descriptor_set_layouts; 40 | Cache pipeline_layouts; 41 | 42 | std::mutex begin_frame_lock; 43 | 44 | std::atomic frame_counter = 0; 45 | std::atomic unique_handle_id_counter = 0; 46 | 47 | std::mutex named_pipelines_lock; 48 | robin_hood::unordered_flat_map named_pipelines; 49 | 50 | std::atomic query_id_counter = 0; 51 | VkPhysicalDeviceProperties physical_device_properties; 52 | 53 | std::mutex swapchains_lock; 54 | plf::colony swapchains; 55 | 56 | std::mutex query_lock; 57 | robin_hood::unordered_map timestamp_result_map; 58 | 59 | void collect(uint64_t absolute_frame) { 60 | // collect rarer resources 61 | static constexpr uint32_t cache_collection_frequency = 16; 62 | auto remainder = absolute_frame % cache_collection_frequency; 63 | switch (remainder) { 64 | /*case 3: 65 | ptc.impl->sampler_cache.collect(cache_collection_frequency); break;*/ // sampler cache can't be collected due to persistent descriptor sets 66 | case 4: 67 | pipeline_layouts.collect(absolute_frame, cache_collection_frequency); 68 | break; 69 | /* case 5: 70 | pipelinebase_cache.collect(absolute_frame, cache_collection_frequency); 71 | break;*/ // can't be collected since we keep the pointer around in PipelineInfos 72 | case 6: 73 | pool_cache.collect(absolute_frame, cache_collection_frequency); 74 | break; 75 | } 76 | } 77 | 78 | ContextImpl(Context& ctx) : 79 | device(ctx.device), 80 | device_vk_resource(std::make_unique(ctx)), 81 | direct_allocator(*device_vk_resource.get()), 82 | pipelinebase_cache(&ctx, &FN::create_fn, &FN::destroy_fn), 83 | pool_cache(&ctx, &FN::create_fn, &FN::destroy_fn), 84 | sampler_cache(&ctx, &FN::create_fn, &FN::destroy_fn), 85 | shader_modules(&ctx, &FN::create_fn, &FN::destroy_fn), 86 | descriptor_set_layouts(&ctx, &FN::create_fn, &FN::destroy_fn), 87 | pipeline_layouts(&ctx, &FN::create_fn, &FN::destroy_fn) { 88 | ctx.vkGetPhysicalDeviceProperties(ctx.physical_device, &physical_device_properties); 89 | } 90 | }; 91 | } // namespace vuk 92 | -------------------------------------------------------------------------------- /include/vuk/ShortAlloc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // http://howardhinnant.github.io/stack_alloc.html 3 | // https://codereview.stackexchange.com/a/31575 4 | // but modified to use a heap arena 5 | #include 6 | #include 7 | #include 8 | 9 | class arena { 10 | static const std::size_t alignment = 16; 11 | std::size_t size_; 12 | char* buf_; 13 | char* ptr_; 14 | 15 | std::size_t align_up(std::size_t n) noexcept { 16 | return (n + (alignment - 1)) & ~(alignment - 1); 17 | } 18 | 19 | bool pointer_in_buffer(char* p) noexcept { 20 | return buf_ <= p && p <= buf_ + size_; 21 | } 22 | 23 | public: 24 | arena(std::size_t N) noexcept { 25 | buf_ = (char*)operator new[](N, (std::align_val_t{ alignment })); 26 | ptr_ = buf_; 27 | size_ = N; 28 | } 29 | ~arena() { 30 | ::operator delete[](buf_, std::align_val_t{ alignment }); 31 | ptr_ = nullptr; 32 | } 33 | arena(const arena& o) { 34 | size_ = o.size_; 35 | buf_ = (char*)operator new[](size_, (std::align_val_t{ alignment })); 36 | ptr_ = buf_; 37 | } 38 | arena& operator=(const arena& o) { 39 | ::operator delete[](buf_, std::align_val_t{ alignment }); 40 | size_ = o.size_; 41 | buf_ = (char*)operator new[](size_, (std::align_val_t{ alignment })); 42 | ptr_ = buf_; 43 | return *this; 44 | }; 45 | 46 | char* allocate(std::size_t n); 47 | void deallocate(char* p, std::size_t n) noexcept; 48 | 49 | std::size_t size() { 50 | return size_; 51 | } 52 | std::size_t used() const { 53 | return static_cast(ptr_ - buf_); 54 | } 55 | void reset() { 56 | ptr_ = buf_; 57 | } 58 | }; 59 | 60 | inline char* arena::allocate(std::size_t n) { 61 | assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena"); 62 | n = align_up(n); 63 | if (buf_ + size_ - ptr_ >= (int64_t)n) { 64 | char* r = ptr_; 65 | ptr_ += n; 66 | return r; 67 | } 68 | return static_cast(::operator new(n)); 69 | } 70 | 71 | inline void arena::deallocate(char* p, std::size_t n) noexcept { 72 | assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena"); 73 | if (pointer_in_buffer(p)) { 74 | n = align_up(n); 75 | if (p + n == ptr_) 76 | ptr_ = p; 77 | } else 78 | ::operator delete(p); 79 | } 80 | 81 | template 82 | class short_alloc { 83 | arena& a_; 84 | 85 | public: 86 | typedef T value_type; 87 | 88 | public: 89 | template 90 | struct rebind { 91 | typedef short_alloc<_Up, N> other; 92 | }; 93 | 94 | short_alloc(arena& a) : a_(a) {} 95 | template 96 | short_alloc(const short_alloc& a) noexcept : a_(a.a_) {} 97 | short_alloc(const short_alloc&) = default; 98 | short_alloc& operator=(const short_alloc&) = delete; 99 | 100 | T* allocate(std::size_t n) { 101 | return reinterpret_cast(a_.allocate(n * sizeof(T))); 102 | } 103 | void deallocate(T* p, std::size_t n) noexcept { 104 | a_.deallocate(reinterpret_cast(p), n * sizeof(T)); 105 | } 106 | 107 | template 108 | friend bool operator==(const short_alloc& x, const short_alloc& y) noexcept; 109 | 110 | template 111 | friend class short_alloc; 112 | }; 113 | 114 | template 115 | inline bool operator==(const short_alloc& x, const short_alloc& y) noexcept { 116 | return N == M && &x.a_ == &y.a_; 117 | } 118 | 119 | template 120 | inline bool operator!=(const short_alloc& x, const short_alloc& y) noexcept { 121 | return !(x == y); 122 | } 123 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7) 2 | project(vuk-examples) 3 | 4 | if(NOT VUK_USE_SHADERC) 5 | message(FATAL_ERROR "Building vuk examples require shaderc for building shaders, enable VUK_USE_SHADERC") 6 | endif() 7 | 8 | FetchContent_Declare( 9 | vk-bootstrap 10 | GIT_REPOSITORY https://github.com/charles-lunarg/vk-bootstrap 11 | GIT_TAG 8e61b2d81c3f5f84339735085ff5651f71bbe1e7 12 | ) 13 | FetchContent_MakeAvailable(vk-bootstrap) 14 | 15 | set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE) 16 | set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE) 17 | set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) 18 | FetchContent_Declare( 19 | glfw 20 | GIT_REPOSITORY https://github.com/glfw/glfw 21 | GIT_TAG 3.3.6 22 | ) 23 | FetchContent_MakeAvailable(glfw) 24 | 25 | FetchContent_Declare( 26 | glm 27 | GIT_REPOSITORY https://github.com/g-truc/glm 28 | GIT_TAG cc98465e3508535ba8c7f6208df934c156a018dc 29 | ) 30 | FetchContent_MakeAvailable(glm) 31 | 32 | set(TRACY_ENABLE ON) 33 | set(TRACY_ON_DEMAND OFF) 34 | FetchContent_Declare(tracy 35 | GIT_REPOSITORY https://github.com/wolfpld/tracy.git 36 | GIT_TAG master 37 | GIT_SHALLOW TRUE 38 | GIT_PROGRESS TRUE 39 | ) 40 | FetchContent_MakeAvailable(tracy) 41 | 42 | add_library(vuk-example-framework) 43 | 44 | SET(imgui_sources ../ext/imgui/imgui.cpp ../ext/imgui/imgui_draw.cpp ../ext/imgui/imgui_demo.cpp ../ext/imgui/imgui_widgets.cpp ../ext/imgui/imgui_tables.cpp ../ext/imgui/backends/imgui_impl_glfw.cpp) 45 | target_sources(vuk-example-framework PRIVATE imgui.cpp stbi.cpp ${imgui_sources}) 46 | target_include_directories(vuk-example-framework SYSTEM PUBLIC ../ext/stb ../ext/imgui) 47 | target_compile_definitions(vuk-example-framework PUBLIC GLM_FORCE_SIZE_FUNC GLM_FORCE_EXPLICIT_CTOR GLM_ENABLE_EXPERIMENTAL GLM_FORCE_RADIANS GLM_FORCE_DEPTH_ZERO_TO_ONE TRACY_VK_USE_SYMBOL_TABLE) 48 | if(VUK_COMPILER_CLANGPP OR VUK_COMPILER_GPP) 49 | target_compile_options(vuk-example-framework PUBLIC -std=c++20 -fno-char8_t) 50 | elseif(MSVC) 51 | target_compile_options(vuk-example-framework PUBLIC /std:c++20 /permissive- /Zc:char8_t-) 52 | endif() 53 | target_link_libraries(vuk-example-framework PUBLIC vuk vk-bootstrap glfw glm TracyClient) 54 | 55 | add_executable(vuk_all_examples) 56 | target_sources(vuk_all_examples PRIVATE example_browser.cpp) 57 | target_link_libraries(vuk_all_examples PRIVATE vuk-example-framework) 58 | target_compile_definitions(vuk_all_examples PUBLIC VUK_EX_PATH_TGT="$" VUK_EX_PATH_ROOT="${CMAKE_SOURCE_DIR}") 59 | 60 | add_library(vuk_example_runner_single OBJECT example_runner_single.cpp) 61 | target_link_libraries(vuk_example_runner_single PRIVATE vuk-example-framework) 62 | target_compile_definitions(vuk-example-framework PUBLIC VUK_EX_PATH_TGT="$" VUK_EX_PATH_ROOT="${CMAKE_SOURCE_DIR}") 63 | 64 | function(ADD_EXAMPLE name) 65 | set(FULL_NAME "vuk_example_${name}") 66 | add_executable(${FULL_NAME}) 67 | 68 | add_library(${FULL_NAME}_obj OBJECT "${name}.cpp") 69 | target_link_libraries(${FULL_NAME}_obj PUBLIC vuk-example-framework) 70 | target_link_libraries(${FULL_NAME} PRIVATE ${FULL_NAME}_obj vuk_example_runner_single) 71 | target_link_libraries(vuk_all_examples PRIVATE ${FULL_NAME}_obj) 72 | endfunction(ADD_EXAMPLE) 73 | 74 | ADD_EXAMPLE(01_triangle) 75 | ADD_EXAMPLE(02_cube) 76 | ADD_EXAMPLE(03_multipass) 77 | ADD_EXAMPLE(04_texture) 78 | ADD_EXAMPLE(05_deferred) 79 | ADD_EXAMPLE(06_msaa) 80 | ADD_EXAMPLE(07_commands) 81 | ADD_EXAMPLE(08_pipelined_compute) 82 | ADD_EXAMPLE(09_persistent_descriptorset) 83 | ADD_EXAMPLE(10_baby_renderer) 84 | ADD_EXAMPLE(11_composition) 85 | ADD_EXAMPLE(12_rt_pipeline) 86 | -------------------------------------------------------------------------------- /include/vuk/ImageAttachment.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "vuk/Buffer.hpp" 4 | #include "vuk/Image.hpp" 5 | #include "vuk/vuk_fwd.hpp" 6 | #include 7 | 8 | namespace vuk { 9 | struct ImageAttachment { 10 | Image image = {}; 11 | ImageView image_view = {}; 12 | 13 | ImageCreateFlags image_flags = {}; 14 | ImageType image_type = ImageType::e2D; 15 | ImageTiling tiling = ImageTiling::eOptimal; 16 | ImageUsageFlags usage = ImageUsageFlagBits::eInfer; 17 | Dimension3D extent = Dimension3D::framebuffer(); 18 | Format format = Format::eUndefined; 19 | Samples sample_count = Samples::eInfer; 20 | bool allow_srgb_unorm_mutable = false; 21 | ImageViewCreateFlags image_view_flags = {}; 22 | ImageViewType view_type = ImageViewType::eInfer; 23 | ComponentMapping components; 24 | 25 | uint32_t base_level = VK_REMAINING_MIP_LEVELS; 26 | uint32_t level_count = VK_REMAINING_MIP_LEVELS; 27 | 28 | uint32_t base_layer = VK_REMAINING_ARRAY_LAYERS; 29 | uint32_t layer_count = VK_REMAINING_ARRAY_LAYERS; 30 | 31 | bool operator==(const ImageAttachment&) const = default; 32 | 33 | static ImageAttachment from_texture(const vuk::Texture& t) { 34 | return ImageAttachment{ .image = t.image.get(), 35 | .image_view = t.view.get(), 36 | .extent = { Sizing::eAbsolute, { t.extent.width, t.extent.height, t.extent.depth } }, 37 | .format = t.format, 38 | .sample_count = { t.sample_count }, 39 | .base_level = 0, 40 | .level_count = t.level_count, 41 | .base_layer = 0, 42 | .layer_count = t.layer_count }; 43 | } 44 | 45 | constexpr bool has_concrete_image() const noexcept { 46 | return image != Image{}; 47 | } 48 | 49 | constexpr bool has_concrete_image_view() const noexcept { 50 | return image_view != ImageView{}; 51 | } 52 | 53 | constexpr bool may_require_image_view() const noexcept { 54 | return usage == ImageUsageFlagBits::eInfer || 55 | (usage & (ImageUsageFlagBits::eColorAttachment | ImageUsageFlagBits::eDepthStencilAttachment | ImageUsageFlagBits::eSampled | 56 | ImageUsageFlagBits::eStorage | ImageUsageFlagBits::eInputAttachment)) != ImageUsageFlags{}; 57 | } 58 | 59 | constexpr bool is_fully_known() const noexcept { 60 | return image_type != ImageType::eInfer && usage != ImageUsageFlagBits::eInfer && extent.sizing != Sizing::eRelative && extent.extent.width != 0 && 61 | extent.extent.height != 0 && extent.extent.depth != 0 && format != Format::eUndefined && sample_count != Samples::eInfer && 62 | base_level != VK_REMAINING_MIP_LEVELS && level_count != VK_REMAINING_MIP_LEVELS && base_layer != VK_REMAINING_ARRAY_LAYERS && 63 | layer_count != VK_REMAINING_ARRAY_LAYERS && (!may_require_image_view() || view_type != ImageViewType::eInfer); 64 | } 65 | }; 66 | 67 | struct QueueResourceUse { 68 | PipelineStageFlags stages; 69 | AccessFlags access; 70 | ImageLayout layout; // ignored for buffers 71 | DomainFlags domain = DomainFlagBits::eAny; 72 | }; 73 | 74 | union Subrange { 75 | struct Image { 76 | uint32_t base_layer = 0; 77 | uint32_t base_level = 0; 78 | 79 | uint32_t layer_count = VK_REMAINING_ARRAY_LAYERS; 80 | uint32_t level_count = VK_REMAINING_MIP_LEVELS; 81 | 82 | constexpr auto operator<=>(const Image& o) const noexcept = default; 83 | } image = {}; 84 | struct Buffer { 85 | uint64_t offset = 0; 86 | uint64_t size = VK_WHOLE_SIZE; 87 | 88 | constexpr bool operator==(const Buffer& o) const noexcept { 89 | return offset == o.offset && size == o.size; 90 | } 91 | } buffer; 92 | }; 93 | } // namespace vuk -------------------------------------------------------------------------------- /examples/imgui_vert.hpp: -------------------------------------------------------------------------------- 1 | // 1112.2.0 2 | #pragma once 3 | const uint32_t imgui_vert[] = { 4 | 0x07230203,0x00010000,0x0008000b,0x0000002e,0x00000000,0x00020011,0x00000001,0x0006000b, 5 | 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, 6 | 0x000a000f,0x00000000,0x00000004,0x6e69616d,0x00000000,0x0000000b,0x0000000f,0x00000015, 7 | 0x0000001b,0x0000001c,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, 8 | 0x00000000,0x00030005,0x00000009,0x00000000,0x00050006,0x00000009,0x00000000,0x6f6c6f43, 9 | 0x00000072,0x00040006,0x00000009,0x00000001,0x00005655,0x00030005,0x0000000b,0x0074754f, 10 | 0x00040005,0x0000000f,0x6c6f4361,0x0000726f,0x00030005,0x00000015,0x00565561,0x00060005, 11 | 0x00000019,0x505f6c67,0x65567265,0x78657472,0x00000000,0x00060006,0x00000019,0x00000000, 12 | 0x505f6c67,0x7469736f,0x006e6f69,0x00030005,0x0000001b,0x00000000,0x00040005,0x0000001c, 13 | 0x736f5061,0x00000000,0x00060005,0x0000001e,0x73755075,0x6e6f4368,0x6e617473,0x00000074, 14 | 0x00050006,0x0000001e,0x00000000,0x61635375,0x0000656c,0x00060006,0x0000001e,0x00000001, 15 | 0x61725475,0x616c736e,0x00006574,0x00030005,0x00000020,0x00006370,0x00040047,0x0000000b, 16 | 0x0000001e,0x00000000,0x00040047,0x0000000f,0x0000001e,0x00000002,0x00040047,0x00000015, 17 | 0x0000001e,0x00000001,0x00050048,0x00000019,0x00000000,0x0000000b,0x00000000,0x00030047, 18 | 0x00000019,0x00000002,0x00040047,0x0000001c,0x0000001e,0x00000000,0x00050048,0x0000001e, 19 | 0x00000000,0x00000023,0x00000000,0x00050048,0x0000001e,0x00000001,0x00000023,0x00000008, 20 | 0x00030047,0x0000001e,0x00000002,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002, 21 | 0x00030016,0x00000006,0x00000020,0x00040017,0x00000007,0x00000006,0x00000004,0x00040017, 22 | 0x00000008,0x00000006,0x00000002,0x0004001e,0x00000009,0x00000007,0x00000008,0x00040020, 23 | 0x0000000a,0x00000003,0x00000009,0x0004003b,0x0000000a,0x0000000b,0x00000003,0x00040015, 24 | 0x0000000c,0x00000020,0x00000001,0x0004002b,0x0000000c,0x0000000d,0x00000000,0x00040020, 25 | 0x0000000e,0x00000001,0x00000007,0x0004003b,0x0000000e,0x0000000f,0x00000001,0x00040020, 26 | 0x00000011,0x00000003,0x00000007,0x0004002b,0x0000000c,0x00000013,0x00000001,0x00040020, 27 | 0x00000014,0x00000001,0x00000008,0x0004003b,0x00000014,0x00000015,0x00000001,0x00040020, 28 | 0x00000017,0x00000003,0x00000008,0x0003001e,0x00000019,0x00000007,0x00040020,0x0000001a, 29 | 0x00000003,0x00000019,0x0004003b,0x0000001a,0x0000001b,0x00000003,0x0004003b,0x00000014, 30 | 0x0000001c,0x00000001,0x0004001e,0x0000001e,0x00000008,0x00000008,0x00040020,0x0000001f, 31 | 0x00000009,0x0000001e,0x0004003b,0x0000001f,0x00000020,0x00000009,0x00040020,0x00000021, 32 | 0x00000009,0x00000008,0x0004002b,0x00000006,0x00000028,0x00000000,0x0004002b,0x00000006, 33 | 0x00000029,0x3f800000,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8, 34 | 0x00000005,0x0004003d,0x00000007,0x00000010,0x0000000f,0x00050041,0x00000011,0x00000012, 35 | 0x0000000b,0x0000000d,0x0003003e,0x00000012,0x00000010,0x0004003d,0x00000008,0x00000016, 36 | 0x00000015,0x00050041,0x00000017,0x00000018,0x0000000b,0x00000013,0x0003003e,0x00000018, 37 | 0x00000016,0x0004003d,0x00000008,0x0000001d,0x0000001c,0x00050041,0x00000021,0x00000022, 38 | 0x00000020,0x0000000d,0x0004003d,0x00000008,0x00000023,0x00000022,0x00050085,0x00000008, 39 | 0x00000024,0x0000001d,0x00000023,0x00050041,0x00000021,0x00000025,0x00000020,0x00000013, 40 | 0x0004003d,0x00000008,0x00000026,0x00000025,0x00050081,0x00000008,0x00000027,0x00000024, 41 | 0x00000026,0x00050051,0x00000006,0x0000002a,0x00000027,0x00000000,0x00050051,0x00000006, 42 | 0x0000002b,0x00000027,0x00000001,0x00070050,0x00000007,0x0000002c,0x0000002a,0x0000002b, 43 | 0x00000028,0x00000029,0x00050041,0x00000011,0x0000002d,0x0000001b,0x0000000d,0x0003003e, 44 | 0x0000002d,0x0000002c,0x000100fd,0x00010038 45 | }; 46 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | import subprocess, os 18 | 19 | def configureDoxyfile(input_dir, output_dir): 20 | with open('Doxyfile.in', 'r') as file : 21 | filedata = file.read() 22 | 23 | filedata = filedata.replace('@DOXYGEN_INPUT_DIR@', input_dir) 24 | filedata = filedata.replace('@DOXYGEN_OUTPUT_DIR@', output_dir) 25 | 26 | with open('Doxyfile', 'w') as file: 27 | file.write(filedata) 28 | 29 | # Check if we're running on Read the Docs' servers 30 | read_the_docs_build = os.environ.get('READTHEDOCS', None) == 'True' 31 | 32 | # -- Project information ----------------------------------------------------- 33 | 34 | project = 'vuk' 35 | copyright = '2022, Marcell Kiss' 36 | author = 'Marcell Kiss' 37 | 38 | 39 | # -- General configuration --------------------------------------------------- 40 | 41 | # Add any Sphinx extension module names here, as strings. They can be 42 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 43 | # ones. 44 | extensions = [ 45 | 'sphinx.ext.autodoc', 46 | 'sphinx.ext.intersphinx', 47 | 'sphinx.ext.autosectionlabel', 48 | 'sphinx.ext.todo', 49 | 'sphinx.ext.coverage', 50 | 'sphinx.ext.mathjax', 51 | 'sphinx.ext.ifconfig', 52 | 'sphinx.ext.viewcode', 53 | 'sphinx.ext.inheritance_diagram', 54 | "breathe" 55 | ] 56 | 57 | # Add any paths that contain templates here, relative to this directory. 58 | templates_path = ['_templates'] 59 | 60 | # List of patterns, relative to source directory, that match files and 61 | # directories to ignore when looking for source files. 62 | # This pattern also affects html_static_path and html_extra_path. 63 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 64 | 65 | 66 | # -- Options for HTML output ------------------------------------------------- 67 | 68 | # The theme to use for HTML and HTML Help pages. See the documentation for 69 | # a list of builtin themes. 70 | # 71 | html_theme = 'sphinx_rtd_theme' 72 | 73 | # Add any paths that contain custom static files (such as style sheets) here, 74 | # relative to this directory. They are copied after the builtin static files, 75 | # so a file named "default.css" will overwrite the builtin "default.css". 76 | html_static_path = ['_static'] 77 | 78 | html_theme_options = { 79 | 'canonical_url': '', 80 | 'analytics_id': '', # Provided by Google in your dashboard 81 | 'display_version': True, 82 | 'prev_next_buttons_location': 'bottom', 83 | 'style_external_links': False, 84 | 85 | 'logo_only': False, 86 | 87 | # Toc options 88 | 'collapse_navigation': True, 89 | 'sticky_navigation': True, 90 | 'navigation_depth': 4, 91 | 'includehidden': True, 92 | 'titles_only': False 93 | } 94 | # html_logo = '' 95 | # github_url = '' 96 | # html_baseurl = '' 97 | 98 | # Breathe Configuration 99 | breathe_default_project = "vuk" 100 | breathe_domain_by_extension = {"h" : "cpp"} 101 | 102 | breathe_projects = {} 103 | 104 | if read_the_docs_build: 105 | input_dir = '../include/vuk ../include/vuk/resources' 106 | output_dir = 'build' 107 | configureDoxyfile(input_dir, output_dir) 108 | subprocess.call('doxygen', shell=True) 109 | breathe_projects['vuk'] = output_dir + '/xml' -------------------------------------------------------------------------------- /examples/01_triangle.cpp: -------------------------------------------------------------------------------- 1 | #include "example_runner.hpp" 2 | 3 | /* 01_triangle 4 | * In this example we will draw a bufferless triangle, the "Hello world" of graphics programming 5 | * For this, we will need to define our pipeline, and then submit a draw. 6 | * 7 | * These examples are powered by the example framework, which hides some of the code required, as that would be repeated for each example. 8 | * Furthermore it allows launching individual examples and all examples with the example same code. 9 | * Check out the framework (example_runner_*) files if interested! 10 | */ 11 | 12 | namespace { 13 | vuk::Example x{ 14 | // The display name of this example 15 | .name = "01_triangle", 16 | // Setup code, ran once in the beginning 17 | .setup = 18 | [](vuk::ExampleRunner& runner, vuk::Allocator& allocator) { 19 | // Pipelines are created by filling out a vuk::PipelineCreateInfo 20 | // In this case, we only need the shaders, we don't care about the rest of the state 21 | vuk::PipelineBaseCreateInfo pci; 22 | pci.add_glsl(util::read_entire_file((root / "examples/triangle.vert").generic_string()), (root / "examples/triangle.vert").generic_string()); 23 | pci.add_glsl(util::read_entire_file((root / "examples/triangle.frag").generic_string()), (root / "examples/triangle.frag").generic_string()); 24 | // The pipeline is stored with a user give name for simplicity 25 | runner.context->create_named_pipeline("triangle", pci); 26 | }, 27 | // Code ran every frame 28 | .render = 29 | [](vuk::ExampleRunner& runner, vuk::Allocator& frame_allocator, vuk::Future target) { 30 | // We start building a rendergraph 31 | vuk::RenderGraph rg("01"); 32 | // The framework provides us with an image to render to in "target" 33 | // We attach this to the rendergraph named as "01_triangle" 34 | rg.attach_in("01_triangle", std::move(target)); 35 | // The rendergraph is composed of passes (vuk::Pass) 36 | // Each pass declares which resources are used 37 | // And it provides a callback which is executed when this pass is being ran 38 | rg.add_pass({ // For this example, only a color image is needed to write to (our framebuffer) 39 | // The name is declared, and the way it will be used in the pass (color attachment - write) 40 | .resources = { "01_triangle"_image >> vuk::eColorWrite >> "01_triangle_final" }, 41 | .execute = [](vuk::CommandBuffer& command_buffer) { 42 | // Here commands are recorded into the command buffer for rendering 43 | // The commands frequently mimick the Vulkan counterpart, with additional sugar 44 | // The additional sugar is enabled by having a complete view of the rendering 45 | 46 | // Set the viewport to cover the entire framebuffer 47 | command_buffer.set_viewport(0, vuk::Rect2D::framebuffer()); 48 | // Set the scissor area to cover the entire framebuffer 49 | command_buffer.set_scissor(0, vuk::Rect2D::framebuffer()); 50 | command_buffer 51 | .set_rasterization({}) // Set the default rasterization state 52 | .set_color_blend("01_triangle", {}) // Set the default color blend state 53 | .bind_graphics_pipeline("triangle") // Recall pipeline for "triangle" and bind 54 | .draw(3, 1, 0, 0); // Draw 3 vertices 55 | } }); 56 | 57 | // The rendergraph is given to a Future, which takes ownership and binds to the result ("01_triangle_final") 58 | // The example framework takes care of the busywork (submission, presenting) 59 | return vuk::Future{ std::make_unique(std::move(rg)), "01_triangle_final" }; 60 | } 61 | }; 62 | 63 | REGISTER_EXAMPLE(x); 64 | } // namespace -------------------------------------------------------------------------------- /src/Name.cpp: -------------------------------------------------------------------------------- 1 | #include "vuk/Name.hpp" 2 | #include "vuk/Hash.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace { 12 | struct Intern { 13 | static constexpr size_t arr_siz = 2048; 14 | 15 | const char* add(std::string_view s) { 16 | auto hash = hash::fnv1a::hash(s.data(), (uint32_t)s.size(), hash::fnv1a::default_offset_basis); 17 | { 18 | std::shared_lock _(lock); 19 | if (auto it = map.find(hash); it != map.end()) { 20 | return it->second; 21 | } 22 | } 23 | 24 | std::unique_lock _(lock); 25 | // second lookup, under a unique lock, so there are no races 26 | if (auto it = map.find(hash); it != map.end()) { 27 | return it->second; 28 | } 29 | 30 | for (auto& [size, bf] : buffers) { 31 | auto buffer = std::string_view(bf->data(), bf->size()); 32 | auto pos = buffer.find(s); 33 | while (pos != std::string::npos && buffer[pos + s.size()] != '\0') { 34 | pos = buffer.find(s, pos + 1); 35 | } 36 | if (pos == std::string_view::npos) { 37 | if ((size + s.size() + 1) < bf->size()) { 38 | auto osize = size; 39 | s.copy(bf->data() + size, s.size()); 40 | *(bf->data() + size + s.size()) = '\0'; 41 | size += s.size() + 1; 42 | map.emplace(hash, bf->data() + osize); 43 | return bf->data() + osize; 44 | } 45 | } else { // for returning tail substrings 46 | map.emplace(hash, bf->data() + pos); 47 | return bf->data() + pos; 48 | } 49 | } 50 | buffers.resize(buffers.size() + 1); 51 | auto& [nsize, nbuf] = buffers.back(); 52 | nbuf = new std::array{}; 53 | s.copy(nbuf->data(), s.size()); 54 | *(nbuf->data() + s.size()) = '\0'; 55 | nsize += s.size() + 1; 56 | map.emplace(hash, nbuf->data()); 57 | return nbuf->data(); 58 | } 59 | 60 | Intern() { 61 | buffers.resize(1); 62 | buffers[0].first = 1; 63 | buffers[0].second = new std::array; 64 | buffers[0].second->at(0) = '\0'; 65 | } 66 | 67 | // to store the strings 68 | std::vector*>> buffers; 69 | robin_hood::unordered_flat_map map; 70 | std::shared_mutex lock; 71 | }; 72 | 73 | Intern& g_intern() { 74 | static Intern intern; 75 | return intern; 76 | } 77 | } // namespace 78 | 79 | namespace vuk { 80 | Name::Name(const char* str) noexcept { 81 | id = g_intern().add(str); 82 | } 83 | 84 | Name::Name(std::string_view str) noexcept { 85 | id = g_intern().add(str); 86 | } 87 | 88 | std::string_view Name::to_sv() const noexcept { 89 | return id; 90 | } 91 | 92 | bool Name::is_invalid() const noexcept { 93 | return id == &invalid_value[0]; 94 | } 95 | 96 | Name Name::append(std::string_view other) const noexcept { 97 | auto ourlen = strlen(id); 98 | auto theirlen = other.size(); 99 | auto hash = hash::fnv1a::hash(id, (uint32_t)ourlen, hash::fnv1a::default_offset_basis); 100 | hash = hash::fnv1a::hash(other.data(), (uint32_t)theirlen, hash); 101 | 102 | // speculative 103 | { 104 | std::shared_lock _(g_intern().lock); 105 | if (auto it = g_intern().map.find(hash); it != g_intern().map.end()) { 106 | Name n; 107 | n.id = it->second; 108 | return n; 109 | } 110 | } 111 | 112 | std::string app; 113 | app.reserve(ourlen + theirlen); 114 | app.append(id); 115 | app.append(other); 116 | return Name(app); 117 | } 118 | } // namespace vuk 119 | 120 | namespace std { 121 | size_t hash::operator()(vuk::Name const& s) const { 122 | return hash()(s.id); 123 | } 124 | 125 | size_t hash::operator()(vuk::QualifiedName const& s) const { 126 | uint32_t h = (uint32_t)hash()(s.prefix); 127 | ::hash_combine_direct(h, (uint32_t)hash()(s.name)); 128 | return h; 129 | } 130 | } // namespace std -------------------------------------------------------------------------------- /benchmarks/draw_overhead.cpp: -------------------------------------------------------------------------------- 1 | #include "bench_runner.hpp" 2 | 3 | /* 01_triangle 4 | * In this example we will draw a bufferless triangle, the "Hello world" of graphics programming 5 | * For this, we will need to define our pipeline, and then submit a draw. 6 | * 7 | * These examples are powered by the example framework, which hides some of the code required, as that would be repeated for each example. 8 | * Furthermore it allows launching individual examples and all examples with the example same code. 9 | * Check out the framework (example_runner_*) files if interested! 10 | */ 11 | 12 | namespace { 13 | struct V1 { 14 | std::string_view description = "1 iter"; 15 | static constexpr unsigned n_iters = 1; 16 | }; 17 | 18 | struct V2 { 19 | std::string_view description = "100 iters"; 20 | static constexpr unsigned n_iters = 100; 21 | }; 22 | 23 | vuk::Bench x{ 24 | // The display name of this example 25 | .base = { .name = "Dependent vs. non-dependent texture fetch", 26 | // Setup code, ran once in the beginning 27 | .setup = 28 | [](vuk::BenchRunner& runner, vuk::Allocator& frame_allocator) { 29 | // Pipelines are created by filling out a vuk::PipelineCreateInfo 30 | // In this case, we only need the shaders, we don't care about the rest of the state 31 | vuk::PipelineBaseCreateInfo pci; 32 | pci.add_glsl(util::read_entire_file("../../examples/triangle.vert"), "triangle.vert"); 33 | pci.add_glsl(util::read_entire_file("../../examples/triangle.frag"), "triangle.frag"); 34 | // The pipeline is stored with a user give name for simplicity 35 | runner.context->create_named_pipeline("triangle", pci); 36 | }, 37 | .gui = 38 | [](vuk::BenchRunner& runner, vuk::Allocator& frame_allocator) { 39 | } }, 40 | .cases = { { "Dependent, small image", 41 | [](vuk::BenchRunner& runner, vuk::Allocator& frame_allocator, vuk::Query start, vuk::Query end, auto&& parameters) { 42 | auto ptc = ifc.begin(); 43 | vuk::RenderGraph rg; 44 | rg.add_pass({ .resources = { "_final"_image(vuk::eColorWrite) }, .execute = [start, end, parameters](vuk::CommandBuffer& command_buffer) { 45 | vuk::TimedScope _{ command_buffer, start, end }; 46 | command_buffer.set_viewport(0, vuk::Rect2D::framebuffer()); 47 | command_buffer 48 | .set_scissor(0, vuk::Rect2D::framebuffer()) // Set the scissor area to cover the entire framebuffer 49 | .bind_graphics_pipeline("triangle") // Recall pipeline for "triangle" and bind 50 | .draw(3 * parameters.n_iters, 1, 0, 0); // Draw 3 vertices 51 | } }); 52 | return rg; 53 | } }, 54 | { "Non-dependent, small image", 55 | [](vuk::BenchRunner& runner, vuk::Allocator& frame_allocator, vuk::Query start, vuk::Query end, auto&& parameters) { 56 | auto ptc = ifc.begin(); 57 | vuk::RenderGraph rg; 58 | rg.add_pass({ .resources = { "_final"_image(vuk::eColorWrite) }, .execute = [start, end, parameters](vuk::CommandBuffer& command_buffer) { 59 | vuk::TimedScope _{ command_buffer, start, end }; 60 | command_buffer.set_viewport(0, vuk::Rect2D::framebuffer()); 61 | command_buffer.set_scissor(0, vuk::Rect2D::framebuffer()).bind_graphics_pipeline("triangle"); 62 | for (auto i = 0; i < parameters.n_iters; i++) { 63 | command_buffer.draw(3, 1, 0, 0); 64 | } 65 | } }); 66 | return rg; 67 | } } } 68 | }; 69 | 70 | REGISTER_BENCH(x); 71 | } // namespace -------------------------------------------------------------------------------- /src/RenderPass.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Cache.hpp" 4 | #include "CreateInfo.hpp" 5 | #include "vuk/Image.hpp" 6 | #include "vuk/ShortAlloc.hpp" 7 | #include "vuk/Types.hpp" 8 | #include "vuk/vuk_fwd.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | inline bool operator==(VkAttachmentDescription const& lhs, VkAttachmentDescription const& rhs) noexcept { 15 | return (lhs.flags == rhs.flags) && (lhs.format == rhs.format) && (lhs.samples == rhs.samples) && (lhs.loadOp == rhs.loadOp) && (lhs.storeOp == rhs.storeOp) && 16 | (lhs.stencilLoadOp == rhs.stencilLoadOp) && (lhs.stencilStoreOp == rhs.stencilStoreOp) && (lhs.initialLayout == rhs.initialLayout) && 17 | (lhs.finalLayout == rhs.finalLayout); 18 | } 19 | 20 | inline bool operator==(VkSubpassDependency const& lhs, VkSubpassDependency const& rhs) noexcept { 21 | return (lhs.srcSubpass == rhs.srcSubpass) && (lhs.dstSubpass == rhs.dstSubpass) && (lhs.srcStageMask == rhs.srcStageMask) && 22 | (lhs.dstStageMask == rhs.dstStageMask) && (lhs.srcAccessMask == rhs.srcAccessMask) && (lhs.dstAccessMask == rhs.dstAccessMask) && 23 | (lhs.dependencyFlags == rhs.dependencyFlags); 24 | } 25 | 26 | inline bool operator==(VkAttachmentReference const& lhs, VkAttachmentReference const& rhs) noexcept { 27 | return (lhs.attachment == rhs.attachment) && (lhs.layout == rhs.layout); 28 | } 29 | 30 | namespace vuk { 31 | struct SubpassDescription : public VkSubpassDescription { 32 | SubpassDescription() : VkSubpassDescription{} {} 33 | bool operator==(const SubpassDescription& o) const noexcept { 34 | return std::tie(flags, pipelineBindPoint) == std::tie(o.flags, o.pipelineBindPoint); 35 | } 36 | }; 37 | 38 | struct RenderPassCreateInfo : public VkRenderPassCreateInfo { 39 | RenderPassCreateInfo() : VkRenderPassCreateInfo{ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO } {} 40 | std::vector attachments; 41 | std::vector subpass_descriptions; 42 | std::vector subpass_dependencies; 43 | std::vector color_refs; 44 | std::vector resolve_refs; 45 | std::vector> ds_refs; 46 | std::vector color_ref_offsets; 47 | 48 | bool operator==(const RenderPassCreateInfo& o) const noexcept { 49 | return std::forward_as_tuple(flags, attachments, subpass_descriptions, subpass_dependencies, color_refs, color_ref_offsets, ds_refs, resolve_refs) == 50 | std::forward_as_tuple( 51 | o.flags, o.attachments, o.subpass_descriptions, o.subpass_dependencies, o.color_refs, o.color_ref_offsets, o.ds_refs, o.resolve_refs); 52 | } 53 | }; 54 | 55 | template<> 56 | struct create_info { 57 | using type = vuk::RenderPassCreateInfo; 58 | }; 59 | 60 | struct FramebufferCreateInfo : public VkFramebufferCreateInfo { 61 | FramebufferCreateInfo() : VkFramebufferCreateInfo{ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO } {} 62 | std::vector attachments; 63 | vuk::Samples sample_count = vuk::Samples::eInfer; 64 | 65 | bool operator==(const FramebufferCreateInfo& o) const noexcept { 66 | return std::tie(flags, attachments, width, height, renderPass, layers, sample_count) == 67 | std::tie(o.flags, o.attachments, o.width, o.height, o.renderPass, o.layers, o.sample_count); 68 | } 69 | }; 70 | 71 | template<> 72 | struct create_info { 73 | using type = vuk::FramebufferCreateInfo; 74 | }; 75 | } // namespace vuk 76 | 77 | namespace std { 78 | template<> 79 | struct hash { 80 | size_t operator()(vuk::SubpassDescription const& x) const noexcept { 81 | size_t h = 0; 82 | hash_combine(h, x.flags, x.pipelineBindPoint); 83 | return h; 84 | } 85 | }; 86 | 87 | template<> 88 | struct hash { 89 | size_t operator()(vuk::RenderPassCreateInfo const& x) const noexcept { 90 | size_t h = 0; 91 | hash_combine(h, x.flags, x.attachments, x.color_refs, x.color_ref_offsets, x.ds_refs, x.subpass_dependencies, x.subpass_descriptions); 92 | return h; 93 | } 94 | }; 95 | 96 | template<> 97 | struct hash { 98 | size_t operator()(vuk::FramebufferCreateInfo const& x) const noexcept { 99 | size_t h = 0; 100 | hash_combine(h, x.flags, x.attachments, x.width, x.height, x.layers); 101 | return h; 102 | } 103 | }; 104 | } // namespace std 105 | -------------------------------------------------------------------------------- /include/vuk/Exception.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "vuk/Config.hpp" 7 | 8 | namespace vuk { 9 | struct Exception : std::exception { 10 | std::string error_message; 11 | 12 | Exception() {} 13 | Exception(std::string message) : error_message(std::move(message)) {} 14 | 15 | const char* what() const noexcept override { 16 | return error_message.c_str(); 17 | } 18 | 19 | virtual void throw_this() = 0; 20 | }; 21 | 22 | struct ShaderCompilationException : Exception { 23 | using Exception::Exception; 24 | 25 | void throw_this() override { 26 | throw *this; 27 | } 28 | }; 29 | 30 | struct RenderGraphException : Exception { 31 | using Exception::Exception; 32 | 33 | void throw_this() override { 34 | throw *this; 35 | } 36 | }; 37 | 38 | struct VkException : Exception { 39 | VkResult error_code; 40 | 41 | using Exception::Exception; 42 | 43 | VkException(VkResult res) { 44 | error_code = res; 45 | switch (res) { 46 | case VK_ERROR_OUT_OF_HOST_MEMORY: { 47 | error_message = "Out of host memory."; 48 | break; 49 | } 50 | case VK_ERROR_OUT_OF_DEVICE_MEMORY: { 51 | error_message = "Out of device memory."; 52 | break; 53 | } 54 | case VK_ERROR_INITIALIZATION_FAILED: { 55 | error_message = "Initialization failed."; 56 | break; 57 | } 58 | case VK_ERROR_DEVICE_LOST: { 59 | error_message = "Device lost."; 60 | break; 61 | } 62 | case VK_ERROR_MEMORY_MAP_FAILED: { 63 | error_message = "Memory map failed."; 64 | break; 65 | } 66 | case VK_ERROR_LAYER_NOT_PRESENT: { 67 | error_message = "Layer not present."; 68 | break; 69 | } 70 | case VK_ERROR_EXTENSION_NOT_PRESENT: { 71 | error_message = "Extension not present."; 72 | break; 73 | } 74 | case VK_ERROR_FEATURE_NOT_PRESENT: { 75 | error_message = "Feature not present."; 76 | break; 77 | } 78 | case VK_ERROR_INCOMPATIBLE_DRIVER: { 79 | error_message = "Incompatible driver."; 80 | break; 81 | } 82 | case VK_ERROR_TOO_MANY_OBJECTS: { 83 | error_message = "Too many objects."; 84 | break; 85 | } 86 | case VK_ERROR_FORMAT_NOT_SUPPORTED: { 87 | error_message = "Format not supported."; 88 | break; 89 | } 90 | case VK_ERROR_UNKNOWN: { 91 | error_message = "Error unknown."; 92 | break; 93 | } 94 | case VK_SUBOPTIMAL_KHR: { 95 | error_message = "Suboptimal."; 96 | break; 97 | } 98 | case VK_ERROR_OUT_OF_DATE_KHR: { 99 | error_message = "Out of date."; 100 | break; 101 | } 102 | default: 103 | assert(0 && "Unimplemented error."); 104 | break; 105 | } 106 | } 107 | 108 | VkResult code() const { return error_code; } 109 | 110 | void throw_this() override { 111 | throw *this; 112 | } 113 | }; 114 | 115 | struct AllocateException : VkException { 116 | AllocateException(VkResult res) { 117 | error_code = res; 118 | 119 | switch (res) { 120 | case VK_ERROR_OUT_OF_HOST_MEMORY: { 121 | error_message = "Out of host memory."; 122 | break; 123 | } 124 | case VK_ERROR_OUT_OF_DEVICE_MEMORY: { 125 | error_message = "Out of device memory."; 126 | break; 127 | } 128 | case VK_ERROR_INITIALIZATION_FAILED: { 129 | error_message = "Initialization failed."; 130 | break; 131 | } 132 | case VK_ERROR_DEVICE_LOST: { 133 | error_message = "Device lost."; 134 | break; 135 | } 136 | case VK_ERROR_MEMORY_MAP_FAILED: { 137 | error_message = "Memory map failed."; 138 | break; 139 | } 140 | case VK_ERROR_LAYER_NOT_PRESENT: { 141 | error_message = "Layer not present."; 142 | break; 143 | } 144 | case VK_ERROR_EXTENSION_NOT_PRESENT: { 145 | error_message = "Extension not present."; 146 | break; 147 | } 148 | case VK_ERROR_FEATURE_NOT_PRESENT: { 149 | error_message = "Feature not present."; 150 | break; 151 | } 152 | case VK_ERROR_INCOMPATIBLE_DRIVER: { 153 | error_message = "Incompatible driver."; 154 | break; 155 | } 156 | case VK_ERROR_TOO_MANY_OBJECTS: { 157 | error_message = "Too many objects."; 158 | break; 159 | } 160 | case VK_ERROR_FORMAT_NOT_SUPPORTED: { 161 | error_message = "Format not supported."; 162 | break; 163 | } 164 | default: 165 | assert(0 && "Unimplemented error."); 166 | break; 167 | } 168 | } 169 | 170 | void throw_this() override { 171 | throw *this; 172 | } 173 | }; 174 | } // namespace vuk 175 | -------------------------------------------------------------------------------- /include/vuk/Buffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Types.hpp" 4 | #include 5 | 6 | namespace vuk { 7 | enum class BufferUsageFlagBits : VkBufferUsageFlags { 8 | eTransferRead = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 9 | eTransferWrite = VK_BUFFER_USAGE_TRANSFER_DST_BIT, 10 | eUniformTexelBuffer = VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT, 11 | eStorageTexelBuffer = VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT, 12 | eUniformBuffer = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, 13 | eStorageBuffer = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, 14 | eIndexBuffer = VK_BUFFER_USAGE_INDEX_BUFFER_BIT, 15 | eVertexBuffer = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 16 | eIndirectBuffer = VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, 17 | eShaderDeviceAddress = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, 18 | eTransformFeedbackBufferEXT = VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT, 19 | eTransformFeedbackCounterBufferEXT = VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT, 20 | eConditionalRenderingEXT = VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT, 21 | eShaderDeviceAddressEXT = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_EXT, 22 | eShaderDeviceAddressKHR = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_KHR, 23 | eAccelerationStructureBuildInputReadOnlyKHR = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR, 24 | eAccelerationStructureStorageKHR = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR, 25 | eShaderBindingTable = VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR 26 | }; 27 | 28 | using BufferUsageFlags = Flags; 29 | 30 | inline constexpr BufferUsageFlags operator|(BufferUsageFlagBits bit0, BufferUsageFlagBits bit1) noexcept { 31 | return BufferUsageFlags(bit0) | bit1; 32 | } 33 | 34 | inline constexpr BufferUsageFlags operator&(BufferUsageFlagBits bit0, BufferUsageFlagBits bit1) noexcept { 35 | return BufferUsageFlags(bit0) & bit1; 36 | } 37 | 38 | inline constexpr BufferUsageFlags operator^(BufferUsageFlagBits bit0, BufferUsageFlagBits bit1) noexcept { 39 | return BufferUsageFlags(bit0) ^ bit1; 40 | } 41 | 42 | static constexpr vuk::BufferUsageFlags all_buffer_usage_flags = 43 | BufferUsageFlagBits::eTransferRead | BufferUsageFlagBits::eTransferWrite | BufferUsageFlagBits::eUniformTexelBuffer | 44 | BufferUsageFlagBits::eStorageTexelBuffer | BufferUsageFlagBits::eUniformBuffer | BufferUsageFlagBits::eStorageBuffer | BufferUsageFlagBits::eIndexBuffer | 45 | BufferUsageFlagBits::eVertexBuffer | BufferUsageFlagBits::eIndirectBuffer | BufferUsageFlagBits::eShaderDeviceAddress | 46 | BufferUsageFlagBits::eAccelerationStructureBuildInputReadOnlyKHR | BufferUsageFlagBits::eAccelerationStructureStorageKHR | 47 | BufferUsageFlagBits::eShaderBindingTable; 48 | 49 | /// @brief A contiguous portion of GPU-visible memory that can be used for storing buffer-type data 50 | struct Buffer { 51 | void* allocation = nullptr; 52 | VkBuffer buffer = VK_NULL_HANDLE; 53 | size_t offset = 0; 54 | size_t size = ~(0u); 55 | uint64_t device_address = 0; 56 | std::byte* mapped_ptr = nullptr; 57 | MemoryUsage memory_usage; 58 | 59 | constexpr bool operator==(const Buffer& o) const noexcept { 60 | return buffer == o.buffer && offset == o.offset && size == o.size; 61 | } 62 | 63 | constexpr explicit operator bool() const noexcept { 64 | return buffer != VK_NULL_HANDLE; 65 | } 66 | 67 | /// @brief Create a new Buffer by offsetting 68 | [[nodiscard]] Buffer add_offset(VkDeviceSize offset_to_add) { 69 | assert(offset_to_add <= size); 70 | return { allocation, 71 | buffer, 72 | offset + offset_to_add, 73 | size - offset_to_add, 74 | device_address != 0 ? device_address + offset_to_add : 0, 75 | mapped_ptr != nullptr ? mapped_ptr + offset_to_add : nullptr, 76 | memory_usage }; 77 | } 78 | 79 | /// @brief Create a new Buffer that is a subset of the original 80 | [[nodiscard]] Buffer subrange(VkDeviceSize new_offset, VkDeviceSize new_size) { 81 | assert(new_offset + new_size <= size); 82 | return { allocation, 83 | buffer, 84 | offset + new_offset, 85 | new_size, 86 | device_address != 0 ? device_address + new_offset : 0, 87 | mapped_ptr != nullptr ? mapped_ptr + new_offset : nullptr, 88 | memory_usage }; 89 | } 90 | }; 91 | 92 | /// @brief Buffer creation parameters 93 | struct BufferCreateInfo { 94 | /// @brief Memory usage to determine which heap to allocate the memory from 95 | MemoryUsage mem_usage; 96 | /// @brief Size of the Buffer in bytes 97 | VkDeviceSize size; 98 | /// @brief Alignment of the allocated Buffer in bytes 99 | VkDeviceSize alignment = 1; 100 | }; 101 | } // namespace vuk 102 | -------------------------------------------------------------------------------- /include/vuk/Program.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../src/CreateInfo.hpp" 4 | #include "vuk/Config.hpp" 5 | #include "vuk/vuk_fwd.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace vuk { 13 | struct Program { 14 | enum class Type { 15 | einvalid, 16 | euint, 17 | euint64_t, 18 | eint, 19 | eint64_t, 20 | efloat, 21 | edouble, 22 | euvec2, 23 | euvec3, 24 | euvec4, 25 | eivec2, 26 | eivec3, 27 | eivec4, 28 | evec2, 29 | evec3, 30 | evec4, 31 | edvec2, 32 | edvec3, 33 | edvec4, 34 | emat3, 35 | emat4, 36 | edmat3, 37 | edmat4, 38 | eu64vec2, 39 | eu64vec3, 40 | eu64vec4, 41 | ei64vec2, 42 | ei64vec3, 43 | ei64vec4, 44 | estruct 45 | }; 46 | 47 | struct Attribute { 48 | std::string name; 49 | 50 | size_t location; 51 | Type type; 52 | }; 53 | 54 | struct TextureAddress { 55 | unsigned container; 56 | float page; 57 | }; 58 | 59 | struct Member { 60 | std::string name; 61 | std::string type_name; // if this is a struct 62 | Type type; 63 | size_t size; 64 | size_t offset; 65 | unsigned array_size; 66 | std::vector members; 67 | }; 68 | 69 | // always a struct 70 | struct UniformBuffer { 71 | std::string name; 72 | 73 | unsigned binding; 74 | size_t size; 75 | unsigned array_size; 76 | 77 | std::vector members; 78 | 79 | VkShaderStageFlags stage; 80 | }; 81 | 82 | struct StorageBuffer { 83 | std::string name; 84 | 85 | unsigned binding; 86 | size_t min_size; 87 | 88 | bool is_hlsl_counter_buffer = false; 89 | 90 | std::vector members; 91 | 92 | VkShaderStageFlags stage; 93 | }; 94 | 95 | struct StorageImage { 96 | std::string name; 97 | 98 | unsigned array_size; 99 | unsigned binding; 100 | VkShaderStageFlags stage; 101 | }; 102 | 103 | struct SampledImage { 104 | std::string name; 105 | 106 | unsigned array_size; 107 | unsigned binding; 108 | VkShaderStageFlags stage; 109 | }; 110 | 111 | struct CombinedImageSampler { 112 | std::string name; 113 | 114 | unsigned array_size; 115 | unsigned binding; 116 | 117 | bool shadow; // if this is a samplerXXXShadow 118 | 119 | VkShaderStageFlags stage; 120 | }; 121 | 122 | struct Sampler { 123 | std::string name; 124 | 125 | unsigned array_size; 126 | unsigned binding; 127 | 128 | bool shadow; // if this is a samplerShadow 129 | 130 | VkShaderStageFlags stage; 131 | }; 132 | 133 | struct TexelBuffer { 134 | std::string name; 135 | 136 | unsigned binding; 137 | VkShaderStageFlags stage; 138 | }; 139 | 140 | struct SubpassInput { 141 | std::string name; 142 | 143 | unsigned binding; 144 | VkShaderStageFlags stage; 145 | }; 146 | 147 | struct AccelerationStructure { 148 | std::string name; 149 | 150 | unsigned array_size; 151 | unsigned binding; 152 | VkShaderStageFlags stage; 153 | }; 154 | 155 | struct SpecConstant { 156 | unsigned binding; // constant_id 157 | Type type; 158 | 159 | VkShaderStageFlags stage; 160 | }; 161 | 162 | VkShaderStageFlagBits introspect(const uint32_t* ir, size_t word_count); 163 | 164 | std::array local_size; 165 | 166 | std::vector attributes; 167 | std::vector push_constant_ranges; 168 | std::vector spec_constants; 169 | struct Descriptors { 170 | std::vector uniform_buffers; 171 | std::vector storage_buffers; 172 | std::vector storage_images; 173 | std::vector texel_buffers; 174 | std::vector combined_image_samplers; 175 | std::vector sampled_images; 176 | std::vector samplers; 177 | std::vector subpass_inputs; 178 | std::vector acceleration_structures; 179 | 180 | unsigned highest_descriptor_binding = 0; 181 | }; 182 | std::unordered_map sets; 183 | VkShaderStageFlags stages = {}; 184 | void append(const Program& o); 185 | }; 186 | 187 | struct ShaderModule { 188 | VkShaderModule shader_module; 189 | vuk::Program reflection_info; 190 | VkShaderStageFlagBits stage; 191 | }; 192 | 193 | struct ShaderModuleCreateInfo; 194 | 195 | template<> 196 | struct create_info { 197 | using type = vuk::ShaderModuleCreateInfo; 198 | }; 199 | } // namespace vuk 200 | 201 | namespace std { 202 | template<> 203 | struct hash { 204 | size_t operator()(vuk::ShaderModuleCreateInfo const& x) const noexcept; 205 | }; 206 | }; // namespace std 207 | -------------------------------------------------------------------------------- /include/vuk/resources/DeviceLinearResource.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "vuk/Allocator.hpp" 4 | #include "vuk/resources/DeviceNestedResource.hpp" 5 | #include "vuk/resources/DeviceVkResource.hpp" 6 | 7 | #include 8 | 9 | namespace vuk { 10 | /// @brief Represents resources not tied to a frame, that are deallocated only when the resource is destroyed. Not thread-safe. 11 | /// 12 | /// Allocations from this resource are deallocated into the upstream resource when the DeviceLinearResource is destroyed. 13 | /// All resources allocated are automatically deallocated at recycle time - it is not necessary (but not an error) to deallocate them. 14 | struct DeviceLinearResource : DeviceNestedResource { 15 | DeviceLinearResource(DeviceResource& upstream); 16 | ~DeviceLinearResource(); 17 | 18 | DeviceLinearResource(DeviceLinearResource&&); 19 | DeviceLinearResource& operator=(DeviceLinearResource&&); 20 | 21 | Result allocate_semaphores(std::span dst, SourceLocationAtFrame loc) override; 22 | 23 | void deallocate_semaphores(std::span src) override; // noop 24 | 25 | Result allocate_fences(std::span dst, SourceLocationAtFrame loc) override; 26 | 27 | void deallocate_fences(std::span src) override; // noop 28 | 29 | Result allocate_command_buffers(std::span dst, 30 | std::span cis, 31 | SourceLocationAtFrame loc) override; 32 | 33 | void deallocate_command_buffers(std::span src) override; // no-op, deallocated with pools 34 | 35 | Result 36 | allocate_command_pools(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 37 | 38 | void deallocate_command_pools(std::span dst) override; // no-op 39 | 40 | // buffers are lockless 41 | Result allocate_buffers(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 42 | 43 | void deallocate_buffers(std::span src) override; // no-op, linear 44 | 45 | Result 46 | allocate_framebuffers(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 47 | 48 | void deallocate_framebuffers(std::span src) override; // noop 49 | 50 | Result allocate_images(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 51 | 52 | void deallocate_images(std::span src) override; // noop 53 | 54 | Result 55 | allocate_image_views(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 56 | 57 | void deallocate_image_views(std::span src) override; // noop 58 | 59 | Result allocate_persistent_descriptor_sets(std::span dst, 60 | std::span cis, 61 | SourceLocationAtFrame loc) override; 62 | 63 | void deallocate_persistent_descriptor_sets(std::span src) override; // noop 64 | 65 | Result 66 | allocate_descriptor_sets_with_value(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 67 | 68 | Result 69 | allocate_descriptor_sets(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 70 | 71 | void deallocate_descriptor_sets(std::span src) override; 72 | 73 | Result 74 | allocate_timestamp_query_pools(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 75 | 76 | void deallocate_timestamp_query_pools(std::span src) override; // noop 77 | 78 | Result 79 | allocate_timestamp_queries(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 80 | 81 | void deallocate_timestamp_queries(std::span src) override; // noop 82 | 83 | Result allocate_timeline_semaphores(std::span dst, SourceLocationAtFrame loc) override; 84 | 85 | void deallocate_timeline_semaphores(std::span src) override; // noop 86 | 87 | /// @brief Wait for the fences / timeline semaphores referencing this allocator 88 | void wait(); 89 | 90 | /// @brief Release the resources of this resource into the upstream 91 | void free(); 92 | 93 | /// @brief Retrieve the parent Context 94 | /// @return the parent Context 95 | Context& get_context() override { 96 | return upstream->get_context(); 97 | } 98 | 99 | private: 100 | std::unique_ptr impl; 101 | 102 | friend struct DeviceLinearResourceImpl; 103 | }; 104 | } // namespace vuk -------------------------------------------------------------------------------- /benchmarks/bench_runner.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../examples/glfw.hpp" 4 | #include "../examples/utils.hpp" 5 | #include "vuk/AllocatorHelpers.hpp" 6 | #include "vuk/CommandBuffer.hpp" 7 | #include "vuk/Context.hpp" 8 | #include "vuk/RenderGraph.hpp" 9 | #include "vuk/SampledImage.hpp" 10 | #include "vuk/resources/DeviceFrameResource.hpp" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | namespace vuk { 21 | struct BenchRunner; 22 | 23 | struct CaseBase { 24 | std::string_view label; 25 | std::vector subcase_labels; 26 | std::vector> subcases; 27 | std::vector> timings; 28 | std::vector> binned; 29 | std::vector last_stage_ran; 30 | std::vector runs_required; 31 | std::vector est_mean; 32 | std::vector est_variance; 33 | std::vector> min_max; 34 | std::vector mean; 35 | std::vector variance; 36 | }; 37 | 38 | struct BenchBase { 39 | std::string_view name; 40 | std::function setup; 41 | std::function gui; 42 | std::function cleanup; 43 | std::function get_case; 44 | size_t num_cases; 45 | }; 46 | 47 | template 48 | struct Bench { 49 | BenchBase base; 50 | using Params = std::tuple; 51 | 52 | struct Case : CaseBase { 53 | template 54 | Case(std::string_view label, F&& subcase_template) : CaseBase{ label } { 55 | std::apply( 56 | [this, subcase_template](auto&&... ts) { 57 | (subcases.emplace_back([=](BenchRunner& runner, vuk::Allocator& frame_allocator, Query start, Query end) { 58 | return subcase_template(runner, frame_allocator, start, end, ts); 59 | }), 60 | ...); 61 | (subcase_labels.emplace_back(ts.description), ...); 62 | timings.resize(sizeof...(Args)); 63 | runs_required.resize(sizeof...(Args)); 64 | mean.resize(sizeof...(Args)); 65 | variance.resize(sizeof...(Args)); 66 | est_mean.resize(sizeof...(Args)); 67 | est_variance.resize(sizeof...(Args)); 68 | last_stage_ran.resize(sizeof...(Args)); 69 | min_max.resize(sizeof...(Args)); 70 | binned.resize(sizeof...(Args)); 71 | }, 72 | Params{}); 73 | } 74 | }; 75 | 76 | std::vector cases; 77 | }; 78 | } // namespace vuk 79 | 80 | namespace vuk { 81 | 82 | struct BenchRunner { 83 | VkDevice device; 84 | VkPhysicalDevice physical_device; 85 | VkQueue graphics_queue; 86 | std::optional context; 87 | std::optional xdev_rf_alloc; 88 | std::optional global; 89 | vuk::SwapchainRef swapchain; 90 | GLFWwindow* window; 91 | VkSurfaceKHR surface; 92 | vkb::Instance vkbinstance; 93 | vkb::Device vkbdevice; 94 | util::ImGuiData imgui_data; 95 | plf::colony sampled_images; 96 | 97 | Query start, end; 98 | unsigned current_case = 0; 99 | unsigned current_subcase = 0; 100 | unsigned current_stage = 0; 101 | 102 | unsigned num_runs = 0; 103 | 104 | BenchBase* bench; 105 | 106 | BenchRunner(); 107 | 108 | void setup() { 109 | // Setup Dear ImGui context 110 | IMGUI_CHECKVERSION(); 111 | ImGui::CreateContext(); 112 | // Setup Dear ImGui style 113 | ImGui::StyleColorsDark(); 114 | // Setup Platform/Renderer bindings 115 | ImGui_ImplGlfw_InitForVulkan(window, true); 116 | 117 | start = context->create_timestamp_query(); 118 | end = context->create_timestamp_query(); 119 | { imgui_data = util::ImGui_ImplVuk_Init(*global); } 120 | bench->setup(*this, *global); 121 | } 122 | 123 | void render(); 124 | 125 | void cleanup() { 126 | context->wait_idle(); 127 | if (bench->cleanup) { 128 | bench->cleanup(*this, *global); 129 | } 130 | } 131 | 132 | ~BenchRunner() { 133 | imgui_data.font_texture.view.reset(); 134 | imgui_data.font_texture.image.reset(); 135 | xdev_rf_alloc.reset(); 136 | context.reset(); 137 | auto vkDestroySurfaceKHR = (PFN_vkDestroySurfaceKHR)vkbinstance.fp_vkGetInstanceProcAddr(vkbinstance.instance, "vkDestroySurfaceKHR"); 138 | vkDestroySurfaceKHR(vkbinstance.instance, surface, nullptr); 139 | destroy_window_glfw(window); 140 | vkb::destroy_device(vkbdevice); 141 | vkb::destroy_instance(vkbinstance); 142 | } 143 | 144 | static BenchRunner& get_runner() { 145 | static BenchRunner runner; 146 | return runner; 147 | } 148 | }; 149 | } // namespace vuk 150 | 151 | namespace util { 152 | struct Register { 153 | template 154 | Register(vuk::Bench& x) { 155 | vuk::BenchRunner::get_runner().bench = &x.base; 156 | vuk::BenchRunner::get_runner().bench->get_case = [&x](unsigned i) -> vuk::CaseBase& { 157 | return x.cases[i]; 158 | }; 159 | vuk::BenchRunner::get_runner().bench->num_cases = x.cases.size(); 160 | } 161 | }; 162 | } // namespace util 163 | 164 | #define CONCAT_IMPL(x, y) x##y 165 | #define MACRO_CONCAT(x, y) CONCAT_IMPL(x, y) 166 | #define REGISTER_BENCH(x) util::Register MACRO_CONCAT(_reg_, __LINE__)(x) 167 | -------------------------------------------------------------------------------- /examples/example_runner_single.cpp: -------------------------------------------------------------------------------- 1 | #include "example_runner.hpp" 2 | 3 | vuk::SingleSwapchainRenderBundle bundle; 4 | 5 | void vuk::ExampleRunner::render() { 6 | Compiler compiler; 7 | // the examples can all enqueue upload tasks via enqueue_setup. for simplicity, we submit and wait for all the upload tasks before moving on to the render 8 | // loop in a real application, one would have something more complex to handle uploading data it is also possible to wait for the uploads on the GPU by using 9 | // these uploading futures as input 10 | vuk::wait_for_futures_explicit(*superframe_allocator, compiler, futures); 11 | futures.clear(); 12 | 13 | // our main loop 14 | while (!glfwWindowShouldClose(window)) { 15 | // pump the message loop 16 | glfwPollEvents(); 17 | while (suspend) { 18 | glfwWaitEvents(); 19 | } 20 | // advance the frame for the allocators and caches used by vuk 21 | auto& frame_resource = superframe_resource->get_next_frame(); 22 | context->next_frame(); 23 | // create a frame allocator - we can allocate objects for the duration of the frame from this allocator 24 | // all of the objects allocated from this allocator last for this frame, and get recycled automatically, so for this specific allocator, deallocation is 25 | // optional 26 | Allocator frame_allocator(frame_resource); 27 | // acquire an image on the swapchain 28 | bundle = *acquire_one(*context, swapchain, (*present_ready)[context->get_frame_count() % 3], (*render_complete)[context->get_frame_count() % 3]); 29 | // create a rendergraph we will use to prepare a swapchain image for the example to render into 30 | std::shared_ptr rg(std::make_shared("runner")); 31 | // we bind the swapchain to name "_swp" 32 | rg->attach_swapchain("_swp", swapchain); 33 | // clear the "_swp" image and call the cleared image "example_target_image" 34 | rg->clear_image("_swp", "example_target_image", vuk::ClearColor{ 0.3f, 0.5f, 0.3f, 1.0f }); 35 | // bind "example_target_image" as the output of this rendergraph 36 | Future cleared_image_to_render_into{ std::move(rg), "example_target_image" }; 37 | // invoke the render method of the example with the cleared image 38 | Future example_result = examples[0]->render(*this, frame_allocator, std::move(cleared_image_to_render_into)); 39 | // make a new RG that will take care of putting the swapchain image into present and releasing it from the rg 40 | std::shared_ptr rg_p(std::make_shared("presenter")); 41 | rg_p->attach_in("_src", std::move(example_result)); 42 | // we tell the rendergraph that _src will be used for presenting after the rendergraph 43 | rg_p->release_for_present("_src"); 44 | // set up some profiling callbacks for our example Tracy integration 45 | vuk::ProfilingCallbacks cbs; 46 | cbs.user_data = &get_runner(); 47 | cbs.on_begin_command_buffer = [](void* user_data, VkCommandBuffer cbuf) { 48 | ExampleRunner& runner = *reinterpret_cast(user_data); 49 | TracyVkCollect(runner.tracy_graphics_ctx, cbuf); 50 | TracyVkCollect(runner.tracy_transfer_ctx, cbuf); 51 | return (void*)nullptr; 52 | }; 53 | // runs whenever entering a new vuk::Pass 54 | // we start a GPU zone and then keep it open 55 | cbs.on_begin_pass = [](void* user_data, Name pass_name, VkCommandBuffer cmdbuf, DomainFlagBits domain) { 56 | ExampleRunner& runner = *reinterpret_cast(user_data); 57 | void* pass_data = new char[sizeof(tracy::VkCtxScope)]; 58 | if (domain & vuk::DomainFlagBits::eGraphicsQueue) { 59 | #if defined TRACY_ENABLE 60 | new (pass_data) TracyVkZoneTransient(runner.tracy_graphics_ctx, , cmdbuf, pass_name.c_str(), true); 61 | #endif 62 | } else if (domain & vuk::DomainFlagBits::eTransferQueue) { 63 | #if defined TRACY_ENABLE 64 | new (pass_data) TracyVkZoneTransient(runner.tracy_transfer_ctx, , cmdbuf, pass_name.c_str(), true); 65 | #endif 66 | } 67 | 68 | return pass_data; 69 | }; 70 | // runs whenever a pass has ended, we end the GPU zone we started 71 | cbs.on_end_pass = [](void* user_data, void* pass_data) { 72 | auto tracy_scope = reinterpret_cast(pass_data); 73 | #if defined TRACY_ENABLE 74 | tracy_scope->~VkCtxScope(); 75 | #endif 76 | delete reinterpret_cast(pass_data); 77 | }; 78 | // compile the RG that contains all the rendering of the example 79 | auto erg = *compiler.link(std::span{ &rg_p, 1 }, { .callbacks = cbs }); 80 | // submit the compiled commands 81 | auto result = *execute_submit(frame_allocator, std::move(erg), std::move(bundle)); 82 | // present the results 83 | present_to_one(*context, std::move(result)); 84 | // update window title with FPS 85 | if (++num_frames == 16) { 86 | auto new_time = get_time(); 87 | auto delta = new_time - old_time; 88 | auto per_frame_time = delta / 16 * 1000; 89 | old_time = new_time; 90 | num_frames = 0; 91 | set_window_title(std::string("Vuk example [") + std::to_string(per_frame_time) + " ms / " + std::to_string(1000 / per_frame_time) + " FPS]"); 92 | } 93 | } 94 | } 95 | 96 | int main(int argc, char** argv) { 97 | auto path_to_root = std::filesystem::relative(VUK_EX_PATH_ROOT, VUK_EX_PATH_TGT); 98 | root = std::filesystem::canonical(std::filesystem::path(argv[0]).parent_path() / path_to_root); 99 | // very simple error handling in the example framework: we don't check for errors and just let them be converted into exceptions that are caught at top level 100 | try { 101 | vuk::ExampleRunner::get_runner().setup(); 102 | vuk::ExampleRunner::get_runner().render(); 103 | vuk::ExampleRunner::get_runner().cleanup(); 104 | } catch (vuk::Exception& e) { 105 | fprintf(stderr, "%s", e.what()); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /include/vuk/ShaderSource.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../src/CreateInfo.hpp" 4 | #include "vuk/Config.hpp" 5 | #include "vuk/vuk_fwd.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace spirv_cross { 12 | struct SPIRType; 13 | class Compiler; 14 | }; // namespace spirv_cross 15 | namespace vuk { 16 | enum class ShaderSourceLanguage { eGlsl, eHlsl, eSpirv }; 17 | 18 | /// @brief Specifies the HLSL Shader Stage for a given HLSL shader. 19 | enum class HlslShaderStage { 20 | /// @brief Will infer the Shader Stage from the filename. 21 | eInferred, 22 | eVertex, 23 | ePixel, 24 | eCompute, 25 | eGeometry, 26 | eMesh, 27 | eHull, 28 | eDomain, 29 | eAmplification 30 | }; 31 | 32 | struct ShaderCompileOptions { 33 | enum class OptimizationLevel { 34 | O0, 35 | O1, 36 | O2, 37 | O3 38 | } optimization_level = OptimizationLevel::O3; 39 | 40 | std::vector dxc_extra_arguments = { L"-spirv", L"-fvk-use-gl-layout", L"-no-warnings" }; 41 | }; 42 | 43 | /// @brief Wrapper over either a GLSL, HLSL, or SPIR-V source 44 | struct ShaderSource { 45 | ShaderSource() = default; 46 | 47 | ShaderSource(const ShaderSource& o) noexcept { 48 | data = o.data; 49 | if (!data.empty()) { 50 | data_ptr = data.data(); 51 | } else { 52 | data_ptr = o.data_ptr; 53 | } 54 | size = o.size; 55 | language = o.language; 56 | hlsl_stage = o.hlsl_stage; 57 | entry_point = o.entry_point; 58 | } 59 | ShaderSource& operator=(const ShaderSource& o) noexcept { 60 | data = o.data; 61 | if (!data.empty()) { 62 | data_ptr = data.data(); 63 | } else { 64 | data_ptr = o.data_ptr; 65 | } 66 | language = o.language; 67 | size = o.size; 68 | hlsl_stage = o.hlsl_stage; 69 | entry_point = o.entry_point; 70 | 71 | return *this; 72 | } 73 | 74 | ShaderSource(ShaderSource&& o) noexcept { 75 | data = std::move(o.data); 76 | if (!data.empty()) { 77 | data_ptr = data.data(); 78 | } else { 79 | data_ptr = o.data_ptr; 80 | } 81 | size = o.size; 82 | language = o.language; 83 | hlsl_stage = o.hlsl_stage; 84 | entry_point = o.entry_point; 85 | } 86 | ShaderSource& operator=(ShaderSource&& o) noexcept { 87 | data = std::move(o.data); 88 | if (!data.empty()) { 89 | data_ptr = data.data(); 90 | } else { 91 | data_ptr = o.data_ptr; 92 | } 93 | size = o.size; 94 | language = o.language; 95 | hlsl_stage = o.hlsl_stage; 96 | entry_point = o.entry_point; 97 | 98 | return *this; 99 | } 100 | 101 | #if VUK_USE_SHADERC 102 | static ShaderSource glsl(std::string_view source, const ShaderCompileOptions& compile_options, std::string entry_point = "main") { 103 | ShaderSource shader; 104 | shader.data.resize(idivceil(source.size() + 1, sizeof(uint32_t))); 105 | memcpy(shader.data.data(), source.data(), source.size() * sizeof(std::string_view::value_type)); 106 | shader.data_ptr = shader.data.data(); 107 | shader.size = shader.data.size(); 108 | shader.language = ShaderSourceLanguage::eGlsl; 109 | shader.entry_point = std::move(entry_point); 110 | shader.opt_level = compile_options.optimization_level; 111 | return shader; 112 | } 113 | #endif 114 | 115 | #if VUK_USE_DXC 116 | static ShaderSource hlsl(std::string_view source, const ShaderCompileOptions& compile_options, HlslShaderStage stage = HlslShaderStage::eInferred, std::string entry_point = "main") { 117 | ShaderSource shader; 118 | shader.data.resize(idivceil(source.size() + 1, sizeof(uint32_t))); 119 | memcpy(shader.data.data(), source.data(), source.size() * sizeof(std::string_view::value_type)); 120 | shader.data_ptr = shader.data.data(); 121 | shader.size = shader.data.size(); 122 | shader.language = ShaderSourceLanguage::eHlsl; 123 | shader.hlsl_stage = stage; 124 | shader.entry_point = std::move(entry_point); 125 | shader.opt_level = compile_options.optimization_level; 126 | return shader; 127 | } 128 | #endif 129 | 130 | static ShaderSource spirv(std::vector source, std::string entry_point = "main") { 131 | ShaderSource shader; 132 | shader.data = std::move(source); 133 | shader.data_ptr = shader.data.data(); 134 | shader.size = shader.data.size(); 135 | shader.language = ShaderSourceLanguage::eSpirv; 136 | shader.entry_point = std::move(entry_point); 137 | return shader; 138 | } 139 | 140 | static ShaderSource spirv(const uint32_t* source, size_t size, std::string entry_point = "main") { 141 | ShaderSource shader; 142 | shader.data_ptr = source; 143 | shader.size = size; 144 | shader.language = ShaderSourceLanguage::eSpirv; 145 | shader.entry_point = std::move(entry_point); 146 | return shader; 147 | } 148 | 149 | const char* as_c_str() const { 150 | return reinterpret_cast(data_ptr); 151 | } 152 | 153 | const uint32_t* as_spirv() const { 154 | return data_ptr; 155 | } 156 | 157 | const uint32_t* data_ptr = nullptr; 158 | size_t size = 0; 159 | std::vector data; 160 | ShaderSourceLanguage language; 161 | HlslShaderStage hlsl_stage; 162 | std::string entry_point; 163 | ShaderCompileOptions::OptimizationLevel opt_level; 164 | }; 165 | 166 | inline bool operator==(const ShaderSource& a, const ShaderSource& b) noexcept { 167 | bool basics = a.language == b.language && a.size == b.size && a.entry_point == b.entry_point && a.opt_level == b.opt_level; 168 | if (!basics) { 169 | return false; 170 | } 171 | if (a.data_ptr == b.data_ptr) { 172 | return true; 173 | } 174 | return memcmp(a.data_ptr, b.data_ptr, sizeof(uint32_t) * a.size) == 0; 175 | } 176 | 177 | struct ShaderModuleCreateInfo { 178 | ShaderSource source; 179 | std::string filename; 180 | std::vector> defines; 181 | ShaderCompileOptions compile_options = {}; 182 | 183 | bool operator==(const ShaderModuleCreateInfo& o) const noexcept { 184 | return source == o.source && defines == o.defines; 185 | } 186 | }; 187 | } // namespace vuk 188 | -------------------------------------------------------------------------------- /docs/topics/commandbuffer.rst: -------------------------------------------------------------------------------- 1 | CommandBuffer 2 | ============= 3 | 4 | The CommandBuffer class offers a convenient abstraction over command recording, pipeline state and descriptor sets of Vulkan. 5 | 6 | Setting pipeline state 7 | ---------------------- 8 | The CommandBuffer encapsulates the current pipeline and descriptor state. When calling state-setting commands, the current state of the CommandBuffer is updated. The state of the CommandBuffer persists for the duration of the execution callback, and there is no state leakage between callbacks of different passes. 9 | 10 | The various states of the pipeline can be reconfigured by calling the appropriate function, such as :cpp:func:`vuk::CommandBuffer::set_rasterization()`. 11 | 12 | There is no default state - you must explicitly bind all state used for the commands recorded. 13 | 14 | Static and dynamic state 15 | ------------------------ 16 | Vulkan allows some pipeline state to be dynamic. In vuk this is exposed as an optimisation - you may let the CommandBuffer know that certain pipeline state is dynamic by calling :cpp:func:`vuk::CommandBuffer::set_dynamic_state()`. This call changes which states are considered dynamic. Dynamic state is usually cheaper to change than entire pipelines and leads to fewer pipeline compilations, but has more overhead compared to static state - use it when a state changes often. Some state can be set dynamic on some platforms without cost. As with other pipeline state, setting states to be dynamic or static persist only during the callback. 17 | 18 | Binding pipelines & specialization constants 19 | -------------------------------------------- 20 | The CommandBuffer maintains separate bind points for compute and graphics pipelines. The CommandBuffer also maintains an internal buffer of specialization constants that are applied to the pipeline bound. Changing specialization constants will trigger a pipeline compilation when using the pipeline for the first time. 21 | 22 | Binding descriptors & push constants 23 | ------------------------------------ 24 | vuk allows two types of descriptors to be bound: ephemeral and persistent. 25 | 26 | Ephemeral descriptors are bound individually to the CommandBuffer via `bind_XXX()` calls where `XXX` denotes the type of the descriptor (eg. uniform buffer). These descriptors are internally managed by the CommandBuffer and the Allocator it references. Ephemeral descriptors are very convenient to use, but they are limited in the number of bindable descriptors (`VUK_MAX_BINDINGS`) and they incur a small overhead on bind. 27 | 28 | Persistent descriptors are managed by the user via allocation of a PersistentDescriptorSet from Allocator and manually updating the contents. There is no limit on the number of descriptors and binding such descriptor sets do not have an overhead over the direct Vulkan call. Large descriptor arrays (such as the ones used in "bindless" techniques) are only possible via persistent descriptor sets. 29 | 30 | The number of bindable sets is limited by `VUK_MAX_SETS`. Both ephemeral descriptors and persistent descriptor sets retain their bindings until overwritten, disturbed or the the callback ends. 31 | 32 | Push constants can be changed by calling :cpp:func:`vuk::CommandBuffer::push_constants()`. 33 | 34 | Vertex buffers and attributes 35 | ----------------------------- 36 | While vertex buffers are waning in popularity, vuk still offers a convenient API for most attribute arrangements. If advanced addressing schemes are not required, they can be a convenient alternative to vertex pulling. 37 | 38 | The shader declares attributes, which require a `location`. When binding vertex buffers, you are telling vuk where each attribute, corresponding to a `location` can be found. 39 | Each :cpp:func:`vuk::CommandBuffer::bind_vertex_buffer()` binds a single vuk::Buffer, which can contain multiple attributes 40 | 41 | The first two arguments to :cpp:func:`vuk::CommandBuffer::bind_vertex_buffer()` specify the index of the vertex buffer binding and buffer to binding to that binding. 42 | (so if you have 1 vertex buffers, you pass 0, if you have 2 vertex buffers, you have 2 calls where you pass 0 and 1 as `binding` - these don't need to start at 0 or be contiguous but they might as well be) 43 | 44 | In the second part of the arguments you specify which attributes can be found the vertex buffer that is being bound, what is their format, and what is their offset. 45 | For convenience vuk offers a utility called `vuk::Packed` to describe common vertex buffers that contain interleaved attribute data. 46 | 47 | The simplest case is a single attribute per vertex buffer, this is described by calling `bind_vertex_buffer(binding, buffer, location, vuk::Packed{ vuk::Format::eR32G32B32Sfloat })` - with the actual format of the attribute. 48 | Here `vuk::Packed` means that the formats are packed in the buffer, i.e. you have a R32G32B32, then immediately after a R32G32B32, and so on. 49 | 50 | If there are multiple interleaved attributes in a buffer, for example it is [position, normal, position, normal], then you can describe this in a very compact way in vuk if the position attribute location and normal attribute location is consecutive: `bind_vertex_buffer(binding, buffer, first_location, vuk::Packed{ vuk::Format::eR32G32B32Sfloat, vuk::Format::eR32G32B32Sfloat })`. 51 | Finally, you can describe holes in your interleaving by using `vuk::Ignore(byte_size)` in the format list for `vuk::Packed`. 52 | 53 | If your attribute scheme cannot be described like this, you can also use :cpp:func:`vuk::CommandBuffer::bind_vertex_buffer()` with a manually built `span` and computed stride. 54 | 55 | Command recording 56 | ----------------- 57 | Draws and dispatches can be recorded by calling the appropriate function. Any state changes made will be recorded into the underlying Vulkan command buffer, along with the draw or dispatch. 58 | 59 | Error handling 60 | -------------- 61 | The CommandBuffer implements "monadic" error handling, because operations that allocate resources might fail. In this case the CommandBuffer is moved into the error state and subsequent calls do not modify the underlying state. 62 | 63 | .. doxygenclass:: vuk::CommandBuffer 64 | :members: -------------------------------------------------------------------------------- /examples/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace vuk { 19 | struct Pass; 20 | struct RenderGraph; 21 | } // namespace vuk 22 | 23 | namespace util { 24 | struct Vertex { 25 | glm::vec3 position; 26 | glm::vec3 normal; 27 | glm::vec3 tangent; 28 | glm::vec3 bitangent; 29 | glm::vec2 uv_coordinates; 30 | }; 31 | 32 | using Mesh = std::pair, std::vector>; 33 | 34 | inline Mesh generate_cube() { 35 | // clang-format off 36 | return Mesh(std::vector { 37 | // back 38 | Vertex{ {-1, -1, -1}, {0, 0, -1}, {-1, 0, 0}, {0, 1, 0}, {1, 1} }, Vertex{ {1, 1, -1}, {0, 0, -1}, {-1, 0, 0}, {0, 1, 0}, {0, 0} }, 39 | Vertex{ {1, -1, -1}, {0, 0, -1}, {-1, 0, 0}, {0, 1, 0}, {0, 1} }, Vertex{ {1, 1, -1}, {0, 0, -1}, {-1, 0, 0}, {0, 1, 0}, {0, 0} }, 40 | Vertex{ {-1, -1, -1}, {0, 0, -1}, {-1, 0, 0}, {0, 1, 0}, {1, 1} }, Vertex{ {-1, 1, -1}, {0, 0, -1}, {-1, 0, 0}, {0, 1, 0}, {1, 0} }, 41 | // front 42 | Vertex{ {-1, -1, 1}, {0, 0, 1}, {1, 0.0, 0}, {0, 1, 0}, {0, 1} }, Vertex{ {1, -1, 1}, {0, 0, 1},{1, 0.0, 0}, {0, 1, 0}, {1, 1} }, 43 | Vertex{ {1, 1, 1}, {0, 0, 1}, {1, 0.0, 0}, {0, 1, 0}, {1, 0} }, Vertex{ {1, 1, 1}, {0, 0, 1}, {1, 0.0, 0}, {0, 1, 0}, {1, 0} }, 44 | Vertex{ {-1, 1, 1}, {0, 0, 1}, {1, 0.0, 0}, {0, 1, 0}, {0, 0} }, Vertex{ {-1, -1, 1}, {0, 0, 1}, {1, 0.0, 0}, {0, 1, 0}, {0, 1} }, 45 | // left 46 | Vertex{ {-1, 1, -1}, {-1, 0, 0}, {0, 0, 1}, {0, 1, 0}, {0, 0} }, Vertex{ {-1, -1, -1}, {-1, 0, 0}, {0, 0, 1}, {0, 1, 0}, {0, 1} }, 47 | Vertex{ {-1, 1, 1}, {-1, 0, 0}, {0, 0, 1}, {0, 1, 0}, {1, 0} }, Vertex{ {-1, -1, -1}, {-1, 0, 0}, {0, 0, 1}, {0, 1, 0}, {0, 1} }, 48 | Vertex{ {-1, -1, 1}, {-1, 0, 0}, {0, 0, 1}, {0, 1, 0}, {1, 1} }, Vertex{ {-1, 1, 1}, {-1, 0, 0}, {0, 0, 1}, {0, 1, 0}, {1, 0} }, 49 | // right 50 | Vertex{ {1, 1, 1}, {1, 0, 0}, {0, 0, -1}, {0, 1, 0}, {0, 0} }, Vertex{ {1, -1, -1}, {1, 0, 0}, {0, 0, -1}, {0, 1, 0}, {1, 1} }, 51 | Vertex{ {1, 1, -1}, {1, 0, 0}, {0, 0, -1}, {0, 1, 0}, {1, 0} }, Vertex{ {1, -1, -1}, {1, 0, 0}, {0, 0, -1}, {0, 1, 0}, {1, 1} }, 52 | Vertex{ {1, 1, 1}, {1, 0, 0}, {0, 0, -1}, {0, 1, 0}, {0, 0} }, Vertex{ {1, -1, 1}, {1, 0, 0}, {0, 0, -1}, {0, 1, 0}, {0, 1} }, 53 | // bottom 54 | Vertex{ {-1, -1, -1}, {0, -1, 0}, {1, 0, 0}, {0, 0, 1}, {0, 1} }, Vertex{ {1, -1, -1}, {0, -1, 0}, {1, 0, 0}, {0, 0, 1}, {1, 1} }, 55 | Vertex{ {1, -1, 1}, {0, -1, 0}, {1, 0, 0}, {0, 0, 1}, {1, 0} }, Vertex{ {1, -1, 1}, {0, -1, 0}, {1, 0, 0}, {0, 0, 1}, {1, 0} }, 56 | Vertex{ {-1, -1, 1}, {0, -1, 0}, {1, 0, 0}, {0, 0, 1}, {0, 0} }, Vertex{ {-1, -1, -1}, {0, -1, 0}, {1, 0, 0}, {0, 0, 1}, {0, 1} }, 57 | // top 58 | Vertex{ {-1, 1, -1}, {0, 1, 0}, {1, 0, 0}, {0, 0, -1}, {0, 0} }, Vertex{ {1, 1, 1}, {0, 1, 0}, {1, 0, 0}, {0, 0, -1}, {1, 1} }, 59 | Vertex{ {1, 1, -1}, {0, 1, 0}, {1, 0, 0}, {0, 0, -1}, {1, 0} }, Vertex{ {1, 1, 1}, {0, 1, 0}, {1, 0, 0}, {0, 0, -1}, {1, 1} }, 60 | Vertex{ {-1, 1, -1}, {0, 1, 0}, {1, 0, 0}, {0, 0, -1}, {0, 0} }, Vertex{ {-1, 1, 1}, {0, 1, 0}, {1, 0, 0}, {0, 0, -1}, {0, 1} } }, 61 | { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35 }); 62 | // clang-format on 63 | } 64 | 65 | inline vuk::Swapchain make_swapchain(vkb::Device vkbdevice, std::optional old_swapchain) { 66 | vkb::SwapchainBuilder swb(vkbdevice); 67 | swb.set_desired_format(vuk::SurfaceFormatKHR{ vuk::Format::eR8G8B8A8Srgb, vuk::ColorSpaceKHR::eSrgbNonlinear }); 68 | swb.add_fallback_format(vuk::SurfaceFormatKHR{ vuk::Format::eB8G8R8A8Srgb, vuk::ColorSpaceKHR::eSrgbNonlinear }); 69 | swb.set_desired_present_mode((VkPresentModeKHR)vuk::PresentModeKHR::eImmediate); 70 | swb.set_image_usage_flags(VkImageUsageFlagBits::VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VkImageUsageFlagBits::VK_IMAGE_USAGE_TRANSFER_DST_BIT); 71 | if (old_swapchain) { 72 | swb.set_old_swapchain(*old_swapchain); 73 | } 74 | auto vkswapchain = swb.build(); 75 | 76 | vuk::Swapchain sw{}; 77 | auto images = *vkswapchain->get_images(); 78 | auto views = *vkswapchain->get_image_views(); 79 | 80 | for (auto& i : images) { 81 | sw.images.push_back(vuk::Image{ i, nullptr }); 82 | } 83 | for (auto& i : views) { 84 | sw.image_views.emplace_back(); 85 | sw.image_views.back().payload = i; 86 | } 87 | sw.extent = vuk::Extent2D{ vkswapchain->extent.width, vkswapchain->extent.height }; 88 | sw.format = vuk::Format(vkswapchain->image_format); 89 | sw.surface = vkbdevice.surface; 90 | sw.swapchain = vkswapchain->swapchain; 91 | return sw; 92 | } 93 | 94 | struct ImGuiData { 95 | vuk::Texture font_texture; 96 | vuk::SamplerCreateInfo font_sci; 97 | std::unique_ptr font_si; 98 | }; 99 | ImGuiData ImGui_ImplVuk_Init(vuk::Allocator& allocator); 100 | vuk::Future ImGui_ImplVuk_Render(vuk::Allocator& allocator, 101 | vuk::Future target, 102 | ImGuiData& data, 103 | ImDrawData* draw_data, 104 | const plf::colony& sampled_images); 105 | 106 | inline std::string read_entire_file(const std::string& path) { 107 | std::ostringstream buf; 108 | std::ifstream input(path.c_str()); 109 | assert(input); 110 | buf << input.rdbuf(); 111 | return buf.str(); 112 | } 113 | 114 | inline std::vector read_spirv(const std::string& path) { 115 | std::ostringstream buf; 116 | std::ifstream input(path.c_str(), std::ios::ate | std::ios::binary); 117 | assert(input); 118 | size_t file_size = (size_t)input.tellg(); 119 | std::vector buffer(file_size / sizeof(uint32_t)); 120 | input.seekg(0); 121 | input.read(reinterpret_cast(buffer.data()), file_size); 122 | return buffer; 123 | } 124 | } // namespace util -------------------------------------------------------------------------------- /src/tests/frame_allocator.cpp: -------------------------------------------------------------------------------- 1 | #include "TestContext.hpp" 2 | #include "vuk/AllocatorHelpers.hpp" 3 | #include "vuk/Partials.hpp" 4 | #include 5 | 6 | using namespace vuk; 7 | 8 | struct AllocatorChecker : DeviceNestedResource { 9 | int32_t counter = 0; 10 | 11 | AllocatorChecker(DeviceResource& upstream) : DeviceNestedResource(upstream) {} 12 | 13 | Result allocate_buffers(std::span dst, std::span cis, SourceLocationAtFrame loc) override { 14 | counter += cis.size(); 15 | return upstream->allocate_buffers(dst, cis, loc); 16 | } 17 | 18 | void deallocate_buffers(std::span src) override { 19 | counter -= src.size(); 20 | upstream->deallocate_buffers(src); 21 | } 22 | 23 | Result allocate_images(std::span dst, std::span cis, SourceLocationAtFrame loc) override { 24 | counter += cis.size(); 25 | return upstream->allocate_images(dst, cis, loc); 26 | } 27 | 28 | void deallocate_images(std::span src) override { 29 | counter -= src.size(); 30 | upstream->deallocate_images(src); 31 | } 32 | }; 33 | 34 | TEST_CASE("superframe allocator, uncached resource") { 35 | REQUIRE(test_context.prepare()); 36 | 37 | AllocatorChecker ac(*test_context.sfa_resource); 38 | DeviceSuperFrameResource sfr(ac, 2); 39 | 40 | Buffer buf; 41 | BufferCreateInfo bci{ .mem_usage = vuk::MemoryUsage::eCPUonly, .size = 1024 }; 42 | sfr.allocate_buffers(std::span{ &buf, 1 }, std::span{ &bci, 1 }, {}); 43 | sfr.deallocate_buffers(std::span{ &buf, 1 }); 44 | REQUIRE(ac.counter == 1); 45 | sfr.get_next_frame(); 46 | REQUIRE(ac.counter == 1); 47 | sfr.get_next_frame(); 48 | REQUIRE(ac.counter == 0); 49 | } 50 | 51 | /* TEST_CASE("frame allocator, uncached resource") { 52 | REQUIRE(test_context.prepare()); 53 | 54 | AllocatorChecker ac(*test_context.sfa_resource); 55 | DeviceSuperFrameResource sfr(ac, 2); 56 | 57 | Buffer buf; 58 | BufferCreateInfo bci{ .mem_usage = vuk::MemoryUsage::eCPUonly, .size = 1024 }; 59 | auto& fa = sfr.get_next_frame(); 60 | fa.allocate_buffers(std::span{ &buf, 1 }, std::span{ &bci, 1 }, {}); 61 | REQUIRE(ac.counter == 1); 62 | sfr.get_next_frame(); 63 | REQUIRE(ac.counter == 1); 64 | sfr.get_next_frame(); 65 | REQUIRE(ac.counter == 0); 66 | }*/ 67 | 68 | TEST_CASE("frame allocator, cached resource") { 69 | REQUIRE(test_context.prepare()); 70 | 71 | AllocatorChecker ac(*test_context.sfa_resource); 72 | DeviceSuperFrameResource sfr(ac, 2); 73 | 74 | Image im; 75 | ImageCreateInfo ici{ .format = vuk::Format::eR8G8B8A8Srgb, .extent = vuk::Extent3D{100, 100, 1}, .usage = vuk::ImageUsageFlagBits::eColorAttachment }; 76 | auto& fa = sfr.get_next_frame(); 77 | fa.allocate_images(std::span{ &im, 1 }, std::span{ &ici, 1 }, {}); 78 | REQUIRE(ac.counter == 1); 79 | sfr.get_next_frame(); 80 | sfr.force_collect(); 81 | REQUIRE(ac.counter == 1); 82 | sfr.get_next_frame(); 83 | REQUIRE(ac.counter == 1); 84 | sfr.get_next_frame(); 85 | REQUIRE(ac.counter == 0); 86 | } 87 | 88 | TEST_CASE("frame allocator, cached resource identity") { 89 | REQUIRE(test_context.prepare()); 90 | 91 | AllocatorChecker ac(*test_context.sfa_resource); 92 | DeviceSuperFrameResource sfr(ac, 2); 93 | 94 | Image im1; 95 | Image im2; 96 | ImageCreateInfo ici{ .format = vuk::Format::eR8G8B8A8Srgb, .extent = vuk::Extent3D{ 100, 100, 1 }, .usage = vuk::ImageUsageFlagBits::eColorAttachment }; 97 | { 98 | auto& fa = sfr.get_next_frame(); 99 | fa.allocate_images(std::span{ &im1, 1 }, std::span{ &ici, 1 }, {}); 100 | fa.allocate_images(std::span{ &im2, 1 }, std::span{ &ici, 1 }, {}); 101 | } 102 | REQUIRE(im1 != im2); 103 | Image im3; 104 | Image im4; 105 | { 106 | auto& fa = sfr.get_next_frame(); 107 | fa.allocate_images(std::span{ &im3, 1 }, std::span{ &ici, 1 }, {}); 108 | fa.allocate_images(std::span{ &im4, 1 }, std::span{ &ici, 1 }, {}); 109 | } 110 | REQUIRE((im1 == im3 || im1 == im4)); 111 | REQUIRE((im2 == im3 || im2 == im4)); 112 | } 113 | 114 | /* 115 | TEST_CASE("multiframe allocator, uncached resource") { 116 | REQUIRE(test_context.prepare()); 117 | 118 | AllocatorChecker ac(*test_context.sfa_resource); 119 | DeviceSuperFrameResource sfr(ac, 2); 120 | 121 | Buffer buf; 122 | BufferCreateInfo bci{ .mem_usage = vuk::MemoryUsage::eCPUonly, .size = 1024 }; 123 | auto& mfa = sfr.get_multiframe_allocator(3); 124 | mfa.allocate_buffers(std::span{ &buf, 1 }, std::span{ &bci, 1 }, {}); 125 | REQUIRE(ac.counter == 1); 126 | sfr.get_next_frame(); 127 | sfr.get_next_frame(); 128 | sfr.get_next_frame(); 129 | REQUIRE(ac.counter == 0); 130 | }*/ 131 | 132 | 133 | TEST_CASE("multiframe allocator, cached resource") { 134 | REQUIRE(test_context.prepare()); 135 | 136 | AllocatorChecker ac(*test_context.sfa_resource); 137 | DeviceSuperFrameResource sfr(ac, 2); 138 | 139 | Image im; 140 | ImageCreateInfo ici{ .format = vuk::Format::eR8G8B8A8Srgb, .extent = vuk::Extent3D{ 100, 100, 1 }, .usage = vuk::ImageUsageFlagBits::eColorAttachment }; 141 | auto& mfa = sfr.get_multiframe_allocator(3); 142 | mfa.allocate_images(std::span{ &im, 1 }, std::span{ &ici, 1 }, {}); 143 | REQUIRE(ac.counter == 1); 144 | sfr.get_next_frame(); 145 | sfr.get_next_frame(); 146 | sfr.get_next_frame(); 147 | sfr.force_collect(); 148 | REQUIRE(ac.counter == 1); 149 | sfr.get_next_frame(); 150 | REQUIRE(ac.counter == 1); 151 | sfr.get_next_frame(); 152 | REQUIRE(ac.counter == 0); 153 | } 154 | 155 | TEST_CASE("multiframe allocator, cached resource identity for different MFAs") { 156 | REQUIRE(test_context.prepare()); 157 | 158 | AllocatorChecker ac(*test_context.sfa_resource); 159 | DeviceSuperFrameResource sfr(ac, 2); 160 | 161 | Image im1; 162 | Image im2; 163 | ImageCreateInfo ici{ .format = vuk::Format::eR8G8B8A8Srgb, .extent = vuk::Extent3D{ 100, 100, 1 }, .usage = vuk::ImageUsageFlagBits::eColorAttachment }; 164 | { 165 | auto& mfa = sfr.get_multiframe_allocator(3); 166 | mfa.allocate_images(std::span{ &im1, 1 }, std::span{ &ici, 1 }, {}); 167 | mfa.allocate_images(std::span{ &im2, 1 }, std::span{ &ici, 1 }, {}); 168 | } 169 | REQUIRE(im1 != im2); 170 | Image im3; 171 | Image im4; 172 | { 173 | auto& mfa = sfr.get_multiframe_allocator(3); 174 | mfa.allocate_images(std::span{ &im3, 1 }, std::span{ &ici, 1 }, {}); 175 | mfa.allocate_images(std::span{ &im4, 1 }, std::span{ &ici, 1 }, {}); 176 | } 177 | REQUIRE(im3 != im4); 178 | REQUIRE((im3 != im1 && im3 != im2)); 179 | REQUIRE((im4 != im1 && im4 != im2)); 180 | } -------------------------------------------------------------------------------- /include/vuk/resources/DeviceNestedResource.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "vuk/Allocator.hpp" 4 | #include "vuk/Exception.hpp" 5 | #include "vuk/vuk_fwd.hpp" 6 | 7 | namespace vuk { 8 | /// @brief Helper base class for DeviceResources. Forwards all allocations and deallocations to the upstream DeviceResource. 9 | struct DeviceNestedResource : DeviceResource { 10 | explicit DeviceNestedResource(DeviceResource& upstream) : upstream(&upstream) {} 11 | 12 | Result allocate_semaphores(std::span dst, SourceLocationAtFrame loc) override; 13 | 14 | void deallocate_semaphores(std::span sema) override; 15 | 16 | Result allocate_fences(std::span dst, SourceLocationAtFrame loc) override; 17 | 18 | void deallocate_fences(std::span dst) override; 19 | 20 | Result allocate_command_buffers(std::span dst, 21 | std::span cis, 22 | SourceLocationAtFrame loc) override; 23 | 24 | void deallocate_command_buffers(std::span dst) override; 25 | 26 | Result 27 | allocate_command_pools(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 28 | 29 | void deallocate_command_pools(std::span dst) override; 30 | 31 | Result allocate_buffers(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 32 | 33 | void deallocate_buffers(std::span src) override; 34 | 35 | Result 36 | allocate_framebuffers(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 37 | 38 | void deallocate_framebuffers(std::span src) override; 39 | 40 | Result allocate_images(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 41 | 42 | void deallocate_images(std::span src) override; 43 | 44 | Result 45 | allocate_image_views(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 46 | 47 | void deallocate_image_views(std::span src) override; 48 | 49 | Result allocate_persistent_descriptor_sets(std::span dst, 50 | std::span cis, 51 | SourceLocationAtFrame loc) override; 52 | 53 | void deallocate_persistent_descriptor_sets(std::span src) override; 54 | 55 | Result 56 | allocate_descriptor_sets_with_value(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 57 | 58 | Result 59 | allocate_descriptor_sets(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 60 | 61 | void deallocate_descriptor_sets(std::span src) override; 62 | 63 | Result 64 | allocate_descriptor_pools(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 65 | 66 | void deallocate_descriptor_pools(std::span src) override; 67 | 68 | Result 69 | allocate_timestamp_query_pools(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 70 | 71 | void deallocate_timestamp_query_pools(std::span src) override; 72 | 73 | Result 74 | allocate_timestamp_queries(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 75 | 76 | void deallocate_timestamp_queries(std::span src) override; 77 | 78 | Result allocate_timeline_semaphores(std::span dst, SourceLocationAtFrame loc) override; 79 | 80 | void deallocate_timeline_semaphores(std::span src) override; 81 | 82 | Result allocate_acceleration_structures(std::span dst, 83 | std::span cis, 84 | SourceLocationAtFrame loc) override; 85 | 86 | void deallocate_acceleration_structures(std::span src) override; 87 | 88 | void deallocate_swapchains(std::span src) override; 89 | 90 | Result 91 | allocate_graphics_pipelines(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 92 | void deallocate_graphics_pipelines(std::span src) override; 93 | 94 | Result 95 | allocate_compute_pipelines(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 96 | void deallocate_compute_pipelines(std::span src) override; 97 | 98 | Result allocate_ray_tracing_pipelines(std::span dst, 99 | std::span cis, 100 | SourceLocationAtFrame loc) override; 101 | void deallocate_ray_tracing_pipelines(std::span src) override; 102 | 103 | Result 104 | allocate_render_passes(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 105 | void deallocate_render_passes(std::span src) override; 106 | 107 | Context& get_context() override { 108 | return upstream->get_context(); 109 | } 110 | 111 | DeviceResource* upstream = nullptr; 112 | }; 113 | } // namespace vuk -------------------------------------------------------------------------------- /include/vuk/resources/DeviceVkResource.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "vuk/Allocator.hpp" 4 | #include "vuk/Config.hpp" 5 | 6 | namespace vuk { 7 | /// @brief Device resource that performs direct allocation from the resources from the Vulkan runtime. 8 | struct DeviceVkResource final : DeviceResource { 9 | DeviceVkResource(Context& ctx); 10 | ~DeviceVkResource(); 11 | 12 | Result allocate_semaphores(std::span dst, SourceLocationAtFrame loc) override; 13 | 14 | void deallocate_semaphores(std::span src) override; 15 | 16 | Result allocate_fences(std::span dst, SourceLocationAtFrame loc) override; 17 | 18 | void deallocate_fences(std::span src) override; 19 | 20 | Result allocate_command_buffers(std::span dst, 21 | std::span cis, 22 | SourceLocationAtFrame loc) override; 23 | 24 | void deallocate_command_buffers(std::span dst) override; 25 | 26 | Result 27 | allocate_command_pools(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 28 | 29 | void deallocate_command_pools(std::span src) override; 30 | 31 | Result allocate_buffers(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 32 | 33 | void deallocate_buffers(std::span src) override; 34 | 35 | Result 36 | allocate_framebuffers(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 37 | 38 | void deallocate_framebuffers(std::span src) override; 39 | 40 | Result allocate_images(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 41 | 42 | void deallocate_images(std::span src) override; 43 | 44 | Result 45 | allocate_image_views(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 46 | 47 | void deallocate_image_views(std::span src) override; 48 | 49 | Result allocate_persistent_descriptor_sets(std::span dst, 50 | std::span cis, 51 | SourceLocationAtFrame loc) override; 52 | 53 | void deallocate_persistent_descriptor_sets(std::span src) override; 54 | 55 | Result 56 | allocate_descriptor_sets_with_value(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 57 | 58 | Result 59 | allocate_descriptor_sets(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 60 | 61 | void deallocate_descriptor_sets(std::span src) override; 62 | 63 | Result 64 | allocate_descriptor_pools(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 65 | 66 | void deallocate_descriptor_pools(std::span src) override; 67 | 68 | Result 69 | allocate_timestamp_query_pools(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 70 | 71 | void deallocate_timestamp_query_pools(std::span src) override; 72 | 73 | Result 74 | allocate_timestamp_queries(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 75 | 76 | void deallocate_timestamp_queries(std::span src) override; // no-op, deallocate pools 77 | 78 | Result allocate_timeline_semaphores(std::span dst, SourceLocationAtFrame loc) override; 79 | 80 | void deallocate_timeline_semaphores(std::span src) override; 81 | 82 | Result allocate_acceleration_structures(std::span dst, 83 | std::span cis, 84 | SourceLocationAtFrame loc) override; 85 | 86 | void deallocate_acceleration_structures(std::span src) override; 87 | 88 | void deallocate_swapchains(std::span src) override; 89 | 90 | Result allocate_graphics_pipelines(std::span dst, 91 | std::span cis, 92 | SourceLocationAtFrame loc) override; 93 | void deallocate_graphics_pipelines(std::span src) override; 94 | 95 | Result 96 | allocate_compute_pipelines(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 97 | void deallocate_compute_pipelines(std::span src) override; 98 | 99 | Result allocate_ray_tracing_pipelines(std::span dst, 100 | std::span cis, 101 | SourceLocationAtFrame loc) override; 102 | void deallocate_ray_tracing_pipelines(std::span src) override; 103 | 104 | Result 105 | allocate_render_passes(std::span dst, std::span cis, SourceLocationAtFrame loc) override; 106 | void deallocate_render_passes(std::span src) override; 107 | 108 | Context& get_context() override { 109 | return *ctx; 110 | } 111 | 112 | Context* ctx; 113 | VkDevice device; 114 | 115 | private: 116 | struct DeviceVkResourceImpl* impl; 117 | }; 118 | } // namespace vuk -------------------------------------------------------------------------------- /examples/04_texture.cpp: -------------------------------------------------------------------------------- 1 | #include "example_runner.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /* 04_texture 8 | * In this example we will build on the previous examples (02_cube and 03_multipass), but we will make the cube textured. 9 | * 10 | * These examples are powered by the example framework, which hides some of the code required, as that would be repeated for each example. 11 | * Furthermore it allows launching individual examples and all examples with the example same code. 12 | * Check out the framework (example_runner_*) files if interested! 13 | */ 14 | 15 | namespace { 16 | float angle = 0.f; 17 | auto box = util::generate_cube(); 18 | vuk::Unique verts, inds; 19 | // A vuk::Texture is an owned pair of Image and ImageView 20 | // An optional is used here so that we can reset this on cleanup, despite being a global (which is to simplify the code here) 21 | std::optional texture_of_doge; 22 | 23 | vuk::Example x{ 24 | .name = "04_texture", 25 | .setup = 26 | [](vuk::ExampleRunner& runner, vuk::Allocator& allocator) { 27 | { 28 | vuk::PipelineBaseCreateInfo pci; 29 | pci.add_glsl(util::read_entire_file((root / "examples/ubo_test_tex.vert").generic_string()), 30 | (root / "examples/ubo_test_tex.vert").generic_string()); 31 | pci.add_glsl(util::read_entire_file((root / "examples/triangle_depthshaded_tex.frag").generic_string()), 32 | (root / "examples/triangle_depthshaded_tex.frag").generic_string()); 33 | runner.context->create_named_pipeline("textured_cube", pci); 34 | } 35 | 36 | // Use STBI to load the image 37 | int x, y, chans; 38 | auto doge_image = stbi_load((root / "examples/doge.png").generic_string().c_str(), &x, &y, &chans, 4); 39 | 40 | // Similarly to buffers, we allocate the image and enqueue the upload 41 | auto [tex, tex_fut] = create_texture(allocator, vuk::Format::eR8G8B8A8Srgb, vuk::Extent3D{ (unsigned)x, (unsigned)y, 1u }, doge_image, true); 42 | texture_of_doge = std::move(tex); 43 | runner.enqueue_setup(std::move(tex_fut)); 44 | stbi_image_free(doge_image); 45 | 46 | // We set up the cube data, same as in example 02_cube 47 | auto [vert_buf, vert_fut] = create_buffer(allocator, vuk::MemoryUsage::eGPUonly, vuk::DomainFlagBits::eTransferOnGraphics, std::span(box.first)); 48 | verts = std::move(vert_buf); 49 | auto [ind_buf, ind_fut] = create_buffer(allocator, vuk::MemoryUsage::eGPUonly, vuk::DomainFlagBits::eTransferOnGraphics, std::span(box.second)); 50 | inds = std::move(ind_buf); 51 | // For the example, we just ask these that these uploads complete before moving on to rendering 52 | // In an engine, you would integrate these uploads into some explicit system 53 | runner.enqueue_setup(std::move(vert_fut)); 54 | runner.enqueue_setup(std::move(ind_fut)); 55 | }, 56 | .render = 57 | [](vuk::ExampleRunner& runner, vuk::Allocator& frame_allocator, vuk::Future target) { 58 | struct VP { 59 | glm::mat4 view; 60 | glm::mat4 proj; 61 | } vp; 62 | vp.view = glm::lookAt(glm::vec3(0, 1.5, 3.5), glm::vec3(0), glm::vec3(0, 1, 0)); 63 | vp.proj = glm::perspective(glm::degrees(70.f), 1.f, 1.f, 10.f); 64 | vp.proj[1][1] *= -1; 65 | 66 | auto [buboVP, uboVP_fut] = create_buffer(frame_allocator, vuk::MemoryUsage::eCPUtoGPU, vuk::DomainFlagBits::eTransferOnGraphics, std::span(&vp, 1)); 67 | auto uboVP = *buboVP; 68 | 69 | vuk::RenderGraph rg("04"); 70 | rg.attach_in("04_texture", std::move(target)); 71 | // Set up the pass to draw the textured cube, with a color and a depth attachment 72 | rg.add_pass({ .resources = { "04_texture"_image >> vuk::eColorWrite >> "04_texture_final", "04_texture_depth"_image >> vuk::eDepthStencilRW }, 73 | .execute = [uboVP](vuk::CommandBuffer& command_buffer) { 74 | command_buffer.set_viewport(0, vuk::Rect2D::framebuffer()) 75 | .set_scissor(0, vuk::Rect2D::framebuffer()) 76 | .set_rasterization({}) // Set the default rasterization state 77 | // Set the depth/stencil state 78 | .set_depth_stencil(vuk::PipelineDepthStencilStateCreateInfo{ 79 | .depthTestEnable = true, 80 | .depthWriteEnable = true, 81 | .depthCompareOp = vuk::CompareOp::eLessOrEqual, 82 | }) 83 | .broadcast_color_blend({}) // Set the default color blend state 84 | .bind_vertex_buffer(0, 85 | *verts, 86 | 0, 87 | vuk::Packed{ vuk::Format::eR32G32B32Sfloat, 88 | vuk::Ignore{ offsetof(util::Vertex, uv_coordinates) - sizeof(util::Vertex::position) }, 89 | vuk::Format::eR32G32Sfloat }) 90 | .bind_index_buffer(*inds, vuk::IndexType::eUint32) 91 | // Here we bind our vuk::Texture to (set = 0, binding = 2) with default sampler settings 92 | .bind_image(0, 2, *texture_of_doge->view) 93 | .bind_sampler(0, 2, {}) 94 | .bind_graphics_pipeline("textured_cube") 95 | .bind_buffer(0, 0, uboVP); 96 | glm::mat4* model = command_buffer.map_scratch_buffer(0, 1); 97 | *model = static_cast(glm::angleAxis(glm::radians(angle), glm::vec3(0.f, 1.f, 0.f))); 98 | command_buffer.draw_indexed(box.second.size(), 1, 0, 0, 0); 99 | } }); 100 | 101 | angle += 180.f * ImGui::GetIO().DeltaTime; 102 | 103 | rg.attach_and_clear_image("04_texture_depth", { .format = vuk::Format::eD32Sfloat }, vuk::ClearDepthStencil{ 1.0f, 0 }); 104 | return vuk::Future{ std::make_unique(std::move(rg)), "04_texture_final" }; 105 | }, 106 | 107 | // Perform cleanup for the example 108 | .cleanup = 109 | [](vuk::ExampleRunner& runner, vuk::Allocator& allocator) { 110 | verts.reset(); 111 | inds.reset(); 112 | // We release the texture resources 113 | texture_of_doge.reset(); 114 | } 115 | }; 116 | 117 | REGISTER_EXAMPLE(x); 118 | } // namespace --------------------------------------------------------------------------------