This commit is contained in:
Martin Slachta
2026-07-18 14:31:15 +02:00
commit a04f0dc262
3343 changed files with 1140208 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
project(modules)
add_subdirectory(common)
add_subdirectory(base)
add_subdirectory(window)
add_subdirectory(render_graph)
add_subdirectory(reflect)
+45
View File
@@ -0,0 +1,45 @@
project(loft_base)
file(GLOB FILES
./src/*.cpp
./src/shaders/*.cpp
./src/io/*.cpp
./src/resources/*.cpp)
file(GLOB HEADERS
./include/*.h
./include/*.hpp
./include/shaders/*.h
./include/shaders/*.hpp
./include/resources/*.h
./include/resources/*.hpp
./include/io/*.h
./include/io/*.hpp
./include/debug/*.h
./include/debug/*.hpp
)
add_library(loft_base OBJECT ${FILES})
add_library(loft::base ALIAS loft_base)
target_sources(loft_base
PUBLIC FILE_SET HEADERS
BASE_DIRS include
FILES ${HEADERS})
set_target_properties(loft_base PROPERTIES POSITION_INDEPENDENT_CODE 1)
find_package(Vulkan REQUIRED)
target_link_libraries(${PROJECT_NAME}
PUBLIC
GPUOpen::VulkanMemoryAllocator
volk::volk
)
target_include_directories(${PROJECT_NAME}
PUBLIC
${PROJECT_SOURCE_DIR}/include/
)
+560
View File
@@ -0,0 +1,560 @@
# CMAKE generated file: DO NOT EDIT!
# Generated by "Unix Makefiles" Generator, CMake Version 3.30
# Default target executed when no arguments are given to make.
default_target: all
.PHONY : default_target
# Allow only one "make -f Makefile2" at a time, but pass parallelism.
.NOTPARALLEL:
#=============================================================================
# Special targets provided by cmake.
# Disable implicit rules so canonical targets will work.
.SUFFIXES:
# Disable VCS-based implicit rules.
% : %,v
# Disable VCS-based implicit rules.
% : RCS/%
# Disable VCS-based implicit rules.
% : RCS/%,v
# Disable VCS-based implicit rules.
% : SCCS/s.%
# Disable VCS-based implicit rules.
% : s.%
.SUFFIXES: .hpux_make_needs_suffix_list
# Command-line flag to silence nested $(MAKE).
$(VERBOSE)MAKESILENT = -s
#Suppress display of executed commands.
$(VERBOSE).SILENT:
# A target that is always out of date.
cmake_force:
.PHONY : cmake_force
#=============================================================================
# Set environment variables for the build.
# The shell in which to execute make rules.
SHELL = /bin/sh
# The CMake executable.
CMAKE_COMMAND = /usr/bin/cmake
# The command to remove a file.
RM = /usr/bin/cmake -E rm -f
# Escaping for special characters.
EQUALS = =
# The top-level source directory on which CMake was run.
CMAKE_SOURCE_DIR = /home/martin/projects/mmo
# The top-level build directory on which CMake was run.
CMAKE_BINARY_DIR = /home/martin/projects/mmo
#=============================================================================
# Targets provided globally by CMake.
# Special rule for the target edit_cache
edit_cache:
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running CMake cache editor..."
/usr/bin/ccmake -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR)
.PHONY : edit_cache
# Special rule for the target edit_cache
edit_cache/fast: edit_cache
.PHONY : edit_cache/fast
# Special rule for the target rebuild_cache
rebuild_cache:
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running CMake to regenerate build system..."
/usr/bin/cmake --regenerate-during-build -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR)
.PHONY : rebuild_cache
# Special rule for the target rebuild_cache
rebuild_cache/fast: rebuild_cache
.PHONY : rebuild_cache/fast
# The main all target
all: cmake_check_build_system
cd /home/martin/projects/mmo && $(CMAKE_COMMAND) -E cmake_progress_start /home/martin/projects/mmo/CMakeFiles /home/martin/projects/mmo/external/loft/modules/base//CMakeFiles/progress.marks
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 external/loft/modules/base/all
$(CMAKE_COMMAND) -E cmake_progress_start /home/martin/projects/mmo/CMakeFiles 0
.PHONY : all
# The main clean target
clean:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 external/loft/modules/base/clean
.PHONY : clean
# The main clean target
clean/fast: clean
.PHONY : clean/fast
# Prepare targets for installation.
preinstall: all
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 external/loft/modules/base/preinstall
.PHONY : preinstall
# Prepare targets for installation.
preinstall/fast:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 external/loft/modules/base/preinstall
.PHONY : preinstall/fast
# clear depends
depend:
cd /home/martin/projects/mmo && $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1
.PHONY : depend
# Convenience name for target.
external/loft/modules/base/CMakeFiles/loft_base.dir/rule:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 external/loft/modules/base/CMakeFiles/loft_base.dir/rule
.PHONY : external/loft/modules/base/CMakeFiles/loft_base.dir/rule
# Convenience name for target.
loft_base: external/loft/modules/base/CMakeFiles/loft_base.dir/rule
.PHONY : loft_base
# fast build rule for target.
loft_base/fast:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/build
.PHONY : loft_base/fast
src/FramebufferBuilder.o: src/FramebufferBuilder.cpp.o
.PHONY : src/FramebufferBuilder.o
# target to build an object file
src/FramebufferBuilder.cpp.o:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/FramebufferBuilder.cpp.o
.PHONY : src/FramebufferBuilder.cpp.o
src/FramebufferBuilder.i: src/FramebufferBuilder.cpp.i
.PHONY : src/FramebufferBuilder.i
# target to preprocess a source file
src/FramebufferBuilder.cpp.i:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/FramebufferBuilder.cpp.i
.PHONY : src/FramebufferBuilder.cpp.i
src/FramebufferBuilder.s: src/FramebufferBuilder.cpp.s
.PHONY : src/FramebufferBuilder.s
# target to generate assembly for a file
src/FramebufferBuilder.cpp.s:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/FramebufferBuilder.cpp.s
.PHONY : src/FramebufferBuilder.cpp.s
src/Gpu.o: src/Gpu.cpp.o
.PHONY : src/Gpu.o
# target to build an object file
src/Gpu.cpp.o:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/Gpu.cpp.o
.PHONY : src/Gpu.cpp.o
src/Gpu.i: src/Gpu.cpp.i
.PHONY : src/Gpu.i
# target to preprocess a source file
src/Gpu.cpp.i:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/Gpu.cpp.i
.PHONY : src/Gpu.cpp.i
src/Gpu.s: src/Gpu.cpp.s
.PHONY : src/Gpu.s
# target to generate assembly for a file
src/Gpu.cpp.s:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/Gpu.cpp.s
.PHONY : src/Gpu.cpp.s
src/Instance.o: src/Instance.cpp.o
.PHONY : src/Instance.o
# target to build an object file
src/Instance.cpp.o:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/Instance.cpp.o
.PHONY : src/Instance.cpp.o
src/Instance.i: src/Instance.cpp.i
.PHONY : src/Instance.i
# target to preprocess a source file
src/Instance.cpp.i:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/Instance.cpp.i
.PHONY : src/Instance.cpp.i
src/Instance.s: src/Instance.cpp.s
.PHONY : src/Instance.s
# target to generate assembly for a file
src/Instance.cpp.s:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/Instance.cpp.s
.PHONY : src/Instance.cpp.s
src/io/file.o: src/io/file.cpp.o
.PHONY : src/io/file.o
# target to build an object file
src/io/file.cpp.o:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/io/file.cpp.o
.PHONY : src/io/file.cpp.o
src/io/file.i: src/io/file.cpp.i
.PHONY : src/io/file.i
# target to preprocess a source file
src/io/file.cpp.i:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/io/file.cpp.i
.PHONY : src/io/file.cpp.i
src/io/file.s: src/io/file.cpp.s
.PHONY : src/io/file.s
# target to generate assembly for a file
src/io/file.cpp.s:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/io/file.cpp.s
.PHONY : src/io/file.cpp.s
src/resources/Buffer.o: src/resources/Buffer.cpp.o
.PHONY : src/resources/Buffer.o
# target to build an object file
src/resources/Buffer.cpp.o:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/Buffer.cpp.o
.PHONY : src/resources/Buffer.cpp.o
src/resources/Buffer.i: src/resources/Buffer.cpp.i
.PHONY : src/resources/Buffer.i
# target to preprocess a source file
src/resources/Buffer.cpp.i:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/Buffer.cpp.i
.PHONY : src/resources/Buffer.cpp.i
src/resources/Buffer.s: src/resources/Buffer.cpp.s
.PHONY : src/resources/Buffer.s
# target to generate assembly for a file
src/resources/Buffer.cpp.s:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/Buffer.cpp.s
.PHONY : src/resources/Buffer.cpp.s
src/resources/BufferBusWriter.o: src/resources/BufferBusWriter.cpp.o
.PHONY : src/resources/BufferBusWriter.o
# target to build an object file
src/resources/BufferBusWriter.cpp.o:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/BufferBusWriter.cpp.o
.PHONY : src/resources/BufferBusWriter.cpp.o
src/resources/BufferBusWriter.i: src/resources/BufferBusWriter.cpp.i
.PHONY : src/resources/BufferBusWriter.i
# target to preprocess a source file
src/resources/BufferBusWriter.cpp.i:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/BufferBusWriter.cpp.i
.PHONY : src/resources/BufferBusWriter.cpp.i
src/resources/BufferBusWriter.s: src/resources/BufferBusWriter.cpp.s
.PHONY : src/resources/BufferBusWriter.s
# target to generate assembly for a file
src/resources/BufferBusWriter.cpp.s:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/BufferBusWriter.cpp.s
.PHONY : src/resources/BufferBusWriter.cpp.s
src/resources/DefaultAllocator.o: src/resources/DefaultAllocator.cpp.o
.PHONY : src/resources/DefaultAllocator.o
# target to build an object file
src/resources/DefaultAllocator.cpp.o:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/DefaultAllocator.cpp.o
.PHONY : src/resources/DefaultAllocator.cpp.o
src/resources/DefaultAllocator.i: src/resources/DefaultAllocator.cpp.i
.PHONY : src/resources/DefaultAllocator.i
# target to preprocess a source file
src/resources/DefaultAllocator.cpp.i:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/DefaultAllocator.cpp.i
.PHONY : src/resources/DefaultAllocator.cpp.i
src/resources/DefaultAllocator.s: src/resources/DefaultAllocator.cpp.s
.PHONY : src/resources/DefaultAllocator.s
# target to generate assembly for a file
src/resources/DefaultAllocator.cpp.s:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/DefaultAllocator.cpp.s
.PHONY : src/resources/DefaultAllocator.cpp.s
src/resources/Image.o: src/resources/Image.cpp.o
.PHONY : src/resources/Image.o
# target to build an object file
src/resources/Image.cpp.o:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/Image.cpp.o
.PHONY : src/resources/Image.cpp.o
src/resources/Image.i: src/resources/Image.cpp.i
.PHONY : src/resources/Image.i
# target to preprocess a source file
src/resources/Image.cpp.i:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/Image.cpp.i
.PHONY : src/resources/Image.cpp.i
src/resources/Image.s: src/resources/Image.cpp.s
.PHONY : src/resources/Image.s
# target to generate assembly for a file
src/resources/Image.cpp.s:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/Image.cpp.s
.PHONY : src/resources/Image.cpp.s
src/resources/ImageBusWriter.o: src/resources/ImageBusWriter.cpp.o
.PHONY : src/resources/ImageBusWriter.o
# target to build an object file
src/resources/ImageBusWriter.cpp.o:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/ImageBusWriter.cpp.o
.PHONY : src/resources/ImageBusWriter.cpp.o
src/resources/ImageBusWriter.i: src/resources/ImageBusWriter.cpp.i
.PHONY : src/resources/ImageBusWriter.i
# target to preprocess a source file
src/resources/ImageBusWriter.cpp.i:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/ImageBusWriter.cpp.i
.PHONY : src/resources/ImageBusWriter.cpp.i
src/resources/ImageBusWriter.s: src/resources/ImageBusWriter.cpp.s
.PHONY : src/resources/ImageBusWriter.s
# target to generate assembly for a file
src/resources/ImageBusWriter.cpp.s:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/ImageBusWriter.cpp.s
.PHONY : src/resources/ImageBusWriter.cpp.s
src/resources/ImageView.o: src/resources/ImageView.cpp.o
.PHONY : src/resources/ImageView.o
# target to build an object file
src/resources/ImageView.cpp.o:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/ImageView.cpp.o
.PHONY : src/resources/ImageView.cpp.o
src/resources/ImageView.i: src/resources/ImageView.cpp.i
.PHONY : src/resources/ImageView.i
# target to preprocess a source file
src/resources/ImageView.cpp.i:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/ImageView.cpp.i
.PHONY : src/resources/ImageView.cpp.i
src/resources/ImageView.s: src/resources/ImageView.cpp.s
.PHONY : src/resources/ImageView.s
# target to generate assembly for a file
src/resources/ImageView.cpp.s:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/ImageView.cpp.s
.PHONY : src/resources/ImageView.cpp.s
src/resources/MipmapGenerator.o: src/resources/MipmapGenerator.cpp.o
.PHONY : src/resources/MipmapGenerator.o
# target to build an object file
src/resources/MipmapGenerator.cpp.o:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/MipmapGenerator.cpp.o
.PHONY : src/resources/MipmapGenerator.cpp.o
src/resources/MipmapGenerator.i: src/resources/MipmapGenerator.cpp.i
.PHONY : src/resources/MipmapGenerator.i
# target to preprocess a source file
src/resources/MipmapGenerator.cpp.i:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/MipmapGenerator.cpp.i
.PHONY : src/resources/MipmapGenerator.cpp.i
src/resources/MipmapGenerator.s: src/resources/MipmapGenerator.cpp.s
.PHONY : src/resources/MipmapGenerator.s
# target to generate assembly for a file
src/resources/MipmapGenerator.cpp.s:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/resources/MipmapGenerator.cpp.s
.PHONY : src/resources/MipmapGenerator.cpp.s
src/shaders/GlslShaderBuilder.o: src/shaders/GlslShaderBuilder.cpp.o
.PHONY : src/shaders/GlslShaderBuilder.o
# target to build an object file
src/shaders/GlslShaderBuilder.cpp.o:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/GlslShaderBuilder.cpp.o
.PHONY : src/shaders/GlslShaderBuilder.cpp.o
src/shaders/GlslShaderBuilder.i: src/shaders/GlslShaderBuilder.cpp.i
.PHONY : src/shaders/GlslShaderBuilder.i
# target to preprocess a source file
src/shaders/GlslShaderBuilder.cpp.i:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/GlslShaderBuilder.cpp.i
.PHONY : src/shaders/GlslShaderBuilder.cpp.i
src/shaders/GlslShaderBuilder.s: src/shaders/GlslShaderBuilder.cpp.s
.PHONY : src/shaders/GlslShaderBuilder.s
# target to generate assembly for a file
src/shaders/GlslShaderBuilder.cpp.s:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/GlslShaderBuilder.cpp.s
.PHONY : src/shaders/GlslShaderBuilder.cpp.s
src/shaders/Pipeline.o: src/shaders/Pipeline.cpp.o
.PHONY : src/shaders/Pipeline.o
# target to build an object file
src/shaders/Pipeline.cpp.o:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/Pipeline.cpp.o
.PHONY : src/shaders/Pipeline.cpp.o
src/shaders/Pipeline.i: src/shaders/Pipeline.cpp.i
.PHONY : src/shaders/Pipeline.i
# target to preprocess a source file
src/shaders/Pipeline.cpp.i:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/Pipeline.cpp.i
.PHONY : src/shaders/Pipeline.cpp.i
src/shaders/Pipeline.s: src/shaders/Pipeline.cpp.s
.PHONY : src/shaders/Pipeline.s
# target to generate assembly for a file
src/shaders/Pipeline.cpp.s:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/Pipeline.cpp.s
.PHONY : src/shaders/Pipeline.cpp.s
src/shaders/PipelineBuilder.o: src/shaders/PipelineBuilder.cpp.o
.PHONY : src/shaders/PipelineBuilder.o
# target to build an object file
src/shaders/PipelineBuilder.cpp.o:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/PipelineBuilder.cpp.o
.PHONY : src/shaders/PipelineBuilder.cpp.o
src/shaders/PipelineBuilder.i: src/shaders/PipelineBuilder.cpp.i
.PHONY : src/shaders/PipelineBuilder.i
# target to preprocess a source file
src/shaders/PipelineBuilder.cpp.i:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/PipelineBuilder.cpp.i
.PHONY : src/shaders/PipelineBuilder.cpp.i
src/shaders/PipelineBuilder.s: src/shaders/PipelineBuilder.cpp.s
.PHONY : src/shaders/PipelineBuilder.s
# target to generate assembly for a file
src/shaders/PipelineBuilder.cpp.s:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/PipelineBuilder.cpp.s
.PHONY : src/shaders/PipelineBuilder.cpp.s
src/shaders/SpirvShaderBuilder.o: src/shaders/SpirvShaderBuilder.cpp.o
.PHONY : src/shaders/SpirvShaderBuilder.o
# target to build an object file
src/shaders/SpirvShaderBuilder.cpp.o:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/SpirvShaderBuilder.cpp.o
.PHONY : src/shaders/SpirvShaderBuilder.cpp.o
src/shaders/SpirvShaderBuilder.i: src/shaders/SpirvShaderBuilder.cpp.i
.PHONY : src/shaders/SpirvShaderBuilder.i
# target to preprocess a source file
src/shaders/SpirvShaderBuilder.cpp.i:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/SpirvShaderBuilder.cpp.i
.PHONY : src/shaders/SpirvShaderBuilder.cpp.i
src/shaders/SpirvShaderBuilder.s: src/shaders/SpirvShaderBuilder.cpp.s
.PHONY : src/shaders/SpirvShaderBuilder.s
# target to generate assembly for a file
src/shaders/SpirvShaderBuilder.cpp.s:
cd /home/martin/projects/mmo && $(MAKE) $(MAKESILENT) -f external/loft/modules/base/CMakeFiles/loft_base.dir/build.make external/loft/modules/base/CMakeFiles/loft_base.dir/src/shaders/SpirvShaderBuilder.cpp.s
.PHONY : src/shaders/SpirvShaderBuilder.cpp.s
# Help Target
help:
@echo "The following are some of the valid targets for this Makefile:"
@echo "... all (the default if no target is provided)"
@echo "... clean"
@echo "... depend"
@echo "... edit_cache"
@echo "... rebuild_cache"
@echo "... loft_base"
@echo "... src/FramebufferBuilder.o"
@echo "... src/FramebufferBuilder.i"
@echo "... src/FramebufferBuilder.s"
@echo "... src/Gpu.o"
@echo "... src/Gpu.i"
@echo "... src/Gpu.s"
@echo "... src/Instance.o"
@echo "... src/Instance.i"
@echo "... src/Instance.s"
@echo "... src/io/file.o"
@echo "... src/io/file.i"
@echo "... src/io/file.s"
@echo "... src/resources/Buffer.o"
@echo "... src/resources/Buffer.i"
@echo "... src/resources/Buffer.s"
@echo "... src/resources/BufferBusWriter.o"
@echo "... src/resources/BufferBusWriter.i"
@echo "... src/resources/BufferBusWriter.s"
@echo "... src/resources/DefaultAllocator.o"
@echo "... src/resources/DefaultAllocator.i"
@echo "... src/resources/DefaultAllocator.s"
@echo "... src/resources/Image.o"
@echo "... src/resources/Image.i"
@echo "... src/resources/Image.s"
@echo "... src/resources/ImageBusWriter.o"
@echo "... src/resources/ImageBusWriter.i"
@echo "... src/resources/ImageBusWriter.s"
@echo "... src/resources/ImageView.o"
@echo "... src/resources/ImageView.i"
@echo "... src/resources/ImageView.s"
@echo "... src/resources/MipmapGenerator.o"
@echo "... src/resources/MipmapGenerator.i"
@echo "... src/resources/MipmapGenerator.s"
@echo "... src/shaders/GlslShaderBuilder.o"
@echo "... src/shaders/GlslShaderBuilder.i"
@echo "... src/shaders/GlslShaderBuilder.s"
@echo "... src/shaders/Pipeline.o"
@echo "... src/shaders/Pipeline.i"
@echo "... src/shaders/Pipeline.s"
@echo "... src/shaders/PipelineBuilder.o"
@echo "... src/shaders/PipelineBuilder.i"
@echo "... src/shaders/PipelineBuilder.s"
@echo "... src/shaders/SpirvShaderBuilder.o"
@echo "... src/shaders/SpirvShaderBuilder.i"
@echo "... src/shaders/SpirvShaderBuilder.s"
.PHONY : help
#=============================================================================
# Special targets to cleanup operation of make.
# Special rule to run CMake to check the build system integrity.
# No rule that depends on this can have commands that come from listfiles
# because they might be regenerated.
cmake_check_build_system:
cd /home/martin/projects/mmo && $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0
.PHONY : cmake_check_build_system
+3
View File
@@ -0,0 +1,3 @@
#pragma once
@@ -0,0 +1,33 @@
#pragma once
#include <volk.h>
#include <vector>
#include "Gpu.hpp"
struct Framebuffer {
VkFramebuffer framebuffer;
Framebuffer(VkFramebuffer fb);
Framebuffer(const Gpu* gpu, const VkRenderPass renderpass, const VkExtent2D extent,
const std::vector<VkImageView>& attachments);
};
class FramebufferBuilder {
private:
VkRenderPass m_renderpass;
VkExtent2D m_extent;
std::vector<ImageView> m_attachments;
public:
FramebufferBuilder(VkRenderPass renderpass, VkExtent2D extent);
FramebufferBuilder(VkRenderPass renderpass, VkExtent2D extent, uint32_t numAttachments);
FramebufferBuilder(VkRenderPass renderpass, VkExtent2D extent, std::vector<ImageView> attachments);
FramebufferBuilder& set_attachment(uint32_t index, VkImageView view);
Framebuffer build(const Gpu* gpu);
};
+120
View File
@@ -0,0 +1,120 @@
#pragma once
#include "result.hpp"
#include "props.hpp"
#include "Instance.hpp"
#include "Surface.hpp"
#include "resources/GpuAllocator.h"
#include <vector>
#include <memory>
#include <optional>
#include <stdexcept>
/**
* Abstract interface for a GPU
*/
class Gpu {
private:
const Instance* m_instance;
VkPhysicalDevice m_gpu = VK_NULL_HANDLE;
VkDevice m_dev = VK_NULL_HANDLE;
VkDescriptorPool m_descriptorPool{};
// Queue for a general commands
VkQueue m_graphicsQueue{};
uint32_t m_graphicsQueueIdx{};
VkCommandPool m_graphicsCommandPool{};
// Queue for a transfer commands
VkQueue m_transferQueue{};
uint32_t m_transferQueueIdx{};
VkCommandPool m_transferCommandPool{};
// Queue for present commands
VkQueue m_presentQueue{};
uint32_t m_presentQueueIdx{};
VkCommandPool m_presentCommandPool{};
VkCommandBuffer m_tracyCommandBuffer;
std::vector<int32_t> get_queues(std::optional<Surface*> surface);
ResultCode create_logical_device(std::optional<Surface*> surface);
ResultCode choose_gpu(VkPhysicalDevice *pOut);
ResultCode create_descriptor_pool();
std::unique_ptr<GpuAllocator> m_pAllocator;
public:
GET(m_gpu, gpu);
GET(m_dev, dev);
[[nodiscard]] const Instance* instance() const {
return m_instance;
}
GET(m_descriptorPool, descriptor_pool);
GET(m_graphicsCommandPool, graphics_command_pool);
GET(m_graphicsQueue, graphics_queue);
GET(m_transferCommandPool, transfer_command_pool);
GET(m_transferQueue, transfer_queue);
GET(m_tracyCommandBuffer, tracy_cmd_buf);
GET(m_pAllocator.get(), memory);
explicit
Gpu(const Instance* instance, std::optional<Surface*> surface);
~Gpu();
// Forbid copy
Gpu(const Gpu&) = delete;
static result<Gpu, ResultCode> create(std::shared_ptr<const Instance> instance, VkSurfaceKHR surface);
inline std::vector<uint32_t> present_queue_ids() const {
return {m_presentQueueIdx};
}
inline uint32_t transfer_queue_idx() const {
return m_transferQueueIdx;
}
void enqueue_present(VkPresentInfoKHR *pPresentInfo) const;
void enqueue_graphics(VkSubmitInfo2 *pSubmitInfo, VkFence fence) const;
void enqueue_transfer(VkSubmitInfo *pSubmitInfo, VkFence fence) const;
inline VkSemaphore create_semaphore() const {
VkSemaphoreCreateInfo semaphore_info = {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
};
VkSemaphore semaphore = VK_NULL_HANDLE;
if(vkCreateSemaphore(this->dev(), &semaphore_info, nullptr, &semaphore)) {
throw std::runtime_error("Failed to create semaphore");
}
return semaphore;
}
inline VkFence create_fence(bool is_signaled) const {
VkFenceCreateInfo fence_info = {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.flags = is_signaled ? VK_FENCE_CREATE_SIGNALED_BIT : (VkFenceCreateFlagBits)0
};
VkFence fence = VK_NULL_HANDLE;
if(vkCreateFence(this->dev(), &fence_info, nullptr, &fence)) {
throw std::runtime_error("Failed to create fence");
}
return fence;
}
};
+17
View File
@@ -0,0 +1,17 @@
//
// Created by martin on 11/12/23.
//
#ifndef LOFT_GPUSCHEDULER_H
#define LOFT_GPUSCHEDULER_H
#include <volk.h>
class GpuScheduler {
public:
virtual void enqueue_transfer(VkSubmitInfo submitInfo) = 0;
virtual void enqueue_graphics(VkSubmitInfo submitInfo) = 0;
virtual void enqueue_present(VkPresentInfoKHR presentInfo) = 0;
};
#endif //LOFT_GPUSCHEDULER_H
+128
View File
@@ -0,0 +1,128 @@
#pragma once
#include <iterator>
#include <volk.h>
#include <iostream>
#include <string>
#include <vector>
#include <set>
#include <algorithm>
#include <vulkan/vulkan_core.h>
#include "props.hpp"
#include "debug/Debug.hpp"
/**
* Maintains a connection to a GPU driver
*/
class Instance {
private:
VkInstance m_instance;
public:
GET(m_instance, instance);
// disable copy
Instance(const Instance&) = delete;
Instance() = delete;
Instance(const Instance&& other) {
this->m_instance = other.m_instance;
}
/**
* Creates new instance and initializes a connection to a GPU driver
* @param applicationName Name of the application
* @param engineName Name of the rendering engine
* @param extensions Additional Vulkan extensions you might want to enable
* @param callback Debug log callback
*/
Instance(const std::string applicationName,
const std::string engineName,
std::vector<std::string> extensions,
std::vector<std::string> layers,
lft::dbg::lft_log_callback callback);
static std::vector<std::string> find_unsupported_layers(
std::vector<std::string>& required_layers
) {
uint32_t available_layers_count = 0;
vkEnumerateInstanceLayerProperties(&available_layers_count, nullptr);
if(available_layers_count == 0) {
return required_layers;
}
std::vector<VkLayerProperties> available_layers(available_layers_count);
vkEnumerateInstanceLayerProperties(&available_layers_count, available_layers.data());
std::vector<std::string> available_layer_names(available_layers_count);
std::transform(available_layers.begin(), available_layers.end(),
available_layer_names.begin(),
[](const VkLayerProperties& props) {
return std::string(props.layerName);
});
std::sort(available_layer_names.begin(), available_layer_names.end());
std::sort(required_layers.begin(), required_layers.end());
std::vector<std::string> unsupported_layers;
std::set_difference(required_layers.begin(), required_layers.end(),
available_layer_names.begin(), available_layer_names.end(),
std::back_inserter(unsupported_layers));
return unsupported_layers;
}
/**
* Checks if the required extensions are available
* @return A vector of indices of not available extensionse
*/
static std::vector<std::string> check_extensions(const std::vector<std::string>& requiredExtensions) {
auto supportedExtensions = list_extensions();
std::vector<std::string> unsupported_extensions;
std::set_difference(requiredExtensions.begin(), requiredExtensions.end(),
supportedExtensions.begin(), supportedExtensions.end(),
std::back_inserter(unsupported_extensions));
return unsupported_extensions;
}
/**
* Gets a list of available physical devices
* @return A vector of VkPhysicalDevice objects representing available physical devices
*/
[[nodiscard]] inline std::vector<VkPhysicalDevice> list_physical_devices() const {
uint32_t numDevices = 0;
vkEnumeratePhysicalDevices(m_instance, &numDevices, nullptr);
std::vector<VkPhysicalDevice> devices(numDevices);
vkEnumeratePhysicalDevices(m_instance, &numDevices, devices.data());
return devices;
}
/**
* Gets a list of available instance extensions
* @return A vector of strings representing available instance extensions
*/
[[nodiscard]] static inline std::vector<std::string> list_extensions() {
uint32_t numExtensions = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &numExtensions, nullptr);
std::vector<VkExtensionProperties> extensions(numExtensions);
vkEnumerateInstanceExtensionProperties(nullptr, &numExtensions, extensions.data());
std::vector<std::string> extension_names(numExtensions);
std::transform(extensions.begin(), extensions.end(),
extension_names.begin(),
[](const VkExtensionProperties& props) {
return std::string(props.extensionName);
});
return extension_names;
}
};
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <exception>
#include <string>
namespace lft {
class GpuException : public std::exception {
std::string m_reason;
public:
GpuException(const std::string& reason) : m_reason(reason) {
}
virtual const char* what() const noexcept override {
return m_reason.c_str();
}
};
}
+2
View File
@@ -0,0 +1,2 @@
#pragma once
+120
View File
@@ -0,0 +1,120 @@
#pragma once
#include <optional>
#include <algorithm>
#include <stdexcept>
#include "shaders/Pipeline.hpp"
namespace lft {
class Recording;
class RecordingBindPoint {
private:
const Recording* m_recording;
Pipeline m_pipeline;
VkPipelineBindPoint m_bind_point;
public:
RecordingBindPoint(
const Recording* recording,
const Pipeline pipeline,
VkPipelineBindPoint bind_point
);
const RecordingBindPoint& bind_descriptor_set(
uint32_t set,
VkDescriptorSet descriptor_set
) const;
const RecordingBindPoint& bind_descriptor_sets(
uint32_t first_set,
const std::vector<VkDescriptorSet>& descriptor_sets
) const;
const RecordingBindPoint& push_constants(
VkShaderStageFlags shader_stages,
uint32_t offset, uint32_t size, const void* data
) const;
};
class Recording {
private:
VkCommandBuffer m_cmdbuf;
std::optional<RecordingBindPoint> m_latest_graphics_pipeline;
public:
GET(m_cmdbuf, cmdbuf);
Recording(VkCommandBuffer cmdbuf) : m_cmdbuf(cmdbuf) {}
/**
* Binds pipeline. Will bind all the descriptor sets, etc. to it after.
*/
inline const RecordingBindPoint bind_graphics_pipeline(const Pipeline& pipeline) const {
return RecordingBindPoint(this, pipeline, VK_PIPELINE_BIND_POINT_GRAPHICS);
}
/**
* Binds pipeline. Will bind all the descriptor sets, etc. to it after.
*/
inline const RecordingBindPoint bind_compute_pipeline(const Pipeline& pipeline) const {
return RecordingBindPoint(this, pipeline, VK_PIPELINE_BIND_POINT_COMPUTE);
}
inline const Recording& draw(
uint32_t vertex_count,
uint32_t instance_count,
uint32_t first_vertex,
uint32_t first_instance
) const {
vkCmdDraw(m_cmdbuf, vertex_count, instance_count, first_vertex, first_instance);
return *this;
}
inline const Recording& draw_indexed(
uint32_t index_count,
uint32_t instance_count,
uint32_t first_index,
uint32_t vertex_offset,
uint32_t first_instance
) const {
vkCmdDrawIndexed(m_cmdbuf, index_count, instance_count, first_index, vertex_offset, first_instance);
return *this;
}
inline const Recording& bind_vertex_buffers(
const std::vector<Buffer> buffers,
std::vector<std::size_t> offsets
) const {
std::vector<VkBuffer> vertex_buffers(buffers.size());
std::transform(buffers.begin(), buffers.end(), vertex_buffers.begin(),
[](const Buffer& buffer) { return buffer.buf; });
vkCmdBindVertexBuffers(m_cmdbuf, 0, vertex_buffers.size(), vertex_buffers.data(), offsets.data());
return *this;
}
inline const Recording& bind_index_buffer(
const Buffer index_buffer,
std::size_t offset,
VkIndexType index_type
) const {
vkCmdBindIndexBuffer(m_cmdbuf, index_buffer.buf, offset, index_type);
return *this;
}
inline const Recording& dispatch(
uint32_t group_count_x,
uint32_t group_count_y,
uint32_t group_count_z
) const {
vkCmdDispatch(m_cmdbuf, group_count_x, group_count_y, group_count_z);
return *this;
}
};
}
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include <volk.h>
#include "Gpu.hpp"
#include "RenderPassLayout.hpp"
class RenderContext {
private:
VkCommandBuffer m_commandBuffer;
Gpu *m_pGpu;
RenderPassLayout *m_pLayout;
VkFramebuffer m_framebuffer;
public:
GET(m_commandBuffer, command_buffer);
GET(m_framebuffer, framebuffer);
GET(m_pGpu, gpu);
GET(m_pLayout, layout);
RenderContext(Gpu *pGpu, VkCommandBuffer commandBuffer,
RenderPassLayout *pLayout, VkFramebuffer framebuffer) :
m_pGpu(pGpu), m_commandBuffer(commandBuffer), m_pLayout(pLayout),
m_framebuffer(framebuffer) {
}
};
@@ -0,0 +1,9 @@
#pragma once
#include <volk.h>
struct RenderPassLayout {
VkRenderPass renderpass;
VkDescriptorSetLayout setLayout[4];
VkPipelineLayout layout;
};
+83
View File
@@ -0,0 +1,83 @@
#pragma once
#include <memory>
#include <stdexcept>
#include <volk.h>
#include "Gpu.hpp"
namespace lft {
class SamplerBuilder {
private:
VkSamplerCreateInfo m_create_info;
public:
SamplerBuilder() : m_create_info({}) {
m_create_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
}
inline SamplerBuilder& address_mode_u(VkSamplerAddressMode address_mode) {
m_create_info.addressModeU = address_mode;
return *this;
}
inline SamplerBuilder& address_mode_v(VkSamplerAddressMode address_mode) {
m_create_info.addressModeV = address_mode;
return *this;
}
inline SamplerBuilder& address_mode_w(VkSamplerAddressMode address_mode) {
m_create_info.addressModeW = address_mode;
return *this;
}
inline SamplerBuilder& address_mode(VkSamplerAddressMode address_mode) {
address_mode_u(address_mode);
address_mode_v(address_mode);
address_mode_w(address_mode);
return *this;
}
inline SamplerBuilder& min_filter(VkFilter filter) {
m_create_info.minFilter = filter;
return *this;
}
inline SamplerBuilder& mag_filter(VkFilter filter) {
m_create_info.magFilter = filter;
return *this;
}
inline SamplerBuilder& filter(VkFilter filter) {
min_filter(filter);
mag_filter(filter);
return *this;
}
inline SamplerBuilder& mipmap_mode(VkSamplerMipmapMode mipmap_mode) {
m_create_info.mipmapMode = mipmap_mode;
return *this;
}
inline SamplerBuilder& border_color(VkBorderColor color) {
m_create_info.borderColor = color;
return *this;
}
inline SamplerBuilder& lod(float min_lod, float max_lod) {
m_create_info.minLod = min_lod;
m_create_info.maxLod = max_lod;
return *this;
}
inline VkSampler build(const Gpu* gpu) {
VkSampler sampler = VK_NULL_HANDLE;
if(vkCreateSampler(gpu->dev(), &m_create_info, nullptr, &sampler)) {
throw std::runtime_error("Failed to create sampler");
}
return sampler;
}
};
}
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include <utility>
#include <volk.h>
#include "Instance.hpp"
/**
* Surface is a target for the Gpu instance to render to.
*/
struct Surface {
private:
VkInstance m_instance;
VkSurfaceKHR m_vk_surface;
public:
GET(m_vk_surface, surface);
Surface(const Instance* instance, VkSurfaceKHR vk_surface) :
m_instance(instance->instance()), m_vk_surface(vk_surface) {}
Surface(const Surface&) = delete;
Surface(Surface&& other) noexcept :
m_instance(std::exchange(other.m_instance, nullptr)),
m_vk_surface(std::exchange(other.m_vk_surface, nullptr)) {
}
Surface& operator=(const Surface& other) = delete;
Surface& operator=(const Surface&& other) noexcept {
this->m_vk_surface = other.m_vk_surface;
this->m_instance = other.m_instance;
return *this;
}
~Surface() {
vkDestroySurfaceKHR(m_instance, m_vk_surface, nullptr);
}
};
@@ -0,0 +1,41 @@
#pragma once
#include "Recording.hpp"
#include <volk.h>
namespace lft {
class ImageSubresource {
};
class ImageSubresourceRange {
VkImageSubresourceRange m_range;
public:
ImageSubresourceRange(const Image& image,
uint32_t base_mip_level, uint32_t level_count,
uint32_t base_array_layer, uint32_t layer_count) {
m_range.aspectMask = image.m_aspect_mask;
m_range.baseMipLevel = base_mip_level;
m_range.levelCount = level_count;
m_range.baseArrayLayer = base_array_layer;
m_range.layerCount = layer_count;
}
static ImageSubresourceRange full(const Image& image) {
return ImageSubresourceRange(image, 0, VK_REMAINING_MIP_LEVELS,
0, VK_REMAINING_ARRAY_LAYERS);
}
static ImageSubresourceRange mipmap(const Image& image, uint32_t base_mip_level, uint32_t level_count) {
return ImageSubresourceRange(image, 0, VK_REMAINING_MIP_LEVELS,
0, VK_REMAINING_ARRAY_LAYERS);
}
};
}
+58
View File
@@ -0,0 +1,58 @@
//
// Created by martin on 8/1/24.
//
#pragma once
#ifndef LOFT_DEBUG_HPP
#define LOFT_DEBUG_HPP
#include <string>
#include <stdarg.h>
namespace lft::dbg {
enum LogMessageSeverity {
info = 0,
warning,
error
};
enum LogMessageType {
general = 0,
validation,
performance
};
typedef void(*lft_log_callback)(LogMessageSeverity, LogMessageType, const char *__restrict __format, va_list args);
}
extern lft::dbg::lft_log_callback g_logCallback;
#define LOG_INFO(fmt, ...) g_logCallback(lft::dbg::LogMessageSeverity::info, lft::dbg::LogMessageType::general, fmt, __VA_ARGS__)
#define LOG_WARN(fmt, ...) g_logCallback(lft::dbg::LogMessageSeverity::warning, lft::dbg::LogMessageType::general, fmt, __VA_ARGS__)
#define LOG_FAIL(fmt, ...) g_logCallback(lft::dbg::LogMessageSeverity::error, lft::dbg::LogMessageType::general, fmt, __VA_ARGS__)
namespace lft::log {
static void info(const char* __format, ...) {
va_list list;
va_start(list, __format);
// g_logCallback(lft::dbg::LogMessageSeverity::info, lft::dbg::LogMessageType::general, __format, list);
va_end(list);
}
static void warn(const char* __format, ...) {
va_list list;
va_start(list, __format);
// g_logCallback(lft::dbg::LogMessageSeverity::warning, lft::dbg::LogMessageType::general, __format, list);
va_end(list);
}
static void fail(const char* __format, ...) {
va_list list;
va_start(list, __format);
// g_logCallback(lft::dbg::LogMessageSeverity::error, lft::dbg::LogMessageType::general, __format, list);
va_end(list);
}
}
#endif //LOFT_DEBUG_HPP
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include "Gpu.hpp"
class MockGpu : Gpu {
public:
MockGpu();
};
+32
View File
@@ -0,0 +1,32 @@
//
// Created by martin on 7/1/24.
//
#ifndef LOFT_SHADERBINARY_H
#define LOFT_SHADERBINARY_H
#include <utility>
#include <vector>
#include <cstdint>
struct ShaderBinary {
private:
std::vector<uint32_t> m_data;
public:
inline const std::vector<uint32_t>& data() const {
return m_data;
}
inline const uint32_t code_size() const {
// last integer is '\0' => should not be included in code size
return m_data.size() - 1;
}
explicit ShaderBinary(std::vector<uint32_t> data) :
m_data(std::move(data)) {
}
};
#endif //LOFT_SHADERBINARY_H
+14
View File
@@ -0,0 +1,14 @@
#include <cstdint>
#include <string>
#include "ShaderBinary.h"
namespace io::file {
/**
* Reads file on path in as binary.
*
* @param path Path of the file
* @param pOutSize Pointer to the size_t variable to which the number of
* bytes of the file will be set.
*/
ShaderBinary read_binary(const std::string& path);
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#if _WIN32
#define GET(slot, name) const inline decltype(slot) name() const { return slot; }
#define REF(slot, name) const inline decltype(slot)& name() const { return slot; }
#else
#define GET(slot, name) [[nodiscard("Do not discard result of a getter! Use the result!")]] \
const inline __typeof(slot) name() const { return slot; }
#define REF(slot, name) [[nodiscard("Do not discard result of a getter! Use the result!")]] \
const inline __typeof(slot)& name() const { return slot; }
#endif
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <string>
#include <volk.h>
#include <memory>
#include "GpuAllocation.h"
class Gpu;
/**
* Buffer
* Device memory suballocation with generic data inside.
*/
struct Buffer {
VkBuffer buf;
GpuAllocation allocation;
Buffer() : Buffer(VK_NULL_HANDLE, {}) {
}
Buffer(VkBuffer buffer, GpuAllocation allocation) :
buf(buffer), allocation(allocation) {
}
Buffer(const Buffer&& a) noexcept :
buf(a.buf), allocation(a.allocation) {
}
Buffer(const Buffer& a) noexcept :
buf(a.buf), allocation(a.allocation) {
}
void set_debug_name(const Gpu* gpu, const std::string& name) const;
/* disable copy */
};
@@ -0,0 +1,37 @@
#pragma once
#include "Gpu.hpp"
#include <vector>
/**
* Writes data to a buffer by using a staging buffer
*/
class BufferBusWriter {
private:
const Gpu* m_gpu;
Buffer m_stagingBuffer;
size_t m_busSize;
VkCommandBuffer m_stagingCommandBuffer;
void *m_pData;
size_t m_unflushedSize;
uint32_t m_numWrites;
std::vector<std::pair<Buffer*, VkBufferCopy>> m_writes;
VkFence m_fence;
int create_staging_command_buffer();
int create_staging_buffer(size_t size);
public:
BufferBusWriter(const Gpu* gpu, size_t size);
~BufferBusWriter();
void write(Buffer* pTarget, void *pData, size_t offset, size_t size);
void flush();
void wait();
};
@@ -0,0 +1,41 @@
#pragma once
#include "resources/GpuAllocator.h"
#include "vk_mem_alloc.h"
class DefaultAllocator : public GpuAllocator {
private:
VmaAllocator m_allocator;
public:
explicit DefaultAllocator(Gpu *pGpu);
int create_image(ImageCreateInfo *pImageInfo,
MemoryAllocationInfo *pAllocInfo,
Image *pOut) override;
int create_buffer(BufferCreateInfo *pBufferInfo,
MemoryAllocationInfo *pAllocInfo,
Buffer *pOut) override;
void destroy_buffer(Buffer *pBuffer) override {
}
void destroy_image(Image *pImage) override {
}
inline void map(GpuAllocation& allocation, void **pData) override {
vmaMapMemory(m_allocator, allocation.allocation, pData);
}
inline void unmap(GpuAllocation& allocation) override {
vmaUnmapMemory(m_allocator, allocation.allocation);
}
inline VkResult flush(GpuAllocation& allocation, size_t offset, size_t size) override {
vmaFlushAllocation(m_allocator, allocation.allocation, offset, size);
return vmaInvalidateAllocation(m_allocator, allocation.allocation, offset, size);
}
};
@@ -0,0 +1,10 @@
#pragma once
#include "vk_mem_alloc.h"
/*
* Gpu memory node.
*/
struct GpuAllocation {
VmaAllocation allocation;
};
@@ -0,0 +1,57 @@
#pragma once
#include "Image.hpp"
#include "Buffer.hpp"
#include "GpuAllocation.h"
struct BufferCreateInfo {
size_t size;
VkBufferUsageFlags usage;
bool isExclusive;
};
struct ImageCreateInfo {
VkExtent2D extent;
VkFormat format;
VkImageUsageFlags usage;
VkImageAspectFlags aspectMask;
uint32_t arrayLayers;
uint32_t mipLevels;
};
enum MemoryUsage {
MEMORY_USAGE_AUTO = 0,
MEMORY_USAGE_AUTO_PREFER_DEVICE = 1,
MEMORY_USAGE_AUTO_PREFER_HOST = 2
};
struct MemoryAllocationInfo {
MemoryUsage usage;
VkMemoryPropertyFlags requiredFlags;
};
class Gpu;
/**
* Allocating of memory
*/
class GpuAllocator {
protected:
public:
virtual int create_buffer(BufferCreateInfo *pBufferInfo,
MemoryAllocationInfo *pAllocInfo, Buffer *pOut) = 0;
virtual int create_image(ImageCreateInfo *pImageInfo,
MemoryAllocationInfo *pAllocInfo, Image *pOut) = 0;
virtual void map(GpuAllocation& allocation, void **pData) = 0;
virtual void unmap(GpuAllocation& allocation) = 0;
virtual void destroy_buffer(Buffer *pBuffer) = 0;
virtual void destroy_image(Image *pImage) = 0;
virtual VkResult flush(GpuAllocation& allocation,
size_t offset, size_t size) = 0;
};
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include <string>
#include <volk.h>
#include <memory>
#include "resources/GpuAllocation.h"
#include "ImageView.hpp"
class Gpu;
struct Image {
VkImage img;
VkImageAspectFlagBits m_aspect_mask;
uint32_t m_layer_count;
uint32_t m_level_count;
GpuAllocation allocation;
public:
Image() : Image(VK_NULL_HANDLE, {}) {
}
Image(VkImage img, GpuAllocation allocation) :
img(img), allocation(allocation) {
}
ImageView create_view(const Gpu* gpu, VkFormat format,
VkImageSubresourceRange subresource);
void set_debug_name(const Gpu* gpu, const std::string& name) const;
};
@@ -0,0 +1,54 @@
#pragma once
#include "Gpu.hpp"
class ImageBusWriter {
private:
const Gpu* m_gpu;
/**
* Image to which we are writing
*/
Image *m_pTarget;
uint32_t m_formatSize;
uint32_t m_imageSize;
/**
* Intermediate staging buffer to pass data from heap 3 to heap 0
*/
Buffer m_stagingBuffer;
size_t m_stagingBufferSize;
VkCommandBuffer m_stagingCommandBuffer;
/**
* Pointing to where the staging buffer is mapped
*/
void *m_pMappedData;
std::vector<VkBufferImageCopy> m_writes;
uint32_t m_numWrites;
VkFence m_fence;
int create_staging_buffer(size_t size);
int create_staging_command_buffer();
int create_fence();
public:
ImageBusWriter(const Gpu* gpu, Image *pImage,
VkExtent2D extent, uint32_t formatSize,
size_t maxWrites);
void write(VkBufferImageCopy region, void *pData, size_t size);
void set_target(Image *pTarget) {
flush();
m_pTarget = pTarget;
}
void flush();
};
@@ -0,0 +1,22 @@
#pragma once
#include <volk.h>
#include <string>
#include <memory>
#include <vulkan/vulkan_core.h>
class Gpu;
struct ImageView {
VkImageView view;
ImageView() :
view(VK_NULL_HANDLE) {
}
ImageView(VkImageView view) :
view(view) {
}
void set_debug_name(const std::shared_ptr<const Gpu>& gpu, const std::string& name) const;
};
@@ -0,0 +1,24 @@
#pragma once
#include <cstdint>
#include <volk.h>
#include "Gpu.hpp"
class MipmapGenerator {
private:
const Gpu* m_gpu;
VkCommandBuffer m_commandBuffer;
VkFence m_fence;
VkFence create_fence(const Gpu* gpu);
VkCommandBuffer create_command_buffer(const Gpu* gpu);
public:
explicit MipmapGenerator(const Gpu* gpu);
uint32_t generate(Image image, VkImageLayout oldLayout, VkExtent2D extent, VkImageSubresourceRange range);
};
+63
View File
@@ -0,0 +1,63 @@
#pragma once
#include <signal.h>
#include <utility>
#define EXPECT(expression,msg) if(!(expression)) { throw std::runtime_error(msg); }
enum ResultCode {
RESULT_OK,
RESULT_INVALID_ARGUMENT,
RESULT_DRW_FAILURE,
RESULT_GPU_ALLOCATION_FAIL,
RESULT_GPU_RESOURCE_FAIL,
RESULT_GPU_IMAGE_MEMORY_FAIL,
RESULT_DRW_FAILED_CREATE_SHADER,
RESULT_GPU_DEVICE_CREATION_FAILED,
RESULT_GPU_COMMAND_POOL_CREATION_FAILED,
RESULT_GPU_DESCRIPTOR_POOL_CREATION_FAILED,
RESULT_NO_AVAILABLE_GPU,
};
/**
* Result. Returns either TResult or TError
*/
template<typename TResult, typename TError>
union result {
private:
bool m_isOk;
struct {
bool m_isOk;
TResult m_result;
} m_result;
struct {
bool m_isOk;
TError m_error;
} m_error;
public:
result(TResult result) :
m_result({
.m_isOk = true,
.m_result = result
}) {
}
~result() {
}
static result ok(TResult value) { return std::move(result(value)); }
static result err(TError err) { return { .m_error = err }; }
void expect(const char* msg) { raise(SIGINT); }
bool is_ok() const { return m_isOk; }
};
@@ -0,0 +1,25 @@
#pragma once
#include <vulkan/vulkan.h>
/**
* Defines blending operations for a graphics pipeline.
*/
struct BlendingInfo {
VkBlendOp colorBlendOp;
VkBlendFactor srcColorBlendFactor;
VkBlendFactor dstColorBlendFactor;
VkBlendOp alphaBlendOp;
VkBlendFactor srcAlphaBlendFactor;
VkBlendFactor dstAlphaBlendFactor;
BlendingInfo() :
colorBlendOp(VK_BLEND_OP_ADD),
srcColorBlendFactor(VK_BLEND_FACTOR_SRC_ALPHA),
dstColorBlendFactor(VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA),
alphaBlendOp(VK_BLEND_OP_ADD),
srcAlphaBlendFactor(VK_BLEND_FACTOR_SRC_ALPHA),
dstAlphaBlendFactor(VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA) {
}
};
@@ -0,0 +1,21 @@
#pragma once
#include "Shader.hpp"
#include "Pipeline.hpp"
#include <volk.h>
namespace lft {
class ComputePipelineBuilder {
private:
const Shader* m_shader;
VkPipelineLayout m_layout;
public:
ComputePipelineBuilder(const Shader* shader, VkPipelineLayout layout);
Pipeline build(const Gpu* gpu);
};
}
@@ -0,0 +1,8 @@
#pragma once
#include "ShaderBuilder.hpp"
class GlslShaderBuilder : public ShaderBuilder {
public:
Shader from_file(std::string path) override;
};
+66
View File
@@ -0,0 +1,66 @@
#pragma once
#include <volk.h>
#include <vector>
#include <optional>
#include "RenderContext.hpp"
#include "Shader.hpp"
#include "Gpu.hpp"
/**
* Abstracts a Vulkan pipeline
*/
class Pipeline {
VkPipeline m_pipeline;
VkPipelineLayout m_layout;
public:
/**
* Wraps new pipeline with layout
* @param layout pipeline layout
* @param pipeline pipeline
*/
Pipeline(VkPipelineLayout layout, VkPipeline pipeline) :
m_layout(layout), m_pipeline(pipeline) {
}
VkPipeline pipeline() const {
return m_pipeline;
}
VkPipelineLayout pipeline_layout() const {
return m_layout;
}
Pipeline& use(RenderContext *pRenderContext) {
vkCmdBindPipeline(pRenderContext->command_buffer(),
VK_PIPELINE_BIND_POINT_GRAPHICS,
m_pipeline);
return *this;
}
Pipeline& bind_input_set(RenderContext *pRenderContext, uint32_t idx, uint32_t num, VkDescriptorSet *pSets) {
vkCmdBindDescriptorSets(pRenderContext->command_buffer(),
VK_PIPELINE_BIND_POINT_GRAPHICS,
m_layout,
idx, num, pSets,
0, nullptr);
return *this;
}
inline void set_debug_name(const Gpu* gpu, const std::string name) const {
#if LOFT_DEBUG
VkDebugUtilsObjectNameInfoEXT nameInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
.objectType = VK_OBJECT_TYPE_PIPELINE,
.objectHandle = (uint64_t)pipeline(),
.pObjectName = name.c_str(),
};
vkSetDebugUtilsObjectNameEXT(gpu->dev(), &nameInfo);
#endif
}
};
@@ -0,0 +1,147 @@
#pragma once
#include "Gpu.hpp"
#include "Shader.hpp"
#include "VertexBinding.h"
#include "VertexAttribute.h"
#include "BlendingInfo.h"
#include "Pipeline.hpp"
#include <volk.h>
#include <stdexcept>
#include <algorithm>
#include <utility>
class PipelineLayoutBuilder {
private:
std::vector<VkDescriptorSetLayout> m_layouts;
std::vector<VkPushConstantRange> m_pushConstantRanges;
uint32_t m_numLayouts;
public:
PipelineLayoutBuilder() :
m_layouts(4), m_pushConstantRanges(), m_numLayouts(0) {
}
PipelineLayoutBuilder& input_set(uint32_t idx, VkDescriptorSetLayout setLayout) {
if(idx >= m_layouts.size()) {
throw std::runtime_error("As of now, only 4 descriptor set layouts are supported");
}
m_layouts[idx] = setLayout;
m_numLayouts = std::max(m_numLayouts, idx + 1);
return *this;
}
PipelineLayoutBuilder& push_constant_range(uint32_t offset, uint32_t size, VkShaderStageFlags stages) {
m_pushConstantRanges.push_back({
.stageFlags = stages,
.offset = offset,
.size = size,
});
return *this;
}
VkPipelineLayout build(const Gpu* gpu) {
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.setLayoutCount = m_numLayouts,
.pSetLayouts = m_layouts.data(),
.pushConstantRangeCount = (uint32_t)m_pushConstantRanges.size(),
.pPushConstantRanges = m_pushConstantRanges.data(),
};
VkPipelineLayout inputLayout = VK_NULL_HANDLE;
if (vkCreatePipelineLayout(gpu->dev(), &pipelineLayoutInfo, nullptr, &inputLayout)) {
throw std::runtime_error("Failed to build pipeline layout");
}
return inputLayout;
}
};
class PipelineBuilder {
private:
const Gpu* m_gpu;
VkPipelineLayout m_layout;
VkRenderPass m_renderpass;
VkViewport m_viewport;
VkRect2D m_scissor;
VkPipelineRasterizationStateCreateInfo m_rasterInfo;
VkPipelineInputAssemblyStateCreateInfo m_inputAssemblyInfo;
VkPipelineDepthStencilStateCreateInfo m_depthStencilInfo;
VkPipelineVertexInputStateCreateInfo m_vertexInputInfo;
std::vector<VkPipelineColorBlendAttachmentState> m_blendingInfo;
std::vector<VkPipelineShaderStageCreateInfo> stages;
std::vector<VertexBinding> m_vertexBindings;
std::vector<VertexAttribute> m_vertexAttributes;
public:
inline size_t num_attachments() { return m_blendingInfo.size(); }
PipelineBuilder(const Gpu* gpu, const VkViewport& viewport,
VkPipelineLayout layout, VkRenderPass outputLayout,
uint32_t numAttachments,
const Shader* vertexShader, const Shader* fragmentShader);
inline PipelineBuilder& set_vertex_input_info(std::vector<VertexBinding> bindings,
std::vector<VertexAttribute> attributes) {
m_vertexBindings = std::move(bindings);
m_vertexAttributes = std::move(attributes);
return *this;
}
/* Rasterization */
inline PipelineBuilder& polygon_mode(VkPolygonMode mode) {
this->m_rasterInfo.polygonMode = mode;
return *this;
}
inline PipelineBuilder& cull_mode(VkCullModeFlags flags) {
this->m_rasterInfo.cullMode = flags;
return *this;
}
inline PipelineBuilder& topology(VkPrimitiveTopology topology) {
this->m_inputAssemblyInfo.topology = topology;
return *this;
}
inline PipelineBuilder& set_depth_bias(float depthBiasConstantFactor, float depthBiasClamp,
float depthBiasSlopeFactor) {
m_rasterInfo.depthBiasEnable = true,
m_rasterInfo.depthBiasConstantFactor = depthBiasConstantFactor;
m_rasterInfo.depthBiasClamp = depthBiasClamp;
m_rasterInfo.depthBiasSlopeFactor = depthBiasSlopeFactor;
return *this;
}
inline PipelineBuilder& unset_blending(uint32_t attachmentIdx) {
m_blendingInfo[attachmentIdx].blendEnable = false;
return *this;
}
inline PipelineBuilder& set_blending(uint32_t attachmentIdx, BlendingInfo blendingInfo) {
m_blendingInfo[attachmentIdx] = {
.blendEnable = true,
.srcColorBlendFactor = blendingInfo.srcColorBlendFactor,
.dstColorBlendFactor = blendingInfo.dstColorBlendFactor,
.colorBlendOp = blendingInfo.colorBlendOp,
.srcAlphaBlendFactor = blendingInfo.srcAlphaBlendFactor,
.dstAlphaBlendFactor = blendingInfo.dstAlphaBlendFactor,
.alphaBlendOp = blendingInfo.alphaBlendOp,
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT |
VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT |
VK_COLOR_COMPONENT_A_BIT
};
return *this;
}
Pipeline build();
};
@@ -0,0 +1,59 @@
#pragma once
#include <vector>
#include <stdexcept>
#include <volk.h>
#include "Gpu.hpp"
namespace lft {
class PipelineLayoutBuilder {
private:
std::vector<VkDescriptorSetLayout> m_layouts;
std::vector<VkPushConstantRange> m_pushConstantRanges;
uint32_t m_numLayouts;
public:
PipelineLayoutBuilder() :
m_layouts(4), m_pushConstantRanges(), m_numLayouts(0) {
}
PipelineLayoutBuilder& input_set(uint32_t idx, VkDescriptorSetLayout setLayout) {
if(idx >= m_layouts.size()) {
throw std::runtime_error("As of now, only 4 descriptor set layouts are supported");
}
m_layouts[idx] = setLayout;
m_numLayouts = std::max(m_numLayouts, idx + 1);
return *this;
}
PipelineLayoutBuilder& push_constant_range(uint32_t offset, uint32_t size, VkShaderStageFlags stages) {
m_pushConstantRanges.push_back({
.stageFlags = stages,
.offset = offset,
.size = size,
});
return *this;
}
VkPipelineLayout build(const Gpu* gpu) {
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.setLayoutCount = m_numLayouts,
.pSetLayouts = m_layouts.data(),
.pushConstantRangeCount = (uint32_t)m_pushConstantRanges.size(),
.pPushConstantRanges = m_pushConstantRanges.data(),
};
VkPipelineLayout inputLayout = VK_NULL_HANDLE;
if (vkCreatePipelineLayout(gpu->dev(), &pipelineLayoutInfo, nullptr, &inputLayout)) {
throw std::runtime_error("Failed to build pipeline layout");
}
return inputLayout;
}
};
}
@@ -0,0 +1,72 @@
#pragma once
#include <volk.h>
#include <memory>
#include <vulkan/vulkan_core.h>
#include "Gpu.hpp"
namespace lft {
class SamplerBuilder {
VkSamplerCreateInfo m_sampler_info = {};
public:
SamplerBuilder() {
m_sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
}
inline SamplerBuilder& mag_filter(VkFilter filter) {
m_sampler_info.magFilter = filter;
return *this;
}
inline SamplerBuilder& min_filter(VkFilter filter) {
m_sampler_info.minFilter = filter;
return *this;
}
inline SamplerBuilder& filter(VkFilter filter) {
mag_filter(filter);
min_filter(filter);
return *this;
}
inline SamplerBuilder& address_mode_u(VkSamplerAddressMode address_mode) {
m_sampler_info.addressModeU = address_mode;
return *this;
}
inline SamplerBuilder& address_mode_v(VkSamplerAddressMode address_mode) {
m_sampler_info.addressModeV = address_mode;
return *this;
}
inline SamplerBuilder& address_mode_w(VkSamplerAddressMode address_mode) {
m_sampler_info.addressModeW = address_mode;
return *this;
}
inline SamplerBuilder& address_mode(VkSamplerAddressMode address_mode) {
address_mode_u(address_mode);
address_mode_v(address_mode);
address_mode_w(address_mode);
return *this;
}
inline SamplerBuilder& border_color(VkBorderColor border_color) {
m_sampler_info.borderColor = border_color;
return *this;
}
VkSampler build(std::shared_ptr<Gpu> gpu) {
VkSampler sampler = VK_NULL_HANDLE;
if(vkCreateSampler(gpu->dev(), &m_sampler_info, nullptr, &sampler)) {
throw std::runtime_error("failed to create sampler");
}
return sampler;
}
};
}
+49
View File
@@ -0,0 +1,49 @@
//
// Created by martin on 10/24/23.
//
#ifndef LOFT_SHADER_HPP
#define LOFT_SHADER_HPP
#include "Gpu.hpp"
#include "io/ShaderBinary.h"
#include <volk.h>
#include <utility>
struct Shader {
private:
VkShaderModule m_module;
public:
Shader(Shader&& s) :
m_module(s.m_module) {
}
Shader(VkShaderModule m) :
m_module(m) {
}
const inline Shader& set_name(const Gpu* gpu, const std::string &name) {
#if LOFT_DEBUG
VkDebugUtilsObjectNameInfoEXT nameInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
.objectType = VK_OBJECT_TYPE_SHADER_MODULE,
.objectHandle = (uint64_t) m_module,
.pObjectName = name.c_str(),
};
vkSetDebugUtilsObjectNameEXT(gpu->dev(), &nameInfo);
#endif
return *this;
}
[[nodiscard]] inline VkShaderModule module() const {
return m_module;
}
};
#endif //LOFT_SHADER_HPP
@@ -0,0 +1,17 @@
//
// Created by martin on 10/24/23.
//
#ifndef LOFT_SHADERBUILDER_HPP
#define LOFT_SHADERBUILDER_HPP
#include <string>
#include "Shader.hpp"
class ShaderBuilder {
public:
virtual Shader from_file(std::string path) = 0;
};
#endif //LOFT_SHADERBUILDER_HPP
@@ -0,0 +1,279 @@
#pragma once
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
#include <volk.h>
#include <memory>
#include <exception>
#include <assert.h>
#include <stdexcept>
#include <print>
#include "resources/Buffer.hpp"
#include "resources/Image.hpp"
#include "Gpu.hpp"
/**
* ShaderInputSetBufferWrite
* For having uniform buffer
*/
struct ShaderInputSetBufferWrite {
private:
VkDescriptorType m_type;
uint32_t m_binding;
VkDescriptorBufferInfo m_bufferInfo;
public:
/**
* Creates new shader input set write for Buffer
*/
ShaderInputSetBufferWrite(VkDescriptorType type, uint32_t binding, const Buffer& buffer,
uint32_t offset, uint32_t size) :
m_bufferInfo(
{
.buffer = buffer.buf,
.offset = offset,
.range = size
}
),
m_binding(binding),
m_type(type) {
}
const VkDescriptorBufferInfo* info() {
return &m_bufferInfo;
}
};
struct ShaderInputSetImageWrite {
private:
VkDescriptorType m_type;
uint32_t m_binding;
VkDescriptorImageInfo m_imageInfo;
public:
ShaderInputSetImageWrite(uint32_t binding, const ImageView& view, VkSampler sampler) :
m_imageInfo(
{
.sampler = sampler,
.imageView = view.view,
.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
}
),
m_binding(binding),
m_type(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) {
}
const VkDescriptorImageInfo* info() {
return &m_imageInfo;
}
};
union ShaderInputSetWrite {
private:
struct {
VkDescriptorType type;
uint32_t binding;
} common;
ShaderInputSetBufferWrite buffer;
ShaderInputSetImageWrite image;
public:
ShaderInputSetWrite() :
common() {
}
explicit ShaderInputSetWrite(ShaderInputSetBufferWrite bufferWrite) :
buffer(bufferWrite) {
}
explicit ShaderInputSetWrite(ShaderInputSetImageWrite imageWrite) :
image(imageWrite) {
}
uint32_t is_buffer_write(VkDescriptorType type) {
return type > VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER;
}
VkWriteDescriptorSet to_write(VkDescriptorSet set) {
return {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = set,
.dstBinding = common.binding,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = common.type,
.pImageInfo = is_buffer_write(common.type) ? nullptr : image.info(),
.pBufferInfo = is_buffer_write(common.type) ? buffer.info() : nullptr,
};
}
};
/**
* ShaderInputSetBuilder
*
* Builds descriptor sets
*/
struct ShaderInputSetBuilder {
private:
std::vector<ShaderInputSetWrite> m_writes;
public:
explicit ShaderInputSetBuilder() :
m_writes() {
}
ShaderInputSetBuilder& image(uint32_t binding, const ImageView& imageView, VkSampler sampler) {
assert(imageView.view != VK_NULL_HANDLE);
assert(sampler != VK_NULL_HANDLE);
m_writes.push_back(ShaderInputSetWrite(ShaderInputSetImageWrite(binding, imageView, sampler)));
return *this;
}
ShaderInputSetBuilder& buffer(const VkDescriptorType type, const uint32_t binding, const Buffer& buffer, const uint32_t offset, const uint32_t size) {
assert(buffer.buf != VK_NULL_HANDLE);
m_writes.push_back(ShaderInputSetWrite(ShaderInputSetBufferWrite(type, binding, buffer, offset, size)));
return *this;
}
/**
* Allocates new descriptor sets from layout.
* All the writes must be set already.
* @param pGpu Gpu on which to perform
* @param layout Descriptor set layout
* @return Allocated descriptor set with all the writes.
*/
VkDescriptorSet build(const Gpu* gpu, VkDescriptorSetLayout layout) {
VkDescriptorSetAllocateInfo descriptorInfo = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
.descriptorPool = gpu->descriptor_pool(),
.descriptorSetCount = 1,
.pSetLayouts = &layout
};
VkDescriptorSet set = VK_NULL_HANDLE;
if(vkAllocateDescriptorSets(gpu->dev(), &descriptorInfo, &set)) {
throw std::runtime_error("Failed to allocate descriptor set");
}
std::vector<VkWriteDescriptorSet> writes(m_writes.size());
for(uint32_t i = 0; i < m_writes.size(); i++) {
writes[i] = m_writes[i].to_write(set);
}
vkUpdateDescriptorSets(gpu->dev(), writes.size(), writes.data(),
0, nullptr);
return set;
}
};
struct ShaderInputSet {
private:
VkDescriptorSet m_descriptorSet;
public:
inline const VkDescriptorSet descriptor_set() const {
return m_descriptorSet;
}
ShaderInputSet(VkDescriptorSet descriptorSet) :
m_descriptorSet(descriptorSet) {
}
ShaderInputSet(const Gpu* gpu, VkDescriptorSetLayout layout) :
m_descriptorSet(VK_NULL_HANDLE) {
VkDescriptorSetAllocateInfo descriptorInfo = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
.descriptorPool = gpu->descriptor_pool(),
.descriptorSetCount = 1,
.pSetLayouts = &layout
};
if(vkAllocateDescriptorSets(gpu->dev(), &descriptorInfo, &m_descriptorSet)) {
throw std::runtime_error("Failed to allocate descriptor set");
}
}
};
enum ShaderInputWriteType {
};
class ShaderInputSetWriter {
private:
const Gpu* m_gpu;
std::vector<VkWriteDescriptorSet> m_writes;
std::vector<std::vector<VkDescriptorImageInfo>> m_imageWrites;
std::vector<std::vector<VkDescriptorBufferInfo>> m_bufferWrites;
public:
explicit ShaderInputSetWriter(const Gpu* gpu) :
m_gpu(gpu) {
}
ShaderInputSetWriter& write_images(const ShaderInputSet& dstInputSet,
const uint32_t dstBinding,
const uint32_t dstArrayElement,
const VkDescriptorType type,
const std::vector<VkDescriptorImageInfo>& writes) {
/* copy write data, to not lose it */
m_imageWrites.push_back(writes);
m_writes.push_back({
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = dstInputSet.descriptor_set(),
.dstBinding = dstBinding,
.dstArrayElement = dstArrayElement,
.descriptorCount = (uint32_t)writes.size(),
.descriptorType = type,
.pImageInfo = m_imageWrites[m_imageWrites.size() - 1].data()
});
return *this;
}
ShaderInputSetWriter& write_buffer(const ShaderInputSet& dstInputSet,
const uint32_t dstBinding,
const uint32_t dstArrayElement,
const VkDescriptorType type,
const std::vector<VkDescriptorBufferInfo>& writes) {
/* copy write data, to not lose it */
m_bufferWrites.push_back(writes);
m_writes.push_back({
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = dstInputSet.descriptor_set(),
.dstBinding = dstBinding,
.dstArrayElement = dstArrayElement,
.descriptorCount = (uint32_t)writes.size(),
.descriptorType = type,
.pBufferInfo = m_bufferWrites[m_bufferWrites.size() - 1].data()
});
return *this;
}
ShaderInputSetWriter& write() {
vkUpdateDescriptorSets(m_gpu->dev(), m_writes.size(), m_writes.data(), 0, nullptr);
return *this;
}
};
@@ -0,0 +1,98 @@
#pragma once
#include "Gpu.hpp"
#include <vector>
#include <volk.h>
#include <assert.h>
#include <stdexcept>
class ShaderInputSetLayoutBuilder {
private:
std::vector<VkDescriptorSetLayoutBinding> m_bindings;
public:
ShaderInputSetLayoutBuilder() :
m_bindings() {
}
ShaderInputSetLayoutBuilder(std::vector<VkDescriptorSetLayoutBinding> bindings) :
m_bindings(bindings) {
}
ShaderInputSetLayoutBuilder& uniform_buffer(
uint32_t binding,
VkShaderStageFlags shader_stages = VK_SHADER_STAGE_ALL
) {
m_bindings.push_back({
.binding = binding,
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.descriptorCount = 1,
.stageFlags = shader_stages,
});
return *this;
}
ShaderInputSetLayoutBuilder& image(
uint32_t binding,
VkShaderStageFlags shader_stages = VK_SHADER_STAGE_ALL
) {
m_bindings.push_back({
.binding = binding,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = 1,
.stageFlags = shader_stages,
});
return *this;
}
ShaderInputSetLayoutBuilder& n_images(
uint32_t binding,
uint32_t count,
VkShaderStageFlags shader_stages = VK_SHADER_STAGE_ALL
) {
m_bindings.push_back({
.binding = binding,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = count,
.stageFlags = shader_stages,
});
return *this;
}
ShaderInputSetLayoutBuilder& binding(
uint32_t binding,
VkDescriptorType type,
uint32_t count,
VkShaderStageFlags stages
) {
m_bindings.push_back({
.binding = binding,
.descriptorType = type,
.descriptorCount = count,
.stageFlags = stages,
});
return *this;
}
VkDescriptorSetLayout build(const Gpu* gpu) const {
VkDescriptorSetLayoutCreateInfo layoutInfo = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.bindingCount = (uint32_t)m_bindings.size(),
.pBindings = m_bindings.data(),
};
VkDescriptorSetLayout layout = VK_NULL_HANDLE;
if(vkCreateDescriptorSetLayout(gpu->dev(), &layoutInfo, nullptr, &layout)) {
throw std::runtime_error("Failed to create descriptor set layout");
}
return layout;
}
};
@@ -0,0 +1,23 @@
//
// Created by martin on 10/24/23.
//
#ifndef LOFT_SPIRVSHADERBUILDER_HPP
#define LOFT_SPIRVSHADERBUILDER_HPP
#include "ShaderBuilder.hpp"
#include "Gpu.hpp"
class SpirvShaderBuilder : public ShaderBuilder {
private:
const Gpu* m_gpu;
public:
SpirvShaderBuilder(const Gpu* gpu);
Shader from_binary(const std::vector<uint32_t>& code) const;
Shader from_file(std::string path) override;
};
#endif //LOFT_SPIRVSHADERBUILDER_HPP
@@ -0,0 +1,15 @@
#pragma once
#include <cstdint>
#include <volk.h>
struct VertexAttribute {
uint32_t location;
uint32_t binding;
VkFormat format;
uint32_t offset;
VkVertexInputAttributeDescription get_vk() {
return *(VkVertexInputAttributeDescription*)this;
}
};
@@ -0,0 +1,13 @@
#pragma once
#include <volk.h>
struct VertexBinding {
uint32_t binding;
uint32_t stride;
VkVertexInputRate inputRate;
VkVertexInputBindingDescription get_vk() {
return *(VkVertexInputBindingDescription*)this;
}
};
+63
View File
@@ -0,0 +1,63 @@
#include <stdexcept>
#include "FramebufferBuilder.hpp"
Framebuffer::Framebuffer(VkFramebuffer framebuffer) :
framebuffer(framebuffer) {
}
Framebuffer::Framebuffer(const Gpu* gpu, const VkRenderPass renderpass, const VkExtent2D extent,
const std::vector<VkImageView>& attachments) {
VkFramebufferCreateInfo framebufferInfo = {
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.renderPass = renderpass,
.attachmentCount = (uint32_t)attachments.size(),
.pAttachments = attachments.data(),
.width = extent.width,
.height = extent.height,
.layers = 1,
};
if(vkCreateFramebuffer(gpu->dev(), &framebufferInfo, nullptr, &framebuffer)) {
throw std::runtime_error("Failed to create framebuffer");
}
}
FramebufferBuilder::FramebufferBuilder(VkRenderPass renderpass, VkExtent2D extent) :
FramebufferBuilder(renderpass, extent, 4) {
}
FramebufferBuilder::FramebufferBuilder(VkRenderPass renderpass, VkExtent2D extent, uint32_t numAttachments) :
m_renderpass(renderpass), m_extent(extent), m_attachments(numAttachments) {
}
FramebufferBuilder::FramebufferBuilder(VkRenderPass renderpass, VkExtent2D extent, std::vector<ImageView> attachments) :
m_renderpass(renderpass), m_extent(extent), m_attachments(attachments) {
}
Framebuffer FramebufferBuilder::build(const Gpu* gpu) {
VkFramebufferCreateInfo framebufferInfo = {
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.renderPass = m_renderpass,
.attachmentCount = (uint32_t)m_attachments.size(),
.pAttachments = (VkImageView*)m_attachments.data(),
.width = m_extent.width,
.height = m_extent.height,
.layers = 1,
};
VkFramebuffer framebuffer;
if(vkCreateFramebuffer(gpu->dev(), &framebufferInfo, nullptr, &framebuffer)) {
throw std::runtime_error("Failed to create framebuffer");
}
return {framebuffer};
}
FramebufferBuilder &FramebufferBuilder::set_attachment(uint32_t idx, VkImageView view) {
m_attachments[idx] = ImageView(view);
return *this;
}
+339
View File
@@ -0,0 +1,339 @@
#include "Gpu.hpp"
#include "resources/DefaultAllocator.h"
#include "result.hpp"
#include "result.hpp"
#include <vector>
#include <iostream>
#include <memory>
#include <cstring>
static const char *DEVICE_EXTENSIONS[] = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME,
VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME,
VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME,
VK_EXT_ROBUSTNESS_2_EXTENSION_NAME
};
static const int NUM_DEVICE_EXTENSIONS = sizeof(DEVICE_EXTENSIONS) / sizeof(*DEVICE_EXTENSIONS);
#if VK_LAYERS_ENABLE
static const char* LAYERS[] = {
"VK_LAYER_KHRONOS_validation"
};
static const int NUM_LAYERS = sizeof(LAYERS) / sizeof(*LAYERS);
#else
#define LAYERS nullptr
static const int NUM_LAYERS = 0;
#endif
int32_t get_graphics_score(VkQueueFamilyProperties props) {
bool isSupported = props.queueFlags | VK_QUEUE_GRAPHICS_BIT;
return isSupported;
}
int32_t get_transfer_score(VkQueueFamilyProperties props) {
bool isSupported = props.queueFlags | VK_QUEUE_TRANSFER_BIT;
bool isDedicated = props.queueFlags & ~(VK_QUEUE_GRAPHICS_BIT);
return (isDedicated << 1) * isSupported;
}
int32_t get_present_score(VkQueueFamilyProperties props, bool isPresentSupported) {
return isPresentSupported;
}
std::vector<int32_t> Gpu::get_queues(std::optional<Surface*> surface) {
uint32_t numQueues = 0;
vkGetPhysicalDeviceQueueFamilyProperties(m_gpu, &numQueues, 0x0);
auto properties = std::vector<VkQueueFamilyProperties>(numQueues);
vkGetPhysicalDeviceQueueFamilyProperties(m_gpu, &numQueues,
properties.data());
if(numQueues == 0) {
throw std::runtime_error("Failed to find any queue family");
}
int32_t graphicsQueue = -1;
int32_t transferQueue = -1;
int32_t transferScore = 0;
int32_t presentQueue = -1;
int32_t presentScore = 0;
uint32_t i = 0;
for(auto& prop : properties) {
VkBool32 isPresentSupported = false;
if(surface.has_value()) {
vkGetPhysicalDeviceSurfaceSupportKHR(m_gpu, i, surface.value()->surface(), &isPresentSupported);
} else {
isPresentSupported = true;
}
bool isTaken = false;
int32_t iterGraphicsScore = get_graphics_score(prop) * (graphicsQueue == -1);
int32_t iterTransferScore = get_transfer_score(prop);
int32_t iterPresentScore = get_present_score(prop, isPresentSupported);
if(iterGraphicsScore > 0) {
graphicsQueue = i;
isTaken = true;
}
if((iterTransferScore | (!isTaken << 2)) > transferScore) {
transferQueue = i;
transferScore = (iterTransferScore & (!isTaken << 2));
isTaken = true;
}
if((iterPresentScore | (!isTaken << 2)) > presentScore) {
presentQueue = i;
presentScore = (iterPresentScore & (!isTaken << 2));
isTaken = true;
}
}
return { graphicsQueue, transferQueue, presentQueue };
}
ResultCode Gpu::create_logical_device(std::optional<Surface*> supportedSurface) {
auto queueFamilies = get_queues(supportedSurface);
std::vector<VkDeviceQueueCreateInfo> queueInfos(queueFamilies.size());
float priority = 1.0f;
int32_t x = 0;
for(int32_t i = 0; i < queueFamilies.size(); i++) {
bool isDuplicate = false;
for(int32_t y = i - 1; y >= 0; y--) {
if(queueFamilies[i] == queueFamilies[y]) {
isDuplicate = true;
break;
}
}
if(isDuplicate) continue;
queueInfos[x] = {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = (uint32_t)queueFamilies[x],
.queueCount = 1,
.pQueuePriorities = &priority
};
x++;
}
VkPhysicalDeviceFeatures gpuFeatures = { };
vkGetPhysicalDeviceFeatures(m_gpu, &gpuFeatures);
VkPhysicalDeviceSynchronization2Features syncFeatures = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES,
.pNext = nullptr,
.synchronization2 = true,
};
VkPhysicalDeviceRobustness2FeaturesEXT robustness = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT,
.pNext = &syncFeatures,
.robustBufferAccess2 = true,
.robustImageAccess2 = true,
.nullDescriptor = true
};
VkPhysicalDeviceCoherentMemoryFeaturesAMD coherentMemoryFeatures {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COHERENT_MEMORY_FEATURES_AMD,
.pNext = &robustness,
.deviceCoherentMemory = true
};
VkDeviceCreateInfo deviceInfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.pNext = &coherentMemoryFeatures,
.queueCreateInfoCount = (uint32_t)x,
.pQueueCreateInfos = queueInfos.data(),
.enabledLayerCount = NUM_LAYERS,
.ppEnabledLayerNames = LAYERS,
.enabledExtensionCount = NUM_DEVICE_EXTENSIONS,
.ppEnabledExtensionNames = DEVICE_EXTENSIONS,
.pEnabledFeatures = &gpuFeatures,
};
if(vkCreateDevice(m_gpu, &deviceInfo, NULL, &m_dev)) {
return RESULT_GPU_DEVICE_CREATION_FAILED;
}
VkCommandPoolCreateInfo poolInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
.queueFamilyIndex = (uint32_t)queueFamilies[0],
};
if(vkCreateCommandPool(m_dev, &poolInfo, nullptr, &m_graphicsCommandPool)) {
return RESULT_GPU_COMMAND_POOL_CREATION_FAILED;
}
vkGetDeviceQueue(m_dev, queueFamilies[0], 0, &m_graphicsQueue);
if(queueFamilies[0] == queueFamilies[1]) {
if(vkCreateCommandPool(m_dev, &poolInfo, nullptr, &m_transferCommandPool)) {
return RESULT_GPU_COMMAND_POOL_CREATION_FAILED;
}
m_transferQueue = m_graphicsQueue;
} else {
poolInfo.queueFamilyIndex = (uint32_t)queueFamilies[1];
if(vkCreateCommandPool(m_dev, &poolInfo, nullptr, &m_transferCommandPool)) {
return RESULT_GPU_COMMAND_POOL_CREATION_FAILED;
}
vkGetDeviceQueue(m_dev, queueFamilies[1], 0, &m_transferQueue);
}
if(vkCreateCommandPool(m_dev, &poolInfo, nullptr, &m_presentCommandPool)) {
return RESULT_GPU_COMMAND_POOL_CREATION_FAILED;
}
if(queueFamilies[0] == queueFamilies[2]) {
m_presentQueue = m_graphicsQueue;
} else if(queueFamilies[1] == queueFamilies[2]) {
m_presentQueue = m_transferQueue;
} if(queueFamilies[2] != queueFamilies[1] &&
queueFamilies[2] != queueFamilies[0]) {
vkGetDeviceQueue(m_dev, queueFamilies[2], 0, &m_presentQueue);
}
m_graphicsQueueIdx = queueFamilies[0];
m_transferQueueIdx = queueFamilies[1];
m_presentQueueIdx = queueFamilies[2];
return RESULT_OK;
}
ResultCode Gpu::choose_gpu(VkPhysicalDevice *pOut) {
uint32_t numDevices = 0;
vkEnumeratePhysicalDevices(m_instance->instance(), &numDevices, nullptr);
if(numDevices == 0) {
throw std::runtime_error("No GPU supporting Vulkan was found. Try installing Vulkan drivers. Remember that some GPUs does not need to support Vulkan.");
}
auto devices = std::vector<VkPhysicalDevice>(numDevices);
vkEnumeratePhysicalDevices(m_instance->instance(), &numDevices, devices.data());
VkPhysicalDevice chosen = VK_NULL_HANDLE;
for(auto& device : devices) {
VkPhysicalDeviceProperties props;
vkGetPhysicalDeviceProperties(device, &props);
uint32_t numExtensions;
vkEnumerateDeviceExtensionProperties(device, nullptr, &numExtensions, nullptr);
std::vector<VkExtensionProperties> extensions(numExtensions);
vkEnumerateDeviceExtensionProperties(device, nullptr, &numExtensions, extensions.data());
for(int x = 0; x < NUM_DEVICE_EXTENSIONS; x++) {
bool isFound = false;
for(int y = 0; y < numExtensions; y++) {
if(!strcmp(extensions[y].extensionName, DEVICE_EXTENSIONS[x])) {
isFound = true;
break;
}
}
if(!isFound) {
lft::log::fail("Device %s does not support required device extension: (%s)",
props.deviceName, DEVICE_EXTENSIONS[x]);
continue;
}
}
chosen = device;
}
if(chosen == VK_NULL_HANDLE) {
throw std::runtime_error("No GPU supporting all required features was found. Try updating your graphics card driver. This however might not help on older devices.");
}
VkPhysicalDeviceProperties props;
vkGetPhysicalDeviceProperties(chosen, &props);
lft::log::info("Selected GPU: (%s)", props.deviceName);
*pOut = chosen;
return RESULT_OK;
}
ResultCode Gpu::create_descriptor_pool() {
VkDescriptorPoolSize poolSizes[] =
{
{
.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.descriptorCount = 1000,
}, {
.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = 1000
}, {
.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1000
}
};
VkDescriptorPoolCreateInfo poolInfo = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.maxSets = 1000,
.poolSizeCount = sizeof(poolSizes) / sizeof(*poolSizes),
.pPoolSizes = poolSizes,
};
if (vkCreateDescriptorPool(dev(), &poolInfo, nullptr, &m_descriptorPool)) {
return RESULT_GPU_DESCRIPTOR_POOL_CREATION_FAILED;
}
return RESULT_OK;
}
Gpu::Gpu(const Instance* instance, std::optional<Surface*> supportedSurface) :
m_instance(instance), m_pAllocator(nullptr) {
if(choose_gpu(&m_gpu)) {
throw std::runtime_error("Failed to choose gpu");
}
if(create_logical_device(supportedSurface)) {
throw std::runtime_error("Failed to create logical device");
}
if(create_descriptor_pool()) {
throw std::runtime_error("Failed to create descriptor pool");
}
m_pAllocator = std::make_unique<DefaultAllocator>(DefaultAllocator(this));
}
Gpu::~Gpu() {
vkDestroyDescriptorPool(m_dev, m_descriptorPool, nullptr);
vkDestroyCommandPool(m_dev, m_graphicsCommandPool, nullptr);
vkDestroyCommandPool(m_dev, m_transferCommandPool, nullptr);
vkDestroyCommandPool(m_dev, m_presentCommandPool, nullptr);
vkDestroyDevice(m_dev, nullptr);
}
void Gpu::enqueue_present(VkPresentInfoKHR *pPresentInfo) const {
if(vkQueuePresentKHR(m_presentQueue, pPresentInfo)) {
throw std::runtime_error("Failed to present");
}
}
void Gpu::enqueue_graphics(VkSubmitInfo2 *pSubmitInfo, VkFence fence) const {
if(vkQueueSubmit2KHR(m_graphicsQueue, 1, pSubmitInfo, fence)) {
throw std::runtime_error("Failed to submit to graphics queue");
}
}
void Gpu::enqueue_transfer(VkSubmitInfo *pSubmitInfo, VkFence fence) const {
vkQueueSubmit(m_transferQueue, 1, pSubmitInfo, fence);
}
+172
View File
@@ -0,0 +1,172 @@
#include <iostream>
#include <numeric>
#include <sstream>
#include <cstring>
#include <stdexcept>
#include <iterator>
#include "Instance.hpp"
#include "result.hpp"
#include "debug/Debug.hpp"
#if LOFT_DEBUG && VK_LAYERS_ENABLE
static const char* LAYERS[] = {
"VK_LAYER_KHRONOS_validation"
};
static const int NUM_LAYERS = sizeof(LAYERS) / sizeof(*LAYERS);
#else
#define LAYERS nullptr
static const int NUM_LAYERS = 0;
#endif
static const char* EXTENSIONS[] = {
VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
};
static const int NUM_EXTENSIONS = sizeof(EXTENSIONS) / sizeof(*EXTENSIONS);
static const char *DEVICE_EXTENSIONS[] = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME,
};
static const int NUM_DEVICE_EXTENSIONS = sizeof(DEVICE_EXTENSIONS) / sizeof(*DEVICE_EXTENSIONS);
static bool IS_INITIALIZED = false;
lft::dbg::lft_log_callback g_logCallback = nullptr;
VkBool32
vk_dbg_callback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
VkDebugUtilsMessageTypeFlagsEXT type,
const VkDebugUtilsMessengerCallbackDataEXT* pData,
void *pUserData) {
if(g_logCallback != nullptr) {
g_logCallback(lft::dbg::LogMessageSeverity::error,
lft::dbg::LogMessageType::general,
pData->pMessage, {});
} else {
std::cout << pData->pMessage << std::endl;
}
return VK_FALSE;
}
std::string get_unsupported_layers_error_mesg(std::vector<std::string> unsupported_layers) {
std::stringstream str;
std::copy(unsupported_layers.begin(), unsupported_layers.end(),
std::ostream_iterator<std::string>(str, "\n"));
return "Unsupported layers: \n" + str.str();
}
std::string get_unsupported_extensions_error_mesg(std::vector<std::string> unsupported_extensions) {
std::stringstream str;
std::copy(unsupported_extensions.begin(), unsupported_extensions.end(),
std::ostream_iterator<std::string>(str, "\n"));
return "Unsupported extensions: \n" + str.str();
}
std::vector<char*> transform_strings_to_c_strings(std::vector<std::string> strings) {
std::vector<char*> result(strings.size());
std::transform(strings.begin(), strings.end(),
result.begin(),
[](const std::string& str) {
return strndup(str.c_str(), str.length());
});
return result;
}
Instance::Instance(const std::string applicationName,
const std::string engineName,
std::vector<std::string> extensions,
std::vector<std::string> layers,
lft::dbg::lft_log_callback callback) {
if(volkInitialize()) {
throw std::runtime_error("Failed to initialize volk");
}
// check unsupported extensions
auto unsupportedExtensions = check_extensions(extensions);
auto unsupportedLayers = find_unsupported_layers(layers);
if(!unsupportedExtensions.empty()) {
throw std::runtime_error(get_unsupported_extensions_error_mesg(unsupportedExtensions));
}
if(!unsupportedLayers.empty()) {
throw std::runtime_error(get_unsupported_layers_error_mesg(unsupportedLayers));
}
// EXPECT(!unsupportedExtensions.empty(), "Unsupported extensions");
if(callback) {
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
VkApplicationInfo appInfo = {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pApplicationName = "test", // strdup(applicationName.c_str()),
.applicationVersion = VK_MAKE_VERSION(1, 0, 0),
.pEngineName = "test",// strdup(engineName.c_str()),
.apiVersion = VK_API_VERSION_1_1
};
VkDebugUtilsMessengerCreateInfoEXT dbgInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
.pNext = nullptr,
.flags = 0,
.messageSeverity =
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
.messageType =
VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
.pfnUserCallback = vk_dbg_callback,
.pUserData = nullptr
};
std::vector<char*> extension_cstrs = transform_strings_to_c_strings(extensions);
std::vector<char*> layer_cstrs = transform_strings_to_c_strings(layers);
VkInstanceCreateInfo instanceInfo = {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
#if LOFT_DEBUG
.pNext = &dbgInfo,
#endif
.pApplicationInfo = &appInfo,
#if LOFT_DEBUG
.enabledLayerCount = (uint32_t)layer_cstrs.size(),
.ppEnabledLayerNames = layer_cstrs.data(),
#endif
.enabledExtensionCount = (uint32_t)extension_cstrs.size(),
.ppEnabledExtensionNames = extension_cstrs.data(),
};
if(callback != nullptr) {
g_logCallback = callback;
}
EXPECT(vkCreateInstance(&instanceInfo, nullptr, &m_instance) == VK_SUCCESS,
"Failed to create vulkan instance");
volkLoadInstance(m_instance);
lft::log::warn("Instance created successfully");
// cleanup
for(char* str : extension_cstrs) {
delete [] str;
}
for(char* str : layer_cstrs) {
delete [] str;
}
IS_INITIALIZED = true;
}
+55
View File
@@ -0,0 +1,55 @@
#include "Recording.hpp"
namespace lft {
RecordingBindPoint::RecordingBindPoint(
const Recording* recording,
const Pipeline pipeline,
VkPipelineBindPoint bind_point
) : m_recording(recording),
m_pipeline(pipeline),
m_bind_point(bind_point) {
vkCmdBindPipeline(m_recording->cmdbuf(), bind_point, pipeline.pipeline());
}
const RecordingBindPoint& RecordingBindPoint::bind_descriptor_set(
uint32_t set,
VkDescriptorSet descriptor_set
) const {
vkCmdBindDescriptorSets(m_recording->cmdbuf(),
m_bind_point,
m_pipeline.pipeline_layout(),
set,
1, &descriptor_set,
0, nullptr);
return *this;
}
const RecordingBindPoint& RecordingBindPoint::bind_descriptor_sets(
uint32_t first_set,
const std::vector<VkDescriptorSet>& descriptor_sets
) const {
vkCmdBindDescriptorSets(m_recording->cmdbuf(),
m_bind_point,
m_pipeline.pipeline_layout(),
first_set,
descriptor_sets.size(), descriptor_sets.data(),
0, nullptr);
return *this;
}
const RecordingBindPoint& RecordingBindPoint::push_constants(
VkShaderStageFlags shader_stages,
uint32_t offset, uint32_t size, const void* data) const {
vkCmdPushConstants(m_recording->cmdbuf(), m_pipeline.pipeline_layout(), shader_stages,
offset, size, data);
return *this;
}
}
+23
View File
@@ -0,0 +1,23 @@
#include <stdexcept>
#include "io/file.hpp"
#include "io/ShaderBinary.h"
ShaderBinary io::file::read_binary(const std::string& path) {
FILE *f = fopen(path.c_str(), "rb");
if(!f) {
throw std::runtime_error("Failed to open file");
}
fseek(f, 0, SEEK_END);
size_t size = ftell(f);
std::vector<uint32_t> data(size + 1);
fseek(f, 0, SEEK_SET);
fread(data.data(), 4, size, f);
fclose(f);
data[size] = '\0';
return ShaderBinary(data);
}
+19
View File
@@ -0,0 +1,19 @@
//
// Created by martin on 4/5/24.
//
#include "resources/Buffer.hpp"
#include "Gpu.hpp"
void Buffer::set_debug_name(const Gpu* gpu, const std::string& name) const {
#if LOFT_DEBUG && VK_EXT_debug_utils
VkDebugUtilsObjectNameInfoEXT nameInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
.objectType = VK_OBJECT_TYPE_BUFFER,
.objectHandle = (uint64_t)buf,
.pObjectName = name.c_str(),
};
vkSetDebugUtilsObjectNameEXT(gpu->dev(), &nameInfo);
#endif
}
@@ -0,0 +1,147 @@
#include "resources/BufferBusWriter.h"
#include <string.h>
#include <volk.h>
#include <algorithm>
int
BufferBusWriter::create_staging_buffer(size_t size) {
BufferCreateInfo stagingBufferInfo = {
.size = size,
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
.isExclusive = true
};
MemoryAllocationInfo memoryAllocationInfo = {
.usage = MEMORY_USAGE_AUTO_PREFER_HOST,
.requiredFlags = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
};
m_gpu->memory()->create_buffer(&stagingBufferInfo, &memoryAllocationInfo,
&m_stagingBuffer);
return 0;
}
int
BufferBusWriter::create_staging_command_buffer() {
VkCommandBufferAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = m_gpu->transfer_command_pool(),
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
};
vkAllocateCommandBuffers(m_gpu->dev(), &allocInfo, &m_stagingCommandBuffer);
return 0;
}
BufferBusWriter::BufferBusWriter(const Gpu* gpu, size_t size) :
m_gpu(gpu), m_unflushedSize(0), m_busSize(size), m_numWrites(0) {
create_staging_buffer(size);
create_staging_command_buffer();
m_gpu->memory()->map(m_stagingBuffer.allocation, &m_pData);
VkFenceCreateInfo fenceInfo = {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.flags = VK_FENCE_CREATE_SIGNALED_BIT
};
vkCreateFence(m_gpu->dev(), &fenceInfo, nullptr, &m_fence);
}
void BufferBusWriter::write(Buffer* pTarget, void *pData, size_t offset, size_t size) {
char* pUploadData = (char*)pData;
while(size > 0) {
size_t uploadSize = std::min(m_busSize - m_unflushedSize, size);
memcpy((char*)m_pData + m_unflushedSize, pUploadData, uploadSize);
pUploadData += uploadSize;
VkBufferCopy write = {
.srcOffset = m_unflushedSize,
.dstOffset = offset,
.size = uploadSize,
};
m_numWrites++;
if(m_writes.size() <= m_numWrites) {
m_writes.resize(m_numWrites * 2);
}
m_writes[m_numWrites - 1] = std::pair(pTarget, write);
m_unflushedSize += uploadSize;
offset += uploadSize;
size -= uploadSize;
if(m_busSize - m_unflushedSize == 0) {
flush();
// wait, because we are gonna overwrite the data
wait();
}
}
}
void BufferBusWriter::flush() {
if(m_numWrites == 0) {
m_unflushedSize = 0;
return;
}
m_gpu->memory()->flush(m_stagingBuffer.allocation, 0, m_unflushedSize);
wait();
vkResetFences(m_gpu->dev(), 1, &m_fence);
VkCommandBufferBeginInfo beginInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
vkBeginCommandBuffer(m_stagingCommandBuffer, &beginInfo);
for(uint32_t i = 0; i < m_numWrites; i++) {
vkCmdCopyBuffer(m_stagingCommandBuffer, m_stagingBuffer.buf, m_writes[i].first->buf,
1, &m_writes[i].second);
}
vkEndCommandBuffer(m_stagingCommandBuffer);
VkSubmitInfo submitInfo = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &m_stagingCommandBuffer,
};
m_gpu->enqueue_transfer(&submitInfo, m_fence);
m_unflushedSize = 0;
m_numWrites = 0;
}
BufferBusWriter::~BufferBusWriter() {
flush();
wait();
m_gpu->memory()->destroy_buffer(&m_stagingBuffer);
}
void BufferBusWriter::wait() {
vkWaitForFences(m_gpu->dev(), 1, &m_fence, VK_TRUE, UINT64_MAX);
// vkDeviceWaitIdle(m_pGpu->dev());
}
@@ -0,0 +1,101 @@
#include "resources/DefaultAllocator.h"
#define VMA_IMPLEMENTATION
#include "vk_mem_alloc.h"
#include "Gpu.hpp"
DefaultAllocator::DefaultAllocator(Gpu *pGpu) {
VmaVulkanFunctions vma_vulkan_func{};
vma_vulkan_func.vkAllocateMemory = vkAllocateMemory;
vma_vulkan_func.vkBindBufferMemory = vkBindBufferMemory;
vma_vulkan_func.vkBindImageMemory = vkBindImageMemory;
vma_vulkan_func.vkCreateBuffer = vkCreateBuffer;
vma_vulkan_func.vkCreateImage = vkCreateImage;
vma_vulkan_func.vkDestroyBuffer = vkDestroyBuffer;
vma_vulkan_func.vkDestroyImage = vkDestroyImage;
vma_vulkan_func.vkFlushMappedMemoryRanges = vkFlushMappedMemoryRanges;
vma_vulkan_func.vkFreeMemory = vkFreeMemory;
vma_vulkan_func.vkGetBufferMemoryRequirements = vkGetBufferMemoryRequirements;
vma_vulkan_func.vkGetImageMemoryRequirements = vkGetImageMemoryRequirements;
vma_vulkan_func.vkGetPhysicalDeviceMemoryProperties = vkGetPhysicalDeviceMemoryProperties;
vma_vulkan_func.vkGetPhysicalDeviceProperties = vkGetPhysicalDeviceProperties;
vma_vulkan_func.vkInvalidateMappedMemoryRanges = vkInvalidateMappedMemoryRanges;
vma_vulkan_func.vkMapMemory = vkMapMemory;
vma_vulkan_func.vkUnmapMemory = vkUnmapMemory;
vma_vulkan_func.vkCmdCopyBuffer = vkCmdCopyBuffer;
vma_vulkan_func.vkGetDeviceProcAddr = vkGetDeviceProcAddr;
vma_vulkan_func.vkGetInstanceProcAddr = vkGetInstanceProcAddr;
VmaAllocatorCreateInfo allocatorCreateInfo = {
.physicalDevice = pGpu->gpu(),
.device = pGpu->dev(),
.pVulkanFunctions = &vma_vulkan_func,
.instance = pGpu->instance()->instance(),
.vulkanApiVersion = VK_API_VERSION_1_0,
};
vmaCreateAllocator(&allocatorCreateInfo, &m_allocator);
}
VmaMemoryUsage get_vma_memory_usage(MemoryUsage memoryUsage) {
switch(memoryUsage) {
case MEMORY_USAGE_AUTO:
return VMA_MEMORY_USAGE_AUTO;
case MEMORY_USAGE_AUTO_PREFER_DEVICE:
return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
case MEMORY_USAGE_AUTO_PREFER_HOST:
return VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
default:
return VMA_MEMORY_USAGE_AUTO;
}
}
int DefaultAllocator::create_buffer(BufferCreateInfo *pBufferInfo, MemoryAllocationInfo *pAllocInfo, Buffer *pOut) {
VkBufferCreateInfo bufferInfo = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = pBufferInfo->size,
.usage = pBufferInfo->usage,
.sharingMode = pBufferInfo->isExclusive ? VK_SHARING_MODE_EXCLUSIVE : VK_SHARING_MODE_CONCURRENT
};
VmaAllocationCreateInfo allocInfo = {
.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
.usage = get_vma_memory_usage(pAllocInfo->usage),
.requiredFlags = pAllocInfo->requiredFlags,
};
vmaCreateBuffer(m_allocator, &bufferInfo, &allocInfo, &pOut->buf, &pOut->allocation.allocation, nullptr);
return 0;
}
int DefaultAllocator::create_image(ImageCreateInfo *pImageInfo, MemoryAllocationInfo *pAllocInfo, Image *pOut) {
VkImageCreateInfo imageInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = pImageInfo->format,
.extent = {
pImageInfo->extent.width,
pImageInfo->extent.height,
1,
},
.mipLevels = pImageInfo->mipLevels,
.arrayLayers = pImageInfo->arrayLayers,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = pImageInfo->usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
};
VmaAllocationCreateInfo allocInfo = {};
allocInfo.usage = get_vma_memory_usage(pAllocInfo->usage);
allocInfo.requiredFlags = pAllocInfo->requiredFlags;
vmaCreateImage(m_allocator, &imageInfo, &allocInfo, &pOut->img, &pOut->allocation.allocation, nullptr);
pOut->m_layer_count = imageInfo.arrayLayers;
pOut->m_level_count = imageInfo.mipLevels;
return 0;
}
+39
View File
@@ -0,0 +1,39 @@
#include "resources/Image.hpp"
#include "Gpu.hpp"
ImageView
Image::create_view(const Gpu* gpu, VkFormat format,
VkImageSubresourceRange subresource) {
VkImageViewCreateInfo viewInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = img,
.viewType = subresource.layerCount == 1 ? VK_IMAGE_VIEW_TYPE_2D : VK_IMAGE_VIEW_TYPE_2D_ARRAY,
.format = format,
.components = {
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY
},
.subresourceRange = subresource
};
VkImageView result = VK_NULL_HANDLE;
vkCreateImageView(gpu->dev(), &viewInfo, nullptr, &result);
return {result};
}
void Image::set_debug_name(const Gpu* gpu, const std::string& name) const {
#if LOFT_DEBUG
VkDebugUtilsObjectNameInfoEXT nameInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
.objectType = VK_OBJECT_TYPE_IMAGE,
.objectHandle = (uint64_t)img,
.pObjectName = name.c_str(),
};
vkSetDebugUtilsObjectNameEXT(gpu->dev(), &nameInfo);
#endif
}
@@ -0,0 +1,161 @@
#include "resources/ImageBusWriter.h"
#include <string.h>
#include <volk.h>
#include <cmath>
int ImageBusWriter::create_staging_buffer(size_t size) {
BufferCreateInfo stagingBufferInfo = {
.size = size,
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
.isExclusive = true
};
MemoryAllocationInfo memoryAllocationInfo = {
.usage = MEMORY_USAGE_AUTO_PREFER_HOST
};
m_gpu->memory()->create_buffer(&stagingBufferInfo, &memoryAllocationInfo,
&m_stagingBuffer);
m_gpu->memory()->map(m_stagingBuffer.allocation, &m_pMappedData);
return 0;
}
int ImageBusWriter::create_staging_command_buffer() {
VkCommandBufferAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = m_gpu->transfer_command_pool(),
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
};
vkAllocateCommandBuffers(m_gpu->dev(), &allocInfo,
&m_stagingCommandBuffer);
return 0;
}
int ImageBusWriter::create_fence() {
VkFenceCreateInfo fenceInfo = {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.flags = VK_FENCE_CREATE_SIGNALED_BIT
};
vkCreateFence(m_gpu->dev(), &fenceInfo, nullptr, &m_fence);
return 0;
}
ImageBusWriter::ImageBusWriter(const Gpu* gpu, Image *pTarget,
VkExtent2D extent, uint32_t formatSize,
size_t maxWrites) :
m_gpu(gpu),
m_pTarget(pTarget),
m_writes(maxWrites),
m_numWrites(0),
m_formatSize(formatSize),
m_imageSize(extent.width * extent.height * formatSize) {
create_staging_buffer(extent.width * extent.height * formatSize);
create_staging_command_buffer();
create_fence();
}
void ImageBusWriter::write(VkBufferImageCopy write, void *pData, size_t size) {
write.bufferOffset = m_numWrites * m_imageSize;
m_writes[m_numWrites] = write;
memcpy((char*)m_pMappedData + m_numWrites * m_imageSize, pData, size);
m_numWrites++;
if(m_numWrites >= m_writes.size()) {
flush();
}
}
void ImageBusWriter::flush() {
if(m_numWrites == 0) return;
vkWaitForFences(m_gpu->dev(), 1, &m_fence, VK_TRUE, UINT64_MAX);
vkResetFences(m_gpu->dev(), 1, &m_fence);
m_gpu->memory()->flush(m_stagingBuffer.allocation, 0,
m_imageSize * m_numWrites);
VkCommandBufferBeginInfo beginInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
};
vkBeginCommandBuffer(m_stagingCommandBuffer, &beginInfo);
VkImageMemoryBarrier barrierInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = m_pTarget->img,
};
std::vector<VkImageMemoryBarrier> barriers(m_writes.size(), barrierInfo);
uint32_t i = 0;
for(auto& write : m_writes) {
barriers[i++].subresourceRange = {
.aspectMask = write.imageSubresource.aspectMask,
.baseMipLevel = write.imageSubresource.mipLevel,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = write.imageSubresource.baseArrayLayer,
.layerCount = write.imageSubresource.layerCount,
};
}
vkCmdPipelineBarrier(m_stagingCommandBuffer,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0,
0, nullptr,
0, nullptr,
barriers.size(), barriers.data());
vkCmdCopyBufferToImage(
m_stagingCommandBuffer,
m_stagingBuffer.buf,
m_pTarget->img,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
m_numWrites,
m_writes.data()
);
for(auto& barrier : barriers) {
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
}
vkCmdPipelineBarrier(m_stagingCommandBuffer,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0,
0, nullptr,
0, nullptr,
barriers.size(), barriers.data());
vkEndCommandBuffer(m_stagingCommandBuffer);
VkSubmitInfo submitInfo = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &m_stagingCommandBuffer
};
m_gpu->enqueue_transfer(&submitInfo, m_fence);
m_numWrites = 0;
}
+18
View File
@@ -0,0 +1,18 @@
#include "resources/ImageView.hpp"
#include "Gpu.hpp"
void ImageView::set_debug_name(const std::shared_ptr<const Gpu>& gpu, const std::string &name) const {
#if LOFT_DEBUG
VkDebugUtilsObjectNameInfoEXT nameInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
.objectType = VK_OBJECT_TYPE_IMAGE_VIEW,
.objectHandle = (uint64_t)view,
.pObjectName = name.c_str(),
};
vkSetDebugUtilsObjectNameEXT(gpu->dev(), &nameInfo);
#endif
}
@@ -0,0 +1,181 @@
#include "resources/MipmapGenerator.h"
#include "Gpu.hpp"
#include "Recording.hpp"
#include "TransferTaskPipeline.hpp"
#include <vulkan/vulkan_core.h>
MipmapGenerator::MipmapGenerator(const Gpu* gpu) :
m_gpu(gpu), m_commandBuffer(create_command_buffer(gpu)), m_fence(create_fence(gpu)) {
}
VkFence MipmapGenerator::create_fence(const Gpu* gpu) {
VkFenceCreateInfo fenceInfo = {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.flags = VK_FENCE_CREATE_SIGNALED_BIT
};
VkFence fence = VK_NULL_HANDLE;
vkCreateFence(gpu->dev(), &fenceInfo, nullptr, &fence);
return fence;
}
VkCommandBuffer MipmapGenerator::create_command_buffer(const Gpu* gpu) {
VkCommandBufferAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = gpu->transfer_command_pool(),
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
};
VkCommandBuffer cmdbuf = VK_NULL_HANDLE;
vkAllocateCommandBuffers(gpu->dev(), &allocInfo,
&cmdbuf);
return cmdbuf;
}
uint32_t MipmapGenerator::generate(Image image, VkImageLayout oldLayout, VkExtent2D extent, VkImageSubresourceRange range) {
vkWaitForFences(m_gpu->dev(), 1, &m_fence, VK_TRUE, UINT64_MAX);
vkResetFences(m_gpu->dev(), 1, &m_fence);
VkCommandBufferBeginInfo beginInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
};
vkBeginCommandBuffer(m_commandBuffer, &beginInfo);
VkImageMemoryBarrier barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.oldLayout = oldLayout,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = image.img,
.subresourceRange = {
.aspectMask = range.aspectMask,
.baseMipLevel = 1,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = range.layerCount,
},
};
vkCmdPipelineBarrier(m_commandBuffer,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
0, nullptr,
0, nullptr,
1, &barrier);
barrier.subresourceRange.levelCount = 1;
int32_t width = extent.width;
auto layout = oldLayout;
for (uint32_t i = 1; i < range.levelCount; i++) {
uint32_t from_level = i - 1;
uint32_t to_level = i;
std::vector<VkImageMemoryBarrier> barriers(2, barrier);
barriers[0].oldLayout = layout;
barriers[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
barriers[0].subresourceRange.baseMipLevel = from_level;
vkCmdPipelineBarrier(m_commandBuffer,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
0, nullptr,
0, nullptr,
1, barriers.data());
VkImageBlit blit = {
.srcSubresource = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.mipLevel = from_level,
.baseArrayLayer = 0,
.layerCount = range.layerCount,
},
.srcOffsets = {
{ 0, 0, 0 },
{ width, width, 1 }
},
.dstSubresource = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.mipLevel = to_level,
.baseArrayLayer = 0,
.layerCount = range.layerCount
},
.dstOffsets = {
{ 0, 0, 0 },
{
width > 1 ? width / 2 : 1,
width > 1 ? width / 2 : 1,
1
}
},
};
vkCmdBlitImage(m_commandBuffer,
image.img, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
image.img, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1, &blit,
VK_FILTER_LINEAR);
// transfer back to old layout
/* barriers[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
barriers[0].newLayout = oldLayout;
barriers[0].srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barriers[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
vkCmdPipelineBarrier(m_commandBuffer,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,
0, nullptr,
0, nullptr,
1, barriers.data()); */
width /= 2;
layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
}
std::vector<VkImageMemoryBarrier> barriers(2, barrier);
barriers[0].subresourceRange.baseMipLevel = 0;
barriers[0].subresourceRange.levelCount = range.levelCount - 1;
barriers[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
barriers[0].newLayout = oldLayout;
barriers[0].srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barriers[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
barriers[1].subresourceRange.baseMipLevel = range.levelCount - 1;
barriers[1].subresourceRange.levelCount = 1;
barriers[1].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barriers[1].newLayout = oldLayout;
barriers[1].srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barriers[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
vkCmdPipelineBarrier(m_commandBuffer,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,
0, nullptr,
0, nullptr,
2, barriers.data());
vkEndCommandBuffer(m_commandBuffer);
VkSubmitInfo submitInfo = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &m_commandBuffer
};
m_gpu->enqueue_transfer(&submitInfo, m_fence);
return 0;
}
@@ -0,0 +1,35 @@
#include "shaders/ComputePipelineBuilder.hpp"
namespace lft {
ComputePipelineBuilder::ComputePipelineBuilder(const Shader* shader, VkPipelineLayout layout) :
m_shader(shader),
m_layout(layout) {
}
Pipeline ComputePipelineBuilder::build(const Gpu* gpu) {
VkPipelineShaderStageCreateInfo computeShaderStageInfo = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_COMPUTE_BIT,
.module = m_shader->module(),
.pName = "main",
};
VkComputePipelineCreateInfo pipeline_info = {
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
.stage = computeShaderStageInfo,
.layout = m_layout,
};
VkPipeline pipeline = VK_NULL_HANDLE;
if (vkCreateComputePipelines(gpu->dev(),
VK_NULL_HANDLE, 1, &pipeline_info, nullptr,
&pipeline) != VK_SUCCESS) {
throw std::runtime_error("failed to create compute pipeline!");
}
return Pipeline(m_layout, pipeline);
}
}
@@ -0,0 +1,10 @@
//
// Created by martin on 10/24/23.
//
#include "../../include/shaders/GlslShaderBuilder.hpp"
Shader GlslShaderBuilder::from_file(std::string path) {
Shader shader(VK_NULL_HANDLE);
return shader;
}
+2
View File
@@ -0,0 +1,2 @@
#include "shaders/Pipeline.hpp"
@@ -0,0 +1,144 @@
#include "shaders/PipelineBuilder.h"
#include "shaders/Shader.hpp"
#include <vulkan/vulkan_core.h>
PipelineBuilder::PipelineBuilder(const Gpu* gpu, const VkViewport& viewport,
VkPipelineLayout layout, VkRenderPass outputLayout,
uint32_t numAttachments,
const Shader* vertexShader, const Shader* fragmentShader) :
m_gpu(gpu),
m_viewport(viewport),
m_scissor({
.offset = {0, 0},
.extent = {(uint32_t)viewport.width, (uint32_t)std::abs(viewport.height)}
}),
m_layout(layout), m_renderpass(outputLayout),
m_inputAssemblyInfo({
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
.primitiveRestartEnable = false
}),
m_rasterInfo({
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.depthClampEnable = false,
.rasterizerDiscardEnable = false,
.polygonMode = VK_POLYGON_MODE_FILL,
.cullMode = VK_CULL_MODE_BACK_BIT,
.frontFace = VK_FRONT_FACE_CLOCKWISE,
.depthBiasEnable = false,
.lineWidth = 1.0f
}),
m_depthStencilInfo({
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
.depthTestEnable = true,
.depthWriteEnable = true,
.depthCompareOp = VK_COMPARE_OP_LESS,
.depthBoundsTestEnable = false,
.stencilTestEnable = false
}),
m_vertexInputInfo({
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
}),
m_blendingInfo(numAttachments)
{
stages.resize(2);
stages[0] = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_VERTEX_BIT,
.module = vertexShader->module(),
.pName = "main"
};
stages[1] = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
.module = fragmentShader->module(),
.pName = "main",
};
for(uint32_t i = 0; i < num_attachments(); i++) {
m_blendingInfo[i] = {
.blendEnable = false,
.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA,
.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
.colorBlendOp = VK_BLEND_OP_ADD,
.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA,
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
.alphaBlendOp = VK_BLEND_OP_ADD,
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT |
VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT |
VK_COLOR_COMPONENT_A_BIT
};
}
}
Pipeline PipelineBuilder::build() {
m_vertexInputInfo.vertexBindingDescriptionCount = m_vertexBindings.size();
m_vertexInputInfo.pVertexBindingDescriptions = (VkVertexInputBindingDescription*)m_vertexBindings.data();
m_vertexInputInfo.vertexAttributeDescriptionCount = m_vertexAttributes.size();
m_vertexInputInfo.pVertexAttributeDescriptions = (VkVertexInputAttributeDescription*)m_vertexAttributes.data();
/* Multisampling disabled. Use TTA */
VkPipelineMultisampleStateCreateInfo multisampling = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
.sampleShadingEnable = VK_FALSE,
.minSampleShading = 1.0f,
.pSampleMask = nullptr,
.alphaToCoverageEnable = VK_FALSE,
.alphaToOneEnable = VK_FALSE,
};
VkPipelineViewportStateCreateInfo viewportState = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.viewportCount = 1,
.pViewports = &m_viewport,
.scissorCount = 1,
.pScissors = &m_scissor
};
VkPipelineColorBlendStateCreateInfo colorBlending = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
.logicOpEnable = VK_FALSE,
.logicOp = VK_LOGIC_OP_COPY,
.attachmentCount = (unsigned)m_blendingInfo.size(),
.pAttachments = m_blendingInfo.data(),
.blendConstants = { 0.0f, 0.0f, 0.0f, 0.0f}
};
std::vector<VkDynamicState> dynamicStates = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
VkPipelineDynamicStateCreateInfo dynamicState = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.dynamicStateCount = (uint32_t)dynamicStates.size(),
.pDynamicStates = dynamicStates.data()
};
VkGraphicsPipelineCreateInfo pipelineInfo = {
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.stageCount = (uint32_t)stages.size(),
.pStages = stages.data(),
.pVertexInputState = &m_vertexInputInfo,
.pInputAssemblyState = &m_inputAssemblyInfo,
.pViewportState = &viewportState,
.pRasterizationState = &m_rasterInfo,
.pMultisampleState = &multisampling,
.pDepthStencilState = &m_depthStencilInfo,
.pColorBlendState = &colorBlending,
.pDynamicState = &dynamicState,
.layout = m_layout,
.renderPass = m_renderpass,
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = -1,
};
VkPipeline pipeline;
if(vkCreateGraphicsPipelines(m_gpu->dev(), VK_NULL_HANDLE, 1,
&pipelineInfo, nullptr, &pipeline)) {
throw std::runtime_error("Failed to create graphics pipeline");
}
return Pipeline(m_layout, pipeline);
}
@@ -0,0 +1,48 @@
//
// Created by martin on 10/24/23.
//
#include <stdexcept>
#include "shaders/SpirvShaderBuilder.hpp"
#include "io/file.hpp"
SpirvShaderBuilder::SpirvShaderBuilder(const Gpu* gpu) :
m_gpu(gpu) {
}
Shader SpirvShaderBuilder::from_binary(const std::vector<uint32_t>& code) const {
VkShaderModuleCreateInfo moduleInfo = {
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = code.size(),
.pCode = code.data(),
};
VkShaderModule module = VK_NULL_HANDLE;
if(vkCreateShaderModule(m_gpu->dev(), &moduleInfo, nullptr, &module)) {
throw std::runtime_error(std::format("Failed to create shader module from binary. Length was {}", code.size() * sizeof(uint32_t)));
}
return Shader(module);
}
Shader SpirvShaderBuilder::from_file(std::string path) {
auto shaderBinary = io::file::read_binary(path);
VkShaderModuleCreateInfo moduleInfo = {
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = shaderBinary.code_size(),
.pCode = shaderBinary.data().data(),
};
VkShaderModule module = VK_NULL_HANDLE;
if(vkCreateShaderModule(m_gpu->dev(), &moduleInfo, nullptr, &module)) {
throw std::runtime_error("Failed to create shader module");
}
auto shader = Shader(module);
shader.set_name(m_gpu, path);
return shader;
}
+8
View File
@@ -0,0 +1,8 @@
file(GLOB CXXFILES src/*.cpp)
add_library(loft_common OBJECT ${CXXFILES})
add_library(loft::common ALIAS loft_common)
target_include_directories(loft_common
PUBLIC
src/
)
+48
View File
@@ -0,0 +1,48 @@
#include <iostream>
#include "AdjacencyMatrix.hpp"
AdjacencyMatrixNodeHandle& AdjacencyMatrixNodeHandle::add_dependency(uint32_t on) {
m_graph->set(m_idx, on);
return *this;
}
std::vector<uint32_t> AdjacencyMatrixNodeHandle::dependencies() {
return m_graph->get_dependencies(m_idx);
}
bool AdjacencyMatrixNodeHandle::depends_on(uint32_t what) {
return m_graph->get(what, m_idx);
}
void AdjacencyMatrix::find_dft(uint32_t node, uint32_t target, uint32_t maxDepth) {
if(maxDepth == 0) {
throw std::runtime_error("Contains loop");
}
for(uint32_t x = 0; x < m_matrix.size(); x++) {
if(get(x, node)) {
unset(x, target);
find_dft(x, target, maxDepth - 1);
}
}
}
void AdjacencyMatrix::transitive_reduction() {
for(uint32_t x = 0; x < m_matrix.size(); x++) {
for(uint32_t y = 0; y < m_matrix.size(); y++) {
if(get(y, x)) {
find_dft(y, x, m_matrix.size());
}
}
}
}
void AdjacencyMatrix::print() {
for(uint32_t x = 0; x < m_matrix.size(); x++) {
for(uint32_t y = 0; y < m_matrix.size(); y++) {
std::cout << get(x, y) << " ";
}
std::cout << std::endl;
}
}
+189
View File
@@ -0,0 +1,189 @@
#pragma once
#include <cstdint>
#include <vector>
#include <map>
#include "Assert.h"
struct AdjacencyMatrix;
struct AdjacencyMatrixNodeHandle {
private:
AdjacencyMatrix* m_graph;
uint32_t m_idx;
public:
AdjacencyMatrixNodeHandle(
AdjacencyMatrix* graph,
uint32_t idx
) :
m_graph(graph),
m_idx(idx) {
}
AdjacencyMatrixNodeHandle& add_dependency(uint32_t on);
std::vector<uint32_t> dependencies();
bool depends_on(uint32_t what);
};
/**
* Represents a graph as an adjacency matrix.
*/
struct AdjacencyMatrix {
private:
std::vector<std::vector<bool>> m_matrix;
std::map<std::string, uint32_t> m_node_idx;
std::vector<std::string> m_node_names;
void find_dft(uint32_t node, uint32_t target, uint32_t maxDepth);
public:
explicit AdjacencyMatrix(std::vector<std::string> node_names) :
m_matrix(node_names.size(), std::vector<bool>(node_names.size())),
m_node_names(node_names)
{
for(uint32_t x = 0; x < node_names.size(); x++) {
m_node_idx.insert({node_names[x], x});
}
}
bool has_loop() {
return false;
}
const std::vector<std::vector<bool>>& rows () const {
return m_matrix;
}
const std::vector<bool>& row(const std::string& name) const {
ASSERT(m_node_idx.contains(name));
return m_matrix[m_node_idx.at(name)];
}
[[nodiscard]] [[deprecated("Use string variant")]] bool get(uint32_t from, uint32_t to) const {
ASSERT(from < m_matrix.size() && to < m_matrix.size());
return m_matrix[from][to];
}
[[nodiscard]] bool get(const std::string& from, const std::string& to) const {
ASSERT(m_node_idx.contains(from) && m_node_idx.contains(to));
return m_matrix[m_node_idx.at(from)][m_node_idx.at(to)];
}
inline AdjacencyMatrix& set(uint32_t from, uint32_t to) {
ASSERT(from < m_matrix.size() && to < m_matrix.size());
m_matrix[from][to] = true;
return *this;
}
inline AdjacencyMatrix& unset(uint32_t from, uint32_t to) {
ASSERT(from < m_matrix.size() && to < m_matrix.size());
m_matrix[from][to] = false;
return *this;
}
inline AdjacencyMatrix& set(const std::string& from, const std::string& to) {
ASSERT(m_node_idx.contains(from) && m_node_idx.contains(to));
m_matrix[m_node_idx.at(from)][m_node_idx.at(to)] = true;
return *this;
}
inline AdjacencyMatrix& unset(const std::string& from, const std::string& to) {
ASSERT(m_node_idx.contains(from) && m_node_idx.contains(to));
m_matrix[m_node_idx.at(from)][m_node_idx.at(to)] = false;
return *this;
}
/**
* Counts and returns number of dependencies of item at 'to' index
* @param to Index of the dependant
* @return number representing count of dependencies
*/
[[nodiscard]] uint32_t num_dependencies(uint32_t to) const {
ASSERT(to < m_matrix.size());
uint32_t numDependencies = 0;
for(uint32_t x = 0; x < m_matrix.size(); x++) {
if(x == to) continue;
if(get(x, to)) {
numDependencies++;
}
}
return numDependencies;
}
[[nodiscard]] uint32_t num_dependencies_of(const std::string& node) const {
return num_dependencies(m_node_idx.at(node));
}
/**
* Gets all the dependencies of 'to' item as vector
* @param to Index of the dependant
* @return vector of indices of dependencies
*/
[[nodiscard]] std::vector<uint32_t> get_dependencies(uint32_t to) const {
ASSERT(to < m_matrix.size());
uint32_t numDependencies = num_dependencies(to);
std::vector<uint32_t> dependencies(numDependencies);
uint32_t i = 0;
for(uint32_t x = 0; x < m_matrix.size() && i < numDependencies; x++) {
if(x == to) continue;
if(get(x, to)) {
dependencies[i++] = x;
}
}
return dependencies;
}
[[nodiscard]] std::vector<std::string> get_dependencies(const std::string& to) const {
uint32_t numDependencies = num_dependencies(m_node_idx.at(to));
std::vector<std::string> dependencies(numDependencies);
uint32_t i = 0;
for(uint32_t x = 0; x < m_matrix.size() && i < numDependencies; x++) {
if(get(m_node_names[x], to)) {
dependencies[i++] = m_node_names[x];
}
}
return dependencies;
}
[[nodiscard]] std::vector<std::string> get_successors(const std::string& from) const {
std::vector<std::string> successors;
uint32_t i = 0;
for(auto& node : m_node_names) {
if(get(from, node)) {
successors.push_back(node);
}
}
return successors;
}
/**
* Does a transitive reduction on the matrix.
*/
void transitive_reduction();
void print();
};
+14
View File
@@ -0,0 +1,14 @@
//
// Created by martin on 7/5/24.
//
#ifndef LOFT_ASSERT_H
#define LOFT_ASSERT_H
#include <stdexcept>
#include <format>
#include <source_location>
#define ASSERT(expr) if(!(expr)) { throw std::runtime_error(std::format("Assertion failed in {}: {}", std::source_location::current().function_name(), #expr)); }
#endif //LOFT_ASSERT_H
+15
View File
@@ -0,0 +1,15 @@
//
// Created by marti on 7/1/2024.
//
#ifndef LOFT_DISPLAY_H
#define LOFT_DISPLAY_H
namespace lft {
class Display {
public:
virtual void display() const = 0;
};
}
#endif //LOFT_DISPLAY_H
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&); \
void operator=(const TypeName&)
+34
View File
@@ -0,0 +1,34 @@
//
// Created by marti on 7/1/2024.
//
#ifndef LOFT_ERROR_H
#define LOFT_ERROR_H
#include <string>
#include <utility>
#include "Display.h"
namespace lft {
enum ErrorCode {
};
class Error : lft::Display {
private:
ErrorCode m_code;
std::string m_message;
public:
Error(const ErrorCode code, std::string message) :
m_code(code), m_message(std::move(message)) {
}
void display() const override {
}
};
}
#endif //LOFT_ERROR_H
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <vector>
#include <cstdint>
template<typename FromT, typename IntoT>
inline std::vector<IntoT> map(const std::vector<FromT>& original, std::unary_function<FromT, IntoT> func) {
std::vector<IntoT> result(original.size());
for(uint32_t i = 0; i < original; i++) {
result[i] = func(original[i]);
}
return result;
}
+66
View File
@@ -0,0 +1,66 @@
//
// Created by marti on 7/1/2024.
//
#ifndef LOFT_LOG_H
#define LOFT_LOG_H
#include <cstdarg>
#include <cstdio>
#define LOG_ENABLE 1
class Log {
private:
FILE *output;
public:
#if LOG_ENABLE
Log() {
output = stdout;
}
inline void fail(const char* format, ...) const {
va_list args;
va_start(args, format);
fwrite("[FAIL]: ", 1, 8, output);
vfprintf(output, format, args);
fwrite("\n", 1, 1, output);
}
inline void warn(const char* format, ...) const {
va_list args;
va_start(args, format);
vfprintf(output, format, args);
fwrite("\n", 1, 1, output);
}
inline void done(const char* format, ...) const {
va_list args;
va_start(args, format);
vfprintf(output, format, args);
fwrite("\n", 1, 1, output);
}
inline void mesg(const char* format, ...) const {
va_list args;
va_start(args, format);
vfprintf(output, format, args);
fwrite("\n", 1, 1, output);
}
#else
inline void fail(const char* format, ...) const {}
inline void warn(const char* format, ...) const {}
inline void done(const char* format, ...) const {}
inline void mesg(const char* format, ...) const {}
#endif
};
static Log log = Log();
#define FAIL(fmt, ...) log.fail(fmt, ##__VA_ARGS__)
#endif //LOFT_LOG_H
+41
View File
@@ -0,0 +1,41 @@
#pragma once
template<typename TValue, typename TError>
class result {
private:
bool m_isSuccess;
union {
TValue m_value;
TError m_error;
} m_value;
public:
result<TValue, TError>() : m_isSuccess(false) {
}
void set_ok(TValue value) {
m_value.m_value = value;
m_isSuccess = true;
}
void set_fail(TError err) {
m_value.m_error = err;
m_isSuccess = false;
}
};
template<typename TSuccess, typename TError>
result<TSuccess, TError> fail(TError failure) {
auto r = result<TSuccess, TError>();
r.set_fail(failure);
return r;
}
template<typename TSuccess, typename TError>
result<TSuccess, TError> ok(TSuccess value) {
auto r = result<TSuccess, TError>();
r.set_ok(value);
return r;
}
+46
View File
@@ -0,0 +1,46 @@
project(loft_reflect)
FetchContent_Declare(
SpirvReflect
GIT_REPOSITORY https://github.com/KhronosGroup/SPIRV-Reflect
GIT_TAG e55086b044225f9b511ae44dbf9b079d3625943f
GIT_SHALLOW 1
)
set(SPIRV_REFLECT_STATIC_LIB ON CACHE BOOL "" FORCE)
set(SPIRV_REFLECT_EXECUTABLE OFF CACHE BOOL "" FORCE)
set(SPIRV_REFLECT_EXAMPLES OFF CACHE BOOL "" FORCE)
set(SPIRV_REFLECT_TESTS OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(SpirvReflect)
file(GLOB FILES
./src/*.cpp
)
file(GLOB HEADERS
./include/*.hpp
)
add_library(loft_reflect OBJECT ${FILES})
add_library(loft::reflect ALIAS loft_reflect)
target_sources(loft_reflect
PUBLIC FILE_SET HEADERS
BASE_DIRS include
FILES ${HEADERS})
set_target_properties(loft_reflect PROPERTIES POSITION_INDEPENDENT_CODE 1)
target_link_libraries(${PROJECT_NAME}
PUBLIC
loft::base
GPUOpen::VulkanMemoryAllocator
volk::volk
spirv-reflect-static
)
target_include_directories(${PROJECT_NAME}
PUBLIC
${PROJECT_SOURCE_DIR}/include/
)
# add_subdirectory(tests)
@@ -0,0 +1,13 @@
#pragma once
#include <vector>
#include <cstdint>
namespace lft::reflect {
class PipelineLayoutBuilder {
public:
PipelineLayoutBuilder(std::vector<uint32_t> code);
};
}
@@ -0,0 +1,10 @@
#pragma once
namespace lft::reflect {
class DescriptorSetLayoutBuilder {
public:
DescriptorSetLayoutBuilder();
};
}
@@ -0,0 +1,63 @@
#include "PipelineLayoutBuilder.hpp"
#include "shaders/PipelineLayoutBuilder.hpp"
#include <print>
#include "spirv_reflect.h"
namespace lft::reflect {
std::string SpvReflectResultToString(SpvReflectResult result) {
switch (result) {
case SPV_REFLECT_RESULT_SUCCESS: return "Success";
case SPV_REFLECT_RESULT_NOT_READY: return "Not ready";
case SPV_REFLECT_RESULT_ERROR_PARSE_FAILED: return "Parse failed";
case SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED: return "Allocation failed";
case SPV_REFLECT_RESULT_ERROR_RANGE_EXCEEDED: return "Range exceeded";
case SPV_REFLECT_RESULT_ERROR_NULL_POINTER: return "Null pointer";
case SPV_REFLECT_RESULT_ERROR_INTERNAL_ERROR: return "Internal error";
case SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH: return "Count mismatch";
case SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND: return "Element not found";
case SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_CODE_SIZE: return "Invalid SPIR-V code size";
case SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_MAGIC_NUMBER: return "Invalid SPIR-V magic number";
case SPV_REFLECT_RESULT_ERROR_SPIRV_UNEXPECTED_EOF: return "Unexpected end of SPIR-V code";
case SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE: return "Invalid SPIR-V ID reference";
case SPV_REFLECT_RESULT_ERROR_SPIRV_SET_NUMBER_OVERFLOW: return "SPIR-V set number overflow";
case SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_STORAGE_CLASS: return "Invalid SPIR-V storage class";
default: return "Unknown error";
}
}
PipelineLayoutBuilder::PipelineLayoutBuilder(std::vector<uint32_t> code) {
SpvReflectShaderModule module;
SpvReflectResult result = spvReflectCreateShaderModule(
code.size() * 4, (const char*)code.data(), &module
);
if(result != SPV_REFLECT_RESULT_SUCCESS) {
std::println("{}", SpvReflectResultToString(result));
return;
}
uint32_t count = 0;
result = spvReflectEnumerateDescriptorSets(&module, &count, nullptr);
std::vector<SpvReflectDescriptorSet*> sets(count);
result = spvReflectEnumerateDescriptorSets(&module, &count, sets.data());
for(auto& set : sets) {
std::println("Set: {}", set->set);
for(uint32_t i = 0; i < set->binding_count; i++) {
std::println("Binding: {}", set->bindings[i]->binding);
VkDescriptorSetLayoutBinding binding = {
.binding = set->bindings[i]->binding,
.descriptorType = (VkDescriptorType)set->bindings[i]->descriptor_type,
.descriptorCount = set->bindings[i]->count,
};
}
}
}
}
+23
View File
@@ -0,0 +1,23 @@
project(loft_reflect_tests)
find_package(Vulkan)
find_package(Catch2 REQUIRED)
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
include(CTest)
include(Catch)
add_executable(loft_reflect_tests
./PipelineLayoutReflectTests.cpp
)
target_link_libraries(loft_reflect_tests
PRIVATE
loft::base
loft::reflect
Catch2::Catch2WithMain
)
catch_discover_tests(loft_reflect_tests)
@@ -0,0 +1,106 @@
#include "PipelineLayoutBuilder.hpp"
#include <catch2/catch_test_macros.hpp>
static uint32_t test_shader[] = {
0x07230203,0x00010000,0x0008000b,0x0000005f,0x00000000,0x00020011,0x00000001,0x0006000b,
0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001,
0x000e000f,0x00000000,0x00000004,0x6e69616d,0x00000000,0x0000000b,0x0000000f,0x0000001b,
0x00000030,0x00000031,0x00000035,0x00000039,0x0000003b,0x00000041,0x00030003,0x00000002,
0x000001c2,0x00040005,0x00000004,0x6e69616d,0x00000000,0x00050005,0x00000009,0x61746962,
0x6e65676e,0x00000074,0x00040005,0x0000000b,0x6d726f6e,0x00000000,0x00040005,0x0000000f,
0x676e6174,0x00746e65,0x00040005,0x0000001b,0x5474756f,0x00004e42,0x00040005,0x00000030,
0x5074756f,0x0000736f,0x00030005,0x00000031,0x00736f70,0x00050005,0x00000035,0x4e74756f,
0x616d726f,0x0000006c,0x00040005,0x00000039,0x5574756f,0x00000056,0x00030005,0x0000003b,
0x00007675,0x00060005,0x0000003f,0x505f6c67,0x65567265,0x78657472,0x00000000,0x00060006,
0x0000003f,0x00000000,0x505f6c67,0x7469736f,0x006e6f69,0x00070006,0x0000003f,0x00000001,
0x505f6c67,0x746e696f,0x657a6953,0x00000000,0x00070006,0x0000003f,0x00000002,0x435f6c67,
0x4470696c,0x61747369,0x0065636e,0x00070006,0x0000003f,0x00000003,0x435f6c67,0x446c6c75,
0x61747369,0x0065636e,0x00030005,0x00000041,0x00000000,0x00040005,0x00000045,0x656d6143,
0x00006172,0x00050006,0x00000045,0x00000000,0x6a6f7270,0x00000000,0x00050006,0x00000045,
0x00000001,0x77656976,0x00000000,0x00030005,0x00000047,0x006d6163,0x00050005,0x00000057,
0x6e656353,0x646f4e65,0x00000065,0x00070006,0x00000057,0x00000000,0x6e617274,0x726f6673,
0x7864496d,0x00000000,0x00050006,0x00000057,0x00000001,0x6873656d,0x00786449,0x00050005,
0x00000059,0x656a624f,0x6e497463,0x00006f66,0x00050006,0x00000059,0x00000000,0x65646f6e,
0x00000073,0x00050005,0x0000005b,0x656a626f,0x6e497463,0x00006f66,0x00050005,0x0000005c,
0x736e6f63,0x746e6174,0x00000073,0x00070006,0x0000005c,0x00000000,0x6e617274,0x726f6673,
0x7864496d,0x00000000,0x00060006,0x0000005c,0x00000001,0x6574616d,0x6c616972,0x00786449,
0x00060005,0x0000005e,0x68737550,0x736e6f43,0x746e6174,0x00000073,0x00040047,0x0000000b,
0x0000001e,0x00000001,0x00040047,0x0000000f,0x0000001e,0x00000003,0x00040047,0x0000001b,
0x0000001e,0x00000003,0x00040047,0x00000030,0x0000001e,0x00000000,0x00040047,0x00000031,
0x0000001e,0x00000000,0x00040047,0x00000035,0x0000001e,0x00000001,0x00040047,0x00000039,
0x0000001e,0x00000002,0x00040047,0x0000003b,0x0000001e,0x00000002,0x00030047,0x0000003f,
0x00000002,0x00050048,0x0000003f,0x00000000,0x0000000b,0x00000000,0x00050048,0x0000003f,
0x00000001,0x0000000b,0x00000001,0x00050048,0x0000003f,0x00000002,0x0000000b,0x00000003,
0x00050048,0x0000003f,0x00000003,0x0000000b,0x00000004,0x00030047,0x00000045,0x00000002,
0x00040048,0x00000045,0x00000000,0x00000005,0x00050048,0x00000045,0x00000000,0x00000007,
0x00000010,0x00050048,0x00000045,0x00000000,0x00000023,0x00000000,0x00040048,0x00000045,
0x00000001,0x00000005,0x00050048,0x00000045,0x00000001,0x00000007,0x00000010,0x00050048,
0x00000045,0x00000001,0x00000023,0x00000040,0x00040047,0x00000047,0x00000021,0x00000000,
0x00040047,0x00000047,0x00000022,0x00000000,0x00050048,0x00000057,0x00000000,0x00000023,
0x00000000,0x00050048,0x00000057,0x00000001,0x00000023,0x00000004,0x00040047,0x00000058,
0x00000006,0x00000010,0x00030047,0x00000059,0x00000002,0x00050048,0x00000059,0x00000000,
0x00000023,0x00000000,0x00040047,0x0000005b,0x00000021,0x00000001,0x00040047,0x0000005b,
0x00000022,0x00000002,0x00030047,0x0000005c,0x00000002,0x00050048,0x0000005c,0x00000000,
0x00000023,0x00000000,0x00050048,0x0000005c,0x00000001,0x00000023,0x00000004,0x00020013,
0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,0x00000006,0x00000020,0x00040017,
0x00000007,0x00000006,0x00000003,0x00040020,0x00000008,0x00000007,0x00000007,0x00040020,
0x0000000a,0x00000001,0x00000007,0x0004003b,0x0000000a,0x0000000b,0x00000001,0x00040017,
0x0000000d,0x00000006,0x00000004,0x00040020,0x0000000e,0x00000001,0x0000000d,0x0004003b,
0x0000000e,0x0000000f,0x00000001,0x00040015,0x00000013,0x00000020,0x00000000,0x0004002b,
0x00000013,0x00000014,0x00000003,0x00040020,0x00000015,0x00000001,0x00000006,0x00040018,
0x00000019,0x00000007,0x00000003,0x00040020,0x0000001a,0x00000003,0x00000019,0x0004003b,
0x0000001a,0x0000001b,0x00000003,0x0004002b,0x00000006,0x00000020,0x3f800000,0x0004002b,
0x00000006,0x00000021,0x00000000,0x00040020,0x0000002f,0x00000003,0x00000007,0x0004003b,
0x0000002f,0x00000030,0x00000003,0x0004003b,0x0000000a,0x00000031,0x00000001,0x0004002b,
0x00000006,0x00000033,0x3d4ccccd,0x0004003b,0x0000002f,0x00000035,0x00000003,0x00040017,
0x00000037,0x00000006,0x00000002,0x00040020,0x00000038,0x00000003,0x00000037,0x0004003b,
0x00000038,0x00000039,0x00000003,0x00040020,0x0000003a,0x00000001,0x00000037,0x0004003b,
0x0000003a,0x0000003b,0x00000001,0x0004002b,0x00000013,0x0000003d,0x00000001,0x0004001c,
0x0000003e,0x00000006,0x0000003d,0x0006001e,0x0000003f,0x0000000d,0x00000006,0x0000003e,
0x0000003e,0x00040020,0x00000040,0x00000003,0x0000003f,0x0004003b,0x00000040,0x00000041,
0x00000003,0x00040015,0x00000042,0x00000020,0x00000001,0x0004002b,0x00000042,0x00000043,
0x00000000,0x00040018,0x00000044,0x0000000d,0x00000004,0x0004001e,0x00000045,0x00000044,
0x00000044,0x00040020,0x00000046,0x00000002,0x00000045,0x0004003b,0x00000046,0x00000047,
0x00000002,0x00040020,0x00000048,0x00000002,0x00000044,0x0004002b,0x00000042,0x0000004b,
0x00000001,0x00040020,0x00000055,0x00000003,0x0000000d,0x0004001e,0x00000057,0x00000013,
0x00000013,0x0004001c,0x00000058,0x00000057,0x0000003d,0x0003001e,0x00000059,0x00000058,
0x00040020,0x0000005a,0x00000002,0x00000059,0x0004003b,0x0000005a,0x0000005b,0x00000002,
0x0004001e,0x0000005c,0x00000013,0x00000013,0x00040020,0x0000005d,0x00000009,0x0000005c,
0x0004003b,0x0000005d,0x0000005e,0x00000009,0x00050036,0x00000002,0x00000004,0x00000000,
0x00000003,0x000200f8,0x00000005,0x0004003b,0x00000008,0x00000009,0x00000007,0x0004003d,
0x00000007,0x0000000c,0x0000000b,0x0004003d,0x0000000d,0x00000010,0x0000000f,0x0008004f,
0x00000007,0x00000011,0x00000010,0x00000010,0x00000000,0x00000001,0x00000002,0x0007000c,
0x00000007,0x00000012,0x00000001,0x00000044,0x0000000c,0x00000011,0x00050041,0x00000015,
0x00000016,0x0000000f,0x00000014,0x0004003d,0x00000006,0x00000017,0x00000016,0x0005008e,
0x00000007,0x00000018,0x00000012,0x00000017,0x0003003e,0x00000009,0x00000018,0x0004003d,
0x0000000d,0x0000001c,0x0000000f,0x0008004f,0x00000007,0x0000001d,0x0000001c,0x0000001c,
0x00000000,0x00000001,0x00000002,0x0004003d,0x00000007,0x0000001e,0x00000009,0x0004003d,
0x00000007,0x0000001f,0x0000000b,0x00050051,0x00000006,0x00000022,0x0000001d,0x00000000,
0x00050051,0x00000006,0x00000023,0x0000001d,0x00000001,0x00050051,0x00000006,0x00000024,
0x0000001d,0x00000002,0x00050051,0x00000006,0x00000025,0x0000001e,0x00000000,0x00050051,
0x00000006,0x00000026,0x0000001e,0x00000001,0x00050051,0x00000006,0x00000027,0x0000001e,
0x00000002,0x00050051,0x00000006,0x00000028,0x0000001f,0x00000000,0x00050051,0x00000006,
0x00000029,0x0000001f,0x00000001,0x00050051,0x00000006,0x0000002a,0x0000001f,0x00000002,
0x00060050,0x00000007,0x0000002b,0x00000022,0x00000023,0x00000024,0x00060050,0x00000007,
0x0000002c,0x00000025,0x00000026,0x00000027,0x00060050,0x00000007,0x0000002d,0x00000028,
0x00000029,0x0000002a,0x00060050,0x00000019,0x0000002e,0x0000002b,0x0000002c,0x0000002d,
0x0003003e,0x0000001b,0x0000002e,0x0004003d,0x00000007,0x00000032,0x00000031,0x0005008e,
0x00000007,0x00000034,0x00000032,0x00000033,0x0003003e,0x00000030,0x00000034,0x0004003d,
0x00000007,0x00000036,0x0000000b,0x0003003e,0x00000035,0x00000036,0x0004003d,0x00000037,
0x0000003c,0x0000003b,0x0003003e,0x00000039,0x0000003c,0x00050041,0x00000048,0x00000049,
0x00000047,0x00000043,0x0004003d,0x00000044,0x0000004a,0x00000049,0x00050041,0x00000048,
0x0000004c,0x00000047,0x0000004b,0x0004003d,0x00000044,0x0000004d,0x0000004c,0x00050092,
0x00000044,0x0000004e,0x0000004a,0x0000004d,0x0004003d,0x00000007,0x0000004f,0x00000030,
0x00050051,0x00000006,0x00000050,0x0000004f,0x00000000,0x00050051,0x00000006,0x00000051,
0x0000004f,0x00000001,0x00050051,0x00000006,0x00000052,0x0000004f,0x00000002,0x00070050,
0x0000000d,0x00000053,0x00000050,0x00000051,0x00000052,0x00000020,0x00050091,0x0000000d,
0x00000054,0x0000004e,0x00000053,0x00050041,0x00000055,0x00000056,0x00000041,0x00000043,
0x0003003e,0x00000056,0x00000054,0x000100fd,0x00010038
};
TEST_CASE("TestPipelineLayout", "[reflect]") {
std::vector<uint32_t> code(sizeof(test_shader) / sizeof(*test_shader));
memcpy(code.data(), test_shader, sizeof(test_shader));
lft::reflect::PipelineLayoutBuilder builder(code);
}
+19
View File
@@ -0,0 +1,19 @@
file(GLOB CXXFILES src/*.cpp)
add_library(loft_render_graph OBJECT ${CXXFILES})
add_library(loft::render_graph ALIAS loft_render_graph)
target_link_libraries(loft_render_graph
PUBLIC
loft::common
loft::base
loft_window
volk::volk
)
target_include_directories(loft_render_graph
PUBLIC
include/
)
# add_subdirectory(tests)
add_test(NAME RenderGraphBuilderTests COMMAND RenderGraphBuilderTests)
@@ -0,0 +1,24 @@
#pragma once
#include "RenderPass.hpp"
namespace lft::rg {
/*
* DependencyGraph is a class that represents a dynamic dependency graph for tasks in a render graph.
*/
class DependencyGraph {
private:
std::vector<TaskInfo> m_queue;
public:
DependencyGraph();
uint32_t add_task(const TaskInfo& task);
void remove_task(const TaskInfo& task);
std::vector<TaskInfo>& build_queue();
};
}
@@ -0,0 +1,74 @@
#pragma once
#include <vector>
#include "props.hpp"
#include "resources/ImageView.hpp"
#include "Swapchain.hpp"
/**
* Chain of image views. Used for swapchain and render graph output.
*/
struct ImageChain {
private:
std::vector<ImageView> m_images;
VkFormat m_format;
VkExtent2D m_extent;
VkImageLayout m_layout;
public:
GET(m_layout, layout);
GET(m_format, format);
GET(m_extent, extent);
[[nodiscard]] inline uint32_t count() const {
return m_images.size();
}
[[nodiscard]] inline const std::vector<ImageView>& views() const {
return m_images;
}
ImageChain(const ImageChain& other) :
m_format(other.m_format),
m_extent(other.m_extent),
m_layout(other.m_layout) {
m_images.clear();
std::copy(other.m_images.begin(), other.m_images.end(),
std::back_inserter(m_images));
}
ImageChain& operator=(const ImageChain& other) {
m_images.clear();
std::copy(other.m_images.begin(), other.m_images.end(),
std::back_inserter(m_images));
m_format = other.m_format;
m_extent = other.m_extent;
m_layout = other.m_layout;
return *this;
}
ImageChain(ImageChain&&) = delete;
ImageChain& operator=(ImageChain&&) = delete;
ImageChain(VkFormat format,
VkExtent2D extent,
VkImageLayout layout,
const std::vector<ImageView>& images) :
m_format(format),
m_extent(extent),
m_layout(layout),
m_images(images) {
}
static ImageChain from_swapchain(const Swapchain& swapchain) {
return ImageChain(swapchain.format().format,
swapchain.extent(),
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
swapchain.views());
}
};
@@ -0,0 +1,77 @@
#pragma once
#include <cstdint>
#include <map>
#include <memory>
#include "AdjacencyMatrix.hpp"
#include "Gpu.hpp"
#include "RenderGraphBuffer.hpp"
namespace lft::rg {
/**
* Lightweight definition of the render graph to be run.
*/
class RenderGraph {
private:
const Gpu* m_gpu;
std::string m_output_name;
AdjacencyMatrix* m_dependency_matrix;
std::vector<RenderGraphBuffer*> m_buffers;
std::vector<VkFence> m_fences;
uint32_t m_buffer_idx;
void create_fences();
std::vector<VkSemaphoreSubmitInfoKHR> get_wait_semaphores_for(
const RenderGraphBuffer* pBuffer,
uint32_t cmdbuf_idx
) const;
void wait_for_previous_frame(uint32_t buffer_idx);
bool is_recording_invalid(const RenderGraphBuffer& buffer, uint32_t cmdbuf_idx);
void record_command_buffer(
uint32_t buffer_idx,
uint32_t cmdbuf_idx,
uint32_t output_idx
);
void submit_command_buffer(
uint32_t buffer_idx,
uint32_t cmdbuf_idx,
VkSemaphore wait_semaphore,
VkFence fence,
uint32_t output_idx
);
bool is_batch_writing_to_final_image(const Batch& buffer) const;
public:
const RenderGraphBuffer& buffer(uint32_t idx) const {
return *m_buffers[idx];
}
RenderGraph& invalidate(const std::string& name);
RenderGraph(const Gpu* gpu,
const std::string& output_name,
const std::vector<RenderGraphBuffer*>& buffers,
AdjacencyMatrix* dependencies
);
/**
* Runs the render graph. Outputs to final image.
* @param chainImageIdx index of image in the final image chain
* @param final_image_fence fence to wait on for final image. The render graph will attempt to run as much tasks before waiting as possible.
*/
void run(uint32_t chainImageIdx, VkSemaphore semaphore, VkFence final_image_fence);
};
}
@@ -0,0 +1,136 @@
#include <set>
#include <map>
#include <string>
#include <iostream>
#include <volk.h>
#include "ImageChain.hpp"
#include "RenderGraph.hpp"
#include "Resource.hpp"
#include "RenderPass.hpp"
namespace lft::rg {
struct CommandBufferDefinition {
uint32_t first_task_idx;
uint32_t num_tasks;
std::vector<uint32_t> wait_signals_idx;
};
struct GraphAllocationInfo {
const Gpu* gpu;
ImageChain output_chain;
std::string output_name;
std::vector<ImageResourceDescription> resources;
std::vector<TaskInfo> render_passes;
std::vector<CommandBufferDefinition> command_buffers;
};
std::vector<VkCommandBuffer> allocate_command_buffers(const Gpu* gpu, uint32_t count);
class Allocator {
private:
const Gpu* m_gpu;
std::vector<std::map<std::string, ImageResource>> m_resources;
const ImageChain& m_output_chain;
std::string m_output_name;
std::map<std::string, VkRenderPass> m_renderpasses;
std::vector<RenderGraphBuffer> m_buffers;
std::vector<TaskInfo> m_tasks;
ImageResource allocate_image_resources(
const ImageResourceDescription& description,
bool is_color
);
BufferResource allocate_buffer_resource(
const BufferResourceDescription& description
);
void collect_resources(const std::vector<TaskInfo>& tasks);
VkAttachmentDescription2 create_attachment_description(
const ImageResourceDescription& definition,
bool is_color,
std::map<std::string, uint32_t>& resource_count_down,
std::set<std::string>& cleared_resources
);
VkRenderPass allocate_renderpass(
const TaskInfo& task,
std::map<std::string, uint32_t>& resource_count_down,
std::set<std::string>& cleared_resources
);
void prepare_renderpasses(const std::vector<TaskInfo>& tasks);
inline const ImageView get_attachment(
const std::string& name,
uint32_t output_chain_idx
) const {
if(name == m_output_name) {
return m_output_chain.views()[output_chain_idx];
}
if(m_resources[0].find(name) == m_resources[0].end()) {
throw std::runtime_error("Resource " + name + " not found in context");
}
return ImageView(m_resources[0].find(name)->second.image_view);
}
VkFramebuffer create_framebuffer(
const TaskInfo& task,
VkRenderPass render_pass,
uint32_t output_image_idx
);
RenderGraphBuffer allocate_buffer(const GraphAllocationInfo& info, uint32_t buffer_idx);
public:
GET(m_resources.size(), num_buffers);
Allocator(const GraphAllocationInfo& info, uint32_t num_buffers) :
m_gpu(info.gpu),
m_resources(num_buffers),
m_output_chain(info.output_chain),
m_output_name(info.output_name),
m_tasks(info.render_passes)
{
collect_resources(info.render_passes);
prepare_renderpasses(info.render_passes);
for(uint32_t i = 0; i < num_buffers; i++) {
m_buffers.push_back(allocate_buffer(info, i));
}
for(auto& renderpass : info.render_passes) {
/* TaskBuildInfo build_info(info.gpu, num_buffers, {
.x = 0,
.y = 0,
.width = (float)info.output_chain.extent().width,
.height = (float)info.output_chain.extent().height,
.minDepth = 0.0f,
.maxDepth = 1.0f
},
m_renderpasses[renderpass.name()], m_resources);
renderpass.m_build_func(build_info, renderpass.m_pContext); */
}
}
RenderGraph allocate() {
// return RenderGraph(m_gpu, m_buffers);
}
};
}
@@ -0,0 +1,108 @@
#pragma once
#include <unordered_map>
#include <string>
#include "RenderPass.hpp"
#include "Resource.hpp"
#include "Task.hpp"
namespace lft::rg {
struct BatchOutput {
VkCommandBuffer cmdbuf;
bool is_recording_valid;
BatchOutput(VkCommandBuffer cmdbuf);
};
struct Batch {
std::vector<Task> tasks;
std::vector<uint32_t> barriers;
std::vector<BatchOutput> outputs;
VkSemaphore signal;
inline BatchOutput output(uint32_t idx) {
return outputs[idx];
}
Batch(std::vector<BatchOutput> outputs, VkSemaphore signal);
Batch& invalidate_recordings();
Batch& insert_task(uint32_t idx, Task& task);
Batch& update_task(uint32_t idx, Task& task);
Batch& remove_task(uint32_t idx);
bool equals(const Batch& rhs) const;
};
class RenderGraphBuffer {
const Gpu* m_gpu;
uint32_t m_index;
public:
std::unordered_map<std::string, BufferResource> m_buffer_resources;
std::unordered_map<std::string, ImageResource> m_image_resources;
private:
std::vector<Batch> m_batches;
std::vector<VkSemaphore> m_final_semaphores;
public:
GET(m_index, index);
RenderGraphBuffer(
const Gpu* gpu,
uint32_t index,
uint32_t num_outputs);
Batch& batch(uint32_t idx) {
return m_batches[idx];
}
const Batch& batch(uint32_t idx) const {
return m_batches[idx];
}
uint32_t num_batches() const {
return m_batches.size();
}
Batch& insert_batch(uint32_t idx, uint32_t num_outputs);
void remove_batch(uint32_t idx);
VkSemaphore final_signal(uint32_t output_idx) const {
return m_final_semaphores[output_idx];
}
#pragma region IMAGE RESOURCES
bool has_image_resource(const std::string& name) const {
return m_image_resources.find(name) != m_image_resources.end();
}
std::optional<const ImageResource*>
get_image_resource(const std::string& name) const {
if(!has_image_resource(name)) {
return {};
}
return &m_image_resources.find(name)->second;
}
void put_image_resource(
const std::string& name,
const ImageResource& resource
) {
m_image_resources.insert({name, resource});
}
#pragma endregion
bool equals(const RenderGraphBuffer& other) const;
};
}
@@ -0,0 +1,372 @@
#pragma once
#include <string>
#include <unordered_set>
#include <vector>
#include <print>
#include "AdjacencyMatrix.hpp"
#include "RenderGraph.hpp"
#include "ImageChain.hpp"
#include "RenderPass.hpp"
#include "RenderGraphAllocator.hpp"
namespace lft::rg {
class BuilderAllocator {
private:
const Gpu* m_gpu;
ImageChain m_output_chain;
std::string m_output_name;
std::vector<RenderGraphBuffer> m_buffers;
std::unordered_set<std::string> m_updated_tasks;
uint32_t m_num_buffers;
bool is_task_updated(const std::string& name) {
return std::find(m_updated_tasks.begin(),
m_updated_tasks.end(),
name) != m_updated_tasks.end();
}
Task create_graphics_task(
const TaskInfo& task_info,
RenderGraphBuffer* pBuffer,
TaskRenderPass render_pass
);
Task create_compute_task(
const TaskInfo& task_info,
RenderGraphBuffer* pBuffer
);
Task create_task(
const TaskInfo& task_info,
RenderGraphBuffer* pBuffer,
std::unordered_set<std::string>& cleared_resources,
std::unordered_map<std::string, uint32_t>& resource_count_down
);
ImageResource allocate_image_resource(
const ImageResourceDescription& desc
) const;
BufferResource allocate_buffer_resource(const BufferResourceDescription& desc) const;
ImageResourceDescription get_output_image_description() const {
return ImageResourceDescription(m_output_name,
m_output_chain.format(),
m_output_chain.extent(),
(VkClearValue){.color = {0.0f, 0.0f, 0.0f, 0.0f}},
true);
}
ImageResourceDescription correct_resource_description(ImageResourceDescription desc);
/**
* Looks for an image resource in buffer at buffer_idx. Returns if found. Allocates if not found.
* If the wanted image resouce is in the output chain, the output_chain_idx is used.
*/
ImageView get_attachment(
const ImageResourceDescription& desc,
RenderGraphBuffer* pBuffer,
uint32_t output_idx
);
VkSemaphore create_semaphore() {
VkSemaphoreCreateInfo semaphore_info = {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
};
VkSemaphore semaphore;
if(vkCreateSemaphore(m_gpu->dev(), &semaphore_info, nullptr, &semaphore)) {
throw std::runtime_error("Failed to create semaphore");
}
return semaphore;
}
std::vector<VkCommandBuffer> allocate_command_buffer(uint32_t count) {
VkCommandBufferAllocateInfo cmdbuf_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = m_gpu->graphics_command_pool(),
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = count,
};
std::vector<VkCommandBuffer> cmdbufs(cmdbuf_info.commandBufferCount);
if(vkAllocateCommandBuffers(m_gpu->dev(), &cmdbuf_info, cmdbufs.data())) {
throw std::runtime_error("Failed to create command buffer");
}
return cmdbufs;
}
void update_task_queue(
RenderGraphBuffer* pBuffer,
const std::vector<TaskInfo>& task_infos
);
VkViewport get_viewport() {
return (VkViewport) {
.x = 0, .y = (float)m_output_chain.extent().height,
.width = (float)m_output_chain.extent().width,
.height = -(float)m_output_chain.extent().height,
.minDepth = 0, .maxDepth = 1.0
};
}
void update_task_buffer(const Task& task, const RenderGraphBuffer* pBuffer);
bool m_store_all_images = false;
VkExtent2D get_extent(VkExtent2D extent) const {
if(extent.width == 0) {
extent.width = m_output_chain.extent().width;
} if(extent.height == 0) {
extent.height = m_output_chain.extent().height;
}
return extent;
}
VkExtent2D get_extent_for_task(const TaskInfo& task_info) const {
VkExtent2D extent = task_info.m_extent;
if(extent.width == 0) {
extent.width = m_output_chain.extent().width;
} if(extent.height == 0) {
extent.height = m_output_chain.extent().height;
}
return extent;
}
public:
void remove_task(const std::string& name) {
m_updated_tasks.insert(name);
}
void set_store_all_images(bool value) {
m_store_all_images = value;
}
void set_image_chain(const ImageChain& image_chain) {
m_output_chain = image_chain;
for(int i = 0; i < m_buffers[0].num_batches(); i++) {
for(auto& task : m_buffers[0].batch(i).tasks) {
mark_task_updated(task.pDefinition.name());
}
}
for(auto& view : m_output_chain.views()) {
std::println("Set View: {:#06x}", (unsigned long)view.view);
}
}
GET(m_num_buffers, num_buffers);
REF(m_output_chain, image_chain);
BuilderAllocator(const Gpu* gpu,
ImageChain output_chain,
const std::string& output_name,
uint32_t num_buffers) :
m_gpu(gpu),
m_output_chain(output_chain),
m_output_name(output_name),
m_num_buffers(num_buffers)
{
for(uint32_t i = 0; i < num_buffers; i++) {
m_buffers.emplace_back(m_gpu, i, m_output_chain.count());
}
}
void mark_task_updated(const std::string& name) {
m_updated_tasks.insert(name);
}
void add_buffer_resource(
const std::string& name,
const std::vector<Buffer>& buffers,
size_t size
) {
/* if(buffers.size() <= m_output_chain.count()) {
throw std::runtime_error("Buffer count must be greater than output chain count");
} if(m_output_chain.count() % buffers.size() != 0) {
throw std::runtime_error("Buffer count must be a multiple of output chain count");
} */
for(uint32_t i = 0; i < m_buffers.size(); i++) {
m_buffers[i].m_buffer_resources.insert({name, BufferResource(buffers[i % buffers.size()].buf, size)});
}
}
void add_image_resource(
const std::string& name,
const std::vector<ImageResource> images
) {
if(images.size() <= m_output_chain.count()) {
throw std::runtime_error("Resource count must be greater than output chain count");
} if(m_output_chain.count() % images.size() != 0) {
throw std::runtime_error("Resource count must be a multiple of output chain count");
}
for(uint32_t i = 0; i < m_buffers.size(); i++) {
// m_buffers[i].m_image_resources[name] = images[i % images.size()];
}
}
VkAttachmentDescription2 create_attachment_description(
const ImageResourceDescription& definition,
bool is_first_write,
bool is_last_write
);
TaskRenderPass allocate_renderpass(
const TaskInfo& task,
std::unordered_map<std::string, uint32_t>& resource_count_down,
std::unordered_set<std::string>& cleared_resources
);
VkFramebuffer create_framebuffer(
const TaskInfo& task_info,
VkRenderPass renderpass,
RenderGraphBuffer* pBuffer,
uint32_t output_idx
);
RenderGraph allocate(
std::vector<TaskInfo>& tasks,
AdjacencyMatrix *dependencies
);
bool equals(const BuilderAllocator& other) const;
};
std::vector<TaskInfo> topology_sort(std::vector<TaskInfo>& tasks, const std::string& output_name);
class Builder {
private:
std::string m_output_name;
std::map<std::string, uint32_t> m_name_to_task_idx;
std::vector<TaskInfo> m_tasks;
BuilderAllocator m_allocator;
// counter for how many times a resource is written to
std::unordered_map<std::string, uint32_t> m_resource_write_counts;
const TaskInfo& get_task_by_name(const std::string& name) {
if(m_name_to_task_idx.find(name) == m_name_to_task_idx.end()) {
throw std::runtime_error("Task does not exist");
}
return m_tasks[m_name_to_task_idx[name]];
}
bool m_store_all_images;
public:
void store_all_images() {
m_store_all_images = true;
}
void set_image_chain(const ImageChain& output_chain) {
m_allocator.set_image_chain(output_chain);
}
Builder(const Gpu* gpu,
ImageChain output_chain,
const std::string& output_name
) :
m_output_name(output_name),
m_allocator(gpu, output_chain, output_name, 1)
{
}
/**
* Adds allocated buffer resource
*/
void add_buffer_resource(
const std::string& name,
const std::vector<Buffer>& buffers,
size_t size
) {
m_allocator.add_buffer_resource(name, buffers, size);
}
void add_image_resource(
const std::string& name,
const std::vector<ImageResource> images
) {
m_allocator.add_image_resource(name, images);
}
bool is_task_ok(const TaskInfo& task) {
for(auto& dependency : task.dependencies()) {
for(auto& output : task.color_outputs()) {
if(output.name() == dependency) {
return false;
}
}
if(task.depth_output().has_value() &&
task.depth_output()->name() == dependency) {
return false;
}
for(auto& output : task.buffer_outputs()) {
if(output.name() == dependency) {
return false;
}
}
}
return true;
}
void add_task(TaskInfo task) {
if(!is_task_ok(task)) {
throw std::runtime_error("Task " + task.name() + " output to one of it's dependencies. That is prohibited. To simulate this behaviour, for instance in compute shader, allocate the resource yourself and add it with `add_image_resource` or `add_buffer_resource`.");
}
std::string task_name = task.name();
auto found = std::find_if(m_tasks.begin(), m_tasks.end(),
[&task_name](const TaskInfo& i) {
return i.name() == task_name;
});
if(found != m_tasks.end()) {
m_tasks.erase(found);
}
if(task.is_output_to_final()) {
if(!task.has_output(m_output_name)) {
task.add_color_output(m_output_name,
m_allocator.image_chain().format(),
m_allocator.image_chain().extent(),
{0.0f, 0.0f, 0.0f, 1.0f}
);
}
}
m_tasks.push_back(task);
m_name_to_task_idx[task.m_name] = m_tasks.size() - 1;
m_allocator.mark_task_updated(task.name());
}
void remove_task(const std::string& name) {
m_allocator.remove_task(name);
m_tasks.erase(std::remove_if(m_tasks.begin(), m_tasks.end(),
[name](const TaskInfo& task) {
return task.name() == name;
}), m_tasks.end());
}
RenderGraph build();
};
}
@@ -0,0 +1,478 @@
#pragma once
#include <bitset>
#include <vector>
#include <string>
#include <functional>
#include <optional>
#include <memory>
#include <iostream>
#include "Gpu.hpp"
#include "props.hpp"
#include "Resource.hpp"
#include "Recording.hpp"
#include <volk.h>
#include <map>
namespace lft::rg {
class ImageResourceDescription {
private:
std::string m_name;
VkFormat m_format;
VkExtent2D m_extent;
VkClearValue m_clear_value;
bool m_is_color;
public:
REF(m_name, name);
GET(m_format, format);
GET(m_extent, extent);
GET(m_clear_value, clear_value);
GET(m_is_color, is_color);
inline void set_extent(VkExtent2D extent) {
m_extent = extent;
}
ImageResourceDescription(
const std::string& name,
VkFormat format,
VkExtent2D extent,
VkClearValue clear_value,
bool is_color
) :
m_name(name),
m_format(format),
m_extent(extent),
m_clear_value(clear_value),
m_is_color(is_color) {
}
bool equals(const ImageResourceDescription& other) const {
return m_name == other.m_name &&
m_format == other.m_format &&
m_extent.width == other.m_extent.width &&
m_extent.height == other.m_extent.height &&
m_clear_value.color.uint32[0] == other.m_clear_value.color.uint32[0] &&
m_clear_value.color.uint32[1] == other.m_clear_value.color.uint32[1] &&
m_clear_value.color.uint32[2] == other.m_clear_value.color.uint32[2] &&
m_clear_value.color.uint32[3] == other.m_clear_value.color.uint32[3] &&
m_clear_value.depthStencil.depth == other.m_clear_value.depthStencil.depth;
}
};
struct BufferResourceDescription {
private:
std::string m_name;
VkDeviceSize m_size;
public:
REF(m_name, name);
GET(m_size, size);
BufferResourceDescription(
const std::string& name,
VkDeviceSize size
) :
m_name(name),
m_size(size) {
}
bool equals(const BufferResourceDescription& other) const {
return m_name == other.m_name && m_size == other.m_size;
}
};
class TaskRecordInfo {
const Gpu* m_gpu;
lft::Recording m_recording;
uint32_t m_buffer_idx;
uint32_t m_image_idx;
VkViewport m_viewport;
public:
GET(m_gpu, gpu);
REF(m_recording, recording);
GET(m_image_idx, image_idx);
GET(m_buffer_idx, buffer_idx);
GET(m_viewport, viewport);
TaskRecordInfo(
const Gpu* gpu,
lft::Recording recording,
uint32_t buffer_idx,
uint32_t image_in_flight_idx,
VkViewport viewport) :
m_recording(recording),
m_image_idx(image_in_flight_idx),
m_buffer_idx(buffer_idx),
m_gpu(gpu),
m_viewport(viewport) {
}
};
class TaskBuildInfo {
const Gpu* m_gpu;
uint32_t m_buffer_idx;
uint32_t m_num_buffers;
VkViewport m_viewport;
VkRenderPass m_renderpass;
std::unordered_map<std::string, ImageResource> m_resources;
public:
GET(m_gpu, gpu);
GET(m_num_buffers, num_buffers);
GET(m_buffer_idx, buffer_idx);
GET(m_viewport, viewport);
GET(m_renderpass, renderpass);
inline ImageResource get_resource(
const std::string& name
) const {
return m_resources.find(name)->second;
}
TaskBuildInfo(
const Gpu* gpu,
uint32_t buffer_idx,
uint32_t num_buffers,
VkViewport viewport,
VkRenderPass renderpass,
std::unordered_map<std::string, ImageResource> resources) :
m_gpu(gpu),
m_buffer_idx(buffer_idx),
m_num_buffers(num_buffers),
m_viewport(viewport),
m_renderpass(renderpass),
m_resources(resources) {
}
};
enum TaskType {
GRAPHICS_TASK,
COMPUTE_TASK,
RAY_TRACING_TASK
};
struct TaskInfo {
typedef std::function<void(const TaskBuildInfo&, void*)> TaskBuildFunc;
typedef std::function<void(const TaskRecordInfo&, void*)> TaskRecordFunc;
std::string m_name;
TaskType m_type;
void *m_pContext;
TaskBuildFunc m_build_func;
TaskRecordFunc m_record_func;
std::vector<std::string> m_dependencies;
std::vector<std::string> m_recording_dependencies;
std::vector<BufferResourceDescription> m_buffer_outputs;
std::vector<ImageResourceDescription> m_color_outputs;
std::optional<ImageResourceDescription> m_depth_output;
bool m_is_output_to_final;
VkExtent2D m_extent;
REF(m_name, name);
GET(m_type, type);
REF(m_build_func, build_func);
REF(m_record_func, record_func);
REF(m_dependencies, dependencies);
REF(m_recording_dependencies, recording_dependencies);
REF(m_buffer_outputs, buffer_outputs);
REF(m_color_outputs, color_outputs);
REF(m_depth_output, depth_output);
GET(m_is_output_to_final, is_output_to_final);
TaskInfo() {
}
template<typename T>
TaskInfo(const std::string& name,
TaskType type,
T *pContext,
std::function<void(const TaskBuildInfo&, T*)> build_func,
std::function<void(const TaskRecordInfo&, T*)> record_func
) :
m_name(name),
m_type(type),
m_pContext(pContext),
m_build_func(build_func),
m_record_func(record_func),
m_extent(0, 0)
{
}
bool has_output(const std::string& name) const {
if(m_depth_output.has_value() && m_depth_output->name() == name) {
return true;
}
if(std::any_of(m_buffer_outputs.begin(), m_buffer_outputs.end(),
[name](const BufferResourceDescription& output) {
return output.name() == name;
})) {
return true;
}
if(std::any_of(m_color_outputs.begin(), m_color_outputs.end(),
[name](const ImageResourceDescription& output) {
return output.name() == name;
})) {
return true;
}
return false;
}
TaskInfo& add_color_output(const std::string& name,
VkFormat format,
VkExtent2D extent,
VkClearColorValue clear_value) {
m_color_outputs.emplace_back(name, format, extent,
VkClearValue {
.color = clear_value
}, true);
return *this;
}
TaskInfo& set_depth_output(
const std::string& name,
VkFormat format,
VkExtent2D extent,
VkClearDepthStencilValue clear_value
) {
m_depth_output = ImageResourceDescription(name, format, extent,
VkClearValue {
.depthStencil = clear_value
}, false);
return *this;
}
TaskInfo& add_dependency(const std::string& dependency) {
m_dependencies.emplace_back(dependency);
return *this;
}
TaskInfo& add_recording_dependency(const std::string& dependency) {
m_recording_dependencies.emplace_back(dependency);
return *this;
}
TaskInfo& set_extent(VkExtent2D extent) {
m_extent = extent;
return *this;
}
bool equals(const TaskInfo& other) const {
if(this->name() != other.name()) {
std::cout << "Names are not the same: " << name() << " != " << other.name() << std::endl;
return false;
}
if(this->m_type != other.m_type) {
std::cout << "Task types are not the same: " << m_type << " != " << other.m_type << std::endl;
return false;
}
if(m_dependencies != other.m_dependencies) {
std::cout << "Dependencies are different" << std::endl;
return false;
}
if(m_buffer_outputs.size() != other.m_buffer_outputs.size()) {
return false;
}
for(uint32_t i = 0; i < m_buffer_outputs.size(); i++) {
auto output = other.m_buffer_outputs[i];
auto found = std::find_if(
m_buffer_outputs.begin(),
m_buffer_outputs.end(),
[output](const BufferResourceDescription& desc) {
return output.equals(desc);
});
if(found == other.m_buffer_outputs.end()) {
std::cout << "Missing buffer output: " << output.name() << std::endl;
return false;
}
}
for(uint32_t i = 0; i < m_color_outputs.size(); i++) {
auto output = other.m_color_outputs[i];
auto found = std::find_if(
m_color_outputs.begin(),
m_color_outputs.end(),
[output](const ImageResourceDescription& desc) {
return output.equals(desc);
});
if(found == other.m_color_outputs.end()) {
std::cout << "Missing color output: " << output.name() << std::endl;
return false;
}
}
if(m_depth_output.has_value() != other.m_depth_output.has_value()) {
std::cout << "Depth output differ" << std::endl;
return false;
}
if(m_depth_output.has_value() && !m_depth_output->equals(other.m_depth_output.value())) {
std::cout << "Depth output differ" << std::endl;
return false;
}
if(m_extent.width != other.m_extent.width ||
m_extent.height != other.m_extent.height) {
std::cout << std::format("Extent differ: [{},{}] != [{},{}]", m_extent.width, m_extent.height, other.m_extent.width, other.m_extent.height) << std::endl;
return false;
}
return true;
}
};
class ComputeTaskBuilder {
private:
TaskInfo m_task_info;
public:
ComputeTaskBuilder(const std::string& name,
void *pContext,
std::function<void(const TaskBuildInfo&, void*)> build_func,
std::function<void(const TaskRecordInfo&, void*)> record_func
) :
m_task_info(name, COMPUTE_TASK, pContext, build_func, record_func) {
}
ComputeTaskBuilder& add_buffer_output(const std::string& name,
VkDeviceSize size) {
m_task_info.m_buffer_outputs.emplace_back(name, size);
return *this;
}
ComputeTaskBuilder& add_dependency(const std::string& dependency) {
m_task_info.m_dependencies.emplace_back(dependency);
return *this;
}
ComputeTaskBuilder& add_recording_dependency(const std::string& dependency) {
m_task_info.m_recording_dependencies.emplace_back(dependency);
return *this;
}
TaskInfo build() {
return m_task_info;
}
};
class RenderTaskBuilder {
private:
TaskInfo m_task_info;
public:
RenderTaskBuilder(const std::string& name,
void* pContext,
std::function<void(const TaskBuildInfo&, void*)> build_func,
std::function<void(const TaskRecordInfo&, void*)> record_func
) :
m_task_info(name, GRAPHICS_TASK, pContext, build_func, record_func) {
}
RenderTaskBuilder& add_color_output(const std::string& name,
VkFormat format,
VkExtent2D extent = VkExtent2D(0.0f, 0.0f),
VkClearColorValue clear_value = {0.0f, 0.0f, 0.0f, 0.0f}) {
m_task_info.m_color_outputs.emplace_back(name, format, extent,
VkClearValue {
.color = clear_value
}, true);
return *this;
}
RenderTaskBuilder& set_depth_output(
const std::string& name,
VkFormat format,
VkExtent2D extent = VkExtent2D(0.0f, 0.0f),
VkClearDepthStencilValue clear_value = {1.0f, 0}
) {
m_task_info.m_depth_output = ImageResourceDescription(name, format, extent,
VkClearValue {
.depthStencil = clear_value
}, false);
return *this;
}
RenderTaskBuilder& set_output_to_final() {
m_task_info.m_is_output_to_final = true;
return *this;
}
RenderTaskBuilder& add_dependency(const std::string& dependency) {
m_task_info.m_dependencies.emplace_back(dependency);
return *this;
}
RenderTaskBuilder& add_recording_dependency(const std::string& dependency) {
m_task_info.m_recording_dependencies.emplace_back(dependency);
return *this;
}
RenderTaskBuilder& set_extent(VkExtent2D extent) {
m_task_info.m_extent = extent;
return *this;
}
TaskInfo build() {
return m_task_info;
}
};
template<typename T>
RenderTaskBuilder render_task(const std::string& name,
T* pContext,
std::function<void(const TaskBuildInfo&, T*)> build_func,
std::function<void(const TaskRecordInfo&, T*)> record_func
) {
return RenderTaskBuilder(name, (void*)pContext,
[build_func, pContext](const TaskBuildInfo& info, void* ctx) {
build_func(info, pContext);
},
[record_func, pContext](const TaskRecordInfo& info, void* ctx) {
record_func(info, pContext);
});
}
template<typename T>
ComputeTaskBuilder compute_task(const std::string& name,
T* pContext,
std::function<void(const TaskBuildInfo&, T*)> build_func,
std::function<void(const TaskRecordInfo&, T*)> record_func
) {
return ComputeTaskBuilder(name, (void*)pContext,
[build_func, pContext](const TaskBuildInfo& info, void* ctx) {
build_func(info, pContext);
},
[record_func, pContext](const TaskRecordInfo& info, void* ctx) {
record_func(info, pContext);
});
}
}
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include <volk.h>
class ImageResource {
public:
VkImage image;
VkImageView image_view;
VkExtent2D extent;
ImageResource(const ImageResource&) = default;
ImageResource(ImageResource&) = default;
ImageResource(ImageResource&&) = default;
ImageResource(VkImage image, VkImageView image_view, VkExtent2D extent) :
image(image),
image_view(image_view),
extent(extent) {
};
};
class BufferResource {
public:
VkBuffer buffer;
VkDeviceSize size;
BufferResource(VkBuffer buffer, VkDeviceSize size) :
buffer(buffer),
size(size) {
};
};
+93
View File
@@ -0,0 +1,93 @@
#pragma once
#include <cassert>
#include <vector>
#include <iostream>
#include <volk.h>
#include "RenderPass.hpp"
namespace lft::rg {
#define MAX_ATTACHMENT_COUNT 9
#define MAX_COLOR_ATTACHMENT_COUNT MAX_ATTACHMENT_COUNT - 1
struct TaskRenderPassState {
uint32_t num_attachments : 4;
uint32_t resource_flags : 18;
TaskRenderPassState() {
}
TaskRenderPassState(
uint32_t num_color_attachments,
bool has_depth_attachment
) {
assert(num_color_attachments <= MAX_COLOR_ATTACHMENT_COUNT);
num_attachments = num_color_attachments + has_depth_attachment;
}
inline void set_resource_is_first(uint32_t resource_idx) {
assert(resource_idx < MAX_ATTACHMENT_COUNT);
resource_flags |= (1 << resource_idx);
}
inline void set_resource_is_last(uint32_t resource_idx) {
assert(resource_idx < MAX_ATTACHMENT_COUNT);
resource_flags |= (1 << (resource_idx + 9));
}
inline bool is_resource_first(uint32_t resource_idx) const {
assert(resource_idx < MAX_ATTACHMENT_COUNT);
return resource_flags & (1 << resource_idx);
}
inline bool is_resource_last(uint32_t resource_idx) const {
assert(resource_idx < MAX_ATTACHMENT_COUNT);
return resource_flags & (1 << (resource_idx + 9));
}
};
struct TaskRenderPass {
VkRenderPass render_pass;
TaskRenderPassState state;
TaskRenderPass() : render_pass(VK_NULL_HANDLE) {
}
TaskRenderPass(const VkRenderPass rp, const TaskRenderPassState state) :
render_pass(rp),
state(state) {
}
};
struct Task {
TaskInfo pDefinition;
TaskRenderPass render_pass;
std::vector<uint32_t> rp_attachment_states;
std::vector<VkFramebuffer> framebuffer;
VkExtent2D extent;
bool equals(const Task& other) const {
if(!pDefinition.equals(other.pDefinition)) {
return false;
}
if(framebuffer.size() != other.framebuffer.size()) {
std::cout << "Number of framebuffers is not equal" << std::endl;
return false;
}
return true;
}
};
}
+270
View File
@@ -0,0 +1,270 @@
#include "RenderGraph.hpp"
#include "AdjacencyMatrix.hpp"
#include "Recording.hpp"
#include "RenderGraphBuffer.hpp"
#include "RenderPass.hpp"
#include <algorithm>
#include <print>
#include <vulkan/vulkan_core.h>
namespace lft::rg {
void RenderGraph::create_fences() {
VkFenceCreateInfo fence_info = {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.flags = VK_FENCE_CREATE_SIGNALED_BIT
};
m_fences.resize(m_buffers.size());
for(uint32_t fence_idx = 0;
fence_idx < m_buffers.size();
fence_idx++
) {
if(vkCreateFence(m_gpu->dev(),
&fence_info, nullptr, &m_fences[fence_idx])) {
throw std::runtime_error("Failed to create fence");
}
}
}
RenderGraph& RenderGraph::invalidate(const std::string& name) {
/* for(auto& buffer : m_buffers) {
for(uint32_t i = 0; i < buffer.m_command_buffers.size(); i++) {
buffer.m_recording_validity[i] = false;
}
} */
return *this;
}
RenderGraph::RenderGraph(
const Gpu* gpu,
const std::string& output_name,
const std::vector<RenderGraphBuffer*>& buffers,
AdjacencyMatrix* dependencies
) :
m_gpu(gpu),
m_output_name(output_name),
m_buffers(std::move(buffers)),
m_buffer_idx(0),
m_dependency_matrix(dependencies)
{
if(m_buffers.size() == 0) {
throw std::runtime_error("Number of buffers cannot be 0");
}
create_fences();
}
void RenderGraph::wait_for_previous_frame(uint32_t buffer_idx) {
vkWaitForFences(m_gpu->dev(),
1,
&m_fences[buffer_idx],
VK_TRUE, UINT64_MAX);
vkResetFences(m_gpu->dev(),
1,
&m_fences[buffer_idx]);
}
void RenderGraph::record_command_buffer(
uint32_t buffer_idx,
uint32_t batch_idx,
uint32_t output_idx
) {
RenderGraphBuffer* pBuffer = m_buffers[buffer_idx];
VkCommandBuffer cmdbuf = pBuffer->batch(batch_idx).output(output_idx).cmdbuf;
VkCommandBufferBeginInfo cmdbuf_begin_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
};
if(vkBeginCommandBuffer(cmdbuf,
&cmdbuf_begin_info)) {
throw std::runtime_error("Failed to begin command buffer");
}
auto& tasks = pBuffer->batch(batch_idx).tasks;
TaskRecordInfo record_info(
m_gpu,
lft::Recording(cmdbuf),
buffer_idx,
output_idx,
VkViewport {
.x = 0,
.y = (float)tasks[0].extent.height,
.width = (float)tasks[0].extent.width,
.height = -(float)tasks[0].extent.height,
.minDepth = 0.0f,
.maxDepth = 1.0f,
});
for(auto& task : tasks) {
if(task.pDefinition.type() == GRAPHICS_TASK) {
std::vector<VkClearValue> clear_values(
task.pDefinition.color_outputs().size() +
task.pDefinition.depth_output().has_value()
);
for(uint32_t i = 0; i < task.pDefinition.color_outputs().size(); i++) {
clear_values[i] = task.pDefinition.color_outputs()[i].clear_value();
}
if(task.pDefinition.depth_output().has_value()) {
clear_values[task.pDefinition.color_outputs().size()] =
task.pDefinition.depth_output()->clear_value();
}
VkRenderPassBeginInfo render_pass_begin_info = {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.renderPass = task.render_pass.render_pass,
.framebuffer = task.framebuffer[output_idx],
.renderArea = {
.offset = {0, 0},
.extent = task.extent,
},
.clearValueCount = (uint32_t)clear_values.size(),
.pClearValues = clear_values.data(),
};
vkCmdBeginRenderPass(cmdbuf,
&render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
}
task.pDefinition.m_record_func(record_info, task.pDefinition.m_pContext);
if(task.pDefinition.type() == GRAPHICS_TASK) {
vkCmdEndRenderPass(cmdbuf);
}
}
if(vkEndCommandBuffer(cmdbuf)) {
throw std::runtime_error("Failed to end command buffer");
}
}
VkSemaphoreSubmitInfoKHR create_simple_semaphore_submit(VkSemaphore signal) {
return {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO_KHR,
.semaphore = signal,
.value = 1,
.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,
.deviceIndex = 0
};
}
std::vector<VkSemaphoreSubmitInfoKHR> RenderGraph::get_wait_semaphores_for(
const RenderGraphBuffer* pBuffer,
uint32_t batch_idx
) const {
std::vector<VkSemaphoreSubmitInfoKHR> semaphores;
for(auto& task : pBuffer->batch(batch_idx).tasks) {
auto dependencies = m_dependency_matrix->get_dependencies(task.pDefinition.name());
for(int32_t i = batch_idx - 1; i >= 0; i--) {
for(auto dependency : dependencies) {
if(std::find_if(pBuffer->batch(i).tasks.begin(), pBuffer->batch(i).tasks.end(),
[dependency](const Task& other) {
return other.pDefinition.name() == dependency;
}) != pBuffer->batch(i).tasks.end()) {
semaphores.push_back(create_simple_semaphore_submit(pBuffer->batch(i).signal));
break;
}
}
}
}
return semaphores;
}
void RenderGraph::submit_command_buffer(
uint32_t buffer_idx,
uint32_t batch_idx,
VkSemaphore wait_semaphore,
VkFence fence,
uint32_t output_idx
) {
RenderGraphBuffer* pBuffer = m_buffers[buffer_idx];
VkCommandBufferSubmitInfoKHR cmdbuf = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO_KHR,
.commandBuffer = pBuffer->batch(batch_idx).output(output_idx).cmdbuf,
.deviceMask = 0,
};
VkSemaphoreSubmitInfo signal_info = {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
.semaphore = batch_idx == pBuffer->num_batches() - 1 ?
pBuffer->final_signal(output_idx) :
pBuffer->batch(batch_idx).signal,
.value = 1,
.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,
.deviceIndex = 0
};
auto wait_on_semaphores = get_wait_semaphores_for(pBuffer, batch_idx);
if(wait_semaphore) {
wait_on_semaphores.push_back(create_simple_semaphore_submit(wait_semaphore));
}
VkSubmitInfo2 submit_info = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2,
.waitSemaphoreInfoCount = (uint32_t)wait_on_semaphores.size(),
.pWaitSemaphoreInfos = wait_on_semaphores.data(),
.commandBufferInfoCount = 1,
.pCommandBufferInfos = &cmdbuf,
.signalSemaphoreInfoCount = 1,
.pSignalSemaphoreInfos = &signal_info,
};
m_gpu->enqueue_graphics(&submit_info, fence);
}
bool RenderGraph::is_recording_invalid(const RenderGraphBuffer& buffer,
uint32_t cmdbuf_idx) {
return false;
// return !buffer.m_recording_validity[cmdbuf_idx];
}
bool RenderGraph::is_batch_writing_to_final_image(const Batch& buffer) const {
return std::any_of(buffer.tasks.begin(), buffer.tasks.end(),
[this](const Task& task) {
return task.pDefinition.is_output_to_final() || task.pDefinition.has_output(m_output_name);
});
}
void RenderGraph::run(uint32_t chainImageIdx,
VkSemaphore semaphore_signal_for_final_image,
VkFence fence_signal_for_final_image
) {
uint32_t buffer_idx = (m_buffer_idx + 1) % m_buffers.size();
auto& buffer = m_buffers[buffer_idx];
// the render graph manages it's resource and must therefore itself wait
// for them to be free for write.
wait_for_previous_frame(buffer_idx);
bool is_fence_reset = false;
for(uint32_t idx = 0; idx < buffer->num_batches(); idx++) {
// if(is_recording_invalid(buffer, idx - 1)) {
record_command_buffer(buffer_idx, idx, chainImageIdx);
// }
VkFence fence = idx == buffer->num_batches() - 1 ?
m_fences[buffer_idx] : VK_NULL_HANDLE;
VkSemaphore wait_semaphore = VK_NULL_HANDLE;
if (!is_fence_reset && fence_signal_for_final_image && is_batch_writing_to_final_image(buffer->batch(idx))) {
vkWaitForFences(m_gpu->dev(), 1, &fence_signal_for_final_image, VK_TRUE, UINT64_MAX);
vkResetFences(m_gpu->dev(), 1, &fence_signal_for_final_image);
is_fence_reset = true;
}
if(semaphore_signal_for_final_image && is_batch_writing_to_final_image(buffer->batch(idx))) {
wait_semaphore = semaphore_signal_for_final_image;
}
submit_command_buffer(buffer_idx, idx, wait_semaphore, fence, chainImageIdx);
}
}
}
@@ -0,0 +1,364 @@
#include "RenderGraphAllocator.hpp"
#include <stdexcept>
#include <vector>
#include <volk.h>
#include "FramebufferBuilder.hpp"
#include "RenderPass.hpp"
#include "RenderGraph.hpp"
namespace lft::rg {
ImageResource Allocator::allocate_image_resources(
const ImageResourceDescription& description,
bool is_color
) {
MemoryAllocationInfo memory_info = {
.usage = MEMORY_USAGE_AUTO_PREFER_DEVICE
};
ImageCreateInfo image_info = {
.extent = description.extent(),
.format = description.format(),
.usage = VK_IMAGE_USAGE_SAMPLED_BIT |
(VkImageUsageFlags)(is_color ?
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT :
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT),
.aspectMask = (VkImageAspectFlags)(is_color ?
VK_IMAGE_ASPECT_COLOR_BIT :
VK_IMAGE_ASPECT_DEPTH_BIT),
.arrayLayers = 1,
.mipLevels = 1,
};
Image image = {};
m_gpu->memory()->create_image(&image_info, &memory_info, &image);
ImageView view = {};
view = image.create_view(m_gpu, description.format(), {
.aspectMask = image_info.aspectMask,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
});
return ImageResource(image.img, view.view, image_info.extent);
}
BufferResource Allocator::allocate_buffer_resource(const BufferResourceDescription& description) {
BufferCreateInfo buffer_info = {
.size = description.size(),
.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.isExclusive = true,
};
Buffer buffer = {};
m_gpu->memory()->create_buffer(&buffer_info, nullptr, &buffer);
return BufferResource(buffer.buf, buffer_info.size);
}
VkAttachmentDescription2 Allocator::create_attachment_description(
const ImageResourceDescription& definition,
bool is_color,
std::map<std::string, uint32_t>& resource_count_down,
std::set<std::string>& cleared_resources
) {
VkAttachmentLoadOp load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
VkImageLayout initial_layout = VK_IMAGE_LAYOUT_UNDEFINED;
VkImageLayout final_layout = VK_IMAGE_LAYOUT_UNDEFINED;
VkImageLayout middle_layout = is_color ?
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL :
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
VkImageLayout last_layout =
(definition.name() == m_output_name ? m_output_chain.layout() :
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
final_layout = resource_count_down[definition.name()] > 1 ?
middle_layout :
last_layout;
if(cleared_resources.find(definition.name()) != cleared_resources.end()/* context.is_clear(definition.name()) */) {
load_op = VK_ATTACHMENT_LOAD_OP_LOAD;
initial_layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
} else {
cleared_resources.insert(definition.name());
load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
initial_layout = VK_IMAGE_LAYOUT_UNDEFINED;
}
// store op is dont care for the last one and store for every other
VkAttachmentStoreOp store_op = resource_count_down[definition.name()] == 1 ?
VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
// remember to count down the resource
resource_count_down[definition.name()]--;
return {
.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2,
.format = definition.format(),
.samples = VK_SAMPLE_COUNT_1_BIT,
.loadOp = load_op,
.storeOp = store_op,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = initial_layout,
.finalLayout = final_layout
};
}
VkRenderPass Allocator::allocate_renderpass(
const TaskInfo& task,
std::map<std::string, uint32_t>& resource_count_down,
std::set<std::string>& cleared_resources
) {
size_t num_attachments = task.color_outputs().size() + task.depth_output().has_value();
std::vector<VkAttachmentDescription2> descriptions(num_attachments);
std::vector<VkAttachmentReference2> references(num_attachments);
uint32_t i = 0;
for(auto& output : task.color_outputs()) {
descriptions[i] = create_attachment_description(
output, true,
resource_count_down,
cleared_resources);
// ignore sub passes, so one reference per attachment
references[i] = {
.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2,
.attachment = i,
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
};
i++;
}
if (task.depth_output().has_value()) {
descriptions[i] = create_attachment_description(
task.depth_output().value(),
false,
resource_count_down,
cleared_resources);
references[i] = {
.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2,
.attachment = i,
.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
};
}
VkMemoryBarrier2KHR entryBarrier = {
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2_KHR,
.pNext = nullptr,
.srcStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT,
.srcAccessMask = 0,
.dstStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT,
.dstAccessMask = 0
};
const VkSubpassDependency2 subpass_dependencies[] = {
{
.sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2,
.pNext = &entryBarrier,
.srcSubpass = VK_SUBPASS_EXTERNAL,
.dstSubpass = 0,
.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT,
}
};
const VkSubpassDescription2 subpass = {
.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2,
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
.colorAttachmentCount = (uint32_t)references.size() - (task.depth_output().has_value()),
.pColorAttachments = references.data(),
.pDepthStencilAttachment = task.depth_output().has_value() ? &references[i] : nullptr,
};
VkRenderPassCreateInfo2 renderpass_info = {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2,
.attachmentCount = (uint32_t)descriptions.size(),
.pAttachments = descriptions.data(),
.subpassCount = 1,
.pSubpasses = &subpass,
.dependencyCount = 1,
.pDependencies = subpass_dependencies
};
VkRenderPass renderpass;
if(vkCreateRenderPass2KHR(m_gpu->dev(), &renderpass_info, nullptr, &renderpass)) {
throw std::runtime_error("Failed to create render pass");
}
return renderpass;
}
VkFramebuffer Allocator::create_framebuffer(
const TaskInfo& task,
VkRenderPass render_pass,
uint32_t output_image_idx
) {
std::vector<ImageView> attachments(task.color_outputs().size() + task.depth_output().has_value());
uint32_t i = 0;
for(auto& output : task.color_outputs()) {
attachments[i++] = get_attachment(output.name(), output_image_idx).view;
}
if (task.depth_output().has_value()) {
attachments[i++] = get_attachment(task.depth_output()->name(), output_image_idx).view;
}
if(i != attachments.size()) {
throw std::runtime_error("Framebuffer attachments size mismatch");
}
auto fb = FramebufferBuilder(render_pass, task.m_extent, attachments)
.build(m_gpu);
return fb.framebuffer;
}
std::vector<VkCommandBuffer> allocate_command_buffers(const Gpu* gpu, uint32_t count) {
VkCommandBufferAllocateInfo cmdbuf_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = gpu->graphics_command_pool(),
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = count,
};
std::vector<VkCommandBuffer> cmd_bufs(count);
if(vkAllocateCommandBuffers(gpu->dev(), &cmdbuf_info, cmd_bufs.data())) {
throw std::runtime_error("Failed to create command buffer");
}
return cmd_bufs;
}
void Allocator::collect_resources(const std::vector<TaskInfo>& tasks) {
m_resources.resize(num_buffers());
for(auto& task : tasks) {
for(auto& output : task.color_outputs()) {
if(m_resources[0].find(output.name()) == m_resources[0].end()) {
for(uint32_t i = 0; i < num_buffers(); i++) {
auto resource = allocate_image_resources(output, true);
m_resources[i].insert({output.name(), resource});
}
}
}
if(task.depth_output().has_value()) {
auto& depth_output = task.depth_output().value();
if(m_resources[0].find(depth_output.name()) == m_resources[0].end()) {
for(uint32_t i = 0; i < num_buffers(); i++) {
auto resource = allocate_image_resources(depth_output, false);
m_resources[i].insert({depth_output.name(), resource});
}
}
}
}
}
std::map<std::string, uint32_t> count_resource_writes(const std::vector<TaskInfo>& tasks) {
std::map<std::string, uint32_t> resource_count_down;
for(auto& task : tasks) {
for(auto& output : task.color_outputs()) {
if(resource_count_down.find(output.name()) == resource_count_down.end()) {
resource_count_down[output.name()] = 1;
} else {
resource_count_down[output.name()]++;
}
}
if(task.depth_output().has_value()) {
auto& depth_output = task.depth_output().value();
if(resource_count_down.find(depth_output.name()) == resource_count_down.end()) {
resource_count_down[depth_output.name()] = 1;
} else {
resource_count_down[depth_output.name()]++;
}
}
}
return resource_count_down;
}
void Allocator::prepare_renderpasses(const std::vector<TaskInfo>& tasks) {
std::map<std::string, uint32_t> resource_count_down = count_resource_writes(tasks);
std::set<std::string> cleared_resources;
// prepare render passes
for(auto& task : tasks) {
auto rp = allocate_renderpass(task, resource_count_down, cleared_resources);
m_renderpasses.insert({task.name(), rp});
}
}
RenderGraphBuffer Allocator::allocate_buffer(const GraphAllocationInfo& info, uint32_t buffer_idx) {
int cmdbuf_idx = 0;
/* std::vector<RenderGraphCommandBuffer> command_buffers;
for(auto& cmdbuf : info.command_buffers) {
std::vector<VkCommandBuffer> cmdbufs(info.output_chain.count());
std::vector<Task> tasks;
for(uint32_t rp_idx = cmdbuf.first_task_idx;
rp_idx < cmdbuf.first_task_idx + cmdbuf.num_tasks;
rp_idx++
) {
const TaskInfo* rp = &m_tasks[rp_idx];
if(rp->type() == TaskType::GRAPHICS_TASK) {
std::vector<VkFramebuffer> framebuffers(info.output_chain.count());
for(uint32_t image_idx = 0; image_idx < info.output_chain.count(); image_idx++) {
auto fb = create_framebuffer(*rp, m_renderpasses[rp->name()], image_idx);
framebuffers[image_idx] = fb;
cmdbufs[image_idx] = allocate_command_buffers(info.gpu, 1)[0];
}
tasks.push_back({
.pDefinition = *rp,
.render_pass = m_renderpasses[rp->name()],
.framebuffer = framebuffers,
.extent = info.output_chain.extent()
});
} else if(rp->type() == TaskType::COMPUTE_TASK) {
for(uint32_t image_idx = 0; image_idx < info.output_chain.count(); image_idx++) {
cmdbufs[image_idx] = allocate_command_buffers(info.gpu, 1)[0];
}
tasks.push_back({
.pDefinition = *rp,
.render_pass = VK_NULL_HANDLE,
.framebuffer = {},
.extent = info.output_chain.extent()
});
} else {
throw std::runtime_error("Unsupported task type");
}
}
command_buffers.push_back({
.command_buffers = cmdbufs,
.render_passes = tasks,
.signal = create_semaphore(info.gpu),
.dependencies = cmdbuf.wait_signals_idx
}); */
}
// RenderGraphBuffer buffer(command_buffers, m_resources[buffer_idx]);
// return buffer;
// }
}
@@ -0,0 +1,178 @@
#include "RenderGraphBuffer.hpp"
#include <iterator>
#include <unordered_map>
#include <iostream>
#include <algorithm>
std::vector<VkCommandBuffer> allocate_cmdbufs(const Gpu* gpu, uint32_t count) {
VkCommandBufferAllocateInfo cmdbuf_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = gpu->graphics_command_pool(),
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = count,
};
std::vector<VkCommandBuffer> cmdbufs(cmdbuf_info.commandBufferCount);
if(vkAllocateCommandBuffers(gpu->dev(), &cmdbuf_info, cmdbufs.data())) {
throw std::runtime_error("Failed to create command buffer");
}
return cmdbufs;
}
namespace lft::rg {
std::vector<BatchOutput> create_batch_outputs(const Gpu* gpu, uint32_t count) {
auto cmdbufs = allocate_cmdbufs(gpu, count);
std::vector<BatchOutput> outputs;
std::transform(cmdbufs.begin(), cmdbufs.end(),
std::back_inserter(outputs), [](VkCommandBuffer cmdbuf) {
return BatchOutput(cmdbuf);
});
return outputs;
}
BatchOutput::BatchOutput(VkCommandBuffer cmdbuf) :
cmdbuf(cmdbuf),
is_recording_valid(false) {
}
Batch::Batch(std::vector<BatchOutput> outputs, VkSemaphore signal) :
outputs(outputs),
signal(signal) {
}
Batch& Batch::invalidate_recordings() {
for(auto& output : outputs) {
output.is_recording_valid = false;
}
return *this;
}
Batch& Batch::insert_task(uint32_t idx, Task& task) {
tasks.insert(tasks.begin() + idx, task);
invalidate_recordings();
return *this;
}
Batch& Batch::update_task(uint32_t idx, Task& task) {
tasks[idx] = task;
invalidate_recordings();
return *this;
}
Batch& Batch::remove_task(uint32_t idx) {
tasks.erase(tasks.begin() + idx);
invalidate_recordings();
return *this;
}
bool Batch::equals(const Batch& rhs) const {
if(tasks.size() != rhs.tasks.size()) {
return false;
}
for(uint32_t i = 0; i < tasks.size(); i++) {
if(!tasks[i].equals(rhs.tasks[i])) {
return false;
}
}
if(barriers != rhs.barriers) {
return false;
}
if(outputs.size() != rhs.outputs.size()) {
return false;
}
for(uint32_t i = 0; i < outputs.size(); i++) {
if(outputs[i].is_recording_valid != rhs.outputs[i].is_recording_valid) {
return false;
}
}
return true;
}
RenderGraphBuffer::RenderGraphBuffer(
const Gpu* gpu,
uint32_t index,
uint32_t num_outputs
) : m_gpu(gpu), m_index(index), m_final_semaphores(num_outputs) {
for(uint32_t i = 0; i < num_outputs; i++) {
m_final_semaphores[i] = m_gpu->create_semaphore();
}
}
Batch& RenderGraphBuffer::insert_batch(uint32_t idx, uint32_t num_outputs) {
m_batches.insert(m_batches.begin() + idx,
Batch(create_batch_outputs(m_gpu, num_outputs), m_gpu->create_semaphore()));
return m_batches[idx];
}
void RenderGraphBuffer::remove_batch(uint32_t idx) {
m_batches.erase(m_batches.begin() + idx);
}
bool is_buffer_resources_equal(
std::unordered_map<std::string, BufferResource> lhs,
std::unordered_map<std::string, BufferResource> rhs
) {
for(auto& value : rhs) {
if(!lhs.contains(value.first)) {
std::cout << "Missing buffer resource: " << value.first << std::endl;
return false;
}
}
return true;
}
bool is_image_resources_equal(
std::unordered_map<std::string, ImageResource> lhs,
std::unordered_map<std::string, ImageResource> rhs
) {
for(auto& value : rhs) {
if(!lhs.contains(value.first)) {
std::cout << "Missing image resource: " << value.first << std::endl;
return false;
}
}
return true;
}
bool RenderGraphBuffer::equals(const RenderGraphBuffer& other) const {
if(m_batches.size() != other.m_batches.size()) {
std::cout << "Some batches are missing" << std::endl;
return false;
}
for(uint32_t i = 0; i < m_batches.size(); i++) {
if(!m_batches[i].equals(other.m_batches[i])) {
std::cout << "Task queues are not the same" << std::endl;
return false;
}
}
// compare resources
/* if(!is_buffer_resources_equal(m_buffer_resources, other.m_buffer_resources)) {
return false;
} */
if(!is_image_resources_equal(m_image_resources, other.m_image_resources)) {
return false;
}
return true;
}
}
@@ -0,0 +1,804 @@
#include <algorithm>
#include <cstdio>
#include <format>
#include <ostream>
#include <iostream>
#include <queue>
#include <stdexcept>
#include <unordered_map>
#include <unordered_set>
#include <cstring>
#include "RenderGraphBuilder.hpp"
#include "AdjacencyMatrix.hpp"
#include "FramebufferBuilder.hpp"
#include "RenderGraph.hpp"
#include "RenderGraphBuffer.hpp"
#include "RenderPass.hpp"
#include "RenderPassLayout.hpp"
#include "Resource.hpp"
namespace lft::rg {
std::vector<std::string> get_task_names(
const std::vector<TaskInfo>& tasks
) {
std::vector<std::string> names;
std::transform(tasks.begin(), tasks.end(),
names.begin(),
[](const TaskInfo& i) {
return i.name();
});
return names;
}
std::unordered_map<std::string, uint32_t> count_resource_image_writes(const std::vector<TaskInfo>& tasks) {
std::unordered_map<std::string, uint32_t> resource_count_down;
for(auto& task : tasks) {
for(auto& color_output : task.color_outputs()) {
if(resource_count_down.find(color_output.name()) == resource_count_down.end()) {
resource_count_down[color_output.name()] = 1;
} else {
resource_count_down[color_output.name()]++;
}
}
if(task.depth_output().has_value()) {
auto& depth_output = task.depth_output().value();
if(resource_count_down.find(depth_output.name()) == resource_count_down.end()) {
resource_count_down[depth_output.name()] = 1;
} else {
resource_count_down[depth_output.name()]++;
}
}
}
return resource_count_down;
}
inline bool is_first_resource_write(
std::unordered_set<std::string>& cleared_resources,
const std::string& resource
) {
return !cleared_resources.contains(resource);
}
inline bool is_last_resource_write(
std::unordered_map<std::string, uint32_t>& resource_count_down,
const std::string& resource
) {
return resource_count_down[resource] == 1;
}
VkAttachmentDescription2 BuilderAllocator::create_attachment_description(
const ImageResourceDescription& definition,
bool is_first_write,
bool is_last_write
) {
VkAttachmentLoadOp load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
VkImageLayout initial_layout = VK_IMAGE_LAYOUT_UNDEFINED;
VkImageLayout final_layout = VK_IMAGE_LAYOUT_UNDEFINED;
VkImageLayout middle_layout = definition.is_color() ?
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL :
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkImageLayout last_layout =
(definition.name() == m_output_name ? m_output_chain.layout() :
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
// TODO: If the write is not last yet outputs to the output chain, the layout gets overwriten by middle_layout anyway
final_layout = is_last_write ?
(definition.name() == m_output_name ? m_output_chain.layout() : last_layout) :
middle_layout;
if(!is_first_write) {
load_op = VK_ATTACHMENT_LOAD_OP_LOAD;
initial_layout = definition.is_color() ?
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL :
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
} else {
load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
initial_layout = VK_IMAGE_LAYOUT_UNDEFINED;
}
// store op is dont care for the last one and store for every other
VkAttachmentStoreOp store_op = is_last_write && (!m_store_all_images) ?
VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
return {
.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2,
.format = definition.format(),
.samples = VK_SAMPLE_COUNT_1_BIT,
.loadOp = load_op,
.storeOp = store_op,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = initial_layout,
.finalLayout = final_layout
};
}
TaskRenderPass BuilderAllocator::allocate_renderpass(
const TaskInfo& task,
std::unordered_map<std::string, uint32_t>& resource_count_down,
std::unordered_set<std::string>& cleared_resources
) {
size_t num_attachments = task.color_outputs().size() + task.depth_output().has_value();
std::vector<VkAttachmentDescription2> descriptions(num_attachments);
std::vector<VkAttachmentReference2> references(num_attachments);
TaskRenderPassState state(task.color_outputs().size(), task.depth_output().has_value());
uint32_t i = 0;
for(auto& output : task.color_outputs()) {
bool is_first_write = is_first_resource_write(cleared_resources, output.name());
bool is_last_write = is_last_resource_write(resource_count_down, output.name());
if(is_first_write) {
state.set_resource_is_first(i);
} if(is_last_write) {
state.set_resource_is_last(i);
}
descriptions[i] = create_attachment_description(
output,
is_first_write,
is_last_write
);
// ignore sub passes, so one reference per attachment
references[i] = {
.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2,
.attachment = i,
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
};
i++;
}
if (task.depth_output().has_value()) {
bool is_first_write = is_first_resource_write(cleared_resources, task.depth_output()->name());
bool is_last_write = is_last_resource_write(resource_count_down, task.depth_output()->name());
if(is_first_write) {
state.set_resource_is_first(i);
} if(is_last_write) {
state.set_resource_is_last(i);
}
descriptions[i] = create_attachment_description(
task.depth_output().value(),
is_first_write, is_last_write);
references[i] = {
.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2,
.attachment = i,
.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
};
}
VkMemoryBarrier2KHR entryBarrier = {
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2_KHR,
.pNext = nullptr,
.srcStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT,
.srcAccessMask = 0,
.dstStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT,
.dstAccessMask = 0
};
const VkSubpassDependency2 subpass_dependencies[] = {
{
.sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2,
.pNext = &entryBarrier,
.srcSubpass = VK_SUBPASS_EXTERNAL,
.dstSubpass = 0,
.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT,
}
};
const VkSubpassDescription2 subpass = {
.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2,
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
.colorAttachmentCount = (uint32_t)references.size() - (task.depth_output().has_value()),
.pColorAttachments = references.data(),
.pDepthStencilAttachment = task.depth_output().has_value() ? &references[i] : nullptr,
};
VkRenderPassCreateInfo2 renderpass_info = {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2,
.attachmentCount = (uint32_t)descriptions.size(),
.pAttachments = descriptions.data(),
.subpassCount = 1,
.pSubpasses = &subpass,
.dependencyCount = 1,
.pDependencies = subpass_dependencies
};
VkRenderPass renderpass;
if(vkCreateRenderPass2KHR(m_gpu->dev(), &renderpass_info, nullptr, &renderpass)) {
throw std::runtime_error("Failed to create render pass");
}
VkDebugUtilsObjectNameInfoEXT render_pass_dbg_info = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
.objectType = VK_OBJECT_TYPE_RENDER_PASS,
.objectHandle = (uint64_t)renderpass,
.pObjectName = strdup(std::format("[RP] {}", task.name()).c_str()),
};
vkSetDebugUtilsObjectNameEXT(m_gpu->dev(), &render_pass_dbg_info);
return TaskRenderPass(renderpass, state);
}
ImageResource BuilderAllocator::allocate_image_resource(
const ImageResourceDescription& desc
) const {
MemoryAllocationInfo memory_info = {
.usage = MEMORY_USAGE_AUTO_PREFER_DEVICE
};
auto extent = get_extent(desc.extent());
std::println("Creating resource {} {}", extent.width, extent.height);
ImageCreateInfo image_info = {
.extent = extent,
.format = desc.format(),
.usage = VK_IMAGE_USAGE_SAMPLED_BIT |
(VkImageUsageFlags)(desc.is_color() ?
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT :
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT),
.aspectMask = (VkImageAspectFlags)(desc.is_color() ?
VK_IMAGE_ASPECT_COLOR_BIT :
VK_IMAGE_ASPECT_DEPTH_BIT),
.arrayLayers = 1,
.mipLevels = 1,
};
Image image = {};
m_gpu->memory()->create_image(&image_info, &memory_info, &image);
ImageView view = {};
view = image.create_view(m_gpu, desc.format(), {
.aspectMask = image_info.aspectMask,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
});
return ImageResource(image.img, view.view, image_info.extent);
}
BufferResource BuilderAllocator::allocate_buffer_resource(
const BufferResourceDescription& desc
) const {
}
ImageResourceDescription BuilderAllocator::correct_resource_description(ImageResourceDescription desc) {
// correct extent
VkExtent2D extent = desc.extent();
if(extent.width == 0) {
extent.width = m_output_chain.extent().width;
}
if(extent.height == 0) {
extent.height = m_output_chain.extent().height;
}
desc.set_extent(extent);
return desc;
}
ImageView BuilderAllocator::get_attachment(
const ImageResourceDescription& desc,
RenderGraphBuffer* pBuffer,
uint32_t output_idx
) {
if(desc.name() == m_output_name) {
return m_output_chain.views()[output_idx];
}
auto extent = get_extent(desc.extent());
auto attachment = pBuffer->get_image_resource(desc.name());
if(!attachment.has_value() || attachment.value()->extent.width != extent.width ||
attachment.value()->extent.height != extent.height) {
auto resource = allocate_image_resource(desc);
VkDebugUtilsObjectNameInfoEXT img_dbg_info = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
.objectType = VK_OBJECT_TYPE_IMAGE,
.objectHandle = (uint64_t)resource.image,
.pObjectName = strdup(std::format("[IMG:buf({}):out({})]{}", pBuffer->index(), output_idx, desc.name()).c_str()),
};
vkSetDebugUtilsObjectNameEXT(m_gpu->dev(), &img_dbg_info);
VkDebugUtilsObjectNameInfoEXT img_view_dbg_info = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
.objectType = VK_OBJECT_TYPE_IMAGE_VIEW,
.objectHandle = (uint64_t)resource.image_view,
.pObjectName = strdup(std::format("[IMG_VIEW:buf({}):out({})]{}", pBuffer->index(), output_idx, desc.name()).c_str()),
};
vkSetDebugUtilsObjectNameEXT(m_gpu->dev(), &img_view_dbg_info);
pBuffer->put_image_resource(desc.name(), resource);
return resource.image_view;
}
return attachment.value()->image_view;
}
VkFramebuffer BuilderAllocator::create_framebuffer(
const TaskInfo& task_info,
VkRenderPass renderpass,
RenderGraphBuffer *pBuffer,
uint32_t output_idx
) {
uint32_t num_attachments = task_info.color_outputs().size() +
task_info.depth_output().has_value();
std::vector<ImageView> attachments(num_attachments);
uint32_t i = 0;
for(auto& output : task_info.color_outputs()) {
attachments[i++] = get_attachment(output, pBuffer, output_idx).view;
}
if (task_info.depth_output().has_value()) {
attachments[i++] = get_attachment(task_info.depth_output().value(), pBuffer, output_idx);
}
if(i != attachments.size()) {
throw std::runtime_error("Framebuffer attachments size mismatch");
}
auto extent = get_extent_for_task(task_info);
std::println("Creating framebuffer of size: {} {}", extent.width, extent.height);
auto fb = FramebufferBuilder(renderpass, get_extent_for_task(task_info), attachments)
.build(m_gpu);
VkDebugUtilsObjectNameInfoEXT render_pass_dbg_info = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
.objectType = VK_OBJECT_TYPE_FRAMEBUFFER,
.objectHandle = (uint64_t)fb.framebuffer,
.pObjectName = strdup(std::format("[FB:buf({}):out({})]{}", pBuffer->index(), output_idx, task_info.name()).c_str()),
};
vkSetDebugUtilsObjectNameEXT(m_gpu->dev(), &render_pass_dbg_info);
return fb.framebuffer;
}
Task BuilderAllocator::create_graphics_task(
const TaskInfo& task_info,
RenderGraphBuffer* pBuffer,
TaskRenderPass render_pass
) {
Task task = {.pDefinition = task_info, .render_pass = render_pass};
// create framebuffer for each image in output chain
std::vector<VkFramebuffer> framebuffers(m_output_chain.count());
for(
uint32_t image_idx = 0;
image_idx < m_output_chain.count();
image_idx++
) {
auto fb = create_framebuffer(task_info, task.render_pass.render_pass, pBuffer, image_idx);
framebuffers[image_idx] = fb;
}
task.framebuffer = framebuffers;
task.extent = get_extent_for_task(task_info);
return task;
}
Task BuilderAllocator::create_compute_task(
const TaskInfo& task_info,
RenderGraphBuffer* pBuffer
) {
Task task = {.pDefinition = task_info};
return task;
}
Task BuilderAllocator::create_task(
const TaskInfo& task_info,
RenderGraphBuffer* pBuffer,
std::unordered_set<std::string>& cleared_resources,
std::unordered_map<std::string, uint32_t>& resource_count_down
) {
if(task_info.type() == GRAPHICS_TASK) {
auto render_pass = allocate_renderpass(task_info, resource_count_down, cleared_resources);
return create_graphics_task(task_info, pBuffer, render_pass);
} else if(task_info.type() == COMPUTE_TASK) {
return create_compute_task(task_info, pBuffer);
} else {
throw std::runtime_error(std::format("Unknown task type for {}", task_info.name()));
}
}
bool is_render_pass_updated(const Task& old_task,
std::unordered_set<std::string> cleared_resources,
std::unordered_map<std::string, uint32_t> resource_count_down
) {
uint32_t i = 0;
for(; i < old_task.pDefinition.m_color_outputs.size(); i++) {
if(old_task.render_pass.state.is_resource_first(i) != is_first_resource_write(cleared_resources, old_task.pDefinition.m_color_outputs[i].name())) {
return true;
} else if(old_task.render_pass.state.is_resource_last(i) != is_last_resource_write(resource_count_down, old_task.pDefinition.m_color_outputs[i].name())) {
return true;
}
}
if(old_task.pDefinition.depth_output().has_value()) {
if(old_task.render_pass.state.is_resource_first(i) != is_first_resource_write(cleared_resources, old_task.pDefinition.depth_output()->name())) {
return true;
} else if(old_task.render_pass.state.is_resource_last(i) != is_last_resource_write(resource_count_down, old_task.pDefinition.depth_output()->name())) {
return true;
}
}
return false;
}
void BuilderAllocator::update_task_buffer(const Task& task, const RenderGraphBuffer* pBuffer) {
TaskBuildInfo task_build_info(
m_gpu,
pBuffer->index(),
num_buffers(),
get_viewport(),
task.render_pass.render_pass,
pBuffer->m_image_resources
);
task.pDefinition.build_func()(task_build_info, task.pDefinition.m_pContext);
}
void BuilderAllocator::update_task_queue(
RenderGraphBuffer* pBuffer,
const std::vector<TaskInfo>& task_infos
) {
std::unordered_map<std::string, uint32_t> resource_count_down = count_resource_image_writes(task_infos);
std::unordered_set<std::string> cleared_resources;
uint32_t cmdbuf_idx = 0;
int32_t remaining_tasks_in_cmdbuf = pBuffer->num_batches() == 0 ? 0 : pBuffer->batch(0).tasks.size();
// lookahead method
uint32_t next_task_idx = 0;
uint32_t next_batch_idx = 0;
uint32_t queue_idx = 0;
while(next_batch_idx < pBuffer->num_batches()) {
Batch* next_batch = &pBuffer->batch(next_batch_idx);
Task* next_task = &next_batch->tasks[next_task_idx];
if(queue_idx >= task_infos.size()) {
next_batch->remove_task(next_task_idx);
next_task_idx--;
if(next_batch->tasks.size() == 0) {
pBuffer->remove_batch(next_batch_idx);
next_task_idx = 0;
continue;
}
} else {
// if next_task and next_queue_item does not match, we found error
if(task_infos[queue_idx].name() != next_task->pDefinition.name()) {
// if task that should be there is updated, it means it was inserted
// if it would be updated, there would not be a name mismatch
if(is_task_updated(task_infos[queue_idx].name())) {
auto task = create_task(task_infos[queue_idx], pBuffer, cleared_resources, resource_count_down);
// insert batch instead of new
pBuffer->insert_batch(next_batch_idx, m_output_chain.count())
// insert the new task
.insert_task(0, task);
// notify about update
update_task_buffer(task, pBuffer);
// now the next_task and task_infos[queue_idx] should match, let us move on to the next
}
// if task that should be there is not updated (it was already in the queue)
// but task that is actually there is updated, we should remove the current task,
// because the wanted task is next
else if(is_task_updated(next_task->pDefinition.name())) {
next_batch->remove_task(next_task_idx);
if(next_batch->tasks.size() == 0) {
pBuffer->remove_batch(next_batch_idx);
continue;
}
}
}
// if tasks match, but task is marked as updated, we should rebuild the task
else if(is_task_updated(task_infos[queue_idx].name()) ||
is_render_pass_updated(*next_task, cleared_resources, resource_count_down)
) {
std::println("Updating");
auto task = create_task(task_infos[queue_idx], pBuffer, cleared_resources, resource_count_down);
next_batch->update_task(next_task_idx, task);
}
}
for(auto& output : task_infos[queue_idx].color_outputs()) {
resource_count_down[output.name()]--;
cleared_resources.insert(output.name());
}
next_batch = &pBuffer->batch(next_batch_idx);
next_task_idx++;
if(next_task_idx == next_batch->tasks.size()) {
next_task_idx = 0;
next_batch_idx++;
}
queue_idx++;
}
for(; queue_idx < task_infos.size(); queue_idx++) {
auto task = create_task(task_infos[queue_idx], pBuffer, cleared_resources, resource_count_down);
pBuffer->insert_batch(pBuffer->num_batches(), m_output_chain.count())
.insert_task(0, task);
update_task_buffer(task, pBuffer);
for(auto& output : task_infos[queue_idx].color_outputs()) {
resource_count_down[output.name()]--;
cleared_resources.insert(output.name());
}
}
}
RenderGraph BuilderAllocator::allocate(
std::vector<TaskInfo>& task_infos,
AdjacencyMatrix *dependencies
) {
std::println("Task count: {}", task_infos.size());
// for(TaskInfo& task_info : task_infos) {
// if(task_info.m_extent.width == 0.0f) {
// task_info.m_extent.width = m_output_chain.extent().width;
// }
//
// if(task_info.m_extent.height == 0.0f) {
// task_info.m_extent.height = m_output_chain.extent().height;
// }
// }
std::vector<RenderGraphBuffer*> buffers(num_buffers());
for(uint32_t buffer_idx = 0; buffer_idx < num_buffers(); buffer_idx++) {
update_task_queue(&m_buffers[buffer_idx], task_infos);
buffers[buffer_idx] = &m_buffers[buffer_idx];
}
m_updated_tasks.clear();
return RenderGraph(m_gpu, m_output_name, buffers, dependencies);
}
bool BuilderAllocator::equals(const BuilderAllocator& other) const {
if(m_gpu != other.m_gpu) {
std::cout << "GPU mismatch" << std::endl;
return false;
}
if(m_output_name != other.m_output_name) {
std::cout << "Output name mismatch" << std::endl;
return false;
}
if(m_buffers.size() != other.m_buffers.size()) {
std::cout << std::format("Buffer count mismatch: {} != {}", m_buffers.size(), other.m_buffers.size()) << std::endl;
return false;
}
for(uint32_t i = 0; i < m_buffers.size(); i++) {
if(!m_buffers[i].equals(other.m_buffers[i])) {
std::cout << "Buffer mismatch at index " << i << std::endl;
return false;
}
}
if(!std::equal(m_updated_tasks.begin(), m_updated_tasks.end(),
other.m_updated_tasks.begin(), other.m_updated_tasks.end())) {
std::cout << "Updated tasks mismatch" << std::endl;
return false;
}
return true;
}
#pragma region TOPOLOGY SORT
bool is_depending_on(const TaskInfo& task, const TaskInfo& depends_on) {
return std::find_if(task.dependencies().begin(), task.dependencies().end(),
[&](const auto& dependency) {
if(dependency == depends_on.m_name) {
return true;
}
if(depends_on.m_depth_output.has_value() &&
dependency == depends_on.depth_output()->name()) {
return true;
}
return (std::find_if(depends_on.color_outputs().begin(),
depends_on.color_outputs().end(),
[&](const auto& output) {
return output.name() == dependency;
})
!= depends_on.color_outputs().end()) ||
std::find_if(depends_on.buffer_outputs().begin(),
depends_on.buffer_outputs().end(),
[&](const auto& output) {
return output.name() == dependency;
}) != depends_on.buffer_outputs().end();
}) != task.m_dependencies.end();
}
bool writes_to(const TaskInfo& task, const std::string& name) {
if(std::find_if(task.m_color_outputs.begin(), task.m_color_outputs.end(),
[name](const ImageResourceDescription& resource) {return resource.name() == name;}) != task.m_color_outputs.end()) {
return true;
}
if(std::find_if(task.buffer_outputs().begin(), task.buffer_outputs().end(),
[name](const BufferResourceDescription& resource) {return resource.name() == name;}) != task.buffer_outputs().end()) {
return true;
}
return task.m_depth_output.has_value() && task.m_depth_output->name() == name;
}
std::vector<uint32_t> get_adjacent_idxs(
std::vector<TaskInfo>* tasks,
uint32_t adjacent_of
) {
auto& task = (*tasks)[adjacent_of];
std::vector<uint32_t> idxs;
uint32_t idx = 0;
for(auto dependency : *tasks) {
if(dependency.name() == task.name()) {
continue;
}
if(is_depending_on(dependency, task)) {
idxs.push_back(idx);
}
idx++;
}
return idxs;
}
std::vector<std::string> collect_task_names(std::vector<TaskInfo>& tasks) {
std::vector<std::string> names(tasks.size());
std::transform(tasks.begin(), tasks.end(), names.begin(),
[](const TaskInfo& item) { return item.name(); }
);
return names;
}
bool has_common_write(const TaskInfo& task1, const TaskInfo& task2) {
for(auto& write : task1.buffer_outputs()) {
for(auto& write2 : task2.buffer_outputs()) {
if(write2.name() == write.name()) {
return true;
}
}
}
if(task1.depth_output().has_value() && task2.depth_output().has_value() &&
task1.depth_output()->name() == task2.depth_output()->name()) {
return true;
}
for(auto& write : task1.color_outputs()) {
for(auto& write2 : task2.color_outputs()) {
if(write2.name() == write.name()) {
return true;
}
}
}
return false;
}
AdjacencyMatrix* build_adj_matrix(std::vector<TaskInfo>& tasks, const std::string& output_name) {
auto names = collect_task_names(tasks);
names.push_back(output_name);
AdjacencyMatrix* matrix = new AdjacencyMatrix(names);
for(uint32_t y = 0; y < tasks.size(); y++) {
for(uint32_t x = 0; x < tasks.size(); x++) {
if(x == y) {
continue;
}
if(is_depending_on(tasks[y], tasks[x])) {
matrix->set(x, y);
} else {
if(has_common_write(tasks[y], tasks[x]) &&
matrix->get(y, x) == false &&
matrix->get(x, y) == false) {
matrix->set(y, x);
}
}
}
if(writes_to(tasks[y], output_name)) {
matrix->set(y, tasks.size());
}
}
matrix->transitive_reduction();
return matrix;
}
std::vector<TaskInfo> topology_sort(std::vector<TaskInfo>& tasks, const std::string& output_name) {
auto matrix = build_adj_matrix(tasks, output_name);
// get final tasks
std::queue<uint32_t> queue;
std::vector<bool> done(tasks.size(), false);
std::vector<TaskInfo> result;
auto last = matrix->get_dependencies(tasks.size());
for(auto& i : last) {
queue.push(i);
}
while(!queue.empty()) {
auto item = queue.front();
queue.pop();
if(done[item]) {
continue;
}
result.push_back(tasks[item]);
auto dependencies = matrix->get_dependencies(item);
while(dependencies.size() == 1) {
if(done[item]) break;
done[item] = true;
item = dependencies[0];
dependencies = matrix->get_dependencies(item);
result.push_back(tasks[item]);
}
for(auto& dependency : dependencies) {
queue.push(dependency);
}
}
std::reverse(result.begin(), result.end());
return result;
}
#pragma endregion
RenderGraph Builder::build() {
auto sorted_tasks = topology_sort(m_tasks, m_output_name);
m_allocator.set_store_all_images(m_store_all_images);
auto dependencies = build_adj_matrix(m_tasks, m_output_name);
return m_allocator.allocate(sorted_tasks, dependencies);
}
}
+41
View File
@@ -0,0 +1,41 @@
project(loft_render_graph_tests)
find_package(Vulkan QUIET)
find_package(SDL2 REQUIRED)
find_package(Catch2 REQUIRED)
set(LIBS
loft_render_graph
loft_base
loft_common
loft_window
${SDL2_LIBRARIES}
volk)
set(FILES
# ./DependencyGraphTests.cpp
./RenderGraphBuilderTests.cpp
./TopologicalSortTests.cpp
)
add_library(render_graph_unit_tests_sources OBJECT Mock.cpp ${FILES})
target_link_libraries(render_graph_unit_tests_sources
Catch2::Catch2WithMain
${LIBS}
)
add_executable(render_graph_unit_tests)
target_link_libraries(render_graph_unit_tests
PRIVATE
${LIBS}
render_graph_unit_tests_sources
Catch2::Catch2WithMain
)
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
include(CTest)
include(Catch)
catch_discover_tests(render_graph_unit_tests)
@@ -0,0 +1,40 @@
#include "AdjacencyMatrix.hpp"
#include <set>
void test_color_dependency() {
AdjacencyMatrix adj({"task1", "task2", "final"});
adj.set("task1", "task2");
adj.set("task2", "final");
auto deps = adj.get_dependencies("final");
ASSERT(deps == std::vector<std::string>({"task2"}));
deps = adj.get_dependencies("task2");
ASSERT(deps == std::vector<std::string>({"task1"}));
deps = adj.get_dependencies("task1");
ASSERT(deps == std::vector<std::string>({}));
}
void test_color_dependency2() {
AdjacencyMatrix adj({"task1", "task2", "final"});
adj.set("task1", "final");
adj.set("task2", "final");
auto deps = adj.get_dependencies("final");
ASSERT(std::set<std::string>(deps.begin(), deps.end()) == std::set<std::string>({"task1", "task2"}));
deps = adj.get_dependencies("task2");
ASSERT(deps == std::vector<std::string>({}));
deps = adj.get_dependencies("task1");
ASSERT(deps == std::vector<std::string>({}));
}
int main() {
test_color_dependency();
test_color_dependency2();
return 0;
}
+72
View File
@@ -0,0 +1,72 @@
#include "Mock.hpp"
void lft_dbg_callback(lft::dbg::LogMessageSeverity severity,
lft::dbg::LogMessageType type,
const char *__restrict format,
va_list args) {
}
std::unique_ptr<Gpu> create_mock_gpu() {
auto instance = std::make_unique<const Instance>(
"loft", "loft",
std::vector<std::string>({VK_KHR_SURFACE_EXTENSION_NAME}),
std::vector<std::string>(),
lft_dbg_callback);
volkLoadInstance(instance->instance());
return std::make_unique<Gpu>(instance.get(), std::nullopt);
}
ImageChain create_mock_image_chain(
const Gpu* gpu,
uint32_t num_images,
VkExtent2D extent,
VkFormat format
) {
std::vector<ImageView> images(num_images);
for(uint32_t i = 0; i < num_images; i++) {
MemoryAllocationInfo memory_info = {
.usage = MEMORY_USAGE_AUTO_PREFER_DEVICE
};
ImageCreateInfo image_info = {
.extent = extent,
.format = format,
.usage = VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.arrayLayers = 1,
.mipLevels = 1,
};
Image image = {};
gpu->memory()->create_image(&image_info, &memory_info, &image);
ImageView view = {};
view = image.create_view(gpu, format, {
.aspectMask = image_info.aspectMask,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
});
images[i] = view;
}
return ImageChain(format, extent, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, images);
}
lft::rg::RenderTaskBuilder create_empty_task(const std::string& name) {
return lft::rg::render_task<EmptyContext>(
"task1", new EmptyContext,
[](const lft::rg::TaskBuildInfo& info, EmptyContext* ctx) {},
[](const lft::rg::TaskRecordInfo& info, EmptyContext* ctx) {}
);
}
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#ifndef MOCK_DEFINED
#define MOCK_DEFINED 1
#include <memory>
#include "Gpu.hpp"
#include "ImageChain.hpp"
#include "RenderGraphBuilder.hpp"
std::unique_ptr<Gpu> create_mock_gpu();
ImageChain create_mock_image_chain(
const Gpu* gpu,
uint32_t num_images,
VkExtent2D extent,
VkFormat format
);
struct EmptyContext { };
lft::rg::RenderTaskBuilder create_empty_task(const std::string& name);
#endif
@@ -0,0 +1,613 @@
#pragma once
#include <catch2/catch_test_macros.hpp>
#define private public
#include "Mock.hpp"
const VkFormat FMT = VK_FORMAT_R8G8B8A8_UNORM;
const VkExtent2D EXTENT = {
.width = 1024,
.height = 1024
};
struct Struct {
};
TEST_CASE("BothFinalOutputAndColorOutput", "[rg]") {
auto gpu = create_mock_gpu();
auto image_chain = create_mock_image_chain(gpu.get(), 1, EXTENT, FMT);
lft::rg::Builder builder(
gpu.get(), image_chain, "output"
);
EmptyContext ctx;
auto task1 = create_empty_task("task1")
.set_output_to_final()
.add_color_output("output", FMT)
.build();
builder.add_task(task1);
auto rg = builder.build();
REQUIRE(rg.m_buffers[0]->num_batches() == 1);
REQUIRE(rg.m_buffers[0]->batch(0).tasks[0].pDefinition.name() == "task1");
}
void test_render_graph_push() {
auto gpu = create_mock_gpu();
VkImageView a;
VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
ImageChain image_chain = create_mock_image_chain(gpu.get(), 1, EXTENT, FMT);
lft::rg::Builder builder(gpu.get(), image_chain, "output");
Struct data = {};
auto task1 = lft::rg::render_task<Struct>(
"task1", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("output", FMT, EXTENT, {})
.add_color_output("resource1", FMT, EXTENT, {})
.set_extent(EXTENT)
.build();
builder.add_task(task1);
std::cout << "First build" << std::endl;
auto rg = builder.build();
ASSERT(rg.m_buffers[0]->num_batches() == 1);
ASSERT(rg.m_buffers[0]->batch(0).tasks.size() == 1);
ASSERT(rg.m_buffers[0]->batch(0).tasks[0].pDefinition.equals(task1));
auto task2 = create_empty_task("task2")
.add_color_output("output", FMT, EXTENT, {})
.add_dependency("resource1")
.set_extent(EXTENT)
.build();
builder.add_task(task2);
std::cout << "Second build" << std::endl;
rg = builder.build();
ASSERT(rg.m_buffers[0]->num_batches() == 2);
ASSERT(rg.m_buffers[0]->batch(0).tasks.size() == 1);
ASSERT(rg.m_buffers[0]->batch(0).tasks[0].pDefinition.equals(task1));
ASSERT(rg.m_buffers[0]->batch(1).tasks.size() == 1);
ASSERT(rg.m_buffers[0]->batch(1).tasks[0].pDefinition.equals(task2));
auto deps1 = rg.m_dependency_matrix->get_dependencies(1);
ASSERT(deps1.size() == 1);
ASSERT(deps1[0] == 0);
auto deps2 = rg.m_dependency_matrix->get_dependencies("task2");
ASSERT(deps2.size() == 1);
ASSERT(deps2[0] == "task1");
lft::rg::Builder builder2(gpu.get(), image_chain, "output");
builder2.add_task(task1);
builder2.add_task(task2);
std::cout << "Compare build" << std::endl;
builder2.build();
ASSERT(builder.m_allocator.equals(builder2.m_allocator));
ASSERT(builder2.m_allocator.equals(builder.m_allocator));
}
void test_render_graph_insert_begin() {
VkExtent2D extent = {
.width = 1024,
.height = 1024
};
auto gpu = create_mock_gpu();
VkImageView a;
VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
ImageChain image_chain = create_mock_image_chain(gpu.get(), 1, extent, fmt);
lft::rg::Builder builder(gpu.get(), image_chain, "output");
Struct data = {};
auto task1 = lft::rg::render_task<Struct>(
"task1", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("resource1", fmt, extent, {});
auto task2 = lft::rg::render_task<Struct>(
"task2", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("resource2", fmt, extent, {});
auto task3 = lft::rg::render_task<Struct>(
"task3", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("output", fmt, extent, {})
.add_dependency("resource2");
builder.add_task(task2.build());
builder.add_task(task3.build());
builder.build();
task2.add_dependency("resource1");
builder.add_task(task2.build());
builder.add_task(task1.build());
lft::rg::RenderGraph rg = builder.build();
ASSERT(rg.buffer(0).num_batches() == 3);
ASSERT(rg.buffer(0).batch(0).tasks[0].pDefinition.name() == "task1");
ASSERT(rg.buffer(0).batch(1).tasks[0].pDefinition.name() == "task2");
ASSERT(rg.buffer(0).batch(2).tasks[0].pDefinition.name() == "task3");
}
void test_render_graph_insert_middle() {
VkExtent2D extent = {
.width = 1024,
.height = 1024
};
auto gpu = create_mock_gpu();
VkImageView a;
VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
ImageChain image_chain = create_mock_image_chain(gpu.get(), 1, extent, fmt);
lft::rg::Builder builder(gpu.get(), image_chain, "output");
Struct data = {};
auto task1 = lft::rg::render_task<Struct>(
"task1", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("resource1", fmt, extent, {});
auto task2 = lft::rg::render_task<Struct>(
"task2", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("resource2", fmt, extent, {})
.add_dependency("resource1");
auto task3 = lft::rg::render_task<Struct>(
"task3", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("output", fmt, extent, {})
.add_dependency("resource1");
builder.add_task(task1.build());
builder.add_task(task3.build());
builder.build();
task3.add_dependency("resource2");
builder.add_task(task3.build());
builder.add_task(task2.build());
lft::rg::RenderGraph rg = builder.build();
ASSERT(rg.buffer(0).num_batches() == 3);
ASSERT(rg.buffer(0).batch(0).tasks[0].pDefinition.name() == "task1");
ASSERT(rg.buffer(0).batch(1).tasks[0].pDefinition.name() == "task2");
ASSERT(rg.buffer(0).batch(2).tasks[0].pDefinition.name() == "task3");
}
void test_render_graph_extent() {
VkExtent2D extent = {
.width = 1024,
.height = 1024
};
auto gpu = create_mock_gpu();
VkImageView a;
VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
ImageChain image_chain = create_mock_image_chain(gpu.get(), 1, extent, fmt);
lft::rg::Builder builder(gpu.get(), image_chain, "output");
Struct data = {};
auto task1 = lft::rg::render_task<Struct>(
"task1", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("output", fmt, extent, {})
.add_color_output("resource1", fmt, extent, {})
.build();
builder.add_task(task1);
auto task2 = lft::rg::render_task<Struct>(
"task2", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("output", fmt, extent, {})
.add_dependency("resource1")
.build();
builder.add_task(task2);
lft::rg::RenderGraph rg = builder.build();
ASSERT(rg.buffer(0).num_batches() == 2);
// equals tests the extent
ASSERT(!rg.buffer(0).batch(0).tasks[0].pDefinition.equals(task1));
ASSERT(!rg.buffer(0).batch(1).tasks[0].pDefinition.equals(task2));
ASSERT(rg.buffer(0).batch(0).tasks[0].extent.width == extent.width &&
rg.buffer(0).batch(0).tasks[0].extent.height == extent.height);
ASSERT(rg.buffer(0).batch(1).tasks[0].extent.width == extent.width &&
rg.buffer(0).batch(1).tasks[0].extent.height == extent.height);
}
void test_render_graph_update_renderpass() {
std::cout << "Running update test" << std::endl;
VkExtent2D extent = {
.width = 1024,
.height = 1024
};
auto gpu = create_mock_gpu();
VkImageView a;
VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
ImageChain image_chain = create_mock_image_chain(gpu.get(), 1, extent, fmt);
lft::rg::Builder builder(gpu.get(), image_chain, "output");
Struct data = {};
auto task1 = lft::rg::render_task<Struct>(
"task1", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("output", fmt, extent, {})
.add_color_output("resource1", fmt, extent, {})
.set_extent(extent)
.build();
builder.add_task(task1);
auto task2 = lft::rg::render_task<Struct>(
"task2", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("output", fmt, extent, {})
.add_dependency("resource1")
.set_extent(extent)
.build();
builder.add_task(task2);
lft::rg::RenderGraph rg = builder.build();
// update
task1.add_color_output("resource2", fmt, extent, {});
builder.add_task(task1);
VkRenderPass previous_rp = rg.buffer(0).batch(0).tasks[0].render_pass.render_pass;
auto previous_fb = rg.buffer(0).batch(0).tasks[0].framebuffer;
builder.build();
ASSERT(rg.buffer(0).num_batches() == 2);
auto _task1 = rg.buffer(0).batch(0).tasks[0];
auto _task2 = rg.buffer(0).batch(1).tasks[0];
// equals tests the extent
ASSERT(_task1.pDefinition.equals(task1));
ASSERT(_task2.pDefinition.equals(task2));
ASSERT(_task1.render_pass.render_pass != previous_rp);
ASSERT(_task1.framebuffer != previous_fb);
}
void test_render_graph_remove() {
std::cout << "Running update test" << std::endl;
VkExtent2D extent = {
.width = 1024,
.height = 1024
};
auto gpu = create_mock_gpu();
VkImageView a;
VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
ImageChain image_chain = create_mock_image_chain(gpu.get(), 1, extent, fmt);
lft::rg::Builder builder(gpu.get(), image_chain, "output");
Struct data = {};
auto task1 = lft::rg::render_task<Struct>(
"task1", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("output", fmt, extent, {})
.add_color_output("resource1", fmt, extent, {})
.add_color_output("resource2", fmt, extent, {})
.set_extent(extent)
.build();
builder.add_task(task1);
auto task2 = lft::rg::render_task<Struct>(
"task2", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("output", fmt, extent, {})
.add_dependency("resource1")
.set_extent(extent)
.build();
builder.add_task(task1);
builder.add_task(task2);
auto rg = builder.build();
ASSERT(rg.buffer(0).num_batches() == 2);
ASSERT(rg.buffer(0).batch(0).tasks.size() == 1);
ASSERT(rg.buffer(0).batch(1).tasks.size() == 1);
builder.remove_task("task2");
rg = builder.build();
ASSERT(rg.buffer(0).num_batches() == 1);
ASSERT(rg.buffer(0).batch(0).tasks.size() == 1);
}
/*
* Attempts to add task, build the render graph, remove the task again and rebuild repeatedly.
*/
void test_render_graph_remove_and_add() {
std::cout << "Running update test" << std::endl;
VkExtent2D extent = {
.width = 1024,
.height = 1024
};
auto gpu = create_mock_gpu();
VkImageView a;
VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
ImageChain image_chain = create_mock_image_chain(gpu.get(), 4, extent, fmt);
lft::rg::Builder builder(gpu.get(), image_chain, "output");
Struct data = {};
auto task1 = lft::rg::render_task<Struct>(
"task1", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("output", fmt, extent, {})
.add_color_output("resource1", fmt, extent, {})
.add_color_output("resource2", fmt, extent, {})
.set_extent(extent)
.build();
builder.add_task(task1);
auto task2 = lft::rg::render_task<Struct>(
"task2", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("output", fmt, extent, {})
.add_dependency("resource1")
.set_extent(extent)
.build();
builder.add_task(task1);
builder.add_task(task2);
auto rg = builder.build();
ASSERT(rg.buffer(0).num_batches() == 2);
ASSERT(rg.buffer(0).batch(0).tasks.size() == 1);
ASSERT(rg.buffer(0).batch(1).tasks.size() == 1);
for(uint32_t i = 0; i < 10; i++) {
builder.remove_task("task2");
rg = builder.build();
ASSERT(rg.buffer(0).num_batches() == 1);
ASSERT(rg.buffer(0).batch(0).tasks.size() == 1);
builder.add_task(task2);
rg = builder.build();
ASSERT(rg.buffer(0).num_batches() == 2);
ASSERT(rg.buffer(0).batch(0).tasks.size() == 1);
ASSERT(rg.buffer(0).batch(1).tasks.size() == 1);
}
}
void test_buffer_idxs() {
VkExtent2D extent = {
.width = 1024,
.height = 1024
};
auto gpu = create_mock_gpu();
VkImageView a;
VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
ImageChain image_chain = create_mock_image_chain(gpu.get(), 4, extent, fmt);
lft::rg::Builder builder(gpu.get(), image_chain, "output");
Struct data = {};
auto task1 = lft::rg::render_task<Struct>(
"task1", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {
ASSERT(info.buffer_idx() == 0);
},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("output", fmt, extent, {})
.add_color_output("resource1", fmt, extent, {})
.add_color_output("resource2", fmt, extent, {})
.set_extent(extent)
.build();
auto task2 = lft::rg::render_task<Struct>(
"task2", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {
ASSERT(info.buffer_idx() == 0);
},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("output", fmt, extent, {})
.add_dependency("resource1")
.set_extent(extent)
.build();
builder.add_task(task1);
builder.add_task(task2);
auto rg = builder.build();
ASSERT(rg.buffer(0).index() == 0);
}
void test_compute() {
VkExtent2D extent = {
.width = 1024,
.height = 1024
};
auto gpu = create_mock_gpu();
VkImageView a;
VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
ImageChain image_chain = create_mock_image_chain(gpu.get(), 4, extent, fmt);
lft::rg::Builder builder(gpu.get(), image_chain, "output");
Struct data = {};
bool is_build_func_called = false;
auto task1 = lft::rg::compute_task<Struct>(
"task1", &data,
[&](const lft::rg::TaskBuildInfo& info, Struct* ctx) {
ASSERT(info.buffer_idx() == 0);
is_build_func_called = true;
},
[&](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_buffer_output("resource1", 1000)
.build();
auto task2 = lft::rg::render_task<Struct>(
"task2", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {
ASSERT(info.buffer_idx() == 0);
},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("output", fmt, extent, {})
.add_dependency("resource1")
.set_extent(extent)
.build();
builder.add_task(task1);
builder.add_task(task2);
auto rg = builder.build();
ASSERT(is_build_func_called);
ASSERT(rg.buffer(0).batch(0).tasks[0].pDefinition.name() == "task1");
ASSERT(rg.buffer(0).batch(1).tasks[0].pDefinition.name() == "task2");
}
void test_render_graph2() {
VkExtent2D extent = {
.width = 1024,
.height = 1024
};
auto gpu = create_mock_gpu();
VkImageView a;
VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
ImageChain image_chain = create_mock_image_chain(gpu.get(), 4, extent, fmt);
lft::rg::Builder builder(gpu.get(), image_chain, "output");
Struct data = {};
auto task1 = lft::rg::render_task<Struct>(
"task1", &data,
[&](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
[&](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("resource1", VK_FORMAT_R8G8B8A8_UNORM, extent, {0.0, 0.0, 0.0, 0.0})
.build();
auto task2 = lft::rg::render_task<Struct>(
"task2", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_dependency("resource1")
.add_color_output("output", fmt, extent, {})
.build();
auto task3 = lft::rg::compute_task<Struct>(
"task3", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_buffer_output("resource2", 1000)
.build();
auto task4 = lft::rg::render_task<Struct>(
"task4", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("output", fmt, extent, {})
.add_dependency("resource2")
.add_dependency("task2")
.build();
auto task5 = lft::rg::render_task<Struct>(
"task5", &data,
[](const lft::rg::TaskBuildInfo& info, Struct* ctx) {},
[](const lft::rg::TaskRecordInfo& info, Struct* ctx) {})
.add_color_output("output", fmt, extent, {})
.add_dependency("task2")
.build();
builder.add_task(task1);
builder.add_task(task2);
builder.add_task(task3);
builder.add_task(task4);
builder.add_task(task5);
auto rg = builder.build();
auto wait1 = rg.get_wait_semaphores_for(&rg.buffer(0), 0);
ASSERT(rg.buffer(0).batch(0).tasks[0].pDefinition.name() == "task3");
ASSERT(wait1.empty());
auto wait2 = rg.get_wait_semaphores_for(&rg.buffer(0), 1);
ASSERT(rg.buffer(0).batch(1).tasks[0].pDefinition.name() == "task1");
ASSERT(wait2.empty());
auto wait3 = rg.get_wait_semaphores_for(&rg.buffer(0), 2);
ASSERT(rg.buffer(0).batch(2).tasks[0].pDefinition.name() == "task2");
ASSERT(wait3.size() == 1);
ASSERT(wait3[0].semaphore == rg.buffer(0).batch(1).signal);
auto wait4 = rg.get_wait_semaphores_for(&rg.buffer(0), 3);
ASSERT(rg.buffer(0).batch(3).tasks[0].pDefinition.name() == "task5");
ASSERT(wait4.size() == 1);
ASSERT(wait4[0].semaphore == rg.buffer(0).batch(2).signal);
auto wait5 = rg.get_wait_semaphores_for(&rg.buffer(0), 4);
ASSERT(rg.buffer(0).batch(4).tasks[0].pDefinition.name() == "task4");
ASSERT(wait5.size() == 2);
ASSERT(wait5[0].semaphore == rg.buffer(0).batch(2).signal ||
wait5[0].semaphore == rg.buffer(0).batch(0).signal);
ASSERT(wait5[1].semaphore == rg.buffer(0).batch(0).signal ||
wait5[1].semaphore == rg.buffer(0).batch(2).signal);
}
// int main() {
// /* test_render_graph_extent();
// test_render_graph_push();
// test_render_graph_insert_begin();
// test_render_graph_insert_middle();
// test_render_graph_update_renderpass();
// test_render_graph_remove();
// test_buffer_idxs();
// test_compute(); */
// test_render_graph2();
// }
@@ -0,0 +1,166 @@
#pragma once
#include <catch2/catch_test_macros.hpp>
#define private public
#include "Mock.hpp"
const VkFormat FMT = VK_FORMAT_R8G8B8A8_UNORM;
const VkExtent2D EXTENT = {
.width = 1024,
.height = 1024
};
/**
* Test single task
*/
TEST_CASE("SingleTask", "[rg]") {
auto gpu = create_mock_gpu();
auto image_chain = create_mock_image_chain(gpu.get(), 1, EXTENT, FMT);
lft::rg::Builder builder(
gpu.get(), image_chain, "output"
);
EmptyContext ctx;
auto task1 = create_empty_task("task1")
.set_output_to_final()
.build();
builder.add_task(task1);
auto sorted = topology_sort(builder.m_tasks, "output");
REQUIRE(sorted.size() == 1);
REQUIRE(sorted[0].name() == "task1");
}
/**
* Test two tasks where second depends on the first
*/
void test_topological_sort_01() {
auto gpu = create_mock_gpu();
auto image_chain = create_mock_image_chain(gpu.get(), 1, EXTENT, FMT);
lft::rg::Builder builder(
gpu.get(), image_chain, "output"
);
EmptyContext ctx;
auto task1 = lft::rg::render_task<EmptyContext>(
"task1", &ctx,
[](const lft::rg::TaskBuildInfo& info, EmptyContext* ctx) {},
[](const lft::rg::TaskRecordInfo& info, EmptyContext* ctx) {}
).add_color_output("resource1", FMT, EXTENT, {})
.build();
auto task2 = lft::rg::render_task<EmptyContext>(
"task2", &ctx,
[](const lft::rg::TaskBuildInfo& info, EmptyContext* ctx) {},
[](const lft::rg::TaskRecordInfo& info, EmptyContext* ctx) {}
).add_dependency("resource1")
.add_color_output("output", FMT, EXTENT, {})
.build();
builder.add_task(task1);
builder.add_task(task2);
auto sorted = topology_sort(builder.m_tasks, "output");
ASSERT(sorted.size() == 2);
ASSERT(sorted[0].name() == "task1");
ASSERT(sorted[1].name() == "task2");
}
void test_topological_sort_02() {
auto gpu = create_mock_gpu();
auto image_chain = create_mock_image_chain(gpu.get(), 1, EXTENT, FMT);
lft::rg::Builder builder(
gpu.get(), image_chain, "output"
);
EmptyContext ctx;
auto task1 = lft::rg::render_task<EmptyContext>(
"task1", &ctx,
[](const lft::rg::TaskBuildInfo& info, EmptyContext* ctx) {},
[](const lft::rg::TaskRecordInfo& info, EmptyContext* ctx) {}
).add_color_output("resource1", FMT, EXTENT, {})
.build();
auto task2 = lft::rg::render_task<EmptyContext>(
"task2", &ctx,
[](const lft::rg::TaskBuildInfo& info, EmptyContext* ctx) {},
[](const lft::rg::TaskRecordInfo& info, EmptyContext* ctx) {}
).add_dependency("resource1")
.add_color_output("output", FMT, EXTENT, {})
.build();
// inverted - test if it actually sorts
builder.add_task(task2);
builder.add_task(task1);
auto sorted = topology_sort(builder.m_tasks, "output");
ASSERT(sorted.size() == 2);
ASSERT(sorted[0].name() == "task1");
ASSERT(sorted[1].name() == "task2");
}
void test_topological_sort_03() {
auto gpu = create_mock_gpu();
auto image_chain = create_mock_image_chain(gpu.get(), 1, EXTENT, FMT);
EmptyContext ctx;
auto task1 = lft::rg::render_task<EmptyContext>(
"task1", &ctx,
[](const lft::rg::TaskBuildInfo& info, EmptyContext* ctx) {},
[](const lft::rg::TaskRecordInfo& info, EmptyContext* ctx) {}
).add_color_output("resource1", FMT, EXTENT, {})
.build();
auto task2 = lft::rg::render_task<EmptyContext>(
"task2", &ctx,
[](const lft::rg::TaskBuildInfo& info, EmptyContext* ctx) {},
[](const lft::rg::TaskRecordInfo& info, EmptyContext* ctx) {}
).add_color_output("resource2", FMT, EXTENT, {})
.build();
auto task3 = lft::rg::render_task<EmptyContext>(
"task3", &ctx,
[](const lft::rg::TaskBuildInfo& info, EmptyContext* ctx) {},
[](const lft::rg::TaskRecordInfo& info, EmptyContext* ctx) {}
).add_dependency("resource1")
.add_dependency("resource2")
.add_color_output("output", FMT, EXTENT, {})
.build();
lft::rg::Builder builder1(
gpu.get(), image_chain, "output"
);
builder1.add_task(task1);
builder1.add_task(task2);
builder1.add_task(task3);
auto sorted = topology_sort(builder1.m_tasks, "output");
ASSERT(sorted.size() == 3);
ASSERT(sorted[0].name() == "task1" || sorted[0].name() == "task2");
ASSERT(sorted[1].name() == "task1" || sorted[1].name() == "task2");
ASSERT(sorted[0].name() != sorted[1].name());
ASSERT(sorted[2].name() == "task3");
}
// int main() {
// test_topological_sort_01();
// test_topological_sort_02();
// test_topological_sort_03();
//
// return 0;
// }
+5
View File
@@ -0,0 +1,5 @@
int main() {
}

Some files were not shown because too many files have changed in this diff Show More