├── .clang-format ├── .github └── workflows │ └── cmake.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── Run-Android.ps1 ├── Run-Host.ps1 ├── apps ├── CMakeLists.txt ├── demo │ ├── CMakeLists.txt │ └── app.cpp └── test-runner │ ├── CMakeLists.txt │ ├── app.cpp │ └── tests │ ├── json-serde.cpp │ ├── stream.cpp │ ├── util.cpp │ └── zip.cpp ├── bin2c.cpp ├── include └── gft │ ├── args.hpp │ ├── assert.hpp │ ├── fmt.hpp │ ├── geom.hpp │ ├── glslang.hpp │ ├── hal │ ├── buffer.hpp │ ├── builder.hpp │ ├── context.hpp │ ├── depth-image.hpp │ ├── hal.hpp │ ├── image.hpp │ ├── instance.hpp │ ├── invocation.hpp │ ├── render-pass.hpp │ ├── renderer.hpp │ ├── swapchain.hpp │ ├── task.hpp │ └── transaction.hpp │ ├── json-serde.hpp │ ├── json.hpp │ ├── log.hpp │ ├── mesh.hpp │ ├── platform │ ├── macos.hpp │ └── windows.hpp │ ├── pool.hpp │ ├── renderdoc.hpp │ ├── stats.hpp │ ├── stream.hpp │ ├── test.hpp │ ├── util.hpp │ ├── vk-sys.hpp │ ├── vk │ ├── vk-buffer.hpp │ ├── vk-context.hpp │ ├── vk-depth-image.hpp │ ├── vk-image.hpp │ ├── vk-instance.hpp │ ├── vk-invocation.hpp │ ├── vk-render-pass.hpp │ ├── vk-swapchain.hpp │ ├── vk-task.hpp │ ├── vk-transaction.hpp │ └── vk.hpp │ └── zip.hpp ├── scripts └── format-code.sh └── src └── gft ├── args.cpp ├── geom.cpp ├── glslang.cpp ├── hal ├── buf.cpp └── builder.cpp ├── json.cpp ├── log.cpp ├── mesh.cpp ├── platform ├── macos.mm └── windows.cpp ├── renderdoc.cpp ├── stream.cpp ├── test.cpp ├── util.cpp ├── vk ├── renderer.cpp ├── sys.cpp ├── sys.hpp ├── vk-buffer.cpp ├── vk-context.cpp ├── vk-depth-img.cpp ├── vk-image.cpp ├── vk-instance.cpp ├── vk-invocation.cpp ├── vk-render-pass.cpp ├── vk-swapchain.cpp ├── vk-task.cpp └── vk-transaction.cpp └── zip.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Chromium 4 | AccessModifierOffset: -1 5 | AlignAfterOpenBracket: BlockIndent 6 | AlignArrayOfStructures: None 7 | AlignConsecutiveAssignments: 8 | Enabled: false 9 | AcrossEmptyLines: false 10 | AcrossComments: false 11 | AlignCompound: false 12 | PadOperators: true 13 | AlignConsecutiveBitFields: 14 | Enabled: false 15 | AcrossEmptyLines: false 16 | AcrossComments: false 17 | AlignCompound: false 18 | PadOperators: false 19 | AlignConsecutiveDeclarations: 20 | Enabled: false 21 | AcrossEmptyLines: false 22 | AcrossComments: false 23 | AlignCompound: false 24 | PadOperators: false 25 | AlignConsecutiveMacros: 26 | Enabled: false 27 | AcrossEmptyLines: false 28 | AcrossComments: false 29 | AlignCompound: false 30 | PadOperators: false 31 | AlignEscapedNewlines: Left 32 | AlignOperands: Align 33 | AlignTrailingComments: true 34 | AllowAllArgumentsOnNextLine: true 35 | AllowAllParametersOfDeclarationOnNextLine: false 36 | AllowShortEnumsOnASingleLine: true 37 | AllowShortBlocksOnASingleLine: Never 38 | AllowShortCaseLabelsOnASingleLine: false 39 | AllowShortFunctionsOnASingleLine: Empty 40 | AllowShortLambdasOnASingleLine: All 41 | AllowShortIfStatementsOnASingleLine: Never 42 | AllowShortLoopsOnASingleLine: false 43 | AlwaysBreakAfterDefinitionReturnType: None 44 | AlwaysBreakAfterReturnType: None 45 | AlwaysBreakBeforeMultilineStrings: false 46 | AlwaysBreakTemplateDeclarations: Yes 47 | AttributeMacros: 48 | - __capability 49 | BinPackArguments: false 50 | BinPackParameters: false 51 | BraceWrapping: 52 | AfterCaseLabel: false 53 | AfterClass: false 54 | AfterControlStatement: Never 55 | AfterEnum: false 56 | AfterFunction: false 57 | AfterNamespace: false 58 | AfterObjCDeclaration: false 59 | AfterStruct: false 60 | AfterUnion: false 61 | AfterExternBlock: false 62 | BeforeCatch: false 63 | BeforeElse: false 64 | BeforeLambdaBody: false 65 | BeforeWhile: false 66 | IndentBraces: false 67 | SplitEmptyFunction: true 68 | SplitEmptyRecord: true 69 | SplitEmptyNamespace: true 70 | #BreakBeforeBinaryOperators: None 71 | BreakBeforeConceptDeclarations: Always 72 | BreakBeforeBraces: Attach 73 | BreakBeforeInheritanceComma: false 74 | BreakInheritanceList: AfterColon 75 | BreakBeforeTernaryOperators: true 76 | BreakConstructorInitializersBeforeComma: false 77 | BreakConstructorInitializers: AfterColon 78 | BreakAfterJavaFieldAnnotations: false 79 | BreakStringLiterals: false 80 | ColumnLimit: 80 81 | CommentPragmas: '^ IWYU pragma:' 82 | QualifierAlignment: Leave 83 | CompactNamespaces: false 84 | ConstructorInitializerIndentWidth: 2 85 | ContinuationIndentWidth: 2 86 | Cpp11BracedListStyle: false 87 | DeriveLineEnding: true 88 | DerivePointerAlignment: false 89 | DisableFormat: false 90 | EmptyLineAfterAccessModifier: Never 91 | EmptyLineBeforeAccessModifier: Always 92 | ExperimentalAutoDetectBinPacking: false 93 | PackConstructorInitializers: NextLine 94 | BasedOnStyle: '' 95 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 96 | AllowAllConstructorInitializersOnNextLine: true 97 | FixNamespaceComments: true 98 | ForEachMacros: 99 | - foreach 100 | - Q_FOREACH 101 | - BOOST_FOREACH 102 | IfMacros: 103 | - KJ_IF_MAYBE 104 | IncludeBlocks: Preserve 105 | IncludeCategories: 106 | - Regex: '^' 107 | Priority: 2 108 | SortPriority: 0 109 | CaseSensitive: false 110 | - Regex: '^<.*\.h>' 111 | Priority: 1 112 | SortPriority: 0 113 | CaseSensitive: false 114 | - Regex: '^<.*' 115 | Priority: 2 116 | SortPriority: 0 117 | CaseSensitive: false 118 | - Regex: '.*' 119 | Priority: 3 120 | SortPriority: 0 121 | CaseSensitive: false 122 | IncludeIsMainRegex: '([-_](test|unittest))?$' 123 | IncludeIsMainSourceRegex: '' 124 | IndentAccessModifiers: false 125 | IndentCaseLabels: false 126 | IndentCaseBlocks: false 127 | IndentGotoLabels: true 128 | IndentPPDirectives: None 129 | IndentExternBlock: AfterExternBlock 130 | IndentRequiresClause: true 131 | IndentWidth: 2 132 | IndentWrappedFunctionNames: false 133 | InsertBraces: false 134 | InsertTrailingCommas: Wrapped 135 | JavaScriptQuotes: Leave 136 | JavaScriptWrapImports: true 137 | KeepEmptyLinesAtTheStartOfBlocks: false 138 | LambdaBodyIndentation: Signature 139 | MacroBlockBegin: '' 140 | MacroBlockEnd: '' 141 | MaxEmptyLinesToKeep: 2 142 | NamespaceIndentation: None 143 | ObjCBinPackProtocolList: Never 144 | ObjCBlockIndentWidth: 2 145 | ObjCBreakBeforeNestedBlockParam: true 146 | ObjCSpaceAfterProperty: false 147 | ObjCSpaceBeforeProtocolList: true 148 | PenaltyBreakAssignment: 2 149 | PenaltyBreakBeforeFirstCallParameter: 1 150 | PenaltyBreakComment: 300 151 | PenaltyBreakFirstLessLess: 120 152 | PenaltyBreakOpenParenthesis: 0 153 | PenaltyBreakString: 1000 154 | PenaltyBreakTemplateDeclaration: 10 155 | PenaltyExcessCharacter: 1000000 156 | PenaltyReturnTypeOnItsOwnLine: 1000 157 | PenaltyIndentedWhitespace: 0 158 | PointerAlignment: Left 159 | PPIndentWidth: -1 160 | RawStringFormats: 161 | - Language: Cpp 162 | Delimiters: 163 | - cc 164 | - CC 165 | - cpp 166 | - Cpp 167 | - CPP 168 | - 'c++' 169 | - 'C++' 170 | CanonicalDelimiter: '' 171 | BasedOnStyle: google 172 | - Language: TextProto 173 | Delimiters: 174 | - pb 175 | - PB 176 | - proto 177 | - PROTO 178 | EnclosingFunctions: 179 | - EqualsProto 180 | - EquivToProto 181 | - PARSE_PARTIAL_TEXT_PROTO 182 | - PARSE_TEST_PROTO 183 | - PARSE_TEXT_PROTO 184 | - ParseTextOrDie 185 | - ParseTextProtoOrDie 186 | - ParseTestProto 187 | - ParsePartialTestProto 188 | CanonicalDelimiter: pb 189 | BasedOnStyle: google 190 | ReferenceAlignment: Pointer 191 | ReflowComments: true 192 | RemoveBracesLLVM: false 193 | RequiresClausePosition: OwnLine 194 | SeparateDefinitionBlocks: Leave 195 | ShortNamespaceLines: 1 196 | SortIncludes: Never 197 | SortJavaStaticImport: Before 198 | SortUsingDeclarations: true 199 | SpaceAfterCStyleCast: false 200 | SpaceAfterLogicalNot: false 201 | SpaceAfterTemplateKeyword: false 202 | SpaceBeforeAssignmentOperators: true 203 | SpaceBeforeCaseColon: false 204 | SpaceBeforeCpp11BracedList: true 205 | SpaceBeforeCtorInitializerColon: true 206 | SpaceBeforeInheritanceColon: true 207 | SpaceBeforeParens: ControlStatements 208 | SpaceBeforeParensOptions: 209 | AfterControlStatements: true 210 | AfterForeachMacros: true 211 | AfterFunctionDefinitionName: false 212 | AfterFunctionDeclarationName: false 213 | AfterIfMacros: true 214 | AfterOverloadedOperator: false 215 | AfterRequiresInClause: false 216 | AfterRequiresInExpression: false 217 | BeforeNonEmptyParentheses: false 218 | SpaceAroundPointerQualifiers: Default 219 | SpaceBeforeRangeBasedForLoopColon: true 220 | SpaceInEmptyBlock: false 221 | SpaceInEmptyParentheses: false 222 | SpacesBeforeTrailingComments: 1 223 | SpacesInAngles: Never 224 | SpacesInConditionalStatement: false 225 | SpacesInContainerLiterals: true 226 | SpacesInCStyleCastParentheses: false 227 | SpacesInLineCommentPrefix: 228 | Minimum: 1 229 | Maximum: -1 230 | SpacesInParentheses: false 231 | SpacesInSquareBrackets: false 232 | SpaceBeforeSquareBrackets: false 233 | BitFieldColonSpacing: Both 234 | Standard: Auto 235 | StatementAttributeLikeMacros: 236 | - Q_EMIT 237 | StatementMacros: 238 | - Q_UNUSED 239 | - QT_REQUIRE_VERSION 240 | TabWidth: 2 241 | UseCRLF: false 242 | UseTab: Never 243 | WhitespaceSensitiveMacros: 244 | - STRINGIZE 245 | - PP_STRINGIZE 246 | - BOOST_PP_STRINGIZE 247 | - NS_SWIFT_NAME 248 | - CF_SWIFT_NAME 249 | ... 250 | 251 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | BUILD_TYPE: Release 11 | 12 | jobs: 13 | build: 14 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 15 | # You can convert this to a matrix build if you need cross-platform coverage. 16 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Setup Vulkan SDK 22 | uses: humbletim/setup-vulkan-sdk@v1.2.0 23 | with: 24 | vulkan-query-version: latest 25 | vulkan-use-cache: true 26 | - name: Ccache for gh actions 27 | uses: hendrikmuhs/ccache-action@v1.2.3 28 | - uses: snickerbockers/submodules-init@v4 29 | - uses: ashutoshvarma/setup-ninja@v1.1 30 | 31 | - name: Build 32 | run: | 33 | cmake -GNinja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 34 | cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 35 | 36 | - name: Test 37 | working-directory: ${{github.workspace}} 38 | run: build/bin/TestRunner -C ${{env.BUILD_TYPE}} 39 | 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | bin 3 | .vs 4 | .vscode 5 | build*/ 6 | CMakeSettings.json 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third/glslang"] 2 | path = third/glslang 3 | url = https://github.com/KhronosGroup/glslang.git 4 | [submodule "third/VulkanMemoryAllocator"] 5 | path = third/VulkanMemoryAllocator 6 | url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator 7 | [submodule "third/glm"] 8 | path = third/glm 9 | url = https://github.com/g-truc/glm 10 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.10) 2 | 3 | project ("GraphiT" LANGUAGES CXX) 4 | 5 | option(GFT_BUILD_APPS "Build Graphi-T example apps" ON) 6 | 7 | 8 | 9 | set(CMAKE_CXX_STANDARD 17) 10 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 11 | 12 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") 13 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") 14 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") 15 | 16 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") 17 | 18 | if (NOT CMAKE_BUILD_TYPE) 19 | message(STATUS "No build type selected, default to Release") 20 | set(CMAKE_BUILD_TYPE "Release" CACHE PATH "Build Type" FORCE) 21 | endif() 22 | 23 | set(LINK_LIBS 24 | ) 25 | set(INC_DIRS 26 | "${PROJECT_SOURCE_DIR}/include" 27 | ) 28 | 29 | if(APPLE) 30 | find_library(Foundation NAMES Foundation) 31 | find_library(AppKit NAMES AppKit) 32 | find_library(Metal NAMES Metal) 33 | find_library(MetalKit NAMES MetalKit) 34 | list(APPEND LINK_LIBS ${Foundation} ${AppKit} ${Metal} ${MetalKit}) 35 | endif() 36 | 37 | set(BUILD_STATIC_LIBS ON) 38 | add_subdirectory(${PROJECT_SOURCE_DIR}/third/glm) 39 | list(APPEND LINK_LIBS 40 | glm 41 | ) 42 | unset(BUILD_STATIC_LIBS) 43 | 44 | find_package(Vulkan) 45 | add_subdirectory(third/VulkanMemoryAllocator) 46 | if(NOT Vulkan_FOUND) 47 | message("") 48 | message("-- Vulkan not found! Vulkan context is disabled") 49 | message("") 50 | else() 51 | message("-- Vulkan context enabled") 52 | list(APPEND INC_DIRS 53 | ${Vulkan_INCLUDE_DIRS} 54 | third/VulkanMemoryAllocator/include 55 | ) 56 | list(APPEND LINK_LIBS 57 | ${Vulkan_LIBRARIES} 58 | VulkanMemoryAllocator 59 | ) 60 | endif() 61 | 62 | 63 | # Runtime shader compilation support. 64 | set(BUILD_EXTERNAL OFF CACHE BOOL "" FORCE) 65 | add_subdirectory(third/glslang) 66 | list(APPEND INC_DIRS 67 | "${PROJECT_SOURCE_DIR}/third" 68 | ) 69 | list(APPEND LINK_LIBS 70 | glslang 71 | SPIRV 72 | ) 73 | 74 | # Used to inline kernel/shaders. 75 | add_executable(bin2c 76 | "bin2c.cpp" 77 | "include/gft/assert.hpp" 78 | "include/gft/util.hpp" 79 | "src/gft/util.cpp" 80 | ) 81 | 82 | # The GraphiT library. 83 | include_directories(${INC_DIRS}) 84 | file(GLOB_RECURSE SRCS "${PROJECT_SOURCE_DIR}/src/*.cpp") 85 | if (APPLE) 86 | list(APPEND SRCS "${PROJECT_SOURCE_DIR}/src/gft/platform/macos.mm") 87 | endif() 88 | file(GLOB_RECURSE INCS "${PROJECT_SOURCE_DIR}/include/*.hpp") 89 | add_library(GraphiT STATIC ${SRCS} ${INCS}) 90 | target_link_libraries(GraphiT ${LINK_LIBS}) 91 | add_dependencies(GraphiT bin2c) 92 | 93 | # GraphiT example apps. 94 | if(GFT_BUILD_APPS) 95 | make_directory("${CMAKE_BINARY_DIR}/assets/") 96 | add_subdirectory("${PROJECT_SOURCE_DIR}/apps") 97 | endif() 98 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Rendong Liang 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GraphiT 2 | 3 | Handy tools & graphics API abstraction for blazing fast prototyping. 4 | 5 | There exist lots of abstractions of graphics APIs (like [wgpu](https://github.com/gfx-rs/wgpu) and Unreal Engine RHI) but most of them intentionally hide their implementations and data structures over encapsulation and inheritance. Such design is extremely confusing and exhausting in my research projects. [GraphiT](https://github.com/PENGUINLIONG/graphi-t) is thus one of my attempts to get things done more easily. 6 | 7 | GraphiT has all (or most of those) you need for fast prototyping: 8 | 9 | - [Argument parsing](https://github.com/PENGUINLIONG/graphi-t/blob/main/include/gft/args.hpp) with flexibility; 10 | - [JSON ser/de](https://github.com/PENGUINLIONG/graphi-t/blob/main/include/gft/json.hpp) for configuration and structured serialization; 11 | - [Logging](https://github.com/PENGUINLIONG/graphi-t/blob/main/include/gft/json.hpp) with severity filtering & customized output callback; 12 | - [Hardware Abstraction Layer](https://github.com/PENGUINLIONG/graphi-t/blob/main/include/gft/hal/hal.hpp) over [Vulkan](https://github.com/PENGUINLIONG/graphi-t/blob/main/include/gft/vk.hpp) API with every handle accessible; 13 | - [Glslang](https://github.com/PENGUINLIONG/graphi-t/blob/main/include/gft/glslang.hpp) integration for on-the-flight shader compilation; 14 | - [Descriptive statistics](https://github.com/PENGUINLIONG/graphi-t/blob/main/include/gft/stats.hpp) to collect minimum, maximum, mean, etc.; 15 | - [BMP image writer, text editing, timer](https://github.com/PENGUINLIONG/graphi-t/blob/main/include/gft/util.hpp) and a lot more utility functions. 16 | 17 | ## Run Examples 18 | 19 | You can use the following commands to run the GraphiT demo on Windows host and Android device attached via adb: 20 | ```powershell 21 | ./Run-Host.ps1 -a Demo # Run on host. 22 | 23 | ./Run-Android.ps1 -a Demo # Run on attached Android device over adb. 24 | ``` 25 | 26 | ## Use GraphiT in Your Project 27 | 28 | You can create a new repository on Github by clicking at the `Use this template` button in [GraphiT-Template](https://github.com/PENGUINLIONG/GraphiT-Template). It comes with a 'hello world' program using GraphiT as a Git submodule. 29 | 30 | GraphiT can also be integrated by `add_subdirectory` like many other CMake projects. 31 | 32 | ## License 33 | 34 | This project is licensed under either of 35 | 36 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 37 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 38 | 39 | at your option. 40 | 41 | ## Contribution 42 | 43 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in GraphiT by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 44 | -------------------------------------------------------------------------------- /Run-Android.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [string] $AppName 3 | ) 4 | 5 | if (-not(Test-Path "build-android-aarch64")) { 6 | New-Item "build-android-aarch64" -ItemType Directory 7 | } 8 | 9 | Push-Location "build-android-aarch64" 10 | cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE="$env:ANDROID_NDK/build/cmake/android.toolchain.cmake" -DANDROID_ABI="arm64-v8a" -DANDROID_PLATFORM=android-29 -G "Ninja" .. 11 | cmake --build . -t $AppName 12 | 13 | adb push ./assets /data/local/tmp/graphi-t 14 | adb push ./bin /data/local/tmp/graphi-t 15 | adb shell chmod 777 /data/local/tmp/graphi-t/bin/$AppName 16 | adb shell "cd /data/local/tmp/graphi-t/bin && ./$AppName" 17 | Pop-Location 18 | -------------------------------------------------------------------------------- /Run-Host.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [string] $AppName 3 | ) 4 | 5 | if (-not(Test-Path "build-windows-x64")) { 6 | New-Item "build-windows-x64" -ItemType Directory 7 | } 8 | 9 | Push-Location "build-windows-x64" 10 | cmake .. 11 | cmake --build . -t $AppName 12 | Pop-Location 13 | 14 | if ($lastexitcode -ne 0) { 15 | exit 16 | } 17 | 18 | & ./build-windows-x64/bin/Debug/Demo.exe 19 | -------------------------------------------------------------------------------- /apps/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(demo) 2 | add_subdirectory(test-runner) 3 | -------------------------------------------------------------------------------- /apps/demo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(APP_NAME Demo) 2 | 3 | add_executable(${APP_NAME} "app.cpp") 4 | target_link_libraries(${APP_NAME} GraphiT) 5 | -------------------------------------------------------------------------------- /apps/demo/app.cpp: -------------------------------------------------------------------------------- 1 | #include "gft/hal/builder.hpp" 2 | #include "gft/log.hpp" 3 | #include "gft/platform/macos.hpp" 4 | #include "gft/platform/windows.hpp" 5 | #include "gft/renderdoc.hpp" 6 | #include "gft/vk/vk.hpp" 7 | #include "gft/glslang.hpp" 8 | 9 | using namespace liong; 10 | using namespace liong::vk; 11 | using namespace liong::fmt; 12 | 13 | void dbg_enum_dev_descs(const InstanceRef& instance) { 14 | uint32_t ndev = 0; 15 | for (;;) { 16 | auto desc = instance->describe_device(ndev); 17 | if (desc.empty()) { 18 | break; 19 | } 20 | L_INFO("device #", ndev, ": ", desc); 21 | ++ndev; 22 | } 23 | } 24 | void dbg_dump_spv_art( 25 | const std::string& prefix, 26 | glslang::ComputeSpirvArtifact art 27 | ) { 28 | liong::util::save_file( 29 | (prefix + ".comp.spv").c_str(), 30 | art.comp_spv.data(), 31 | art.comp_spv.size() * sizeof(uint32_t) 32 | ); 33 | } 34 | void dbg_dump_spv_art( 35 | const std::string& prefix, 36 | glslang::GraphicsSpirvArtifact art 37 | ) { 38 | liong::util::save_file( 39 | (prefix + ".vert.spv").c_str(), 40 | art.vert_spv.data(), 41 | art.vert_spv.size() * sizeof(uint32_t) 42 | ); 43 | liong::util::save_file( 44 | (prefix + ".frag.spv").c_str(), 45 | art.frag_spv.data(), 46 | art.frag_spv.size() * sizeof(uint32_t) 47 | ); 48 | } 49 | 50 | void guarded_main() { 51 | InstanceRef instance = VulkanInstance::create(); 52 | 53 | dbg_enum_dev_descs(instance); 54 | 55 | std::string hlsl = R"( 56 | void vert( 57 | in float4 InPosition: ATTRIBUTE0, 58 | out float4 OutColor: TEXCOORD0, 59 | out float4 OutPosition: SV_POSITION 60 | ) { 61 | OutColor = float4(1.0f, 1.0f, 0.0f, 1.0f); 62 | OutPosition = float4(InPosition.xy, 0.0f, 1.0f); 63 | } 64 | float4 ColorMultiplier; 65 | 66 | half4 frag( 67 | in float4 InColor: TEXCOORD 68 | ) : SV_TARGET { 69 | return half4((InColor * ColorMultiplier)); 70 | } 71 | )"; 72 | 73 | #if defined(__MACH__) && defined(__APPLE__) 74 | macos::Window window = macos::create_window(1024, 768); 75 | ContextRef ctxt = instance->create_context( // 76 | ContextMetalConfig::build() // 77 | .device_index(0) 78 | .metal_layer(window.metal_layer) 79 | ); 80 | #elif defined(_WIN32) 81 | windows::Window window = windows::create_window(); 82 | ContextRef ctxt = instance->create_context( // 83 | WindowsContextConfig::build() // 84 | .device_index(0) 85 | .hinst(window.hinst) 86 | .hwnd(window.hwnd) 87 | ); 88 | #else 89 | ContextRef ctxt = instance->create_context( // 90 | ContextConfig::build() // 91 | .device_index(0) 92 | ); 93 | #endif 94 | 95 | renderdoc::CaptureGuard capture; 96 | 97 | BufferRef ubo = ctxt->create_buffer( // 98 | BufferConfig::build() 99 | .label("ubo") 100 | .size(4 * sizeof(float)) 101 | .uniform() 102 | .streaming() 103 | ); 104 | 105 | { 106 | float data[4] { 0, 1, 0, 1 }; 107 | ubo->copy_from(data, 1); 108 | } 109 | 110 | BufferRef verts = ctxt->create_buffer( // 111 | BufferConfig::build() 112 | .label("verts") 113 | .size(3 * 3 * sizeof(float)) 114 | .vertex() 115 | .streaming() 116 | ); 117 | { 118 | float data[9] { 1, -1, 0, -1, -1, 0, -1, 1, 0 }; 119 | verts->copy_from(data, 1); 120 | } 121 | 122 | BufferRef idxs = ctxt->create_buffer( // 123 | BufferConfig::build() 124 | .label("idxs") 125 | .size(3 * 4 * sizeof(uint16_t)) 126 | .index() 127 | .streaming() 128 | ); 129 | { 130 | uint16_t data[3] { 0, 1, 2 }; 131 | idxs->copy_from(data, 1); 132 | } 133 | 134 | SwapchainRef swapchain = ctxt->create_swapchain( // 135 | SwapchainConfig::build() 136 | .label("swapchain") 137 | .image_count(3) 138 | .allowed_format(fmt::L_FORMAT_B8G8R8A8_UNORM) 139 | .allowed_format(fmt::L_FORMAT_R8G8B8A8_UNORM) 140 | .color_space(fmt::L_COLOR_SPACE_SRGB) 141 | ); 142 | 143 | RenderPassRef pass = ctxt->create_render_pass( // 144 | RenderPassConfig::build() 145 | .label("pass") 146 | .width(swapchain->get_width()) 147 | .height(swapchain->get_height()) 148 | .clear_store_color_attachment(L_FORMAT_B8G8R8A8_UNORM, L_COLOR_SPACE_SRGB) 149 | ); 150 | 151 | auto art = glslang::compile_graph_hlsl(hlsl, "vert", hlsl, "frag"); 152 | 153 | TaskRef task = pass->create_graphics_task( // 154 | GraphicsTaskConfig::build() 155 | .label("graph_task") 156 | .vertex_shader(art.vert_spv, "main") 157 | .fragment_shader(art.frag_spv, "main") 158 | .topology(liong::hal::L_TOPOLOGY_TRIANGLE) 159 | .uniform_buffer() 160 | ); 161 | 162 | for (;;) { 163 | ImageRef out_img = swapchain->get_current_image(); 164 | 165 | InvocationRef draw_call = task->create_graphics_invocation( // 166 | GraphicsInvocationConfig::build() 167 | .label("draw_call") 168 | .vertex_buffer(verts->view()) 169 | .per_u32_index(idxs->view(), 3) 170 | .resource(ubo->view()) 171 | ); 172 | 173 | InvocationRef main_pass = 174 | pass->create_render_pass_invocation(RenderPassInvocationConfig::build() 175 | .label("main_pass") 176 | .attachment(out_img->view()) 177 | .invocation(draw_call)); 178 | 179 | main_pass->create_transact(TransactionConfig::build())->wait(); 180 | 181 | swapchain->create_present_invocation(PresentInvocationConfig::build()) 182 | ->create_transact(TransactionConfig::build()) 183 | ->wait(); 184 | } 185 | } 186 | 187 | int main(int argc, char** argv) { 188 | try { 189 | renderdoc::initialize(); 190 | glslang::initialize(); 191 | 192 | guarded_main(); 193 | } catch (const std::exception& e) { 194 | L_ERROR("application threw an exception"); 195 | L_ERROR(e.what()); 196 | L_ERROR("application cannot continue"); 197 | } catch (...) { 198 | L_ERROR("application threw an illiterate exception"); 199 | } 200 | 201 | return 0; 202 | } 203 | -------------------------------------------------------------------------------- /apps/test-runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(APP_NAME TestRunner) 2 | 3 | file(GLOB_RECURSE TEST_SRCS "tests/*.cpp") 4 | 5 | add_executable(${APP_NAME} "app.cpp" ${TEST_SRCS}) 6 | target_link_libraries(${APP_NAME} GraphiT) 7 | -------------------------------------------------------------------------------- /apps/test-runner/app.cpp: -------------------------------------------------------------------------------- 1 | #include "gft/log.hpp" 2 | #include "gft/test.hpp" 3 | 4 | using namespace liong; 5 | 6 | int main(int argc, char** argv) { 7 | try { 8 | test::TestRegistry::run_all(); 9 | } catch (const std::exception& e) { 10 | L_ERROR("application threw an exception"); 11 | L_ERROR(e.what()); 12 | L_ERROR("application cannot continue"); 13 | return -1; 14 | } catch (...) { 15 | L_ERROR("application threw an illiterate exception"); 16 | return -1; 17 | } 18 | 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /apps/test-runner/tests/json-serde.cpp: -------------------------------------------------------------------------------- 1 | #include "gft/json-serde.hpp" 2 | 3 | #include "gft/assert.hpp" 4 | #include "gft/log.hpp" 5 | #include "gft/test.hpp" 6 | 7 | enum class TestEnum { 8 | _123 = 123, 9 | }; 10 | 11 | struct TestStructure { 12 | uint32_t a; 13 | bool b; 14 | std::string c; 15 | std::pair d; 16 | std::unique_ptr e; 17 | std::map f; 18 | std::unordered_map g; 19 | std::vector h; 20 | std::array i; 21 | std::uint16_t j[3]; 22 | TestEnum k; 23 | std::optional l; 24 | uint64_t m; 25 | 26 | L_JSON_SERDE_FIELDS(a, b, c, d, e, f, g, h, i, j, k, l, m); 27 | }; 28 | 29 | L_TEST(TestJsonSerde) { 30 | using namespace liong; 31 | using namespace liong::json; 32 | TestStructure ts1{}; 33 | ts1.a = 123; 34 | ts1.b = true; 35 | ts1.c = "123"; 36 | ts1.d = std::make_pair("12", 3); 37 | ts1.e = std::make_unique(123); 38 | ts1.f[12] = "3"; 39 | ts1.g[1] = "23"; 40 | ts1.h.emplace_back(123); 41 | ts1.i = { 1, 2, 3 }; 42 | ts1.j[0] = 1; 43 | ts1.j[1] = 2; 44 | ts1.j[2] = 3; 45 | ts1.k = TestEnum::_123; 46 | ts1.l = 123; 47 | ts1.m = 123123123123123123; 48 | 49 | JsonValue j1 = json::serialize(ts1); 50 | std::string json_lit = json::print(j1); 51 | L_INFO(json_lit); 52 | JsonValue j2 = json::parse(json_lit); 53 | TestStructure ts2{}; 54 | json::deserialize(j2, ts2); 55 | L_ASSERT(json_lit == json::print(json::serialize(ts2))); 56 | L_ASSERT(ts1.m == ts2.m); // Large integers should not be cast to double. 57 | } 58 | -------------------------------------------------------------------------------- /apps/test-runner/tests/stream.cpp: -------------------------------------------------------------------------------- 1 | #include "gft/stream.hpp" 2 | 3 | #include "gft/assert.hpp" 4 | #include "gft/log.hpp" 5 | #include "gft/test.hpp" 6 | 7 | using namespace liong; 8 | 9 | L_TEST(StreamReadWriteRoundTrip) { 10 | stream::WriteStream ws; 11 | ws.append(123); 12 | ws.append(123); 13 | ws.append(123); 14 | 15 | std::vector data = ws.take(); 16 | stream::ReadStream rs(data.data(), data.size()); 17 | L_ASSERT(rs.extract(), 123); 18 | L_ASSERT(rs.extract(), 123); 19 | L_ASSERT(rs.extract(), 123); 20 | } 21 | 22 | L_TEST(StreamReadWriteStructRoundTrip) { 23 | struct X { 24 | uint8_t a = 123; 25 | // Padding is potentially added here. 26 | uint32_t b = 123; 27 | // Padding is potentially added here. 28 | double c = 123; 29 | } xw, xr; 30 | 31 | stream::WriteStream ws; 32 | ws.append(xw); 33 | 34 | std::vector data = ws.take(); 35 | stream::ReadStream rs(data.data(), data.size()); 36 | xr = rs.extract(); 37 | L_ASSERT(xw.a == xr.a); 38 | L_ASSERT(xw.b == xr.b); 39 | L_ASSERT(xw.c == xr.c); 40 | } 41 | -------------------------------------------------------------------------------- /apps/test-runner/tests/util.cpp: -------------------------------------------------------------------------------- 1 | #include "gft/util.hpp" 2 | 3 | #include "gft/assert.hpp" 4 | #include "gft/log.hpp" 5 | #include "gft/test.hpp" 6 | 7 | L_TEST(Crc32Correctness) { 8 | std::string data = "penguinliongpenguinliongpenguinliongpenguinliong"; 9 | uint32_t x = liong::util::crc32(data.data(), data.size()); 10 | L_ASSERT(x == 0xc4c82680); 11 | } 12 | -------------------------------------------------------------------------------- /apps/test-runner/tests/zip.cpp: -------------------------------------------------------------------------------- 1 | #include "gft/assert.hpp" 2 | #include "gft/log.hpp" 3 | #include "gft/test.hpp" 4 | #include "gft/util.hpp" 5 | #include "gft/zip.hpp" 6 | 7 | using namespace liong; 8 | 9 | L_TEST(ZipRoundTrip) { 10 | std::string file_name = "_123/123.txt"; 11 | std::vector data{ 1, 2, 3 }; 12 | 13 | zip::ZipArchive ar{}; 14 | ar.add_file(file_name, data.data(), data.size()); 15 | 16 | std::vector bytes; 17 | ar.to_bytes(bytes); 18 | 19 | zip::ZipArchive ar2 = zip::ZipArchive::from_bytes(bytes); 20 | const zip::ZipFileRecord& record = ar2.get_file(file_name); 21 | L_ASSERT(record.size == 3); 22 | for (size_t i = 0; i < data.size(); ++i) { 23 | L_ASSERT(data.at(i) == ((const uint8_t*)record.data)[i]); 24 | } 25 | } 26 | 27 | L_TEST(ZipMinimalFile) { 28 | std::vector min_zip = { 0x50, 0x4B, 0x05, 0x06, 0x00, 0x00, 29 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 30 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 31 | 0x00, 0x00, 0x00, 0x00 }; 32 | zip::ZipArchive ar = zip::ZipArchive::from_bytes(min_zip); 33 | L_ASSERT(ar.records.size() == 0); 34 | } 35 | 36 | L_TEST(ZipExtractReal) { 37 | // A Zip file generated on macOS with the `zip` command with three files: 38 | // - `_1`: "1" 39 | // - `_2`: 40 | // - `_3`: "123" 41 | std::vector real_zip = { 42 | 0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x13, 43 | 0x5F, 0x55, 0xB7, 0xEF, 0xDC, 0x83, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 44 | 0x00, 0x00, 0x02, 0x00, 0x1C, 0x00, 0x5F, 0x31, 0x55, 0x54, 0x09, 0x00, 45 | 0x03, 0xBE, 0xC1, 0x5E, 0x63, 0xC5, 0xC1, 0x5E, 0x63, 0x75, 0x78, 0x0B, 46 | 0x00, 0x01, 0x04, 0xF5, 0x01, 0x00, 0x00, 0x04, 0x14, 0x00, 0x00, 0x00, 47 | 0x31, 0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 48 | 0x13, 0x5F, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 49 | 0x00, 0x00, 0x00, 0x02, 0x00, 0x1C, 0x00, 0x5F, 0x32, 0x55, 0x54, 0x09, 50 | 0x00, 0x03, 0xC5, 0xC1, 0x5E, 0x63, 0xC5, 0xC1, 0x5E, 0x63, 0x75, 0x78, 51 | 0x0B, 0x00, 0x01, 0x04, 0xF5, 0x01, 0x00, 0x00, 0x04, 0x14, 0x00, 0x00, 52 | 0x00, 0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 53 | 0x13, 0x5F, 0x55, 0xD2, 0x63, 0x48, 0x88, 0x03, 0x00, 0x00, 0x00, 0x03, 54 | 0x00, 0x00, 0x00, 0x02, 0x00, 0x1C, 0x00, 0x5F, 0x33, 0x55, 0x54, 0x09, 55 | 0x00, 0x03, 0xD6, 0xC1, 0x5E, 0x63, 0xD7, 0xC1, 0x5E, 0x63, 0x75, 0x78, 56 | 0x0B, 0x00, 0x01, 0x04, 0xF5, 0x01, 0x00, 0x00, 0x04, 0x14, 0x00, 0x00, 57 | 0x00, 0x31, 0x32, 0x33, 0x50, 0x4B, 0x01, 0x02, 0x1E, 0x03, 0x0A, 0x00, 58 | 0x00, 0x00, 0x00, 0x00, 0x43, 0x13, 0x5F, 0x55, 0xB7, 0xEF, 0xDC, 0x83, 59 | 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x18, 0x00, 60 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0x81, 0x00, 0x00, 61 | 0x00, 0x00, 0x5F, 0x31, 0x55, 0x54, 0x05, 0x00, 0x03, 0xBE, 0xC1, 0x5E, 62 | 0x63, 0x75, 0x78, 0x0B, 0x00, 0x01, 0x04, 0xF5, 0x01, 0x00, 0x00, 0x04, 63 | 0x14, 0x00, 0x00, 0x00, 0x50, 0x4B, 0x01, 0x02, 0x1E, 0x03, 0x0A, 0x00, 64 | 0x00, 0x00, 0x00, 0x00, 0x47, 0x13, 0x5F, 0x55, 0x00, 0x00, 0x00, 0x00, 65 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x18, 0x00, 66 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0x81, 0x3D, 0x00, 67 | 0x00, 0x00, 0x5F, 0x32, 0x55, 0x54, 0x05, 0x00, 0x03, 0xC5, 0xC1, 0x5E, 68 | 0x63, 0x75, 0x78, 0x0B, 0x00, 0x01, 0x04, 0xF5, 0x01, 0x00, 0x00, 0x04, 69 | 0x14, 0x00, 0x00, 0x00, 0x50, 0x4B, 0x01, 0x02, 0x1E, 0x03, 0x0A, 0x00, 70 | 0x00, 0x00, 0x00, 0x00, 0x4F, 0x13, 0x5F, 0x55, 0xD2, 0x63, 0x48, 0x88, 71 | 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x18, 0x00, 72 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0x81, 0x79, 0x00, 73 | 0x00, 0x00, 0x5F, 0x33, 0x55, 0x54, 0x05, 0x00, 0x03, 0xD6, 0xC1, 0x5E, 74 | 0x63, 0x75, 0x78, 0x0B, 0x00, 0x01, 0x04, 0xF5, 0x01, 0x00, 0x00, 0x04, 75 | 0x14, 0x00, 0x00, 0x00, 0x50, 0x4B, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 76 | 0x03, 0x00, 0x03, 0x00, 0xD8, 0x00, 0x00, 0x00, 0xB8, 0x00, 0x00, 0x00, 77 | 0x00, 0x00, 78 | }; 79 | 80 | zip::ZipArchive ar = zip::ZipArchive::from_bytes(real_zip); 81 | L_ASSERT(ar.records.size() == 3); 82 | { 83 | const zip::ZipFileRecord& record = ar.get_file("_1"); 84 | L_ASSERT(record.size == 1); 85 | std::string s( 86 | (const char*)record.data, (const char*)record.data + record.size 87 | ); 88 | L_ASSERT(s == "1"); 89 | L_ASSERT(record.crc32 == 0x83dcefb7); 90 | } 91 | { 92 | const zip::ZipFileRecord& record = ar.get_file("_2"); 93 | L_ASSERT(record.size == 0); 94 | L_ASSERT(record.crc32 == 0x00000000); 95 | } 96 | { 97 | const zip::ZipFileRecord& record = ar.get_file("_3"); 98 | L_ASSERT(record.size == 3); 99 | std::string s( 100 | (const char*)record.data, (const char*)record.data + record.size 101 | ); 102 | L_ASSERT(s == "123"); 103 | L_ASSERT(record.crc32 == 0x884863d2); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /bin2c.cpp: -------------------------------------------------------------------------------- 1 | // # bin2c 2 | // @PENGUINLIONG 3 | #include "gft/assert.hpp" 4 | #include "gft/util.hpp" 5 | 6 | using namespace liong; 7 | 8 | int main(int argc, const char** argv) { 9 | L_ASSERT(argc == 3); 10 | 11 | auto src_path = argv[1]; 12 | std::vector buf; 13 | auto bin = util::load_file(src_path); 14 | 15 | std::stringstream ss{}; 16 | ss << "// This is a generated file; changes may be overwritten.\n" 17 | "const uint8_t data[] = {"; 18 | for (auto x : buf) { 19 | ss << x << ","; 20 | } 21 | ss << "};"; 22 | 23 | auto dst_path = argv[2]; 24 | auto c = ss.str(); 25 | util::save_file(dst_path, c.data(), c.size()); 26 | 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /include/gft/args.hpp: -------------------------------------------------------------------------------- 1 | // Argument parsing utilities. 2 | // @PENGUINLIONG 3 | #pragma once 4 | #include 5 | #include 6 | 7 | namespace liong { 8 | 9 | namespace args { 10 | 11 | struct ArgumentParseConfig { 12 | // Expected number of arguments segments. 13 | uint32_t narg; 14 | // Returns true if the parsing is successful. 15 | bool (*parser)(const char*[], void*); 16 | // Returns the literal of default value. 17 | std::string (*lit)(const void*); 18 | // Destination to be written with parsed value. 19 | void* dst; 20 | }; 21 | 22 | // Optionally initialize argument parser with application name and usage 23 | // description. 24 | extern void init_arg_parse(const char* app_name, const char* desc); 25 | // Get the name of this app set by the user. Empty string is returned if this 26 | // function is called before `init_arg_parse`. 27 | extern const char* get_app_name(); 28 | // Print help message to the standard output. 29 | extern void print_help(); 30 | // Erase the type of argument parser and bind the type-erased parser to the 31 | // value destination. User code MUST ensure the `dst` buffer can contain the 32 | // parsing result. 33 | template 34 | ArgumentParseConfig make_parse_cfg(void* dst) { 35 | ArgumentParseConfig parse_cfg; 36 | parse_cfg.narg = TTypedParser::narg; 37 | parse_cfg.dst = dst; 38 | parse_cfg.parser = &TTypedParser::parse; 39 | parse_cfg.lit = &TTypedParser::lit; 40 | return parse_cfg; 41 | } 42 | // Register customized argument parsing. 43 | extern void reg_arg( 44 | const char* short_flag, 45 | const char* long_flag, 46 | const ArgumentParseConfig& parse_cfg, 47 | const char* help 48 | ); 49 | // Register a structural argument parsing. 50 | template 51 | inline void reg_arg( 52 | const char* short_flag, 53 | const char* long_flag, 54 | typename TTypedParser::arg_ty& dst, 55 | const char* help 56 | ) { 57 | reg_arg(short_flag, long_flag, make_parse_cfg(&dst), help); 58 | } 59 | // Parse arguments. Arguments will be matched against argument parsers 60 | // registered before. 61 | extern void parse_args(int argc, const char** argv); 62 | 63 | // 64 | // Parsers. 65 | // 66 | 67 | template 68 | struct TypedArgumentParser { 69 | typedef struct { 70 | } arg_ty; 71 | // Number of argument entries needed for this argument. 72 | static const uint32_t narg = -1; 73 | // Parser function. Convert the literal in the first parameter into structured 74 | // native representation. Return `true` on success. 75 | static bool parse(const char* lit[], void* dst) { 76 | return false; 77 | } 78 | static std::string lit(const void* src) { 79 | return {}; 80 | } 81 | }; 82 | template<> 83 | struct TypedArgumentParser { 84 | typedef std::string arg_ty; 85 | static const uint32_t narg = 1; 86 | static bool parse(const char* lit[], void* dst) { 87 | *(std::string*)dst = lit[0]; 88 | return true; 89 | } 90 | static std::string lit(const void* src) { 91 | return *(const std::string*)src; 92 | } 93 | }; 94 | template<> 95 | struct TypedArgumentParser { 96 | typedef int arg_ty; 97 | static const uint32_t narg = 1; 98 | static bool parse(const char* lit[], void* dst) { 99 | *(int32_t*)dst = std::atoi(lit[0]); 100 | return true; 101 | } 102 | static std::string lit(const void* src) { 103 | return std::to_string(*(const arg_ty*)src); 104 | } 105 | }; 106 | template<> 107 | struct TypedArgumentParser { 108 | typedef uint32_t arg_ty; 109 | static const uint32_t narg = 1; 110 | static bool parse(const char* lit[], void* dst) { 111 | *(uint32_t*)dst = std::atoi(lit[0]); 112 | return true; 113 | } 114 | static std::string lit(const void* src) { 115 | return std::to_string(*(const arg_ty*)src); 116 | } 117 | }; 118 | template<> 119 | struct TypedArgumentParser { 120 | typedef float arg_ty; 121 | static const uint32_t narg = 1; 122 | static bool parse(const char* lit[], void* dst) { 123 | *(float*)dst = (float)std::atof(lit[0]); 124 | return true; 125 | } 126 | static std::string lit(const void* src) { 127 | return std::to_string(*(const arg_ty*)src); 128 | } 129 | }; 130 | // NOTE: This is used for arguments like `-f true` and `-f false`. If you need a 131 | // boolean argument that don't need to be set explicitly. Use 132 | // `SwitchArgumentParser` instead. 133 | template<> 134 | struct TypedArgumentParser { 135 | typedef bool arg_ty; 136 | static const uint32_t narg = 1; 137 | static bool parse(const char* lit[], void* dst) { 138 | if (strcmp(lit[0], "true") == 0 || strcmp(lit[0], "True") == 0) { 139 | *(bool*)dst = true; 140 | return true; 141 | } else if (strcmp(lit[0], "false") == 0 || strcmp(lit[0], "False") == 0) { 142 | *(bool*)dst = false; 143 | return true; 144 | } else { 145 | return false; 146 | } 147 | } 148 | static std::string lit(const void* src) { 149 | if (*(const arg_ty*)src) { 150 | return "true"; 151 | } else { 152 | return "false"; 153 | } 154 | } 155 | }; 156 | struct SwitchArgumentParser { 157 | typedef bool arg_ty; 158 | static const uint32_t narg = 0; 159 | static bool parse(const char* lit[], void* dst) { 160 | *(bool*)dst = true; 161 | return true; 162 | } 163 | static std::string lit(const void* src) { 164 | return {}; 165 | } 166 | }; 167 | 168 | using IntParser = TypedArgumentParser; 169 | using UintParser = TypedArgumentParser; 170 | using FloatParser = TypedArgumentParser; 171 | using BoolParser = TypedArgumentParser; 172 | using StringParser = TypedArgumentParser; 173 | using SwitchParser = SwitchArgumentParser; 174 | 175 | } // namespace args 176 | 177 | } // namespace liong -------------------------------------------------------------------------------- /include/gft/assert.hpp: -------------------------------------------------------------------------------- 1 | // # Assertion 2 | // @PENGUINLIONG 3 | #pragma once 4 | #include "gft/util.hpp" 5 | 6 | namespace liong { 7 | 8 | class AssertionFailedException : public std::exception { 9 | const char* file; 10 | uint32_t line; 11 | std::string msg; 12 | 13 | public: 14 | inline AssertionFailedException( 15 | const char* file, 16 | uint32_t line, 17 | const std::string& msg 18 | ) : 19 | file(file), line(line), msg(msg) {} 20 | 21 | const char* what() const noexcept override { 22 | return msg.c_str(); 23 | } 24 | }; 25 | 26 | #ifdef NDEBUG 27 | // Release configs. 28 | #define L_ASSERT(pred, ...) 29 | #define L_PANIC(pred, ...) 30 | #else 31 | // Debug configs. 32 | #define L_ASSERT(pred, ...) \ 33 | if (!(pred)) { \ 34 | throw liong::AssertionFailedException( \ 35 | __FILE__, __LINE__, liong::util::format(__VA_ARGS__) \ 36 | ); \ 37 | } 38 | #define L_PANIC(...) L_ASSERT(false, __VA_ARGS__) 39 | #endif 40 | 41 | template 42 | [[noreturn]] inline void panic(const TArgs&... args) { 43 | L_ASSERT(false, args...); 44 | } 45 | template 46 | [[noreturn]] inline void unreachable(const TArgs&... args) { 47 | L_ASSERT(false, "reached unreachable code: ", args...); 48 | } 49 | [[noreturn]] inline void unimplemented() { 50 | L_ASSERT(false, "reached unimplemented code"); 51 | } 52 | 53 | } // namespace liong 54 | -------------------------------------------------------------------------------- /include/gft/fmt.hpp: -------------------------------------------------------------------------------- 1 | // Color and depth-stencil pixel format specification. 2 | // @PENGUINLIONG 3 | #pragma once 4 | #include 5 | #include "gft/assert.hpp" 6 | #include "glm/glm.hpp" 7 | 8 | namespace liong { 9 | 10 | namespace fmt { 11 | 12 | enum Format { 13 | L_FORMAT_UNDEFINED, 14 | L_FORMAT_R8G8B8A8_UNORM, 15 | L_FORMAT_B8G8R8A8_UNORM, 16 | L_FORMAT_B10G11R11_UFLOAT_PACK32, 17 | L_FORMAT_R16G16B16A16_SFLOAT, 18 | L_FORMAT_R32_SFLOAT, 19 | L_FORMAT_R32G32_SFLOAT, 20 | L_FORMAT_R32G32B32A32_SFLOAT, 21 | }; 22 | 23 | enum DepthFormat { 24 | L_DEPTH_FORMAT_UNDEFINED, 25 | L_DEPTH_FORMAT_D16_UNORM, 26 | L_DEPTH_FORMAT_D32_SFLOAT, 27 | }; 28 | 29 | constexpr size_t get_fmt_size(Format fmt) { 30 | return fmt == L_FORMAT_R8G8B8A8_UNORM ? 4 31 | : fmt == L_FORMAT_B8G8R8A8_UNORM ? 4 32 | : fmt == L_FORMAT_B10G11R11_UFLOAT_PACK32 ? 4 33 | : fmt == L_FORMAT_R16G16B16A16_SFLOAT ? 8 34 | : fmt == L_FORMAT_R32_SFLOAT ? 4 35 | : fmt == L_FORMAT_R32G32_SFLOAT ? 8 36 | : fmt == L_FORMAT_R32G32B32A32_SFLOAT ? 16 37 | : 0; 38 | } 39 | constexpr size_t get_fmt_depth_nbit(DepthFormat fmt) { 40 | return fmt == L_DEPTH_FORMAT_D16_UNORM ? 16 41 | : fmt == L_DEPTH_FORMAT_D32_SFLOAT ? 32 42 | : 0; 43 | } 44 | constexpr size_t get_fmt_stencil_nbit(DepthFormat fmt) { 45 | return 0; 46 | } 47 | 48 | template 49 | struct FormatCodec {}; 50 | 51 | template<> 52 | struct FormatCodec { 53 | static void encode(const glm::vec4* src, const void* dst, uint32_t npx) { 54 | for (uint32_t i = 0; i < npx; ++i) { 55 | glm::vec4 vec = src[i]; 56 | ((uint32_t*)dst)[i] = 57 | (((uint32_t)(std::min(std::max(vec.x, 0.0f), 1.0f) * 255.0f)) << 0) | 58 | (((uint32_t)(std::min(std::max(vec.y, 0.0f), 1.0f) * 255.0f)) << 8) | 59 | (((uint32_t)(std::min(std::max(vec.z, 0.0f), 1.0f) * 255.0f)) << 16) | 60 | (((uint32_t)(std::min(std::max(vec.w, 0.0f), 1.0f) * 255.0f)) << 24); 61 | } 62 | } 63 | static void decode(const void* src, glm::vec4* dst, uint32_t npx) { 64 | for (uint32_t i = 0; i < npx; ++i) { 65 | uint32_t pack = ((const uint32_t*)src)[i]; 66 | dst[i] = glm::vec4 { 67 | (float)((pack >> 0) & 0xFF) / 255.0f, 68 | (float)((pack >> 8) & 0xFF) / 255.0f, 69 | (float)((pack >> 16) & 0xFF) / 255.0f, 70 | (float)((pack >> 24) & 0xFF) / 255.0f, 71 | }; 72 | } 73 | } 74 | }; 75 | 76 | // NOTE: It doesn't deal with NaN and infinities correctly. Just doesn't care. 77 | template<> 78 | struct FormatCodec { 79 | static void encode(const glm::vec4* src, const void* dst, uint32_t npx) { 80 | auto float2half = [](uint32_t x) { 81 | uint32_t sign_bit = x >> 31; 82 | uint32_t exponent = (x >> 23) & 255; 83 | if (exponent == 255 || exponent < (127 - 15) || exponent >= 127 + 15) { 84 | return 0; 85 | } 86 | uint32_t fraction = x & 8388607; 87 | unimplemented(); 88 | }; 89 | for (uint32_t i = 0; i < npx; ++i) { 90 | uint16_t f0 = float2half(*(const uint32_t*)(&src[i].x)); 91 | uint16_t f1 = float2half(*(const uint32_t*)(&src[i].y)); 92 | uint16_t f2 = float2half(*(const uint32_t*)(&src[i].z)); 93 | uint16_t f3 = float2half(*(const uint32_t*)(&src[i].w)); 94 | ((uint16_t*)dst)[i * 4 + 0] = f0; 95 | ((uint16_t*)dst)[i * 4 + 1] = f1; 96 | ((uint16_t*)dst)[i * 4 + 2] = f2; 97 | ((uint16_t*)dst)[i * 4 + 3] = f3; 98 | } 99 | } 100 | static void decode(const void* src, glm::vec4* dst, uint32_t npx) { 101 | auto half2float = [](uint16_t x) { 102 | uint32_t sign_bit = x >> 15; 103 | uint32_t exponent = (x >> 10) & 31; 104 | uint32_t mantissa = x & 1023; 105 | if (exponent == 31) { 106 | return (uint32_t)0; 107 | } // NaN or infinity. 108 | exponent = exponent - 15 + 127; 109 | mantissa <<= 23 - 10; 110 | uint32_t out = (sign_bit << 31) | (exponent << 23) | mantissa; 111 | return out; 112 | }; 113 | for (uint32_t i = 0; i < npx; ++i) { 114 | uint32_t f0 = half2float(((const uint16_t*)src)[i * 4 + 0]); 115 | uint32_t f1 = half2float(((const uint16_t*)src)[i * 4 + 1]); 116 | uint32_t f2 = half2float(((const uint16_t*)src)[i * 4 + 2]); 117 | uint32_t f3 = half2float(((const uint16_t*)src)[i * 4 + 3]); 118 | dst[i] = glm::vec4 { 119 | *(const float*)(&f0), 120 | *(const float*)(&f1), 121 | *(const float*)(&f2), 122 | *(const float*)(&f3), 123 | }; 124 | } 125 | } 126 | }; 127 | template<> 128 | struct FormatCodec { 129 | static void encode(const glm::vec4* src, void* dst, uint32_t npx) { 130 | for (uint32_t i = 0; i < npx; ++i) { 131 | ((float*)dst)[i] = src[i].x; 132 | } 133 | } 134 | static void decode(const void* src, glm::vec4* dst, uint32_t npx) { 135 | for (uint32_t i = 0; i < npx; ++i) { 136 | glm::vec4 vec { 137 | ((const float*)src)[i], 138 | 0.0f, 139 | 0.0f, 140 | 0.0f, 141 | }; 142 | dst[i] = std::move(vec); 143 | } 144 | } 145 | }; 146 | 147 | template<> 148 | struct FormatCodec { 149 | static void encode(const glm::vec4* src, void* dst, uint32_t npx) { 150 | for (uint32_t i = 0; i < npx; ++i) { 151 | ((float*)dst)[i * 2 + 0] = src[i].x; 152 | ((float*)dst)[i * 2 + 1] = src[i].y; 153 | } 154 | } 155 | static void decode(const void* src, glm::vec4* dst, uint32_t npx) { 156 | for (uint32_t i = 0; i < npx; ++i) { 157 | glm::vec4 vec { 158 | ((const float*)src)[i * 2 + 0], 159 | ((const float*)src)[i * 2 + 1], 160 | 0.0f, 161 | 0.0f, 162 | }; 163 | dst[i] = std::move(vec); 164 | } 165 | } 166 | }; 167 | 168 | template<> 169 | struct FormatCodec { 170 | static void encode(const glm::vec4* src, void* dst, uint32_t npx) { 171 | for (uint32_t i = 0; i < npx; ++i) { 172 | ((float*)dst)[i * 4 + 0] = src[i].x; 173 | ((float*)dst)[i * 4 + 1] = src[i].y; 174 | ((float*)dst)[i * 4 + 2] = src[i].z; 175 | ((float*)dst)[i * 4 + 3] = src[i].w; 176 | } 177 | } 178 | static void decode(const void* src, glm::vec4* dst, uint32_t npx) { 179 | for (uint32_t i = 0; i < npx; ++i) { 180 | glm::vec4 vec { 181 | ((const float*)src)[i * 4 + 0], 182 | ((const float*)src)[i * 4 + 1], 183 | ((const float*)src)[i * 4 + 2], 184 | ((const float*)src)[i * 4 + 3], 185 | }; 186 | dst[i] = std::move(vec); 187 | } 188 | } 189 | }; 190 | 191 | enum ColorSpace { 192 | L_COLOR_SPACE_LINEAR, 193 | L_COLOR_SPACE_SRGB, 194 | }; 195 | 196 | } // namespace fmt 197 | 198 | } // namespace liong 199 | -------------------------------------------------------------------------------- /include/gft/geom.hpp: -------------------------------------------------------------------------------- 1 | // Geometry algorithms all in right-hand-side systems. 2 | // @PENGUINLIONG 3 | #pragma once 4 | #include 5 | #include "glm/glm.hpp" 6 | 7 | namespace liong { 8 | namespace geom { 9 | 10 | struct Ray { 11 | glm::vec3 p; 12 | glm::vec3 v; 13 | }; 14 | struct Triangle { 15 | glm::vec3 a; 16 | glm::vec3 b; 17 | glm::vec3 c; 18 | }; 19 | struct Aabb { 20 | glm::vec3 min; 21 | glm::vec3 max; 22 | 23 | constexpr glm::vec3 center() const { 24 | return max * 0.5f + min * 0.5f; 25 | } 26 | constexpr glm::vec3 size() const { 27 | return max - min; 28 | } 29 | 30 | static constexpr Aabb from_min_max( 31 | const glm::vec3& min, 32 | const glm::vec3& max 33 | ) { 34 | return Aabb { min, max }; 35 | } 36 | static constexpr Aabb from_center_size( 37 | const glm::vec3& center, 38 | const glm::vec3& size 39 | ) { 40 | return Aabb { center - 0.5f * size, center + 0.5f * size }; 41 | } 42 | static inline Aabb from_points(const glm::vec3* points, size_t npoint) { 43 | Aabb out {}; 44 | glm::vec3 min { 45 | std::numeric_limits::infinity(), 46 | std::numeric_limits::infinity(), 47 | std::numeric_limits::infinity(), 48 | }; 49 | glm::vec3 max { 50 | -std::numeric_limits::infinity(), 51 | -std::numeric_limits::infinity(), 52 | -std::numeric_limits::infinity(), 53 | }; 54 | for (size_t i = 0; i < npoint; ++i) { 55 | const glm::vec3& point = points[i]; 56 | min = glm::min(point, min); 57 | max = glm::max(point, max); 58 | } 59 | return Aabb::from_min_max(min, max); 60 | } 61 | static inline Aabb from_points(const std::vector& points) { 62 | return from_points(points.data(), points.size()); 63 | } 64 | }; 65 | struct Sphere { 66 | glm::vec3 p; 67 | float r; 68 | }; 69 | struct Tetrahedron { 70 | glm::vec3 a; 71 | glm::vec3 b; 72 | glm::vec3 c; 73 | glm::vec3 d; 74 | 75 | constexpr glm::vec3 center() const { 76 | return (a + b + c + d) * 0.25f; 77 | } 78 | }; 79 | struct Plane { 80 | glm::vec3 n; 81 | glm::vec3 u; 82 | glm::vec3 v; 83 | }; 84 | 85 | enum Facing { 86 | L_FACING_NONE, 87 | L_FACING_FRONT, 88 | L_FACING_BACK, 89 | L_FACING_FRONT_AND_BACK, 90 | }; 91 | 92 | 93 | extern bool raycast_tri( 94 | const Ray& ray, 95 | const Triangle& tri, 96 | float& t, 97 | glm::vec2& bary 98 | ); 99 | extern bool raycast_aabb(const Ray& ray, const Aabb& aabb, float& t); 100 | extern bool raycast_sphere(const Ray& ray, const Sphere& sphere, float& t); 101 | extern bool raycast_tet(const Ray& ray, const Tetrahedron& tet, float& t); 102 | 103 | extern bool contains_point_aabb(const Aabb& aabb, const glm::vec3& point); 104 | extern bool contains_point_sphere(const Sphere& sphere, const glm::vec3& point); 105 | extern bool contains_point_tetra( 106 | const Tetrahedron& tet, 107 | const glm::vec3& point, 108 | glm::vec4& bary 109 | ); 110 | 111 | extern bool intersect_tri(const Triangle& tri1, const Triangle& tri2); 112 | extern bool intersect_aabb_tri(const Triangle& tri, const Aabb& aabb); 113 | extern bool intersect_aabb(const Aabb& aabb1, const Aabb& aabb2); 114 | 115 | extern void split_tetra2tris( 116 | const Tetrahedron& tet, 117 | std::vector& tris 118 | ); 119 | extern void split_aabb2tetras(const Aabb& aabb, std::vector& tets); 120 | extern void split_aabb2points(const Aabb& aabb, std::vector& out); 121 | 122 | extern void subdivide_aabb( 123 | const Aabb& aabb, 124 | const glm::uvec3& nslice, 125 | std::vector& out 126 | ); 127 | extern void tile_aabb_ceil( 128 | const Aabb& aabb, 129 | const glm::vec3& tile_size, 130 | std::vector& out 131 | ); 132 | 133 | } // namespace geom 134 | } // namespace liong 135 | -------------------------------------------------------------------------------- /include/gft/glslang.hpp: -------------------------------------------------------------------------------- 1 | // GLSL shader compilation. 2 | // @PENGUINLIONG 3 | #pragma once 4 | #include 5 | #include 6 | #include 7 | 8 | namespace liong { 9 | 10 | namespace glslang { 11 | 12 | enum GlslangTarget { 13 | L_GLSLANG_TARGET_VULKAN_1_0, 14 | L_GLSLANG_TARGET_VULKAN_1_1, 15 | L_GLSLANG_TARGET_VULKAN_1_2, 16 | }; 17 | 18 | void initialize(GlslangTarget target); 19 | void initialize(); 20 | 21 | struct ComputeSpirvArtifact { 22 | std::vector comp_spv; 23 | size_t ubo_size; 24 | }; 25 | ComputeSpirvArtifact compile_comp( 26 | const std::string& comp_src, 27 | const std::string& comp_entry_point 28 | ); 29 | ComputeSpirvArtifact compile_comp_hlsl( 30 | const std::string& comp_src, 31 | const std::string& comp_entry_point 32 | ); 33 | 34 | struct GraphicsSpirvArtifact { 35 | std::vector vert_spv; 36 | std::vector frag_spv; 37 | size_t ubo_size; 38 | }; 39 | GraphicsSpirvArtifact compile_graph( 40 | const std::string& vert_src, 41 | const std::string& vert_entry_point, 42 | const std::string& frag_src, 43 | const std::string& frag_entry_point 44 | ); 45 | GraphicsSpirvArtifact compile_graph_hlsl( 46 | const std::string& vert_src, 47 | const std::string& vert_entry_point, 48 | const std::string& frag_src, 49 | const std::string& frag_entry_point 50 | ); 51 | 52 | } // namespace glslang 53 | 54 | } // namespace liong 55 | -------------------------------------------------------------------------------- /include/gft/hal/buffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/hal/hal.hpp" 3 | #include "gft/log.hpp" 4 | 5 | namespace liong { 6 | namespace hal { 7 | 8 | struct MappedBuffer { 9 | BufferRef buf; 10 | void* mapped; 11 | 12 | MappedBuffer(const BufferRef& buf, MemoryAccess map_access); 13 | MappedBuffer(MappedBuffer&& x); 14 | ~MappedBuffer(); 15 | 16 | inline void copy_to(void* dst, size_t size) const { 17 | std::memcpy(dst, mapped, size); 18 | } 19 | template 20 | inline void copy_to(T* dst, size_t count) const { 21 | copy_to((void*)dst, sizeof(T) * count); 22 | } 23 | template 24 | inline void copy_to(std::vector& dst) const { 25 | copy_to(dst.data(), dst.size()); 26 | } 27 | template 28 | inline void copy_to_aligned(T* dst, size_t count, size_t dev_align) { 29 | for (size_t i = 0; i < count; ++i) { 30 | std::memcpy(&dst[i], (uint8_t*)mapped + i * dev_align, sizeof(T)); 31 | } 32 | } 33 | 34 | inline void copy_from(const void* src, size_t size) const { 35 | std::memcpy(mapped, src, size); 36 | } 37 | template 38 | inline void copy_from(const T* src, size_t count) const { 39 | copy_from((const void*)src, sizeof(T) * count); 40 | } 41 | template 42 | inline void copy_from(const std::vector& src) const { 43 | copy_from(src.data(), src.size()); 44 | } 45 | template 46 | inline void copy_from_aligned(const T* src, size_t count, size_t dev_align) { 47 | for (size_t i = 0; i < count; ++i) { 48 | std::memcpy((uint8_t*)mapped + i * dev_align, &src[i], sizeof(T)); 49 | } 50 | } 51 | }; 52 | 53 | 54 | struct BufferInfo { 55 | std::string label; 56 | size_t size; 57 | MemoryAccess host_access; 58 | BufferUsage usage; 59 | }; 60 | struct Buffer : public std::enable_shared_from_this { 61 | const BufferInfo info; 62 | 63 | Buffer(BufferInfo&& info) : info(std::move(info)) {} 64 | virtual ~Buffer() {} 65 | 66 | virtual void* map(MemoryAccess access) = 0; 67 | virtual void unmap() = 0; 68 | 69 | inline MappedBuffer map_read() { 70 | return MappedBuffer(shared_from_this(), L_MEMORY_ACCESS_READ_BIT); 71 | } 72 | inline MappedBuffer map_write() { 73 | return MappedBuffer(shared_from_this(), L_MEMORY_ACCESS_WRITE_BIT); 74 | } 75 | inline MappedBuffer map_read_write() { 76 | return MappedBuffer( 77 | shared_from_this(), L_MEMORY_ACCESS_READ_BIT | L_MEMORY_ACCESS_WRITE_BIT 78 | ); 79 | } 80 | 81 | inline void copy_to(void* dst, size_t size) { 82 | if (size == 0) { 83 | L_WARN("zero-sized copy is ignored"); 84 | return; 85 | } 86 | L_ASSERT(info.size >= size, "buffser size is small than dst buffer size"); 87 | this->map_read().copy_to(dst, size); 88 | } 89 | template 90 | inline void copy_to(T* dst, size_t count) { 91 | this->map_read().copy_to(dst, count); 92 | } 93 | template 94 | inline void copy_to(std::vector& dst) { 95 | this->map_read().copy_to(dst); 96 | } 97 | template 98 | inline void copy_to_aligned(T* dst, size_t count, size_t dev_align) { 99 | this->map_read().copy_to_aligned(dst, count, dev_align); 100 | } 101 | 102 | inline void copy_from(const void* src, size_t size) { 103 | if (size == 0) { 104 | L_WARN("zero-sized copy is ignored"); 105 | return; 106 | } 107 | L_ASSERT(info.size >= size, "buffser size is small than src buffer size"); 108 | this->map_write().copy_from(src, size); 109 | } 110 | template 111 | inline void copy_from(const T* src, size_t count) { 112 | this->map_write().copy_from(src, count); 113 | } 114 | template 115 | inline void copy_from(const std::vector& src) { 116 | this->map_write().copy_from(src); 117 | } 118 | template 119 | inline void copy_from_aligned(const T* src, size_t count, size_t dev_align) { 120 | this->map_write().copy_from_aligned(src, count, dev_align); 121 | } 122 | 123 | inline BufferView view(size_t offset, size_t size) { 124 | BufferView out {}; 125 | out.buf = shared_from_this(); 126 | out.offset = offset; 127 | out.size = size; 128 | return out; 129 | } 130 | inline BufferView view() { 131 | return this->view(0, info.size); 132 | } 133 | }; 134 | 135 | } // namespace hal 136 | } // namespace liong 137 | -------------------------------------------------------------------------------- /include/gft/hal/context.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/hal/hal.hpp" 3 | 4 | namespace liong { 5 | namespace hal { 6 | 7 | struct ContextInfo { 8 | std::string label; 9 | uint32_t device_index; 10 | }; 11 | struct Context : public std::enable_shared_from_this { 12 | const ContextInfo info; 13 | 14 | Context(ContextInfo&& info) : info(std::move(info)) {} 15 | virtual ~Context() {} 16 | 17 | virtual BufferRef create_buffer(const BufferConfig& cfg) = 0; 18 | virtual ImageRef create_image(const ImageConfig& cfg) = 0; 19 | virtual DepthImageRef create_depth_image(const DepthImageConfig& cfg) = 0; 20 | 21 | virtual SwapchainRef create_swapchain(const SwapchainConfig& cfg) = 0; 22 | virtual RenderPassRef create_render_pass(const RenderPassConfig& cfg) = 0; 23 | 24 | virtual TaskRef create_compute_task(const ComputeTaskConfig& cfg) = 0; 25 | 26 | virtual InvocationRef create_transfer_invocation(const TransferInvocationConfig& cfg) = 0; 27 | virtual InvocationRef create_composite_invocation(const CompositeInvocationConfig& cfg) = 0; 28 | }; 29 | 30 | } // namespace hal 31 | } // namespace liong 32 | -------------------------------------------------------------------------------- /include/gft/hal/depth-image.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/hal/hal.hpp" 3 | 4 | namespace liong { 5 | namespace hal { 6 | 7 | struct DepthImageInfo { 8 | std::string label; 9 | uint32_t width; 10 | uint32_t height; 11 | fmt::DepthFormat depth_format; 12 | DepthImageUsage usage; 13 | }; 14 | struct DepthImage : public std::enable_shared_from_this { 15 | const DepthImageInfo info; 16 | 17 | DepthImage(DepthImageInfo&& info) : info(std::move(info)) {} 18 | virtual ~DepthImage() {} 19 | 20 | virtual DepthImageView view( 21 | uint32_t x_offset, 22 | uint32_t y_offset, 23 | uint32_t width, 24 | uint32_t height, 25 | DepthImageSampler sampler 26 | ) { 27 | DepthImageView out {}; 28 | out.depth_img = shared_from_this(); 29 | out.x_offset = x_offset; 30 | out.y_offset = y_offset; 31 | out.width = width; 32 | out.height = height; 33 | out.sampler = sampler; 34 | return out; 35 | } 36 | inline DepthImageView view( 37 | uint32_t x_offset, 38 | uint32_t y_offset, 39 | uint32_t width, 40 | uint32_t height 41 | ) { 42 | return view( 43 | x_offset, y_offset, width, height, L_DEPTH_IMAGE_SAMPLER_LINEAR 44 | ); 45 | } 46 | inline DepthImageView view(DepthImageSampler sampler) { 47 | return view(0, 0, info.width, info.height, sampler); 48 | } 49 | inline DepthImageView view() { 50 | return view(L_DEPTH_IMAGE_SAMPLER_LINEAR); 51 | } 52 | }; 53 | 54 | } // namespace hal 55 | } // namespace liong 56 | -------------------------------------------------------------------------------- /include/gft/hal/image.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/hal/hal.hpp" 3 | 4 | namespace liong { 5 | namespace hal { 6 | 7 | struct ImageInfo { 8 | std::string label; 9 | uint32_t width; 10 | uint32_t height; 11 | uint32_t depth; 12 | fmt::Format format; 13 | fmt::ColorSpace color_space; 14 | ImageUsage usage; 15 | }; 16 | struct Image : public std::enable_shared_from_this { 17 | const ImageInfo info; 18 | 19 | Image(ImageInfo&& info) : info(std::move(info)) {} 20 | virtual ~Image() {} 21 | 22 | inline ImageView view( 23 | uint32_t x_offset, 24 | uint32_t y_offset, 25 | uint32_t z_offset, 26 | uint32_t width, 27 | uint32_t height, 28 | uint32_t depth, 29 | ImageSampler sampler 30 | ) { 31 | ImageView out {}; 32 | out.img = shared_from_this(); 33 | out.x_offset = x_offset; 34 | out.y_offset = y_offset; 35 | out.z_offset = z_offset; 36 | out.width = width; 37 | out.height = height; 38 | out.depth = depth; 39 | out.sampler = sampler; 40 | return out; 41 | } 42 | inline ImageView view( 43 | uint32_t x_offset, 44 | uint32_t y_offset, 45 | uint32_t z_offset, 46 | uint32_t width, 47 | uint32_t height, 48 | uint32_t depth 49 | ) { 50 | return view( 51 | x_offset, y_offset, z_offset, width, height, depth, L_IMAGE_SAMPLER_LINEAR 52 | ); 53 | } 54 | inline ImageView view(ImageSampler sampler) { 55 | return view(0, 0, 0, info.width, info.height, info.depth, sampler); 56 | } 57 | inline ImageView view() { 58 | return view(L_IMAGE_SAMPLER_LINEAR); 59 | } 60 | }; 61 | 62 | } // namespace hal 63 | } // namespace liong 64 | -------------------------------------------------------------------------------- /include/gft/hal/instance.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/hal/hal.hpp" 3 | 4 | namespace liong { 5 | namespace hal { 6 | 7 | struct Instance : std::enable_shared_from_this { 8 | Instance() {} 9 | virtual ~Instance() {} 10 | 11 | // Generate Human-readable string to describe the properties and capabilities 12 | // of the device at index `idx`. If there is no device at `idx`, an empty 13 | // string is returned. 14 | virtual std::string describe_device(uint32_t device_index) = 0; 15 | 16 | virtual ContextRef create_context(const ContextConfig& cfg) = 0; 17 | virtual ContextRef create_context(const ContextWindowsConfig& cfg) = 0; 18 | virtual ContextRef create_context(const ContextAndroidConfig& cfg) = 0; 19 | virtual ContextRef create_context(const ContextMetalConfig& cfg) = 0; 20 | }; 21 | 22 | } // namespace hal 23 | } // namespace liong 24 | -------------------------------------------------------------------------------- /include/gft/hal/invocation.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/hal/hal.hpp" 3 | 4 | namespace liong { 5 | namespace hal { 6 | 7 | struct Invocation; 8 | typedef std::shared_ptr InvocationRef; 9 | 10 | struct InvocationInfo { 11 | std::string label; 12 | // Submit type of this invocation or the first non-any subinvocation. 13 | SubmitType submit_ty; 14 | }; 15 | struct Invocation : std::enable_shared_from_this { 16 | const InvocationInfo info; 17 | 18 | Invocation(InvocationInfo&& info) : info(std::move(info)) {} 19 | virtual ~Invocation() {} 20 | 21 | // Submit the invocation to device for execution and create a transaction for 22 | // the user to track the execution status. 23 | virtual TransactionRef create_transact(const TransactionConfig& cfg) = 0; 24 | 25 | // Get the execution time of the last WAITED invocation. 26 | virtual double get_time_us() = 0; 27 | // Pre-encode the invocation commands to reduce host-side overhead on constant 28 | // device-side procedures. 29 | virtual void bake() = 0; 30 | }; 31 | 32 | } // namespace hal 33 | } // namespace liong 34 | -------------------------------------------------------------------------------- /include/gft/hal/render-pass.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/hal/hal.hpp" 3 | #include "gft/fmt.hpp" 4 | 5 | namespace liong { 6 | namespace hal { 7 | 8 | struct RenderPassInfo { 9 | std::string label; 10 | uint32_t width; 11 | uint32_t height; 12 | size_t attm_count; 13 | }; 14 | struct RenderPass : public std::enable_shared_from_this { 15 | const RenderPassInfo info; 16 | 17 | RenderPass(RenderPassInfo&& info) : info(std::move(info)) {} 18 | virtual ~RenderPass() {} 19 | 20 | virtual TaskRef create_graphics_task(const GraphicsTaskConfig& cfg) = 0; 21 | virtual InvocationRef create_render_pass_invocation( 22 | const RenderPassInvocationConfig& cfg 23 | ) = 0; 24 | }; 25 | 26 | } // namespace hal 27 | } // namespace liong 28 | -------------------------------------------------------------------------------- /include/gft/hal/renderer.hpp: -------------------------------------------------------------------------------- 1 | // # A simple renderer for debugging. 2 | // @PENGUINLIONG 3 | #pragma once 4 | #include "gft/hal/scoped-hal.hpp" 5 | #include "gft/mesh.hpp" 6 | 7 | #ifndef HAL_IMPL_NAMESPACE 8 | static_assert(false, "please specify the implementation namespace (e.g. `vk`)"); 9 | #endif 10 | 11 | namespace liong { 12 | namespace HAL_IMPL_NAMESPACE { 13 | namespace scoped { 14 | 15 | struct MeshGpu { 16 | const uint32_t nvert; 17 | scoped::Buffer poses; 18 | scoped::Buffer uvs; 19 | scoped::Buffer norms; 20 | 21 | MeshGpu( 22 | const scoped::Context& ctxt, 23 | uint32_t nvert, 24 | bool streaming = true, 25 | bool gc = true 26 | ); 27 | MeshGpu(const scoped::Context& ctxt, const mesh::Mesh& mesh, bool gc = true); 28 | 29 | void write(const mesh::Mesh& mesh); 30 | }; 31 | struct IndexedMeshGpu { 32 | MeshGpu mesh; 33 | const uint32_t ntri; 34 | scoped::Buffer idxs; 35 | 36 | IndexedMeshGpu( 37 | const scoped::Context& ctxt, 38 | uint32_t nvert, 39 | uint32_t ntri, 40 | bool streaming = true, 41 | bool gc = true 42 | ); 43 | IndexedMeshGpu( 44 | const scoped::Context& ctxt, 45 | const mesh::IndexedMesh& idxmesh, 46 | bool gc = true 47 | ); 48 | 49 | void write(const mesh::IndexedMesh& idxmesh); 50 | }; 51 | struct SkinnedMeshGpu { 52 | scoped::Context ctxt; 53 | IndexedMeshGpu idxmesh; 54 | const uint32_t nbone; 55 | 56 | scoped::Buffer rest_poses; 57 | scoped::Buffer ibones; 58 | scoped::Buffer bone_weights; 59 | 60 | scoped::Buffer bone_mats; 61 | 62 | mesh::Skinning skinning; 63 | mesh::SkeletalAnimationCollection skel_anims; 64 | 65 | SkinnedMeshGpu( 66 | const scoped::Context& ctxt, 67 | uint32_t nvert, 68 | uint32_t ntri, 69 | uint32_t nbone, 70 | bool streaming = true, 71 | bool gc = true 72 | ); 73 | SkinnedMeshGpu( 74 | const scoped::Context& ctxt, 75 | const mesh::SkinnedMesh& skinmesh, 76 | bool gc = true 77 | ); 78 | 79 | void write(const mesh::SkinnedMesh& skinmesh); 80 | 81 | scoped::Invocation animate(const std::string& anim_name, float tick); 82 | scoped::Invocation animate(float tick); 83 | }; 84 | 85 | struct TextureGpu { 86 | scoped::Context ctxt; 87 | scoped::Buffer stage_buf; 88 | scoped::Image tex; 89 | 90 | TextureGpu( 91 | const scoped::Context& ctxt, 92 | uint32_t width, 93 | uint32_t height, 94 | bool streaming = true, 95 | bool gc = true 96 | ); 97 | TextureGpu( 98 | const scoped::Context& ctxt, 99 | uint32_t width, 100 | uint32_t height, 101 | const std::vector& pxs, 102 | bool gc = true 103 | ); 104 | 105 | void write(const std::vector& pxs); 106 | }; 107 | 108 | struct RendererInvocationDetail { 109 | std::unique_ptr rpib; 110 | }; 111 | struct Renderer { 112 | scoped::Context ctxt; 113 | scoped::RenderPass pass; 114 | scoped::DepthImage zbuf_img; 115 | scoped::Task lit_task; 116 | scoped::Task wireframe_task; 117 | scoped::Task point_cloud_task; 118 | 119 | scoped::TextureGpu default_tex; 120 | 121 | uint32_t width; 122 | uint32_t height; 123 | glm::vec3 camera_pos; 124 | glm::vec3 model_pos; 125 | glm::vec3 light_dir; 126 | glm::vec3 ambient; 127 | glm::vec3 albedo; 128 | 129 | std::unique_ptr rpib; 130 | 131 | Renderer(const scoped::Context& ctxt, uint32_t width, uint32_t height); 132 | 133 | glm::mat4 get_model2world() const; 134 | glm::mat4 get_world2view() const; 135 | 136 | void set_camera_pos(const glm::vec3& x); 137 | void set_model_pos(const glm::vec3& x); 138 | 139 | Renderer& begin_frame(const scoped::Image& render_target_img); 140 | scoped::Invocation end_frame(); 141 | 142 | Renderer& is_timed(bool is_timed = true); 143 | 144 | Renderer& draw_mesh(const mesh::Mesh& mesh); 145 | 146 | Renderer& draw_idxmesh( 147 | const scoped::IndexedMeshGpu& idxmesh, 148 | const scoped::TextureGpu& tex 149 | ); 150 | Renderer& draw_idxmesh(const scoped::IndexedMeshGpu& idxmesh); 151 | 152 | Renderer& draw_idxmesh( 153 | const mesh::IndexedMesh& idxmesh, 154 | const scoped::TextureGpu& tex 155 | ); 156 | Renderer& draw_idxmesh(const mesh::IndexedMesh& idxmesh); 157 | 158 | Renderer& draw_mesh_wireframe( 159 | const mesh::Mesh& mesh, 160 | const std::vector& colors 161 | ); 162 | Renderer& draw_mesh_wireframe(const mesh::Mesh& mesh, const glm::vec3& color); 163 | Renderer& draw_mesh_wireframe(const mesh::Mesh& mesh); 164 | 165 | Renderer& draw_idxmesh_wireframe( 166 | const mesh::IndexedMesh& idxmesh, 167 | const std::vector& colors 168 | ); 169 | Renderer& draw_idxmesh_wireframe( 170 | const mesh::IndexedMesh& idxmesh, 171 | const glm::vec3& color 172 | ); 173 | Renderer& draw_idxmesh_wireframe(const mesh::IndexedMesh& idxmesh); 174 | 175 | Renderer& draw_point_cloud( 176 | const mesh::PointCloud& point_cloud, 177 | const std::vector& colors 178 | ); 179 | Renderer& draw_point_cloud( 180 | const mesh::PointCloud& point_cloud, 181 | const glm::vec3& colors 182 | ); 183 | Renderer& draw_point_cloud(const mesh::PointCloud& point_cloud); 184 | }; 185 | 186 | struct RenderInvocationBuilder { 187 | RenderInvocationBuilder( 188 | const Renderer& renderer, 189 | const std::string& label = "" 190 | ); 191 | }; 192 | 193 | } // namespace scoped 194 | } // namespace HAL_IMPL_NAMESPACE 195 | } // namespace liong 196 | -------------------------------------------------------------------------------- /include/gft/hal/swapchain.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/hal/hal.hpp" 3 | 4 | namespace liong { 5 | namespace hal { 6 | 7 | struct SwapchainInfo { 8 | std::string label; 9 | uint32_t image_count; 10 | fmt::Format format; 11 | fmt::ColorSpace color_space; 12 | }; 13 | struct Swapchain : public std::enable_shared_from_this { 14 | const SwapchainInfo info; 15 | 16 | Swapchain(SwapchainInfo&& info) : info(std::move(info)) {} 17 | virtual ~Swapchain() {} 18 | 19 | virtual ImageRef get_current_image() = 0; 20 | virtual uint32_t get_width() const = 0; 21 | virtual uint32_t get_height() const = 0; 22 | 23 | virtual InvocationRef create_present_invocation( 24 | const PresentInvocationConfig& cfg 25 | ) = 0; 26 | }; 27 | 28 | } // namespace hal 29 | } // namespace liong 30 | -------------------------------------------------------------------------------- /include/gft/hal/task.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/hal/context.hpp" 3 | 4 | namespace liong { 5 | namespace hal { 6 | 7 | struct Task; 8 | typedef std::shared_ptr TaskRef; 9 | 10 | struct TaskInfo { 11 | std::string label; 12 | SubmitType submit_ty; 13 | }; 14 | struct Task : std::enable_shared_from_this { 15 | const TaskInfo info; 16 | 17 | Task(TaskInfo&& info) : info(std::move(info)) {} 18 | virtual ~Task() {} 19 | 20 | virtual InvocationRef create_graphics_invocation( 21 | const GraphicsInvocationConfig& cfg 22 | ) = 0; 23 | virtual InvocationRef create_compute_invocation( 24 | const ComputeInvocationConfig& cfg 25 | ) = 0; 26 | }; 27 | 28 | } // namespace hal 29 | } // namespace liong 30 | -------------------------------------------------------------------------------- /include/gft/hal/transaction.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/hal/context.hpp" 3 | 4 | namespace liong { 5 | namespace hal { 6 | 7 | struct Transaction; 8 | typedef std::shared_ptr TransactionRef; 9 | 10 | struct TransactionInfo { 11 | std::string label; 12 | }; 13 | struct Transaction : std::enable_shared_from_this { 14 | const TransactionInfo info; 15 | 16 | Transaction(TransactionInfo&& info) : info(std::move(info)) {} 17 | virtual ~Transaction() {} 18 | 19 | // Check whether the transaction is finished. `true` is returned if so. 20 | virtual bool is_done() = 0; 21 | // Wait the invocation submitted to device for execution. Returns immediately 22 | // if the invocation has already been waited. 23 | virtual void wait() = 0; 24 | }; 25 | 26 | } // namespace hal 27 | } // namespace liong 28 | -------------------------------------------------------------------------------- /include/gft/log.hpp: -------------------------------------------------------------------------------- 1 | // Logging infrastructure. 2 | // @PENGUINLIONG 3 | #pragma once 4 | #include 5 | #include "gft/util.hpp" 6 | 7 | #ifndef L_MIN_LOG_LEVEL 8 | #define L_MIN_LOG_LEVEL 0 9 | #endif // L_MIN_LOG_LEVEL 10 | 11 | namespace liong { 12 | namespace log { 13 | 14 | enum LogLevel { 15 | L_LOG_LEVEL_DEBUG = 0, 16 | L_LOG_LEVEL_INFO = 1, 17 | L_LOG_LEVEL_WARNING = 2, 18 | L_LOG_LEVEL_ERROR = 3, 19 | }; 20 | 21 | typedef void (*LogCallback)(LogLevel lv, const std::string& msg); 22 | 23 | namespace detail { 24 | 25 | extern LogCallback l_log_callback__; 26 | extern LogLevel l_filter_lv__; 27 | extern std::string l_indent__; 28 | 29 | } // namespace detail 30 | 31 | void set_log_callback(LogCallback cb); 32 | void set_log_filter_level(LogLevel lv); 33 | template 34 | void log(LogLevel lv, const TArgs&... msg) { 35 | if (detail::l_log_callback__ != nullptr && lv >= detail::l_filter_lv__) { 36 | detail::l_log_callback__(lv, util::format(detail::l_indent__, msg...)); 37 | } 38 | } 39 | 40 | void push_indent(); 41 | void pop_indent(); 42 | 43 | template 44 | inline void debug(const TArgs&... msg) { 45 | #ifdef L_MIN_LOG_LEVEL 46 | if constexpr ((int)LogLevel::L_LOG_LEVEL_DEBUG >= L_MIN_LOG_LEVEL) 47 | #endif // L_MIN_LOG_LEVEL 48 | log(LogLevel::L_LOG_LEVEL_DEBUG, msg...); 49 | } 50 | template 51 | inline void info(const TArgs&... msg) { 52 | #ifdef L_MIN_LOG_LEVEL 53 | if constexpr ((int)LogLevel::L_LOG_LEVEL_INFO >= L_MIN_LOG_LEVEL) 54 | #endif // L_MIN_LOG_LEVEL 55 | log(LogLevel::L_LOG_LEVEL_INFO, msg...); 56 | } 57 | template 58 | inline void warn(const TArgs&... msg) { 59 | #ifdef L_MIN_LOG_LEVEL 60 | if constexpr ((int)LogLevel::L_LOG_LEVEL_WARNING >= L_MIN_LOG_LEVEL) 61 | #endif // L_MIN_LOG_LEVEL 62 | log(LogLevel::L_LOG_LEVEL_WARNING, msg...); 63 | } 64 | template 65 | inline void error(const TArgs&... msg) { 66 | #ifdef L_MIN_LOG_LEVEL 67 | if constexpr ((int)LogLevel::L_LOG_LEVEL_ERROR >= L_MIN_LOG_LEVEL) 68 | #endif // L_MIN_LOG_LEVEL 69 | log(LogLevel::L_LOG_LEVEL_ERROR, msg...); 70 | } 71 | 72 | } // namespace log 73 | } // namespace liong 74 | 75 | #if !defined(L_MIN_LOG_LEVEL) || L_MIN_LOG_LEVEL <= 0 76 | #define L_DEBUG(...) ::liong::log::debug(__VA_ARGS__) 77 | #else 78 | #define L_DEBUG(...) 79 | #endif // defined(L_MIN_LOG_LEVEL) && L_MIN_LOG_LEVEL >= 0 80 | 81 | #if !defined(L_MIN_LOG_LEVEL) || L_MIN_LOG_LEVEL <= 1 82 | #define L_INFO(...) ::liong::log::info(__VA_ARGS__) 83 | #else 84 | #define L_INFO(...) 85 | #endif // defined(L_MIN_LOG_LEVEL) && L_MIN_LOG_LEVEL >= 1 86 | 87 | #if !defined(L_MIN_LOG_LEVEL) || L_MIN_LOG_LEVEL <= 2 88 | #define L_WARN(...) ::liong::log::warn(__VA_ARGS__) 89 | #else 90 | #define L_WARN(...) 91 | #endif // defined(L_MIN_LOG_LEVEL) && L_MIN_LOG_LEVEL >= 2 92 | 93 | #if !defined(L_MIN_LOG_LEVEL) || L_MIN_LOG_LEVEL <= 3 94 | #define L_ERROR(...) ::liong::log::error(__VA_ARGS__) 95 | #else 96 | #define L_ERROR(...) 97 | #endif // defined(L_MIN_LOG_LEVEL) && L_MIN_LOG_LEVEL >= 3 98 | -------------------------------------------------------------------------------- /include/gft/mesh.hpp: -------------------------------------------------------------------------------- 1 | // # 3D Mesh utilities 2 | // @PENGUINLIONG 3 | #pragma once 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "glm/glm.hpp" 10 | #include "glm/ext.hpp" 11 | #include "gft/assert.hpp" 12 | #include "gft/geom.hpp" 13 | 14 | namespace liong { 15 | namespace mesh { 16 | 17 | struct Mesh { 18 | std::vector poses; 19 | std::vector uvs; 20 | std::vector norms; 21 | std::vector colors; 22 | 23 | static Mesh from_tris(const geom::Triangle* tris, size_t ntri); 24 | inline static Mesh from_tris(const std::vector& tris) { 25 | return from_tris(tris.data(), tris.size()); 26 | } 27 | std::vector to_tris() const; 28 | 29 | geom::Aabb aabb() const; 30 | }; 31 | 32 | extern bool try_parse_obj(const std::string& obj, Mesh& mesh); 33 | extern Mesh load_obj(const char* path); 34 | 35 | 36 | struct IndexedMesh { 37 | Mesh mesh; 38 | std::vector idxs; 39 | 40 | static IndexedMesh from_mesh(const Mesh& mesh); 41 | 42 | inline geom::Aabb aabb() const { 43 | return mesh.aabb(); 44 | } 45 | }; 46 | 47 | 48 | struct PointCloud { 49 | std::vector poses; 50 | 51 | geom::Aabb aabb() const; 52 | }; 53 | 54 | 55 | struct Grid { 56 | std::vector grid_lines_x; 57 | std::vector grid_lines_y; 58 | std::vector grid_lines_z; 59 | }; 60 | 61 | extern Grid build_grid(const geom::Aabb& aabb, const glm::uvec3& grid_res); 62 | extern Grid build_grid(const geom::Aabb& aabb, const glm::vec3& grid_interval); 63 | 64 | struct Bin { 65 | geom::Aabb aabb; 66 | std::vector iprims; 67 | }; 68 | struct BinGrid { 69 | Grid grid; 70 | std::vector bins; 71 | 72 | inline std::vector to_aabbs() const { 73 | std::vector out; 74 | for (const auto& bin : bins) { 75 | out.emplace_back(bin.aabb); 76 | } 77 | return out; 78 | } 79 | 80 | inline const Bin& get_bin(uint32_t x, uint32_t y, uint32_t z) const { 81 | L_ASSERT(x < grid.grid_lines_x.size()); 82 | L_ASSERT(y < grid.grid_lines_y.size()); 83 | L_ASSERT(z < grid.grid_lines_z.size()); 84 | const Bin& bin = 85 | bins[(z * grid.grid_lines_y.size() + y) * grid.grid_lines_x.size() + x]; 86 | return bin; 87 | } 88 | 89 | // Returns all bins with primitives contained as well as those in between 90 | // them. There shall be no concave structure in the list of returned bins. 91 | std::vector get_solid() const; 92 | }; 93 | 94 | extern BinGrid bin_point_cloud( 95 | const geom::Aabb& aabb, 96 | const glm::uvec3& grid_res, 97 | const PointCloud& point_cloud 98 | ); 99 | extern BinGrid bin_point_cloud( 100 | const glm::vec3& grid_interval, 101 | const PointCloud& point_cloud 102 | ); 103 | 104 | extern BinGrid bin_mesh( 105 | const geom::Aabb& aabb, 106 | const glm::uvec3& grid_res, 107 | const Mesh& mesh 108 | ); 109 | extern BinGrid bin_mesh(const glm::vec3& grid_interval, const Mesh& mesh); 110 | 111 | extern BinGrid bin_idxmesh( 112 | const geom::Aabb& aabb, 113 | const glm::uvec3& grid_res, 114 | const IndexedMesh& idxmesh 115 | ); 116 | extern BinGrid bin_idxmesh( 117 | const glm::vec3& grid_interval, 118 | const IndexedMesh& idxmesh 119 | ); 120 | 121 | 122 | struct TetrahedralVertex { 123 | glm::vec3 pos; 124 | // Indices to adjacent cells. 125 | std::set ineighbor_cells; 126 | // Indices to adjacent vertices. 127 | std::set ineighbor_verts; 128 | }; 129 | struct TetrahedralCell { 130 | glm::uvec4 itetra_verts; 131 | glm::vec3 center; 132 | }; 133 | struct TetrahedralInterpolant { 134 | // Indices to tettrahedron vertices. 135 | uint32_t itetra_cell; 136 | // Barycentric weights of tetrahedron vertices. 137 | glm::vec4 tetra_weights; 138 | }; 139 | struct TetrahedralMesh { 140 | // Per tetrahedral mesh vertex. 141 | std::vector tetra_verts; 142 | // Per tetrahedral mesh cell. 143 | std::vector tetra_cells; 144 | // Per triangle mesh vertex. 145 | std::vector interps; 146 | 147 | static TetrahedralMesh from_points( 148 | const glm::vec3& grid_interval, 149 | const std::vector& points 150 | ); 151 | std::vector to_points() const; 152 | 153 | void apply_trans(const glm::mat4& trans); 154 | 155 | std::vector to_tetras() const; 156 | Mesh to_mesh() const; 157 | }; 158 | 159 | 160 | struct Bone { 161 | std::string name; 162 | // Parent bone index; -1 if it's a root bone. 163 | int32_t parent; 164 | // Parent bone space to current bone space transform. 165 | glm::mat4 parent_trans; 166 | // Model space to bone space transform. 167 | glm::mat4 offset_trans; 168 | }; 169 | struct Skinning { 170 | std::vector bones; 171 | // Per-vertex bone indices. 172 | std::vector ibones; 173 | // Per-vertex bone weights. 174 | std::vector bone_weights; 175 | }; 176 | 177 | struct BoneKeyFrame { 178 | float tick; 179 | glm::vec3 scale; 180 | glm::quat rotate; 181 | glm::vec3 pos; 182 | 183 | glm::mat4 to_transform() const; 184 | 185 | static BoneKeyFrame lerp( 186 | const BoneKeyFrame& a, 187 | const BoneKeyFrame& b, 188 | float alpha 189 | ); 190 | }; 191 | struct BoneAnimation { 192 | std::vector key_frames; 193 | 194 | glm::mat4 get_local_transform(float tick) const; 195 | }; 196 | struct SkeletalAnimation { 197 | std::string name; 198 | float tick_per_sec; 199 | // For each bone. 200 | std::vector bone_anims; 201 | 202 | glm::mat4 get_bone_transform( 203 | const Skinning& skinning, 204 | uint32_t ibone, 205 | float tick 206 | ) const; 207 | void get_bone_transforms( 208 | const Skinning& skinning, 209 | float tick, 210 | std::vector& out 211 | ) const; 212 | }; 213 | struct SkeletalAnimationCollection { 214 | std::vector skel_anims; 215 | 216 | const SkeletalAnimation& get_skel_anim(const std::string& skel_anim) const; 217 | }; 218 | 219 | struct SkinnedMesh { 220 | IndexedMesh idxmesh; 221 | Skinning skinning; 222 | SkeletalAnimationCollection skel_anims; 223 | 224 | std::vector animate(const std::string& anim_name, float tick); 225 | std::vector animate(float tick); 226 | }; 227 | 228 | } // namespace mesh 229 | } // namespace liong 230 | -------------------------------------------------------------------------------- /include/gft/platform/macos.hpp: -------------------------------------------------------------------------------- 1 | // #macOS platform specific functionalities. 2 | // @PENGUINLIONG 3 | #pragma once 4 | #if defined(__MACH__) && defined(__APPLE__) 5 | #include 6 | #include 7 | 8 | namespace liong { 9 | namespace macos { 10 | 11 | #define NSWindow void 12 | #define CAMetalLayer void 13 | 14 | struct Window { 15 | NSWindow* window; 16 | CAMetalLayer* metal_layer; 17 | }; 18 | 19 | Window create_window(uint32_t width, uint32_t height); 20 | Window create_window(); 21 | 22 | #undef NSWindow 23 | #undef CAMetalLayer 24 | 25 | } // namespace macos 26 | } // namespace liong 27 | #endif // defined(__MACH__) && defined(__APPLE__) 28 | -------------------------------------------------------------------------------- /include/gft/platform/windows.hpp: -------------------------------------------------------------------------------- 1 | // # Windows platform specific functionalities. 2 | // @PENGUINLIONG 3 | #pragma once 4 | #ifdef _WIN32 5 | #define WIN32_LEAN_AND_MEAN 6 | #define NOCOMM 7 | #define NOMINMAX 8 | #include 9 | #undef NOMINMAX 10 | #undef NOCOMM 11 | #undef WIN32_LEAN_AND_MEAN 12 | #include "gft/assert.hpp" 13 | 14 | namespace liong { 15 | namespace windows { 16 | 17 | struct Window { 18 | struct Extra {}; 19 | uint32_t width; 20 | uint32_t height; 21 | HINSTANCE hinst; 22 | HWND hwnd; 23 | }; 24 | extern Window create_window(uint32_t width, uint32_t height); 25 | extern Window create_window(); 26 | 27 | } // namespace windows 28 | } // namespace liong 29 | #endif // _WIN32 -------------------------------------------------------------------------------- /include/gft/pool.hpp: -------------------------------------------------------------------------------- 1 | // General purpose object pool. 2 | // @PENGUINLIONG 3 | #pragma once 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace liong { 10 | namespace pool { 11 | 12 | template 13 | struct PoolInner { 14 | std::map> items; 15 | }; 16 | 17 | template 18 | struct PoolItemInner { 19 | PoolInner* pool; 20 | TKey key; 21 | TValue value; 22 | 23 | PoolItemInner(PoolInner* pool, TKey&& key, TValue&& value) : 24 | pool(pool), key(std::move(key)), value(std::move(value)) {} 25 | ~PoolItemInner() { 26 | pool->items[std::move(key)].emplace_back(std::move(value)); 27 | } 28 | }; 29 | 30 | template 31 | struct PoolItem { 32 | std::shared_ptr> inner; 33 | 34 | PoolItem() = default; 35 | PoolItem(PoolInner* pool, TKey&& key, TValue&& value) : 36 | inner(std::make_shared>( 37 | pool, 38 | std::move(key), 39 | std::move(value) 40 | )) {} 41 | 42 | inline bool is_valid() const { 43 | return inner != nullptr; 44 | } 45 | 46 | inline TValue& value() { 47 | return inner->value; 48 | } 49 | inline const TValue& value() const { 50 | return inner->value; 51 | } 52 | 53 | inline void release() { 54 | inner.reset(); 55 | } 56 | }; 57 | 58 | template 59 | struct Pool { 60 | PoolInner inner; 61 | 62 | inline bool has_free_item(const TKey& key) const { 63 | auto it = inner.items.find(key); 64 | if (it != inner.items.end() && !it->second.empty()) { 65 | return true; 66 | } 67 | return false; 68 | } 69 | inline PoolItem create(TKey&& key, TValue&& value) { 70 | return PoolItem(&inner, std::move(key), std::move(value)); 71 | } 72 | inline PoolItem acquire(TKey&& key) { 73 | std::vector& pool = inner.items.at(key); 74 | TValue value = std::move(pool.back()); 75 | pool.pop_back(); 76 | return create(std::move(key), std::move(value)); 77 | } 78 | }; 79 | 80 | } // namespace pool 81 | } // namespace liong 82 | -------------------------------------------------------------------------------- /include/gft/renderdoc.hpp: -------------------------------------------------------------------------------- 1 | // RenderDoc Integration, only available on Windows. 2 | // @PENGUINLIONG 3 | 4 | namespace liong { 5 | 6 | namespace renderdoc { 7 | 8 | // Initialize RenderDoc. Repeated calls are silently ignored. 9 | // 10 | // WARNING: This API should be called BEFORE ANY CALL TO HAL `initialize`, or 11 | // RenderDoc would fail to hook the graphics APIs and any attempt to capture 12 | // will fail. Also note that, unlike many other modules in GraphiT, this 13 | // `initialize` is not implicitly called by other functions because a strict 14 | // execution order has to be enforced. 15 | extern void initialize(); 16 | 17 | // Kick off a capture session and record all commands coming after this. 18 | // 19 | // WARNING: This API should be called AFTER the creation of HAL `Context`, or 20 | // RenderDoc would crash. 21 | extern void begin_capture(); 22 | // Stop current capture session and launch RenderDoc GUI. 23 | extern void end_capture(); 24 | 25 | // An RAII capture guard. Follow the same rule of `begin/end_capture` when using 26 | // this. 27 | struct CaptureGuard { 28 | inline CaptureGuard() { 29 | begin_capture(); 30 | } 31 | inline ~CaptureGuard() { 32 | end_capture(); 33 | } 34 | }; 35 | 36 | } // namespace renderdoc 37 | 38 | } // namespace liong 39 | -------------------------------------------------------------------------------- /include/gft/stats.hpp: -------------------------------------------------------------------------------- 1 | // # Tools for statistics 2 | // @PENGUINLIONG 3 | #pragma once 4 | #include 5 | #include 6 | #include 7 | #include "gft/log.hpp" 8 | 9 | namespace liong { 10 | 11 | namespace stats { 12 | 13 | template 14 | class MinStats { 15 | T mn_ = std::numeric_limits::max(); 16 | 17 | public: 18 | typedef T value_t; 19 | 20 | // Returns true if the value has been updated. 21 | bool push(T value) { 22 | if (mn_ > value) { 23 | mn_ = value; 24 | return true; 25 | } else { 26 | return false; 27 | } 28 | } 29 | inline bool has_value() const { 30 | return mn_ != std::numeric_limits::max(); 31 | } 32 | operator T() const { 33 | if (!has_value()) { 34 | L_WARN("`MinStats` has not collected any data yet"); 35 | } 36 | return mn_; 37 | } 38 | friend std::ostream& operator<<(std::ostream& out, const MinStats& x) { 39 | out << (T)(x); 40 | return out; 41 | } 42 | }; 43 | template 44 | class MaxStats { 45 | T mx_ = -std::numeric_limits::max(); 46 | 47 | public: 48 | typedef T value_t; 49 | 50 | // Returns true if the value has been updated. 51 | bool push(T value) { 52 | if (mx_ < value) { 53 | mx_ = value; 54 | return true; 55 | } else { 56 | return false; 57 | } 58 | } 59 | inline bool has_value() const { 60 | return mx_ != -std::numeric_limits::max(); 61 | } 62 | operator T() const { 63 | if (!has_value()) { 64 | L_WARN("`MaxStats` has not collected any data yet"); 65 | } 66 | return mx_; 67 | } 68 | friend std::ostream& operator<<(std::ostream& out, const MaxStats& x) { 69 | out << (T)(x); 70 | return out; 71 | } 72 | }; 73 | template 74 | class AvgStats { 75 | T sum_ = 0; 76 | uint64_t n_ = 0; 77 | 78 | public: 79 | typedef T value_t; 80 | 81 | void push(T value) { 82 | sum_ += value; 83 | n_ += 1; 84 | } 85 | inline bool has_value() const { 86 | return n_ != 0; 87 | } 88 | operator T() const { 89 | if (!has_value()) { 90 | L_WARN("`AvgStats` has not collected any data yet"); 91 | } 92 | return sum_ / n_; 93 | } 94 | friend std::ostream& operator<<(std::ostream& out, const AvgStats& x) { 95 | out << (T)(x); 96 | return out; 97 | } 98 | }; 99 | template 100 | class StdStats { 101 | AvgStats avg_ {}; 102 | std::vector values_ {}; 103 | 104 | public: 105 | typedef T value_t; 106 | 107 | void push(T value) { 108 | avg_.push(value); 109 | values_.push_back(value); 110 | } 111 | inline bool has_value() const { 112 | return avg_.has_value(); 113 | } 114 | operator T() const { 115 | if (!has_value()) { 116 | L_WARN("`StdStats` has not collected any data yet"); 117 | } 118 | T avg = avg_; 119 | T sqr_sum = 0; 120 | for (auto value : values_) { 121 | auto temp = value - avg; 122 | sqr_sum += temp * temp; 123 | } 124 | return std::sqrt(sqr_sum / values_.size()); 125 | } 126 | friend std::ostream& operator<<(std::ostream& out, const StdStats& x) { 127 | out << (T)(x); 128 | return out; 129 | } 130 | T avg() const { 131 | return avg_; 132 | } 133 | const std::vector& values() const { 134 | return values_; 135 | } 136 | }; 137 | template 138 | class MedianStats { 139 | std::vector values_ {}; 140 | 141 | public: 142 | typedef T value_t; 143 | 144 | void push(T value) { 145 | values_.push_back(value); 146 | } 147 | inline bool has_value() const { 148 | return !values_.empty(); 149 | } 150 | operator T() { 151 | if (!has_value()) { 152 | L_WARN("`MedianStats` has not collected any data yet"); 153 | } 154 | std::sort(values_.begin(), values_.end()); 155 | size_t imid = values_.size() / 2; 156 | if (values_.size() & 1) { 157 | return values_[imid]; 158 | } else { 159 | return (values_[imid] + values_[imid + 1]) / 2; 160 | } 161 | } 162 | friend std::ostream& operator<<(std::ostream& out, const MedianStats& x) { 163 | out << (T)(x); 164 | return out; 165 | } 166 | }; 167 | 168 | template 169 | class GeomDeltaStats { 170 | TStats stats_ {}; 171 | bool has_ratio_ = false; 172 | typename TStats::value_t ratio_ {}; 173 | 174 | public: 175 | typedef typename TStats::value_t value_t; 176 | 177 | void push(value_t value) { 178 | if (stats_.has_value()) { 179 | ratio_ = value / (value_t)stats_; 180 | has_ratio_ = true; 181 | } 182 | stats_.push(value); 183 | } 184 | inline bool has_value() const { 185 | return has_ratio_; 186 | } 187 | operator value_t() const { 188 | if (!has_value()) { 189 | L_WARN("`GeomDeltaStats` has not collected enough data yet"); 190 | } 191 | return ratio_; 192 | } 193 | friend std::ostream& operator<<( 194 | std::ostream& out, 195 | const GeomDeltaStats& x 196 | ) { 197 | if (x.has_value()) { 198 | out << (typename TStats::value_t)(x.ratio_); 199 | } 200 | return out; 201 | } 202 | }; 203 | template 204 | class ArithDeltaStats { 205 | TStats stats_ {}; 206 | bool has_delta_ = false; 207 | typename TStats::value_t delta_ {}; 208 | 209 | public: 210 | typedef typename TStats::value_t value_t; 211 | 212 | void push(value_t value) { 213 | if (stats_.has_value()) { 214 | delta_ = value - (value_t)stats_; 215 | has_delta_ = true; 216 | } 217 | stats_.push(value); 218 | } 219 | inline bool has_value() const { 220 | return has_delta_; 221 | } 222 | operator value_t() const { 223 | if (!has_value()) { 224 | L_WARN("`ArithDeltaStats` has not collected enough data yet"); 225 | } 226 | return delta_; 227 | } 228 | friend std::ostream& operator<<( 229 | std::ostream& out, 230 | const ArithDeltaStats& x 231 | ) { 232 | if (x.has_value()) { 233 | out << (typename TStats::value_t)(x.delta_); 234 | } 235 | return out; 236 | } 237 | }; 238 | 239 | } // namespace stats 240 | 241 | } // namespace liong 242 | -------------------------------------------------------------------------------- /include/gft/stream.hpp: -------------------------------------------------------------------------------- 1 | // In-memory data stream. 2 | // @PENGUINLIONG 3 | #pragma once 4 | #include 5 | #include 6 | #include 7 | 8 | namespace liong { 9 | namespace stream { 10 | 11 | struct ReadStream { 12 | private: 13 | const void* data_; 14 | size_t size_; 15 | size_t offset_; 16 | 17 | public: 18 | inline ReadStream(const void* data, size_t size) : 19 | data_(data), size_(size), offset_(0) {} 20 | 21 | inline const void* data() const { 22 | return data_; 23 | } 24 | inline const void* pos() const { 25 | return (const uint8_t*)data_ + offset_; 26 | } 27 | inline size_t size() const { 28 | return size_; 29 | } 30 | // Resetting `offset` is not allowed. Create another `ReadStream` instance 31 | // instead if necessary. 32 | inline size_t offset() const { 33 | return offset_; 34 | } 35 | inline size_t size_remain() const { 36 | return size_ - offset_; 37 | } 38 | inline bool ate() const { 39 | return size_ <= offset_; 40 | } 41 | 42 | void peek_data(void* out, size_t size); 43 | void extract_data(void* out, size_t size); 44 | 45 | ReadStream& skip(size_t n); 46 | template 47 | inline ReadStream& skip() { 48 | return skip(sizeof(T)); 49 | } 50 | 51 | template 52 | inline T peek() { 53 | T out {}; 54 | peek_data(&out); 55 | return out; 56 | } 57 | template 58 | inline bool try_peek(T& out) { 59 | if (size_remain() < sizeof(T)) { 60 | return false; 61 | } else { 62 | peek_data(&out, sizeof(T)); 63 | return true; 64 | } 65 | } 66 | template 67 | inline T extract() { 68 | T out {}; 69 | extract_data(&out, sizeof(T)); 70 | return out; 71 | } 72 | template 73 | inline bool try_extract(T& out) { 74 | if (size_remain() < sizeof(T)) { 75 | return false; 76 | } else { 77 | extract_data(&out); 78 | return true; 79 | } 80 | } 81 | template 82 | std::vector extract_all() { 83 | std::vector out {}; 84 | size_t n = size_remain() / sizeof(T); 85 | out.resize(n); 86 | extract_data(out.data(), n * sizeof(T)); 87 | return out; 88 | } 89 | template 90 | std::vector extract_all_map(const std::function& f) { 91 | std::vector tmp {}; 92 | std::vector out {}; 93 | size_t n = size_remain() / sizeof(T); 94 | tmp.resize(n); 95 | out.resize(n); 96 | extract_data(tmp.data(), n * sizeof(T)); 97 | for (size_t i = 0; i < tmp.size(); ++i) { 98 | out[i] = f(tmp[i]); 99 | } 100 | return out; 101 | } 102 | }; 103 | 104 | struct WriteStream { 105 | private: 106 | std::vector data_; 107 | 108 | public: 109 | inline size_t size() const { 110 | return data_.size(); 111 | } 112 | 113 | void append_data(const void* data, size_t size); 114 | 115 | template 116 | inline void append(const T& x) { 117 | append_data(&x, sizeof(T)); 118 | } 119 | template 120 | inline void append(const std::vector& data) { 121 | append_data(data.data(), data.size() * sizeof(T)); 122 | } 123 | 124 | inline std::vector take() { 125 | return std::move(data_); 126 | } 127 | }; 128 | 129 | } // namespace stream 130 | } // namespace liong 131 | -------------------------------------------------------------------------------- /include/gft/test.hpp: -------------------------------------------------------------------------------- 1 | // Unit test infrastructure. 2 | // @PENGUINLIONG 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace liong { 9 | 10 | namespace test { 11 | 12 | struct TestReport { 13 | uint64_t nsucc; 14 | uint64_t nfail; 15 | }; 16 | 17 | struct TestRegistry { 18 | struct Entry { 19 | std::function f; 20 | }; 21 | std::map tests; 22 | 23 | TestRegistry(); 24 | 25 | static TestRegistry& get_inst(); 26 | static TestReport run_all(); 27 | 28 | int reg(const std::string& name, std::function&& func); 29 | }; 30 | 31 | } // namespace test 32 | 33 | } // namespace liong 34 | 35 | #define L_TEST(name) \ 36 | extern void l_test_##name(); \ 37 | int L_TEST_MARKER_##name = \ 38 | ::liong::test::TestRegistry::get_inst().reg(#name, l_test_##name); \ 39 | void l_test_##name() 40 | -------------------------------------------------------------------------------- /include/gft/util.hpp: -------------------------------------------------------------------------------- 1 | // # HAL independent utilities 2 | // @PENGUINLIONG 3 | #pragma once 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace liong { 16 | 17 | namespace util { 18 | 19 | // - [String Processing] ------------------------------------------------------- 20 | 21 | bool starts_with(const std::string& start, const std::string& str); 22 | bool ends_with(const std::string& end, const std::string& str); 23 | std::vector split(char sep, const std::string& str); 24 | std::string trim(const std::string& str); 25 | std::string replace_all(const std::string& str, const std::string& from, const std::string& to); 26 | // Fill the template by replacing the placeholders with the given arguments. 27 | // The placeholder is in the form of "${name}". 28 | std::string fill_template(const std::string& templ, const std::map& args); 29 | 30 | namespace { 31 | 32 | template 33 | struct format_impl_t; 34 | template<> 35 | struct format_impl_t<> { 36 | static inline void format_impl(std::stringstream& ss) {} 37 | }; 38 | template 39 | struct format_impl_t { 40 | static inline void format_impl(std::stringstream& ss, const T& x) { 41 | ss << x; 42 | } 43 | }; 44 | template 45 | struct format_impl_t { 46 | static inline void format_impl( 47 | std::stringstream& ss, 48 | const T& x, 49 | const TArgs&... others 50 | ) { 51 | format_impl_t::format_impl(ss, x); 52 | format_impl_t::format_impl(ss, others...); 53 | } 54 | static inline void join_impl( 55 | std::stringstream& ss, 56 | const std::string& sep, 57 | const T& x, 58 | const TArgs&... others 59 | ) { 60 | format_impl_t::format_impl(ss, x); 61 | ss << sep; 62 | format_impl_t::format_impl(ss, others...); 63 | } 64 | }; 65 | 66 | } // namespace 67 | 68 | template 69 | std::string join(const std::string& sep, const std::array& strs) { 70 | std::stringstream ss {}; 71 | bool first = true; 72 | for (const auto& str : strs) { 73 | if (first) { 74 | first = false; 75 | } else { 76 | ss << sep; 77 | } 78 | ss << str; 79 | } 80 | return ss.str(); 81 | } 82 | template 83 | std::string join(const std::string& sep, const std::vector& strs) { 84 | std::stringstream ss {}; 85 | bool first = true; 86 | for (const auto& str : strs) { 87 | if (first) { 88 | first = false; 89 | } else { 90 | ss << sep; 91 | } 92 | ss << str; 93 | } 94 | return ss.str(); 95 | } 96 | template 97 | inline std::string join(const std::string& sep, const TArgs&... args) { 98 | std::stringstream ss {}; 99 | format_impl_t::join_impl(ss, sep, args...); 100 | return ss.str(); 101 | } 102 | template 103 | inline std::string format(const TArgs&... args) { 104 | std::stringstream ss {}; 105 | format_impl_t::format_impl(ss, args...); 106 | return ss.str(); 107 | } 108 | 109 | // - [File I/O] ---------------------------------------------------------------- 110 | 111 | extern std::vector load_file(const char* path); 112 | extern std::string load_text(const char* path); 113 | extern void save_file(const char* path, const void* data, size_t size); 114 | extern void save_text(const char* path, const std::string& txt); 115 | 116 | void save_bmp(const uint32_t* pxs, uint32_t w, uint32_t h, const char* path); 117 | void save_bmp(const float* pxs, uint32_t w, uint32_t h, const char* path); 118 | 119 | // - [Bitfield Manipulation] --------------------------------------------------- 120 | 121 | template 122 | T count_set_bits(T bitset) { 123 | T count = 0; 124 | for (T i = 0; i < sizeof(T) * 8; ++i) { 125 | if (((bitset >> i) & 1) != 0) { 126 | ++count; 127 | } 128 | } 129 | return count; 130 | } 131 | template 132 | T count_clear_bits(T bitset) { 133 | T count = 0; 134 | for (T i = 0; i < sizeof(T) * 8; ++i) { 135 | if (((bitset >> i) & 1) == 0) { 136 | ++count; 137 | } 138 | } 139 | return count; 140 | } 141 | 142 | // - [Data Transformation] ----------------------------------------------------- 143 | 144 | template 145 | std::vector arrange(T a, T b, T step) { 146 | std::vector out; 147 | for (T i = a; i < b; i += step) { 148 | out.emplace_back(i); 149 | } 150 | return out; 151 | } 152 | template 153 | std::vector arrange(T a, T b) { 154 | return arrange(a, b, 1); 155 | } 156 | template 157 | std::vector arrange(T b) { 158 | return arrange(0, b); 159 | } 160 | 161 | template 162 | std::vector map( 163 | const std::vector& xs, 164 | const std::function& f 165 | ) { 166 | std::vector out; 167 | out.reserve(xs.size()); 168 | for (const auto& x : xs) { 169 | out.emplace_back(f(x)); 170 | } 171 | return out; 172 | } 173 | 174 | template 175 | std::vector reinterpret_data(const void* data, size_t size) { 176 | std::vector out; 177 | // L_ASSERT(size % sizeof(T) == 0, 178 | // "cannot reinterpret data with size not aligned to the given type"); 179 | out.resize(size / sizeof(T)); 180 | std::memcpy(out.data(), data, size); 181 | return out; 182 | } 183 | template 184 | std::vector reinterpret_data(const std::vector& x) { 185 | return reinterpret_data(x.data(), x.size()); 186 | } 187 | 188 | // - [Timing & Temporal Control] ----------------------------------------------- 189 | 190 | struct Timer { 191 | std::chrono::time_point beg, end; 192 | 193 | inline void tic() { 194 | beg = std::chrono::high_resolution_clock::now(); 195 | } 196 | inline void toc() { 197 | end = std::chrono::high_resolution_clock::now(); 198 | } 199 | 200 | inline double us() const { 201 | std::chrono::duration dt = end - beg; 202 | return dt.count(); 203 | } 204 | }; 205 | 206 | void sleep_for_us(uint64_t t); 207 | 208 | // - [Index & Size Manipulation] ----------------------------------------------- 209 | 210 | constexpr size_t div_down(size_t x, size_t align) { 211 | return x / align; 212 | } 213 | constexpr size_t div_up(size_t x, size_t align) { 214 | return div_down(x + (align - 1), align); 215 | } 216 | constexpr size_t align_down(size_t x, size_t align) { 217 | return div_down(x, align) * align; 218 | } 219 | constexpr size_t align_up(size_t x, size_t align) { 220 | return div_up(x, align) * align; 221 | } 222 | 223 | constexpr void push_idx(size_t& aggr_idx, size_t i, size_t dim_size) { 224 | aggr_idx = aggr_idx * dim_size + i; 225 | } 226 | constexpr size_t pop_idx(size_t& aggr_idx, size_t dim_size) { 227 | size_t out = aggr_idx % dim_size; 228 | aggr_idx /= dim_size; 229 | return out; 230 | } 231 | 232 | // - [CRC32] ------------------------------------------------------------------- 233 | 234 | uint32_t crc32(const void* data, size_t size); 235 | 236 | } // namespace util 237 | 238 | } // namespace liong 239 | -------------------------------------------------------------------------------- /include/gft/vk/vk-buffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/hal/buffer.hpp" 3 | #include "gft/vk/vk-context.hpp" 4 | 5 | namespace liong { 6 | namespace vk { 7 | 8 | struct VulkanBuffer; 9 | typedef std::shared_ptr VulkanBufferRef; 10 | 11 | struct BufferDynamicDetail { 12 | VkPipelineStageFlags stage; 13 | VkAccessFlags access; 14 | }; 15 | struct VulkanBuffer : public Buffer { 16 | VulkanContextRef ctxt; 17 | 18 | sys::BufferRef buf; 19 | BufferDynamicDetail dyn_detail; 20 | 21 | static BufferRef create(const ContextRef& ctxt, const BufferConfig& cfg); 22 | VulkanBuffer(VulkanContextRef ctxt, BufferInfo&& info); 23 | ~VulkanBuffer(); 24 | 25 | void* map(MemoryAccess map_access); 26 | void unmap(); 27 | 28 | inline static VulkanBufferRef from_hal(const BufferRef& ref) { 29 | return std::static_pointer_cast(ref); 30 | } 31 | }; 32 | 33 | } // namespace vk 34 | } // namespace liong 35 | -------------------------------------------------------------------------------- /include/gft/vk/vk-context.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/hal/context.hpp" 3 | #include "gft/vk/vk-instance.hpp" 4 | #include "gft/pool.hpp" 5 | #include "gft/vk-sys.hpp" 6 | 7 | namespace liong { 8 | namespace vk { 9 | 10 | struct VulkanContext; 11 | typedef std::shared_ptr VulkanContextRef; 12 | 13 | typedef pool::Pool CommandPoolPool; 14 | typedef pool::PoolItem CommandPoolPoolItem; 15 | 16 | struct DescriptorSetKey { 17 | std::string inner; 18 | 19 | static DescriptorSetKey create(const std::vector& rsc_tys); 20 | 21 | inline friend bool operator<( 22 | const DescriptorSetKey& a, 23 | const DescriptorSetKey& b 24 | ) { 25 | return a.inner < b.inner; 26 | } 27 | }; 28 | typedef pool::Pool DescriptorSetPool; 29 | typedef pool::PoolItem 30 | DescriptorSetPoolItem; 31 | 32 | typedef pool::Pool QueryPoolPool; 33 | typedef pool::PoolItem QueryPoolPoolItem; 34 | 35 | struct ContextSubmitDetail { 36 | uint32_t qfam_idx; 37 | VkQueue queue; 38 | }; 39 | struct ContextDescriptorSetDetail { 40 | std::map desc_set_layouts; 41 | // Descriptor pools to hold references. 42 | std::vector desc_pools; 43 | DescriptorSetPool desc_set_pool; 44 | }; 45 | struct VulkanContext : public Context { 46 | VulkanInstanceRef inst; 47 | 48 | sys::DeviceRef dev; 49 | sys::SurfaceRef surf; 50 | std::map submit_details; 51 | std::map img_samplers; 52 | std::map depth_img_samplers; 53 | ContextDescriptorSetDetail desc_set_detail; 54 | CommandPoolPool cmd_pool_pool; 55 | QueryPoolPool query_pool_pool; 56 | sys::AllocatorRef allocator; 57 | 58 | static ContextRef create(const InstanceRef& inst, const ContextConfig& cfg); 59 | static ContextRef create( 60 | const InstanceRef& inst, 61 | const ContextWindowsConfig& cfg 62 | ); 63 | static ContextRef create( 64 | const InstanceRef& inst, 65 | const ContextAndroidConfig& cfg 66 | ); 67 | static ContextRef create( 68 | const InstanceRef& inst, 69 | const ContextMetalConfig& cfg 70 | ); 71 | 72 | VulkanContext(VulkanInstanceRef inst, ContextInfo&& info); 73 | virtual ~VulkanContext(); 74 | 75 | inline VkPhysicalDevice physdev() const { 76 | return inst->physdev_details.at(info.device_index).physdev; 77 | } 78 | inline const VkPhysicalDeviceProperties& physdev_prop() const { 79 | return inst->physdev_details.at(info.device_index).prop; 80 | } 81 | inline const VkPhysicalDeviceFeatures& physdev_feat() const { 82 | return inst->physdev_details.at(info.device_index).feat; 83 | } 84 | 85 | sys::DescriptorSetLayoutRef get_desc_set_layout( 86 | const std::vector& rsc_tys 87 | ); 88 | DescriptorSetPoolItem acquire_desc_set( 89 | const std::vector& rsc_tys 90 | ); 91 | 92 | CommandPoolPoolItem acquire_cmd_pool(SubmitType submit_ty); 93 | 94 | QueryPoolPoolItem acquire_query_pool(); 95 | 96 | inline static VulkanContextRef from_hal(const ContextRef& ref) { 97 | return std::static_pointer_cast(ref); 98 | } 99 | 100 | virtual BufferRef create_buffer(const BufferConfig& cfg) override final; 101 | virtual ImageRef create_image(const ImageConfig& cfg) override final; 102 | virtual DepthImageRef create_depth_image(const DepthImageConfig& cfg 103 | ) override final; 104 | virtual SwapchainRef create_swapchain(const SwapchainConfig& cfg 105 | ) override final; 106 | virtual RenderPassRef create_render_pass(const RenderPassConfig& cfg 107 | ) override final; 108 | 109 | virtual TaskRef create_compute_task(const ComputeTaskConfig& cfg) override final; 110 | 111 | virtual InvocationRef create_transfer_invocation( 112 | const TransferInvocationConfig& cfg 113 | ) override final; 114 | 115 | virtual InvocationRef create_composite_invocation(const CompositeInvocationConfig& cfg) override final; 116 | }; 117 | 118 | } // namespace vk 119 | } // namespace liong 120 | -------------------------------------------------------------------------------- /include/gft/vk/vk-depth-image.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/hal/depth-image.hpp" 3 | #include "gft/vk/vk-context.hpp" 4 | 5 | namespace liong { 6 | namespace vk { 7 | 8 | struct VulkanDepthImage; 9 | typedef std::shared_ptr VulkanDepthImageRef; 10 | 11 | struct DepthImageDynamicDetail { 12 | VkPipelineStageFlags stage; 13 | VkAccessFlags access; 14 | VkImageLayout layout; 15 | }; 16 | struct VulkanDepthImage : public DepthImage { 17 | VulkanContextRef ctxt; 18 | sys::ImageRef img; 19 | sys::ImageViewRef img_view; 20 | DepthImageDynamicDetail dyn_detail; 21 | 22 | static DepthImageRef create( 23 | const ContextRef& ctxt, 24 | const DepthImageConfig& cfg 25 | ); 26 | VulkanDepthImage(VulkanContextRef ctxt, DepthImageInfo&& info); 27 | ~VulkanDepthImage(); 28 | 29 | inline static VulkanDepthImageRef from_hal(const DepthImageRef& ref) { 30 | return std::static_pointer_cast(ref); 31 | } 32 | }; 33 | 34 | inline VkFormat depth_format2vk(fmt::DepthFormat fmt) { 35 | using namespace fmt; 36 | switch (fmt) { 37 | case L_DEPTH_FORMAT_D16_UNORM: 38 | return VK_FORMAT_D16_UNORM; 39 | case L_DEPTH_FORMAT_D32_SFLOAT: 40 | return VK_FORMAT_D32_SFLOAT; 41 | default: 42 | panic("unsupported depth format"); 43 | } 44 | return VK_FORMAT_UNDEFINED; 45 | } 46 | 47 | } // namespace vk 48 | } // namespace liong 49 | -------------------------------------------------------------------------------- /include/gft/vk/vk-image.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/hal/image.hpp" 3 | #include "gft/vk/vk-context.hpp" 4 | 5 | namespace liong { 6 | namespace vk { 7 | 8 | struct VulkanImage; 9 | typedef std::shared_ptr VulkanImageRef; 10 | 11 | struct ImageDynamicDetail { 12 | VkPipelineStageFlags stage; 13 | VkAccessFlags access; 14 | VkImageLayout layout; 15 | }; 16 | struct VulkanImage : public Image { 17 | VulkanContextRef ctxt; // Lifetime bound. 18 | sys::ImageRef img; 19 | sys::ImageViewRef img_view; 20 | ImageDynamicDetail dyn_detail; 21 | 22 | static ImageRef create(const ContextRef& ctxt, const ImageConfig& cfg); 23 | VulkanImage(VulkanContextRef ctxt, ImageInfo&& info); 24 | ~VulkanImage(); 25 | 26 | inline static VulkanImageRef from_hal(const ImageRef& ref) { 27 | return std::static_pointer_cast(ref); 28 | } 29 | }; 30 | 31 | inline VkFormat format2vk(fmt::Format format, fmt::ColorSpace color_space) { 32 | using namespace fmt; 33 | switch (format) { 34 | case L_FORMAT_R8G8B8A8_UNORM: 35 | if (color_space == L_COLOR_SPACE_SRGB) { 36 | return VK_FORMAT_R8G8B8A8_SRGB; 37 | } else { 38 | return VK_FORMAT_R8G8B8A8_UNORM; 39 | } 40 | case L_FORMAT_B8G8R8A8_UNORM: 41 | if (color_space == L_COLOR_SPACE_SRGB) { 42 | return VK_FORMAT_B8G8R8A8_SRGB; 43 | } else { 44 | return VK_FORMAT_B8G8R8A8_UNORM; 45 | } 46 | case L_FORMAT_B10G11R11_UFLOAT_PACK32: 47 | return VK_FORMAT_B10G11R11_UFLOAT_PACK32; 48 | case L_FORMAT_R16G16B16A16_SFLOAT: 49 | return VK_FORMAT_R16G16B16A16_SFLOAT; 50 | case L_FORMAT_R32_SFLOAT: 51 | return VK_FORMAT_R32_SFLOAT; 52 | case L_FORMAT_R32G32_SFLOAT: 53 | return VK_FORMAT_R32G32_SFLOAT; 54 | case L_FORMAT_R32G32B32A32_SFLOAT: 55 | return VK_FORMAT_R32G32B32A32_SFLOAT; 56 | default: 57 | panic("unrecognized pixel format"); 58 | } 59 | return VK_FORMAT_UNDEFINED; 60 | } 61 | 62 | } // namespace vk 63 | } // namespace liong 64 | -------------------------------------------------------------------------------- /include/gft/vk/vk-instance.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/hal/instance.hpp" 3 | #include "gft/vk-sys.hpp" 4 | 5 | namespace liong { 6 | namespace vk { 7 | 8 | using namespace liong::hal; 9 | 10 | struct VulkanInstance; 11 | typedef std::shared_ptr VulkanInstanceRef; 12 | 13 | struct InstancePhysicalDeviceDetail { 14 | VkPhysicalDevice physdev; 15 | VkPhysicalDeviceProperties prop; 16 | VkPhysicalDeviceFeatures feat; 17 | VkPhysicalDeviceMemoryProperties mem_prop; 18 | std::vector qfam_props; 19 | std::map ext_props; 20 | std::string desc; 21 | }; 22 | struct VulkanInstance : public Instance { 23 | uint32_t api_ver; 24 | sys::InstanceRef inst; 25 | sys::DebugUtilsMessengerRef debug_utils_messenger; 26 | std::vector physdev_details; 27 | bool is_imported; 28 | 29 | static VulkanInstanceRef create(bool debug); 30 | static VulkanInstanceRef create(uint32_t api_ver, sys::InstanceRef&& inst); 31 | ~VulkanInstance(); 32 | 33 | inline static VulkanInstanceRef from_hal(const InstanceRef& ref) { 34 | return std::static_pointer_cast(ref); 35 | } 36 | 37 | virtual std::string describe_device(uint32_t device_index) override final; 38 | 39 | virtual ContextRef create_context(const ContextConfig& config) override final; 40 | virtual ContextRef create_context(const ContextWindowsConfig& config 41 | ) override final; 42 | virtual ContextRef create_context(const ContextAndroidConfig& config 43 | ) override final; 44 | virtual ContextRef create_context(const ContextMetalConfig& config 45 | ) override final; 46 | }; 47 | 48 | } // namespace vk 49 | } // namespace liong 50 | -------------------------------------------------------------------------------- /include/gft/vk/vk-invocation.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/hal/invocation.hpp" 3 | #include "gft/vk/vk-context.hpp" 4 | #include "gft/vk/vk-render-pass.hpp" 5 | #include "gft/vk/vk-swapchain.hpp" 6 | #include "gft/vk/vk-task.hpp" 7 | 8 | namespace liong { 9 | namespace vk { 10 | 11 | struct VulkanInvocation; 12 | typedef std::shared_ptr VulkanInvocationRef; 13 | 14 | struct TransactionSubmitDetail { 15 | SubmitType submit_ty; 16 | CommandPoolPoolItem cmd_pool; 17 | sys::CommandBufferRef cmdbuf; 18 | VkQueue queue; 19 | sys::SemaphoreRef wait_sema; 20 | sys::SemaphoreRef signal_sema; 21 | bool is_submitted; 22 | }; 23 | struct TransactionLike { 24 | const VulkanContextRef ctxt; 25 | 26 | std::vector submit_details; 27 | std::vector fences; 28 | VkCommandBufferLevel level; 29 | // Some invocations cannot be followedby subsequent invocations, e.g. 30 | // presentation. 31 | bool is_frozen; 32 | 33 | inline TransactionLike( 34 | const VulkanContextRef& ctxt, 35 | VkCommandBufferLevel level 36 | ) : 37 | ctxt(ctxt), submit_details(), level(level), is_frozen(false) {} 38 | }; 39 | 40 | struct InvocationTransitionDetail { 41 | std::vector> buf_transit; 42 | std::vector> img_transit; 43 | std::vector> depth_img_transit; 44 | 45 | inline void reg(BufferView buf_view, BufferUsage usage) { 46 | buf_transit.emplace_back(std::make_pair( 47 | std::move(buf_view), std::move(usage) 48 | )); 49 | } 50 | inline void reg(ImageView img_view, ImageUsage usage) { 51 | img_transit.emplace_back(std::make_pair( 52 | std::move(img_view), std::move(usage) 53 | )); 54 | } 55 | inline void reg(DepthImageView depth_img_view, DepthImageUsage usage) { 56 | depth_img_transit.emplace_back( 57 | std::make_pair( 58 | std::move(depth_img_view), std::move(usage) 59 | ) 60 | ); 61 | } 62 | }; 63 | struct InvocationCopyBufferToBufferDetail { 64 | VkBufferCopy bc; 65 | sys::BufferRef src; 66 | sys::BufferRef dst; 67 | }; 68 | struct InvocationCopyBufferToImageDetail { 69 | VkBufferImageCopy bic; 70 | sys::BufferRef src; 71 | sys::ImageRef dst; 72 | }; 73 | struct InvocationCopyImageToBufferDetail { 74 | VkBufferImageCopy bic; 75 | sys::ImageRef src; 76 | sys::BufferRef dst; 77 | }; 78 | struct InvocationCopyImageToImageDetail { 79 | VkImageCopy ic; 80 | sys::ImageRef src; 81 | sys::ImageRef dst; 82 | }; 83 | struct InvocationComputeDetail { 84 | VulkanTaskRef task; 85 | VkPipelineBindPoint bind_pt; 86 | DescriptorSetPoolItem desc_set; 87 | DispatchSize workgrp_count; 88 | }; 89 | struct InvocationGraphicsDetail { 90 | VulkanTaskRef task; 91 | VkPipelineBindPoint bind_pt; 92 | DescriptorSetPoolItem desc_set; 93 | std::vector vert_bufs; 94 | std::vector vert_buf_offsets; 95 | sys::BufferRef idx_buf; 96 | VkDeviceSize idx_buf_offset; 97 | uint32_t ninst; 98 | uint32_t nvert; 99 | IndexType idx_ty; 100 | uint32_t nidx; 101 | }; 102 | struct InvocationRenderPassDetail { 103 | RenderPassRef pass; 104 | FramebufferPoolItem framebuf; 105 | std::vector attms; 106 | bool is_baked; 107 | std::vector subinvokes; 108 | }; 109 | struct InvocationPresentDetail { 110 | VulkanSwapchainRef swapchain; 111 | }; 112 | struct InvocationCompositeDetail { 113 | std::vector subinvokes; 114 | }; 115 | struct InvocationBakingDetail { 116 | CommandPoolPoolItem cmd_pool; 117 | sys::CommandBufferRef cmdbuf; 118 | }; 119 | struct VulkanInvocation : public Invocation { 120 | // Execution context of the invocation. 121 | VulkanContextRef ctxt; 122 | // Case-by-case implementations. 123 | std::unique_ptr b2b_detail; 124 | std::unique_ptr b2i_detail; 125 | std::unique_ptr i2b_detail; 126 | std::unique_ptr i2i_detail; 127 | std::unique_ptr comp_detail; 128 | std::unique_ptr graph_detail; 129 | std::unique_ptr pass_detail; 130 | std::unique_ptr present_detail; 131 | std::unique_ptr composite_detail; 132 | // Managed transitioning of resources referenced by invocation. 133 | InvocationTransitionDetail transit_detail; 134 | // Query pool for device-side timing, if required. 135 | QueryPoolPoolItem query_pool; 136 | // Baking artifacts. Currently we don't support baking render pass invocations 137 | // and those with switching submit types. 138 | std::unique_ptr bake_detail; 139 | 140 | static InvocationRef create( 141 | const ContextRef& ctxt, 142 | const TransferInvocationConfig& cfg 143 | ); 144 | static InvocationRef create( 145 | const TaskRef& task, 146 | const ComputeInvocationConfig& cfg 147 | ); 148 | static InvocationRef create( 149 | const TaskRef& task, 150 | const GraphicsInvocationConfig& cfg 151 | ); 152 | static InvocationRef create( 153 | const RenderPassRef& pass, 154 | const RenderPassInvocationConfig& cfg 155 | ); 156 | static InvocationRef create( 157 | const ContextRef& ctxt, 158 | const CompositeInvocationConfig& cfg 159 | ); 160 | static InvocationRef create( 161 | const SwapchainRef& swapchain, 162 | const PresentInvocationConfig& cfg 163 | ); 164 | VulkanInvocation(const VulkanContextRef& ctxt, InvocationInfo&& info); 165 | ~VulkanInvocation(); 166 | 167 | void record(TransactionLike& transact) const; 168 | 169 | virtual TransactionRef create_transact(const TransactionConfig& cfg 170 | ) override final; 171 | 172 | virtual double get_time_us() override final; 173 | virtual void bake() override final; 174 | 175 | inline static VulkanInvocationRef from_hal(const InvocationRef& ref) { 176 | return std::static_pointer_cast(ref); 177 | } 178 | }; 179 | 180 | } // namespace vk 181 | } // namespace liong 182 | -------------------------------------------------------------------------------- /include/gft/vk/vk-render-pass.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/pool.hpp" 3 | #include "gft/hal/render-pass.hpp" 4 | #include "gft/vk/vk-context.hpp" 5 | 6 | namespace liong { 7 | namespace vk { 8 | 9 | struct VulkanRenderPass; 10 | typedef std::shared_ptr VulkanRenderPassRef; 11 | 12 | struct FramebufferKey { 13 | std::string inner; 14 | 15 | static FramebufferKey create( 16 | const VulkanRenderPass& pass, 17 | const std::vector& rsc_views 18 | ); 19 | 20 | friend inline bool operator<( 21 | const FramebufferKey& a, 22 | const FramebufferKey& b 23 | ) { 24 | return a.inner < b.inner; 25 | } 26 | }; 27 | 28 | typedef pool::Pool FramebufferPool; 29 | typedef pool::PoolItem FramebufferPoolItem; 30 | 31 | struct VulkanRenderPass : public RenderPass { 32 | VulkanContextRef ctxt; 33 | 34 | sys::RenderPassRef pass; 35 | std::vector clear_values; 36 | 37 | static RenderPassRef create( 38 | const ContextRef& ctxt, 39 | const RenderPassConfig& cfg 40 | ); 41 | VulkanRenderPass(const VulkanContextRef& ctxt, RenderPassInfo&& info); 42 | ~VulkanRenderPass(); 43 | 44 | inline static VulkanRenderPassRef from_hal(const RenderPassRef& ref) { 45 | return std::static_pointer_cast(ref); 46 | } 47 | 48 | FramebufferPool framebuf_pool; 49 | FramebufferPoolItem acquire_framebuf(const std::vector& attms); 50 | 51 | TaskRef create_graphics_task(const GraphicsTaskConfig& cfg) override final; 52 | InvocationRef create_render_pass_invocation( 53 | const RenderPassInvocationConfig& cfg 54 | ) override final; 55 | }; 56 | 57 | } // namespace vk 58 | } // namespace liong 59 | -------------------------------------------------------------------------------- /include/gft/vk/vk-swapchain.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/hal/swapchain.hpp" 3 | #include "gft/vk/vk-context.hpp" 4 | #include "gft/vk/vk-image.hpp" 5 | 6 | namespace liong { 7 | namespace vk { 8 | 9 | struct VulkanSwapchain; 10 | typedef std::shared_ptr VulkanSwapchainRef; 11 | 12 | struct SwapchainDynamicDetail { 13 | uint32_t width; 14 | uint32_t height; 15 | std::vector imgs; 16 | std::unique_ptr img_idx; 17 | }; 18 | struct VulkanSwapchain : public Swapchain { 19 | VulkanContextRef ctxt; 20 | sys::SwapchainRef swapchain; 21 | std::unique_ptr dyn_detail; 22 | 23 | static SwapchainRef create( 24 | const ContextRef& ctxt, 25 | const SwapchainConfig& cfg 26 | ); 27 | VulkanSwapchain(VulkanContextRef ctxt, SwapchainInfo&& info); 28 | ~VulkanSwapchain(); 29 | 30 | ImageRef get_current_image(); 31 | uint32_t get_width() const; 32 | uint32_t get_height() const; 33 | 34 | void recreate(); 35 | 36 | inline static VulkanSwapchainRef from_hal(const SwapchainRef& ref) { 37 | return std::static_pointer_cast(ref); 38 | } 39 | 40 | InvocationRef create_present_invocation(const PresentInvocationConfig& cfg); 41 | }; 42 | 43 | inline VkColorSpaceKHR color_space2vk(fmt::ColorSpace cspace) { 44 | using namespace fmt; 45 | switch (cspace) { 46 | case L_COLOR_SPACE_SRGB: 47 | return VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; 48 | default: 49 | panic("unsupported color space"); 50 | } 51 | } 52 | 53 | } // namespace vk 54 | } // namespace liong 55 | -------------------------------------------------------------------------------- /include/gft/vk/vk-task.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/hal/task.hpp" 3 | #include "gft/vk/vk-context.hpp" 4 | #include "gft/vk/vk-render-pass.hpp" 5 | 6 | namespace liong { 7 | namespace vk { 8 | 9 | struct VulkanTask; 10 | typedef std::shared_ptr VulkanTaskRef; 11 | 12 | struct TaskResourceDetail { 13 | sys::PipelineLayoutRef pipe_layout; 14 | std::vector rsc_tys; 15 | }; 16 | struct VulkanTask : public Task { 17 | VulkanContextRef ctxt; 18 | VulkanRenderPassRef pass; // Only for graphics task. 19 | 20 | sys::PipelineRef pipe; 21 | DispatchSize workgrp_size; // Only for compute task. 22 | TaskResourceDetail rsc_detail; 23 | 24 | static TaskRef create(const ContextRef& ctxt, const ComputeTaskConfig& cfg); 25 | static TaskRef create( 26 | const RenderPassRef& pass, 27 | const GraphicsTaskConfig& cfg 28 | ); 29 | 30 | VulkanTask(const ContextRef& ctxt, TaskInfo&& info); 31 | VulkanTask(const RenderPassRef& pass, TaskInfo&& info); 32 | ~VulkanTask(); 33 | 34 | inline static VulkanTaskRef from_hal(const TaskRef& ref) { 35 | return std::static_pointer_cast(ref); 36 | } 37 | 38 | InvocationRef create_graphics_invocation(const GraphicsInvocationConfig& cfg); 39 | InvocationRef create_compute_invocation(const ComputeInvocationConfig& cfg); 40 | }; 41 | 42 | } // namespace vk 43 | } // namespace liong 44 | -------------------------------------------------------------------------------- /include/gft/vk/vk-transaction.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/hal/transaction.hpp" 3 | #include "gft/vk/vk-invocation.hpp" 4 | 5 | namespace liong { 6 | namespace vk { 7 | 8 | struct VulkanTransaction; 9 | typedef std::shared_ptr VulkanTransactionRef; 10 | 11 | struct VulkanTransaction : public Transaction { 12 | VulkanContextRef ctxt; 13 | std::vector submit_details; 14 | std::vector fences; 15 | bool is_waited; 16 | 17 | static TransactionRef create( 18 | const InvocationRef& invoke, 19 | const TransactionConfig& cfg 20 | ); 21 | VulkanTransaction(VulkanContextRef ctxt, TransactionInfo&& info); 22 | ~VulkanTransaction(); 23 | 24 | virtual bool is_done() override final; 25 | virtual void wait() override final; 26 | }; 27 | 28 | } // namespace vk 29 | } // namespace liong 30 | -------------------------------------------------------------------------------- /include/gft/vk/vk.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gft/vk/vk-instance.hpp" 3 | #include "gft/vk/vk-context.hpp" 4 | #include "gft/vk/vk-buffer.hpp" 5 | #include "gft/vk/vk-image.hpp" 6 | #include "gft/vk/vk-depth-image.hpp" 7 | #include "gft/vk/vk-swapchain.hpp" 8 | #include "gft/vk/vk-task.hpp" 9 | #include "gft/vk/vk-invocation.hpp" 10 | #include "gft/vk/vk-transaction.hpp" 11 | -------------------------------------------------------------------------------- /include/gft/zip.hpp: -------------------------------------------------------------------------------- 1 | // Uncompressed Zip archive I/O. 2 | // @PENGUINLIONG 3 | #pragma once 4 | #include 5 | #include 6 | #include 7 | 8 | namespace liong { 9 | namespace zip { 10 | 11 | struct ZipFileRecord { 12 | std::string file_name; 13 | const void* data; 14 | size_t size; 15 | uint32_t crc32; 16 | }; 17 | 18 | struct ZipArchive { 19 | std::vector records; 20 | std::map file_name2irecord; 21 | 22 | static ZipArchive from_bytes(const uint8_t* data, size_t size); 23 | static ZipArchive from_bytes(const std::vector& out); 24 | 25 | inline const ZipFileRecord& get_file(const std::string& file_name) const { 26 | return records.at(file_name2irecord.at(file_name)); 27 | } 28 | 29 | // `data` has to be kept alive through out the archive's lifetime. 30 | void add_file(const std::string& file_name, const void* data, size_t size); 31 | void to_bytes(std::vector& out) const; 32 | }; 33 | 34 | } // namespace zip 35 | } // namespace liong 36 | -------------------------------------------------------------------------------- /scripts/format-code.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/bash 2 | find apps -iname *.h -o -iname *.hpp -o -iname *.cpp | xargs clang-format -i 3 | find src -iname *.h -o -iname *.hpp -o -iname *.cpp | xargs clang-format -i 4 | find include -iname *.h -o -iname *.hpp -o -iname *.cpp | xargs clang-format -i 5 | -------------------------------------------------------------------------------- /src/gft/args.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "gft/args.hpp" 5 | #include "gft/assert.hpp" 6 | 7 | namespace liong { 8 | 9 | namespace args { 10 | 11 | struct ArgumentHelp { 12 | std::string short_flag; 13 | std::string long_flag; 14 | std::string help; 15 | }; 16 | struct ArgumentConfig { 17 | std::string app_name = "[APPNAME]"; 18 | std::string desc; 19 | // Short flag name -> ID. 20 | std::map short_map; 21 | // Long flag name -> ID. 22 | std::map long_map; 23 | // Argument parsing info. 24 | std::vector parse_cfgs; 25 | // Argument help info. 26 | std::vector helps; 27 | } arg_cfg; 28 | 29 | 30 | void init_arg_parse(const char* app_name, const char* desc) { 31 | arg_cfg.app_name = app_name; 32 | arg_cfg.desc = desc; 33 | } 34 | const char* get_app_name() { 35 | return arg_cfg.app_name.c_str(); 36 | } 37 | void print_help() { 38 | std::cout << "usage: " << arg_cfg.app_name << " [OPTIONS]" << std::endl; 39 | if (!arg_cfg.desc.empty()) { 40 | std::cout << arg_cfg.desc << std::endl; 41 | } 42 | for (const auto& help : arg_cfg.helps) { 43 | std::cout << help.short_flag << "\t" << help.long_flag << "\t\t" 44 | << help.help << std::endl; 45 | } 46 | std::cout << "-h\t--help\t\tPrint this message." << std::endl; 47 | std::exit(0); 48 | } 49 | void report_unknown_arg(const char* arg) { 50 | std::cout << "unknown argument: " << arg << std::endl; 51 | print_help(); 52 | } 53 | 54 | void reg_arg( 55 | const char* short_flag, 56 | const char* long_flag, 57 | const ArgumentParseConfig& parse_cfg, 58 | const char* help 59 | ) { 60 | using std::strlen; 61 | size_t i = arg_cfg.parse_cfgs.size(); 62 | if (strlen(short_flag) == 2 && short_flag[0] == '-') { 63 | arg_cfg.short_map[short_flag[1]] = i; 64 | } 65 | if (strlen(long_flag) > 3 && long_flag[1] == '-' && long_flag[0] == '-') { 66 | arg_cfg.long_map[long_flag + 2] = i; 67 | } 68 | arg_cfg.parse_cfgs.emplace_back(parse_cfg); 69 | std::string help_str = help; 70 | auto lit = parse_cfg.lit(parse_cfg.dst); 71 | if (!lit.empty()) { 72 | help_str += " (default=" + lit + ")"; 73 | } 74 | ArgumentHelp arg_help { short_flag, long_flag, help_str }; 75 | arg_cfg.helps.emplace_back(std::move(arg_help)); 76 | } 77 | 78 | void parse_args(int argc, const char** argv) { 79 | auto i = 1; 80 | int iarg_entry = -1; 81 | while (i < argc || iarg_entry >= 0) { 82 | if (iarg_entry >= 0) { 83 | auto& parse_cfg = arg_cfg.parse_cfgs[iarg_entry]; 84 | bool res = parse_cfg.parser(argv + i, parse_cfg.dst); 85 | L_ASSERT(res, "unable to parse argument"); 86 | L_ASSERT((argc - i >= parse_cfg.narg), "no enough argument segments"); 87 | i += parse_cfg.narg; 88 | iarg_entry = -1; 89 | } else { 90 | const char* arg = argv[i]; 91 | if (arg[0] != '-') { 92 | // Free argument. 93 | panic("free argument is currently unsupported"); 94 | } else if (arg[1] != '-') { 95 | if (arg[1] == 'h') { 96 | print_help(); 97 | } 98 | // Short flag argument. 99 | auto it = arg_cfg.short_map.find(arg[1]); 100 | if (it != arg_cfg.short_map.end()) { 101 | iarg_entry = it->second; 102 | } else { 103 | report_unknown_arg(arg); 104 | } 105 | ++i; 106 | } else { 107 | if (std::strcmp(arg + 2, "help") == 0) { 108 | print_help(); 109 | } 110 | // Long flag argument. 111 | auto it = (arg_cfg.long_map.find(arg + 2)); 112 | if (it != arg_cfg.long_map.end()) { 113 | iarg_entry = it->second; 114 | } else { 115 | report_unknown_arg(arg); 116 | } 117 | ++i; 118 | } 119 | } 120 | } 121 | } 122 | 123 | } // namespace args 124 | 125 | } // namespace liong 126 | -------------------------------------------------------------------------------- /src/gft/geom.cpp: -------------------------------------------------------------------------------- 1 | #include "gft/geom.hpp" 2 | 3 | namespace liong { 4 | namespace geom { 5 | 6 | using namespace glm; 7 | 8 | bool contains_point_aabb(const Aabb& aabb, const vec3& point) { 9 | return aabb.min.x <= point.x && aabb.min.y <= point.y && 10 | aabb.min.z <= point.z && aabb.max.x >= point.x && 11 | aabb.max.y >= point.y && aabb.max.z >= point.z; 12 | } 13 | bool contains_point_sphere(const Sphere& sphere, const vec3& point) { 14 | return (point - sphere.p).length() <= sphere.r; 15 | } 16 | bool contains_point_tetra( 17 | const Tetrahedron& tetra, 18 | const vec3& point, 19 | vec4& bary 20 | ) { 21 | vec4 v0(tetra.a, 1); 22 | vec4 v1(tetra.b, 1); 23 | vec4 v2(tetra.c, 1); 24 | vec4 v3(tetra.d, 1); 25 | vec4 p0(point, 1); 26 | const float det0 = glm::determinant(mat4(v0, v1, v2, v3)); 27 | const float det1 = glm::determinant(mat4(p0, v1, v2, v3)); 28 | const float det2 = glm::determinant(mat4(v0, p0, v2, v3)); 29 | const float det3 = glm::determinant(mat4(v0, v1, p0, v3)); 30 | const float det4 = glm::determinant(mat4(v0, v1, v2, p0)); 31 | bary = vec4 { 32 | det1 / det0, 33 | det2 / det0, 34 | det3 / det0, 35 | det4 / det0, 36 | }; 37 | if (bary.x < 0.0f || bary.y < 0.0f || bary.z < 0.0f || bary.w < 0.0f) { 38 | return false; 39 | } 40 | return true; 41 | } 42 | 43 | bool intersect_aabb(const Aabb& aabb1, const Aabb& aabb2) { 44 | return aabb1.min.x <= aabb2.max.x || aabb1.min.y <= aabb2.max.y || 45 | aabb1.min.z <= aabb2.max.z || aabb1.max.x >= aabb2.min.x || 46 | aabb1.max.y >= aabb2.min.y || aabb1.max.z >= aabb2.min.z; 47 | } 48 | 49 | void split_tetra2tris(const Tetrahedron& tet, std::vector& out) { 50 | Triangle t0 { tet.a, tet.b, tet.c }; 51 | Triangle t1 { tet.a, tet.b, tet.d }; 52 | Triangle t2 { tet.a, tet.c, tet.d }; 53 | Triangle t3 { tet.b, tet.c, tet.d }; 54 | 55 | out.emplace_back(std::move(t0)); 56 | out.emplace_back(std::move(t1)); 57 | out.emplace_back(std::move(t2)); 58 | out.emplace_back(std::move(t3)); 59 | } 60 | 61 | void split_aabb2tetras(const Aabb& aabb, std::vector& out) { 62 | // Any cube can be split into five tetrahedra and in this function we split 63 | // the AABB in a hard-coded pattern for simplicity. 64 | // 65 | // A___________B 66 | // /| /| 67 | // / | / | 68 | // D/__|______C/ | Y 69 | // | | | | | 70 | // | E|_______|_F| |____X 71 | // | / | / / 72 | // |/_________|/ Z 73 | // H G 74 | // 75 | // BHCG, BHGF, BDCH, BAHE, BADH, BEHF 76 | // 77 | // Note: The first three vertices forms a triangle pointing out of the 78 | // tetrahedron in a right-hand system. That is, the 4th vertex is behind the 79 | // plane formed by the first vertices. 80 | 81 | glm::vec3 a(aabb.min.x, aabb.max.y, aabb.min.z); 82 | glm::vec3 b(aabb.max.x, aabb.max.y, aabb.min.z); 83 | glm::vec3 c(aabb.max.x, aabb.max.y, aabb.max.z); 84 | glm::vec3 d(aabb.min.x, aabb.max.y, aabb.max.z); 85 | glm::vec3 e(aabb.min.x, aabb.min.y, aabb.min.z); 86 | glm::vec3 f(aabb.max.x, aabb.min.y, aabb.min.z); 87 | glm::vec3 g(aabb.max.x, aabb.min.y, aabb.max.z); 88 | glm::vec3 h(aabb.min.x, aabb.min.y, aabb.max.z); 89 | 90 | Tetrahedron t0 { b, h, c, g }; 91 | Tetrahedron t1 { b, h, g, f }; 92 | Tetrahedron t2 { b, d, c, h }; 93 | Tetrahedron t3 { b, a, h, e }; 94 | Tetrahedron t4 { b, a, d, h }; 95 | Tetrahedron t5 { b, e, h, f }; 96 | 97 | out.emplace_back(std::move(t0)); 98 | out.emplace_back(std::move(t1)); 99 | out.emplace_back(std::move(t2)); 100 | out.emplace_back(std::move(t3)); 101 | out.emplace_back(std::move(t4)); 102 | out.emplace_back(std::move(t5)); 103 | } 104 | 105 | void subdivide_aabb( 106 | const Aabb& aabb, 107 | const glm::uvec3& nslice, 108 | std::vector& out 109 | ) { 110 | glm::vec3 size = aabb.size() / glm::vec3(nslice); 111 | 112 | for (uint32_t z = 0; z < nslice.z; ++z) { 113 | float z_min = aabb.min.z + z * size.z; 114 | float z_max = 115 | z + 1 == nslice.y ? aabb.max.z : aabb.min.z + (z + 1) * size.z; 116 | for (uint32_t y = 0; y < nslice.y; ++y) { 117 | float y_min = aabb.min.y + y * size.y; 118 | float y_max = 119 | y + 1 == nslice.y ? aabb.max.y : aabb.min.y + (y + 1) * size.y; 120 | for (uint32_t x = 0; x < nslice.x; ++x) { 121 | float x_min = aabb.min.x + x * size.x; 122 | float x_max = 123 | x + 1 == nslice.x ? aabb.max.x : aabb.min.x + (x + 1) * size.x; 124 | 125 | Aabb aabb2 {}; 126 | aabb2.min = glm::vec3(x_min, y_min, z_min); 127 | aabb2.max = glm::vec3(x_max, y_max, z_max); 128 | } 129 | } 130 | } 131 | } 132 | 133 | void split_aabb2points(const Aabb& aabb, std::vector& out) { 134 | glm::vec3 v0(aabb.min.x, aabb.min.y, aabb.min.z); 135 | glm::vec3 v1(aabb.max.x, aabb.min.y, aabb.min.z); 136 | glm::vec3 v2(aabb.min.x, aabb.max.y, aabb.min.z); 137 | glm::vec3 v3(aabb.max.x, aabb.max.y, aabb.min.z); 138 | glm::vec3 v4(aabb.min.x, aabb.min.y, aabb.max.z); 139 | glm::vec3 v5(aabb.max.x, aabb.min.y, aabb.max.z); 140 | glm::vec3 v6(aabb.min.x, aabb.max.y, aabb.max.z); 141 | glm::vec3 v7(aabb.max.x, aabb.max.y, aabb.max.z); 142 | 143 | out.emplace_back(v0); 144 | out.emplace_back(v1); 145 | out.emplace_back(v2); 146 | out.emplace_back(v3); 147 | out.emplace_back(v4); 148 | out.emplace_back(v5); 149 | out.emplace_back(v6); 150 | out.emplace_back(v7); 151 | } 152 | 153 | void tile_aabb_ceil( 154 | const Aabb& aabb, 155 | const glm::vec3& tile_size, 156 | std::vector& out 157 | ) { 158 | glm::uvec3 nslice(glm::ceil(aabb.size() / tile_size)); 159 | glm::vec3 size2 = glm::vec3(nslice) * tile_size; 160 | Aabb aabb2 = Aabb::from_center_size(aabb.center(), size2); 161 | subdivide_aabb(aabb, nslice, out); 162 | } 163 | 164 | } // namespace geom 165 | } // namespace liong 166 | -------------------------------------------------------------------------------- /src/gft/hal/buf.cpp: -------------------------------------------------------------------------------- 1 | #include "gft/hal/buffer.hpp" 2 | 3 | namespace liong { 4 | namespace hal { 5 | 6 | MappedBuffer::MappedBuffer(const BufferRef& buf, MemoryAccess map_access) : 7 | buf(buf), mapped(buf->map(map_access)) {} 8 | MappedBuffer::MappedBuffer(MappedBuffer&& x) : 9 | buf(std::exchange(x.buf, nullptr)), 10 | mapped(std::exchange(x.mapped, nullptr)) {} 11 | MappedBuffer::~MappedBuffer() { 12 | if (mapped) { 13 | buf->unmap(); 14 | mapped = nullptr; 15 | } 16 | } 17 | 18 | } // namespace hal 19 | } // namespace liong 20 | -------------------------------------------------------------------------------- /src/gft/hal/builder.cpp: -------------------------------------------------------------------------------- 1 | #include "gft/hal/builder.hpp" 2 | #include "gft/hal/hal.hpp" 3 | 4 | namespace liong { 5 | namespace hal { 6 | 7 | InstanceConfigBuilder InstanceConfig::build() { 8 | return {}; 9 | } 10 | 11 | ContextConfigBuilder ContextConfig::build() { 12 | return {}; 13 | } 14 | ContextWindowsConfigBuilder ContextWindowsConfig::build() { 15 | return {}; 16 | } 17 | ContextAndroidConfigBuilder ContextAndroidConfig::build() { 18 | return {}; 19 | } 20 | ContextMetalConfigBuilder ContextMetalConfig::build() { 21 | return {}; 22 | } 23 | 24 | BufferConfigBuilder BufferConfig::build() { 25 | return {}; 26 | } 27 | 28 | ImageConfigBuilder ImageConfig::build() { 29 | return {}; 30 | } 31 | 32 | DepthImageConfigBuilder DepthImageConfig::build() { 33 | return {}; 34 | } 35 | 36 | SwapchainConfigBuilder SwapchainConfig::build() { 37 | return {}; 38 | } 39 | 40 | ComputeTaskConfigBuilder ComputeTaskConfig::build() { 41 | return {}; 42 | } 43 | 44 | GraphicsTaskConfigBuilder GraphicsTaskConfig::build() { 45 | return {}; 46 | } 47 | 48 | AttachmentConfigBuilder AttachmentConfig::build() { 49 | return {}; 50 | } 51 | RenderPassConfigBuilder RenderPassConfig::build() { 52 | return {}; 53 | } 54 | 55 | TransferInvocationConfigBuilder TransferInvocationConfig::build() { 56 | return {}; 57 | } 58 | ComputeInvocationConfigBuilder ComputeInvocationConfig::build() { 59 | return {}; 60 | } 61 | GraphicsInvocationConfigBuilder GraphicsInvocationConfig::build() { 62 | return {}; 63 | } 64 | RenderPassInvocationConfigBuilder RenderPassInvocationConfig::build() { 65 | return {}; 66 | } 67 | CompositeInvocationConfigBuilder CompositeInvocationConfig::build() { 68 | return {}; 69 | } 70 | PresentInvocationConfigBuilder PresentInvocationConfig::build() { 71 | return {}; 72 | } 73 | 74 | TransactionConfigBuilder TransactionConfig::build() { 75 | return {}; 76 | } 77 | 78 | } // namespace hal 79 | } // namespace liong 80 | -------------------------------------------------------------------------------- /src/gft/log.cpp: -------------------------------------------------------------------------------- 1 | #include "gft/log.hpp" 2 | 3 | namespace liong { 4 | namespace log { 5 | 6 | namespace detail { 7 | 8 | void l_default_log_callback__(LogLevel lv, const std::string& msg) { 9 | switch (lv) { 10 | case L_LOG_LEVEL_DEBUG: 11 | printf("[\x1b[90mDEBUG\x1B[0m] %s\n", msg.c_str()); 12 | break; 13 | case L_LOG_LEVEL_INFO: 14 | printf("[\x1B[32mINFO\x1B[0m] %s\n", msg.c_str()); 15 | break; 16 | case L_LOG_LEVEL_WARNING: 17 | printf("[\x1B[33mWARN\x1B[0m] %s\n", msg.c_str()); 18 | break; 19 | case L_LOG_LEVEL_ERROR: 20 | printf("[\x1B[31mERROR\x1B[0m] %s\n", msg.c_str()); 21 | break; 22 | } 23 | } 24 | 25 | LogCallback l_log_callback__ = &l_default_log_callback__; 26 | LogLevel l_filter_lv__ = LogLevel::L_LOG_LEVEL_DEBUG; 27 | std::string l_indent__ = ""; 28 | 29 | } // namespace detail 30 | 31 | 32 | void set_log_callback(LogCallback cb) { 33 | detail::l_log_callback__ = cb; 34 | } 35 | void set_log_filter_level(LogLevel lv) { 36 | detail::l_filter_lv__ = lv; 37 | } 38 | 39 | void push_indent() { 40 | detail::l_indent__ += " "; 41 | } 42 | void pop_indent() { 43 | detail::l_indent__.resize(detail::l_indent__.size() - 2); 44 | } 45 | 46 | } // namespace log 47 | 48 | } // namespace liong 49 | -------------------------------------------------------------------------------- /src/gft/platform/macos.mm: -------------------------------------------------------------------------------- 1 | #if defined(__APPLE__) && defined(__MACH__) 2 | 3 | #include "gft/platform/macos.hpp" 4 | #import 5 | #import 6 | 7 | @interface GraphiTViewController : NSViewController 8 | @end 9 | @implementation GraphiTViewController 10 | { 11 | MTKView *_view; 12 | NSRect _frame; 13 | } 14 | 15 | - (instancetype)initWithFrame:(NSRect)frame 16 | { 17 | self = [super init]; 18 | _frame = frame; 19 | return self; 20 | } 21 | 22 | - (void)loadView 23 | { 24 | self.view = [[MTKView alloc] initWithFrame:_frame]; 25 | self.title = @"GraphiT"; 26 | self.preferredContentSize = _frame.size; 27 | } 28 | 29 | - (void)viewDidLoad 30 | { 31 | [super viewDidLoad]; 32 | _view = (MTKView *)self.view; 33 | _view.device = MTLCreateSystemDefaultDevice(); 34 | 35 | if(!_view.device) 36 | { 37 | NSLog(@"Metal is not supported on this device"); 38 | self.view = [[NSView alloc] initWithFrame:self.view.frame]; 39 | return; 40 | } 41 | } 42 | @end 43 | @interface GraphiTWindow : NSWindow 44 | @end 45 | @implementation GraphiTWindow 46 | -(void)close 47 | { 48 | [super close]; 49 | } 50 | -(void)sendEvent:(NSEvent *)event 51 | { 52 | [super sendEvent:event]; 53 | [NSApp stopModal]; 54 | } 55 | @end 56 | 57 | namespace liong { 58 | namespace macos { 59 | 60 | Window create_window(uint32_t width, uint32_t height) { 61 | NSRect frame = NSMakeRect(0, 0, width, height); 62 | 63 | [NSApplication sharedApplication]; 64 | NSViewController* viewController = [[GraphiTViewController alloc] initWithFrame:frame]; 65 | CAMetalLayer* metal_layer = (CAMetalLayer*)viewController.view.layer; 66 | NSWindow* window = [GraphiTWindow windowWithContentViewController:viewController]; 67 | [NSApp finishLaunching]; 68 | 69 | // The following is basically the same as one iteration of `[NSApp run]`; 70 | // A first iteraion is required, otherwise the window won't show. 71 | do { 72 | // This automatically calls `[window makeKeyAndOrderFront:nil]`. 73 | [NSApp runModalForWindow:window]; 74 | } while (false); 75 | 76 | Window out {}; 77 | out.window = (void*)window; 78 | out.metal_layer = (void*)metal_layer; 79 | return out; 80 | } 81 | Window create_window() { 82 | return create_window(1024, 768); 83 | } 84 | 85 | } // namespace macos 86 | } // namespace liong 87 | 88 | #endif // defined(__APPLE__) && defined(__MACH__) 89 | -------------------------------------------------------------------------------- /src/gft/platform/windows.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | #include "gft/platform/windows.hpp" 3 | 4 | namespace liong { 5 | namespace windows { 6 | 7 | LRESULT 8 | wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { 9 | return DefWindowProc(hwnd, msg, wparam, lparam); 10 | } 11 | 12 | Window create_window(uint32_t width, uint32_t height) { 13 | const uint32_t DEFAULT_WINDOW_X = 200; 14 | const uint32_t DEFAULT_WINDOW_Y = 100; 15 | const uint32_t DEFAULT_WINDOW_WIDTH = 1024; 16 | const uint32_t DEFAULT_WINDOW_HEIGHT = 768; 17 | 18 | if (width == 0) { 19 | width = DEFAULT_WINDOW_WIDTH; 20 | } 21 | if (height == 0) { 22 | height = DEFAULT_WINDOW_HEIGHT; 23 | } 24 | 25 | LPSTR MODULE_NAME = TEXT("GraphiT"); 26 | LPSTR WINDOW_CLASS_NAME = TEXT("GraphiTWindowClass"); 27 | LPSTR WINDOW_NAME = TEXT("GraphiTWindow"); 28 | 29 | HINSTANCE hinst = GetModuleHandle(MODULE_NAME); 30 | 31 | WNDCLASS wnd_cls; 32 | wnd_cls.style = CS_HREDRAW | CS_OWNDC | CS_VREDRAW; 33 | wnd_cls.lpfnWndProc = (WNDPROC)wnd_proc; 34 | wnd_cls.cbClsExtra = NULL; // No extra 35 | wnd_cls.cbWndExtra = sizeof(Window::Extra); // window data. 36 | wnd_cls.hInstance = hinst; 37 | wnd_cls.hIcon = LoadIcon(NULL, IDI_WINLOGO); // Default icon. 38 | wnd_cls.hCursor = LoadCursor(NULL, IDC_ARROW); // Default cursor. 39 | wnd_cls.hbrBackground = NULL; 40 | wnd_cls.lpszMenuName = NULL; 41 | wnd_cls.lpszClassName = WINDOW_CLASS_NAME; 42 | ATOM res = RegisterClass(&wnd_cls); 43 | L_ASSERT(res, "cannot register window class"); 44 | 45 | RECT rect = {0, 0, width, height}; 46 | DWORD style = WS_OVERLAPPEDWINDOW; 47 | DWORD exstyle = WS_EX_WINDOWEDGE | WS_EX_APPWINDOW; 48 | AdjustWindowRectEx(&rect, style, FALSE, exstyle); 49 | 50 | HWND hwnd = CreateWindowEx( 51 | exstyle, 52 | WINDOW_CLASS_NAME, 53 | WINDOW_NAME, 54 | style, 55 | DEFAULT_WINDOW_X, 56 | DEFAULT_WINDOW_Y, 57 | rect.right - rect.left, 58 | rect.bottom - rect.top, 59 | NULL, 60 | NULL, 61 | hinst, 62 | NULL 63 | ); 64 | L_ASSERT(hwnd != NULL, "cannot create window"); 65 | 66 | ShowWindow(hwnd, SW_SHOW); 67 | 68 | Window out{}; 69 | out.width = width; 70 | out.height = height; 71 | out.hinst = hinst; 72 | out.hwnd = hwnd; 73 | return out; 74 | } 75 | Window create_window() { 76 | return create_window(0, 0); 77 | } 78 | 79 | } // namespace windows 80 | } // namespace liong 81 | #endif // _WIN32 82 | -------------------------------------------------------------------------------- /src/gft/renderdoc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "gft/assert.hpp" 3 | #include "gft/util.hpp" 4 | #include "gft/log.hpp" 5 | #include "gft/renderdoc.hpp" 6 | #ifdef _WIN32 7 | #define WIN32_LEAN_AND_MEAN 8 | #include 9 | #define RENDERDOC_CC __cdecl 10 | #else 11 | #define RENDERDOC_CC 12 | #endif // _WIN32 13 | 14 | 15 | // https://github.com/baldurk/renderdoc/blob/v1.x/renderdoc/api/app/renderdoc_app.h 16 | typedef void* RENDERDOC_IgnoredApi; 17 | typedef void* RENDERDOC_IgnoredHandle; 18 | typedef enum RENDERDOC_Version { 19 | eRENDERDOC_API_Version_1_0_0 = 10000, // RENDERDOC_API_1_0_0 = 1 00 00 20 | } RENDERDOC_Version; 21 | 22 | typedef int(RENDERDOC_CC* pRENDERDOC_GetAPI 23 | )(RENDERDOC_Version version, void** outAPIPointers); 24 | 25 | typedef uint32_t(RENDERDOC_CC* pRENDERDOC_GetNumCaptures)(); 26 | typedef uint32_t(RENDERDOC_CC* pRENDERDOC_GetCapture 27 | )(uint32_t idx, char* filename, uint32_t* pathlength, uint64_t* timestamp); 28 | typedef uint32_t(RENDERDOC_CC* pRENDERDOC_LaunchReplayUI 29 | )(uint32_t connectTargetControl, const char* cmdline); 30 | 31 | typedef void(RENDERDOC_CC* pRENDERDOC_StartFrameCapture 32 | )(RENDERDOC_IgnoredHandle device, RENDERDOC_IgnoredHandle wndHandle); 33 | typedef uint32_t(RENDERDOC_CC* pRENDERDOC_EndFrameCapture 34 | )(RENDERDOC_IgnoredHandle device, RENDERDOC_IgnoredHandle wndHandle); 35 | 36 | typedef struct RENDERDOC_API_1_0_0 { 37 | RENDERDOC_IgnoredApi GetAPIVersion; 38 | 39 | RENDERDOC_IgnoredApi SetCaptureOptionU32; 40 | RENDERDOC_IgnoredApi SetCaptureOptionF32; 41 | 42 | RENDERDOC_IgnoredApi GetCaptureOptionU32; 43 | RENDERDOC_IgnoredApi GetCaptureOptionF32; 44 | 45 | RENDERDOC_IgnoredApi SetFocusToggleKeys; 46 | RENDERDOC_IgnoredApi SetCaptureKeys; 47 | 48 | RENDERDOC_IgnoredApi GetOverlayBits; 49 | RENDERDOC_IgnoredApi MaskOverlayBits; 50 | 51 | RENDERDOC_IgnoredApi RemoveHooks; 52 | RENDERDOC_IgnoredApi UnloadCrashHandler; 53 | 54 | RENDERDOC_IgnoredApi SetCaptureFilePathTemplate; 55 | RENDERDOC_IgnoredApi GetCaptureFilePathTemplate; 56 | 57 | pRENDERDOC_GetNumCaptures GetNumCaptures; 58 | pRENDERDOC_GetCapture GetCapture; 59 | 60 | RENDERDOC_IgnoredApi TriggerCapture; 61 | 62 | RENDERDOC_IgnoredApi IsTargetControlConnected; 63 | pRENDERDOC_LaunchReplayUI LaunchReplayUI; 64 | 65 | RENDERDOC_IgnoredApi SetActiveWindow; 66 | 67 | pRENDERDOC_StartFrameCapture StartFrameCapture; 68 | RENDERDOC_IgnoredApi IsFrameCapturing; 69 | pRENDERDOC_EndFrameCapture EndFrameCapture; 70 | } RENDERDOC_API_1_0_0; 71 | 72 | 73 | namespace liong { 74 | 75 | namespace renderdoc { 76 | 77 | struct Context { 78 | RENDERDOC_API_1_0_0* api; 79 | Context(RENDERDOC_API_1_0_0* api) : api(api) {} 80 | virtual ~Context() {}; 81 | 82 | virtual void begin_capture() { api->StartFrameCapture(nullptr, nullptr); }; 83 | virtual void end_capture() { 84 | if (api->EndFrameCapture(nullptr, nullptr) != 1) { 85 | L_WARN("renderdoc failed to capture this scoped frame"); 86 | return; 87 | } 88 | 89 | // Kick off the GUI. 90 | std::string path; 91 | uint32_t size = 1024; 92 | path.resize(size); 93 | uint32_t icapture = api->GetNumCaptures() - 1; 94 | if (api->GetCapture(icapture, (char*)path.data(), &size, nullptr) != 1) { 95 | L_WARN( 96 | "cannot get renderdoc capture path, will not launch replay ui for this " 97 | "one" 98 | ); 99 | } else { 100 | api->LaunchReplayUI(1, path.c_str()); 101 | L_INFO("launched renderdoc replay ui for captured frame #", icapture); 102 | } 103 | }; 104 | }; 105 | std::shared_ptr ctxt = nullptr; 106 | 107 | #ifdef _WIN32 108 | struct WindowsContext : public Context { 109 | HMODULE mod; 110 | bool should_release; 111 | WindowsContext(RENDERDOC_API_1_0_0* api, HMODULE mod, bool should_release) : 112 | Context(api), mod(mod), should_release(should_release) {} 113 | virtual ~WindowsContext() override final { 114 | if (should_release) { 115 | FreeLibrary(mod); 116 | L_INFO("renderdoc is unloaded"); 117 | } 118 | } 119 | }; 120 | #endif // _WIN32 121 | 122 | 123 | static bool is_first_call = true; 124 | void initialize() { 125 | if (ctxt != nullptr) { 126 | return; 127 | } 128 | 129 | if (!is_first_call) { 130 | return; 131 | } 132 | is_first_call = false; 133 | 134 | #ifdef _WIN32 135 | { 136 | HMODULE mod = GetModuleHandleA("renderdoc.dll"); 137 | bool should_release; 138 | if (mod != NULL) { 139 | should_release = false; 140 | } else { 141 | // Find renderdoc on local disk. 142 | std::string path; 143 | LSTATUS err = ERROR_MORE_DATA; 144 | while (err == ERROR_MORE_DATA && path.size() < 2048) { 145 | path.resize(path.size() + 256); 146 | DWORD cap = path.size(); 147 | // HKEY reg_key; 148 | // RegOpenKeyA(HKEY_LOCAL_MACHINE, 149 | // "SOFTWARE\\Classes\\RenderDoc.RDCCapture.1\\DefaultIcon", 150 | // ®_key); 151 | err = RegGetValueA( 152 | HKEY_LOCAL_MACHINE, 153 | "SOFTWARE\\Classes\\RenderDoc.RDCCapture." 154 | "1\\DefaultIcon\\", 155 | "", 156 | RRF_RT_REG_EXPAND_SZ | RRF_NOEXPAND, 157 | NULL, 158 | (void*)path.data(), 159 | &cap 160 | ); 161 | } 162 | if (err != ERROR_SUCCESS || path.empty()) { 163 | L_WARN( 164 | "failed to find renderdoc on local installtion, renderdoc " 165 | "utils become nops" 166 | ); 167 | return; 168 | } else { 169 | // Rewrite the path to renderdoc. 170 | path.resize(std::strlen(path.c_str())); 171 | if (util::ends_with("qrenderdoc.exe", path)) { 172 | path.resize(path.size() - 14); 173 | } else { 174 | path.clear(); 175 | } 176 | path += "renderdoc.dll"; 177 | } 178 | 179 | mod = LoadLibraryA(path.c_str()); 180 | if (mod == NULL) { 181 | L_WARN( 182 | "failed to load renderdoc library from installtion, " 183 | "renderdoc utils become nops" 184 | ); 185 | return; 186 | } 187 | should_release = true; 188 | } 189 | 190 | auto get_api = (pRENDERDOC_GetAPI)GetProcAddress(mod, "RENDERDOC_GetAPI"); 191 | RENDERDOC_API_1_0_0* api; 192 | if (get_api == nullptr || get_api(eRENDERDOC_API_Version_1_0_0, (void**)&api) != 1 || api == nullptr) { 193 | L_WARN( 194 | "failed to get renderdoc api from running instance, renderdoc " 195 | "apis become nops" 196 | ); 197 | return; 198 | } else { 199 | ctxt = std::shared_ptr( 200 | (Context*)new WindowsContext(api, mod, should_release) 201 | ); 202 | } 203 | } 204 | #else // _WIN32 205 | { 206 | L_WARN( 207 | "renderdoc is not supported on current platform, renderdoc utils become " 208 | "nops" 209 | ); 210 | } 211 | #endif // _WIN32 212 | 213 | if (ctxt) { 214 | L_INFO("renderdoc is ready to capture"); 215 | } 216 | } 217 | 218 | bool is_captureing = false; 219 | void begin_capture() { 220 | L_ASSERT(!is_captureing, "cannot begin capture session inside another"); 221 | is_captureing = true; 222 | if (ctxt == nullptr) { 223 | if (is_first_call) { 224 | panic("renderdoc must be initialized before any capture"); 225 | } else { 226 | L_WARN( 227 | "frame capture is attempted but it will be ignored because renderdoc " 228 | "failed to initialize" 229 | ); 230 | } 231 | } else { 232 | ctxt->begin_capture(); 233 | } 234 | } 235 | void end_capture() { 236 | L_ASSERT(is_captureing, "cannot end a capture out of any session"); 237 | is_captureing = false; 238 | if (ctxt == nullptr) { 239 | if (is_first_call) { 240 | panic("renderdoc must be initialized before any capture"); 241 | } 242 | } else { 243 | ctxt->end_capture(); 244 | } 245 | } 246 | 247 | } // namespace renderdoc 248 | 249 | } // namespace liong 250 | -------------------------------------------------------------------------------- /src/gft/stream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "gft/stream.hpp" 3 | #include "gft/assert.hpp" 4 | 5 | namespace liong { 6 | namespace stream { 7 | 8 | ReadStream& ReadStream::skip(size_t n) { 9 | L_ASSERT(size_ >= offset_ + n); 10 | offset_ += n; 11 | return *this; 12 | } 13 | void ReadStream::peek_data(void* out, size_t size) { 14 | L_ASSERT(size_remain() >= size); 15 | const void* buf = (const uint8_t*)data_ + offset_; 16 | std::memcpy(out, buf, size); 17 | } 18 | void ReadStream::extract_data(void* out, size_t size) { 19 | peek_data(out, size); 20 | offset_ += size; 21 | } 22 | 23 | void WriteStream::append_data(const void* data, size_t size) { 24 | size_t offset = data_.size(); 25 | data_.resize(offset + size); 26 | std::memcpy(data_.data() + offset, data, size); 27 | } 28 | 29 | } // namespace stream 30 | } // namespace liong 31 | -------------------------------------------------------------------------------- /src/gft/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "gft/test.hpp" 3 | #include "gft/log.hpp" 4 | 5 | namespace liong { 6 | 7 | namespace test { 8 | 9 | TestRegistry TEST_REG; 10 | 11 | 12 | TestRegistry::TestRegistry() {} 13 | 14 | TestRegistry& TestRegistry::get_inst() { 15 | static std::unique_ptr inner; 16 | if (inner == nullptr) { 17 | inner = std::make_unique(); 18 | } 19 | return *inner; 20 | } 21 | 22 | int TestRegistry::reg(const std::string& name, std::function&& func) { 23 | tests.emplace(name, Entry { func }); 24 | return 0; 25 | } 26 | 27 | TestReport TestRegistry::run_all() { 28 | const auto& tests = get_inst().tests; 29 | 30 | TestReport out {}; 31 | if (tests.empty()) { 32 | L_INFO("no test to run"); 33 | return out; 34 | } else { 35 | L_INFO("scheduling ", tests.size(), " tests"); 36 | } 37 | 38 | for (const auto& pair : tests) { 39 | L_INFO("[", pair.first, "]"); 40 | log::push_indent(); 41 | try { 42 | pair.second.f(); 43 | } catch (const std::exception& e) { 44 | L_ERROR("unit test '", pair.first, "' threw an exception"); 45 | L_ERROR(e.what()); 46 | ++out.nfail; 47 | continue; 48 | } catch (...) { 49 | L_ERROR("unit test '", pair.first, "' threw an illiterate exception"); 50 | ++out.nfail; 51 | continue; 52 | } 53 | log::pop_indent(); 54 | ++out.nsucc; 55 | } 56 | return out; 57 | } 58 | 59 | } // namespace test 60 | 61 | } // namespace liong 62 | -------------------------------------------------------------------------------- /src/gft/util.cpp: -------------------------------------------------------------------------------- 1 | #include "gft/util.hpp" 2 | #include "gft/assert.hpp" 3 | #include 4 | #include 5 | 6 | namespace liong { 7 | 8 | namespace util { 9 | 10 | std::vector load_file(const char* path) { 11 | std::ifstream f(path, std::ios::ate | std::ios::binary | std::ios::in); 12 | L_ASSERT(f.is_open(), "unable to open file: ", path); 13 | size_t size = f.tellg(); 14 | f.seekg(std::ios::beg); 15 | std::vector buf; 16 | buf.resize(size); 17 | f.read((char*)buf.data(), size); 18 | f.close(); 19 | return buf; 20 | } 21 | std::string load_text(const char* path) { 22 | std::ifstream f(path, std::ios::ate | std::ios::binary | std::ios::in); 23 | L_ASSERT(f.is_open(), "unable to open file: ", path); 24 | size_t size = f.tellg(); 25 | f.seekg(std::ios::beg); 26 | std::string buf; 27 | buf.reserve(size + 1); 28 | buf.resize(size); 29 | f.read((char*)buf.data(), size); 30 | f.close(); 31 | return buf; 32 | } 33 | void save_file(const char* path, const void* data, size_t size) { 34 | std::ofstream f(path, std::ios::trunc | std::ios::out | std::ios::binary); 35 | L_ASSERT(f.is_open(), "unable to open file: ", path); 36 | f.write((const char*)data, size); 37 | f.close(); 38 | } 39 | void save_text(const char* path, const std::string& txt) { 40 | std::ofstream f(path, std::ios::trunc | std::ios::out); 41 | L_ASSERT(f.is_open(), "unable to open file: ", path); 42 | f.write(txt.c_str(), txt.size()); 43 | f.close(); 44 | } 45 | 46 | // Save an array of 8-bit unsigned int colors with RGBA channels packed from LSB 47 | // to MSB in a 32-bit unsigned int into a bitmap file. 48 | void save_bmp(const uint32_t* pxs, uint32_t w, uint32_t h, const char* path) { 49 | std::fstream f(path, std::ios::out | std::ios::binary | std::ios::trunc); 50 | f.write("BM", 2); 51 | uint32_t img_size = w * h * sizeof(uint32_t); 52 | uint32_t bmfile_hdr[] = { 14 + 108 + img_size, 0, 14 + 108 }; 53 | f.write((const char*)bmfile_hdr, sizeof(bmfile_hdr)); 54 | uint32_t bmcore_hdr[] = { 55 | 108, w, h, 1 | (32 << 16), 56 | 3, img_size, 2835, 2835, 57 | 0, 0, 0x000000FF, 0x0000FF00, 58 | 0x00FF0000, 0xFF000000, 0x57696E20, 0, 59 | 0, 0, 0, 0, 60 | 0, 0, 0, 0, 61 | 0, 0, 0, 62 | }; 63 | f.write((const char*)bmcore_hdr, sizeof(bmcore_hdr)); 64 | uint32_t buf; 65 | for (auto i = 0; i < h; ++i) { 66 | for (auto j = 0; j < w; ++j) { 67 | buf = pxs[(h - i - 1) * w + j]; 68 | f.write((const char*)&buf, sizeof(uint32_t)); 69 | } 70 | } 71 | f.flush(); 72 | f.close(); 73 | } 74 | // Save an array of 32-bit floating point colors with RGBA channels into a 75 | // bitmap file. 76 | void save_bmp(const float* pxs, uint32_t w, uint32_t h, const char* path) { 77 | std::vector packed_pxs; 78 | packed_pxs.resize(w * h * 4); 79 | size_t npx = (size_t)w * (size_t)h; 80 | for (size_t i = 0; i < npx; ++i) { 81 | uint32_t r = (uint32_t)((float)pxs[i * 4 + 0] * 255.0f); 82 | uint32_t g = (uint32_t)((float)pxs[i * 4 + 1] * 255.0f); 83 | uint32_t b = (uint32_t)((float)pxs[i * 4 + 2] * 255.0f); 84 | uint32_t a = (uint32_t)((float)pxs[i * 4 + 3] * 255.0f); 85 | packed_pxs[i] = (r << 0) | (g << 8) | (b << 16) | (a << 24); 86 | } 87 | save_bmp(packed_pxs.data(), w, h, path); 88 | } 89 | 90 | void sleep_for_us(uint64_t t) { 91 | std::this_thread::sleep_for(std::chrono::microseconds(t)); 92 | } 93 | 94 | bool starts_with(const std::string& start, const std::string& str) { 95 | if (str.size() < start.size()) { 96 | return false; 97 | } 98 | for (size_t i = 0; i < start.size(); ++i) { 99 | if (str[i] != start[i]) { 100 | return false; 101 | } 102 | } 103 | return true; 104 | } 105 | bool ends_with(const std::string& end, const std::string& str) { 106 | if (str.size() < end.size()) { 107 | return false; 108 | } 109 | size_t offset = str.size() - end.size(); 110 | for (size_t i = 0; i < end.size(); ++i) { 111 | if (str[offset + i] != end[i]) { 112 | return false; 113 | } 114 | } 115 | return true; 116 | } 117 | std::vector split(char sep, const std::string& str) { 118 | std::vector out; 119 | 120 | auto beg = str.begin(); 121 | auto pos = beg; 122 | auto end = str.end(); 123 | 124 | while (pos != end) { 125 | if (*pos == sep) { 126 | if (beg != pos) { 127 | out.emplace_back(beg, pos); 128 | } 129 | ++pos; 130 | beg = pos; 131 | } else { 132 | ++pos; 133 | } 134 | } 135 | if (beg != end) { 136 | out.emplace_back(beg, end); 137 | } 138 | 139 | return out; 140 | } 141 | std::string trim(const std::string& str) { 142 | auto beg = str.begin(); 143 | auto end = str.end(); 144 | 145 | char c; 146 | while (beg != end) { 147 | c = *beg; 148 | if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { 149 | ++beg; 150 | } else { 151 | break; 152 | } 153 | } 154 | auto next_end = end; 155 | while (beg != end) { 156 | c = *(--next_end); 157 | if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { 158 | end = next_end; 159 | } else { 160 | break; 161 | } 162 | } 163 | return std::string(beg, end); 164 | } 165 | 166 | std::string replace_all(const std::string& str, const std::string& from, const std::string& to) { 167 | std::string out; 168 | out.reserve(str.size()); 169 | size_t pos = 0; 170 | size_t next_pos = 0; 171 | while (true) { 172 | next_pos = str.find(from, pos); 173 | if (next_pos == std::string::npos) { 174 | out.append(str, pos, str.size() - pos); 175 | break; 176 | } else { 177 | out.append(str, pos, next_pos - pos); 178 | out.append(to); 179 | pos = next_pos + from.size(); 180 | } 181 | } 182 | return out; 183 | } 184 | 185 | std::string fill_template(const std::string& templ, const std::map& args) { 186 | std::string out; 187 | out.reserve(templ.size()); 188 | size_t pos = 0; 189 | size_t next_pos = 0; 190 | while (true) { 191 | next_pos = templ.find("${", pos); 192 | if (next_pos == std::string::npos) { 193 | out.append(templ, pos, templ.size() - pos); 194 | break; 195 | } else { 196 | out.append(templ, pos, next_pos - pos); 197 | pos = next_pos + 2; 198 | next_pos = templ.find("}", pos); 199 | L_ASSERT(next_pos != std::string::npos, "invalid template: ", templ); 200 | std::string arg_name = std::string(templ, pos, next_pos - pos); 201 | auto arg_it = args.find(arg_name); 202 | L_ASSERT(arg_it != args.end(), "missing argument: ", arg_name); 203 | out.append(arg_it->second); 204 | pos = next_pos + 1; 205 | } 206 | } 207 | return out; 208 | } 209 | 210 | /* 211 | ** The crc32 is licensed under the Apache License, Version 2.0, and a copy of 212 | ** the license is included in this file. 213 | ** 214 | ** Author:Wang Yaofu voipman@qq.com 215 | ** Description: The source file of class crc32. 216 | ** CRC32 implementation according to IEEE standards. 217 | ** Polynomials are represented in LSB-first form 218 | ** following parameters: 219 | ** Width : 32 bit 220 | ** Poly : 0xedb88320 221 | ** Output for "123456789" : 0xCBF43926 222 | */ 223 | uint32_t crc32(const void* data, size_t size) { 224 | static uint32_t LUT[] = { 225 | 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, 226 | 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, 227 | 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 228 | 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 229 | 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, 230 | 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 231 | 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 232 | 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, 233 | 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, 234 | 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, 235 | 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 236 | 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 237 | 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, 238 | 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, 239 | 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 240 | 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, 241 | 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 242 | 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, 243 | 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 244 | 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, 245 | 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, 246 | 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 247 | 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 248 | 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, 249 | 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, 250 | 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, 251 | 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, 252 | 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, 253 | 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, 254 | 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, 255 | 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 256 | 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, 257 | 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, 258 | 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, 259 | 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 260 | 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, 261 | 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, 262 | 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, 263 | 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 264 | 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, 265 | 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, 266 | 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, 267 | 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 268 | 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, 269 | 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, 270 | 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 271 | 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 272 | 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, 273 | 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, 274 | 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, 275 | 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 276 | 0x2d02ef8dL 277 | }; 278 | uint32_t crc32val = 0; 279 | crc32val ^= 0xFFFFFFFF; 280 | 281 | for (size_t i = 0; i < size; i++) { 282 | uint32_t x = ((const uint8_t*)data)[i]; 283 | crc32val = LUT[(crc32val ^ x) & 0xFF] ^ ((crc32val >> 8) & 0x00FFFFFF); 284 | } 285 | 286 | return crc32val ^ 0xFFFFFFFF; 287 | } 288 | 289 | } // namespace util 290 | 291 | } // namespace liong 292 | -------------------------------------------------------------------------------- /src/gft/vk/sys.hpp: -------------------------------------------------------------------------------- 1 | // # Wrapper for common Vulkan procedures. 2 | // @PENGUINLIONG 3 | #pragma once 4 | #include 5 | 6 | #include 7 | 8 | #ifdef _WIN32 9 | #define WIN32_LEAN_AND_MEAN 10 | #include 11 | #include "vulkan/vulkan_win32.h" 12 | #endif // _WIN32 13 | 14 | #if defined(ANDROID) || defined(__ANDROID__) 15 | #include "vulkan/vulkan_android.h" 16 | #endif // defined(ANDROID) || defined(__ANDROID__) 17 | 18 | #if defined(__MACH__) && defined(__APPLE__) 19 | #include "vulkan/vulkan_metal.h" 20 | #endif // defined(__MACH__) && defined(__APPLE__) 21 | 22 | #include "gft/vk-sys.hpp" 23 | #include "gft/log.hpp" 24 | 25 | namespace liong { 26 | namespace vk { 27 | namespace sys { 28 | 29 | // VkDebugUtilsMessengerEXT 30 | extern sys::DebugUtilsMessengerRef create_debug_utils_messenger(VkInstance inst 31 | ); 32 | 33 | // VkInstance 34 | extern sys::InstanceRef create_inst(uint32_t api_ver, bool debug); 35 | 36 | // VkPhysicalDevice 37 | extern std::vector collect_physdevs(VkInstance inst); 38 | extern std::map collect_physdev_ext_props( 39 | VkPhysicalDevice physdev 40 | ); 41 | extern VkPhysicalDeviceProperties get_physdev_prop(VkPhysicalDevice physdev); 42 | extern VkPhysicalDeviceMemoryProperties get_physdev_mem_prop( 43 | VkPhysicalDevice physdev 44 | ); 45 | extern VkPhysicalDeviceFeatures get_physdev_feat(VkPhysicalDevice physdev); 46 | extern std::vector collect_qfam_props( 47 | VkPhysicalDevice physdev 48 | ); 49 | 50 | // VkDevice 51 | extern sys::DeviceRef create_dev( 52 | VkPhysicalDevice physdev, 53 | const std::vector dqcis, 54 | const std::vector enabled_ext_names, 55 | const VkPhysicalDeviceFeatures& enabled_feat 56 | ); 57 | VkQueue get_dev_queue(VkDevice dev, uint32_t qfam_idx, uint32_t queue_idx); 58 | 59 | // VkSampler 60 | extern sys::SamplerRef create_sampler( 61 | VkDevice dev, 62 | VkFilter filter, 63 | VkSamplerMipmapMode mip_mode, 64 | float max_aniso, 65 | VkCompareOp cmp_op 66 | ); 67 | 68 | // VkDescriptorSetLayout 69 | extern sys::DescriptorSetLayoutRef create_desc_set_layout( 70 | VkDevice dev, 71 | const std::vector& dslbs 72 | ); 73 | 74 | // VkPipelineLayout 75 | extern sys::PipelineLayoutRef create_pipe_layout( 76 | VkDevice dev, 77 | VkDescriptorSetLayout desc_set_layout 78 | ); 79 | 80 | // VkShaderModule 81 | extern ShaderModuleRef create_shader_mod( 82 | VkDevice dev, 83 | const uint32_t* spv, 84 | size_t spv_size 85 | ); 86 | 87 | // VkPipeline 88 | extern sys::PipelineRef create_comp_pipe( 89 | VkDevice dev, 90 | VkPipelineLayout pipe_layout, 91 | const VkPipelineShaderStageCreateInfo& pssci 92 | ); 93 | extern sys::PipelineRef create_graph_pipe( 94 | VkDevice dev, 95 | VkPipelineLayout pipe_layout, 96 | VkRenderPass pass, 97 | uint32_t width, 98 | uint32_t height, 99 | const VkPipelineInputAssemblyStateCreateInfo& piasci, 100 | const VkPipelineRasterizationStateCreateInfo& prsci, 101 | const std::array psscis 102 | ); 103 | 104 | 105 | } // namespace sys 106 | 107 | inline void request_name( 108 | const char* category, 109 | const char* expect, 110 | const char* actual, 111 | std::vector& out 112 | ) { 113 | if (std::strcmp(expect, actual) == 0) { 114 | out.push_back(expect); 115 | L_INFO("enabled ", category, " ", expect); 116 | } 117 | } 118 | inline void request_instance_extension( 119 | const char* expect, 120 | const char* actual, 121 | std::vector& out 122 | ) { 123 | request_name("instance extension", expect, actual, out); 124 | } 125 | inline void request_instance_layer( 126 | const char* expect, 127 | const char* actual, 128 | std::vector& out 129 | ) { 130 | request_name("instance layer", expect, actual, out); 131 | } 132 | inline void request_device_extension( 133 | const char* expect, 134 | const char* actual, 135 | std::vector& out 136 | ) { 137 | request_name("device extension", expect, actual, out); 138 | } 139 | 140 | } // namespace vk 141 | } // namespace liong 142 | -------------------------------------------------------------------------------- /src/gft/vk/vk-buffer.cpp: -------------------------------------------------------------------------------- 1 | #include "sys.hpp" 2 | #include "gft/assert.hpp" 3 | #include "gft/util.hpp" 4 | #include "gft/log.hpp" 5 | #include "gft/vk/vk-buffer.hpp" 6 | 7 | namespace liong { 8 | namespace vk { 9 | 10 | constexpr VmaMemoryUsage _host_access2vma_usage(MemoryAccess host_access) { 11 | if (host_access == 0) { 12 | return VMA_MEMORY_USAGE_GPU_ONLY; 13 | } else if (host_access == L_MEMORY_ACCESS_READ_BIT) { 14 | return VMA_MEMORY_USAGE_GPU_TO_CPU; 15 | } else if (host_access == L_MEMORY_ACCESS_WRITE_BIT) { 16 | return VMA_MEMORY_USAGE_CPU_TO_GPU; 17 | } else { 18 | return VMA_MEMORY_USAGE_CPU_ONLY; 19 | } 20 | } 21 | BufferRef VulkanBuffer::create( 22 | const ContextRef& ctxt, 23 | const BufferConfig& cfg 24 | ) { 25 | VulkanContextRef ctxt_ = VulkanContext::from_hal(ctxt); 26 | 27 | size_t min_alloc_size = 16; 28 | VkBufferCreateInfo bci{}; 29 | bci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; 30 | bci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; 31 | if (cfg.usage & L_BUFFER_USAGE_TRANSFER_SRC_BIT) { 32 | bci.usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT; 33 | min_alloc_size = std::max( 34 | min_alloc_size, ctxt_->physdev_prop().limits.minMemoryMapAlignment 35 | ); 36 | } 37 | if (cfg.usage & L_BUFFER_USAGE_TRANSFER_DST_BIT) { 38 | bci.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; 39 | } 40 | if (cfg.usage & L_BUFFER_USAGE_UNIFORM_BIT) { 41 | bci.usage |= 42 | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; 43 | min_alloc_size = std::max( 44 | min_alloc_size, 45 | ctxt_->physdev_prop().limits.minUniformBufferOffsetAlignment 46 | ); 47 | } 48 | if (cfg.usage & L_BUFFER_USAGE_STORAGE_BIT) { 49 | bci.usage |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | 50 | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | 51 | VK_BUFFER_USAGE_TRANSFER_DST_BIT; 52 | min_alloc_size = std::max( 53 | min_alloc_size, 54 | ctxt_->physdev_prop().limits.minStorageBufferOffsetAlignment 55 | ); 56 | } 57 | if (cfg.usage & L_BUFFER_USAGE_VERTEX_BIT) { 58 | bci.usage |= 59 | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; 60 | } 61 | if (cfg.usage & L_BUFFER_USAGE_INDEX_BIT) { 62 | bci.usage |= 63 | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; 64 | } 65 | bci.size = std::max(cfg.size, min_alloc_size); 66 | 67 | VmaAllocationCreateInfo aci{}; 68 | aci.usage = _host_access2vma_usage(cfg.host_access); 69 | 70 | sys::BufferRef buf = sys::Buffer::create(*ctxt_->allocator, &bci, &aci); 71 | 72 | BufferDynamicDetail dyn_detail{}; 73 | dyn_detail.access = 0; 74 | dyn_detail.stage = VK_PIPELINE_STAGE_HOST_BIT; 75 | 76 | BufferInfo info{}; 77 | info.label = cfg.label; 78 | info.host_access = cfg.host_access; 79 | info.size = cfg.size; 80 | info.usage = cfg.usage; 81 | 82 | VulkanBufferRef out = std::make_shared(ctxt_, std::move(info)); 83 | out->ctxt = ctxt_; 84 | out->buf = std::move(buf); 85 | out->dyn_detail = std::move(dyn_detail); 86 | L_DEBUG("created buffer '", cfg.label, "'"); 87 | 88 | return out; 89 | } 90 | VulkanBuffer::VulkanBuffer(VulkanContextRef ctxt, BufferInfo&& info) : 91 | Buffer(std::move(info)), ctxt(ctxt) {} 92 | VulkanBuffer::~VulkanBuffer() { 93 | if (buf) { 94 | L_DEBUG("destroyed buffer '", info.label, "'"); 95 | } 96 | } 97 | 98 | void* VulkanBuffer::map(MemoryAccess map_access) { 99 | L_ASSERT(map_access != 0, "memory map access must be read, write or both"); 100 | 101 | void* mapped = nullptr; 102 | VK_ASSERT << vmaMapMemory(*ctxt->allocator, buf->alloc, &mapped); 103 | 104 | dyn_detail.access = map_access == L_MEMORY_ACCESS_READ_BIT 105 | ? VK_ACCESS_HOST_READ_BIT 106 | : VK_ACCESS_HOST_WRITE_BIT; 107 | dyn_detail.stage = VK_PIPELINE_STAGE_HOST_BIT; 108 | 109 | L_DEBUG("mapped buffer '", info.label, "'"); 110 | return (uint8_t*)mapped; 111 | } 112 | void VulkanBuffer::unmap() { 113 | vmaUnmapMemory(*ctxt->allocator, buf->alloc); 114 | L_DEBUG("unmapped buffer '", info.label, "'"); 115 | } 116 | 117 | } // namespace vk 118 | } // namespace liong 119 | -------------------------------------------------------------------------------- /src/gft/vk/vk-depth-img.cpp: -------------------------------------------------------------------------------- 1 | #include "sys.hpp" 2 | #include "gft/assert.hpp" 3 | #include "gft/util.hpp" 4 | #include "gft/log.hpp" 5 | #include "gft/vk/vk-depth-image.hpp" 6 | 7 | namespace liong { 8 | namespace vk { 9 | 10 | DepthImageRef VulkanDepthImage::create( 11 | const ContextRef& ctxt, 12 | const DepthImageConfig& depth_img_cfg 13 | ) { 14 | VulkanContextRef ctxt_ = VulkanContext::from_hal(ctxt); 15 | 16 | VkFormat fmt = depth_format2vk(depth_img_cfg.depth_format); 17 | VkImageUsageFlags usage = 0; 18 | SubmitType init_submit_ty = L_SUBMIT_TYPE_ANY; 19 | 20 | if (depth_img_cfg.usage & L_DEPTH_IMAGE_USAGE_SAMPLED_BIT) { 21 | usage |= VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; 22 | init_submit_ty = L_SUBMIT_TYPE_ANY; 23 | } 24 | if (depth_img_cfg.usage & L_DEPTH_IMAGE_USAGE_ATTACHMENT_BIT) { 25 | usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT | 26 | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; 27 | init_submit_ty = L_SUBMIT_TYPE_GRAPHICS; 28 | } 29 | // KEEP THIS AFTER ANY SUBMIT TYPES. 30 | if (depth_img_cfg.usage & L_DEPTH_IMAGE_USAGE_SUBPASS_DATA_BIT) { 31 | usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT | 32 | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT; 33 | init_submit_ty = L_SUBMIT_TYPE_GRAPHICS; 34 | } 35 | 36 | // Check whether the device support our use case. 37 | VkImageFormatProperties ifp; 38 | VK_ASSERT << vkGetPhysicalDeviceImageFormatProperties( 39 | ctxt_->physdev(), 40 | fmt, 41 | VK_IMAGE_TYPE_2D, 42 | VK_IMAGE_TILING_OPTIMAL, 43 | usage, 44 | 0, 45 | &ifp 46 | ); 47 | 48 | VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; 49 | 50 | VkImageCreateInfo ici{}; 51 | ici.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; 52 | ici.imageType = VK_IMAGE_TYPE_2D; 53 | ici.format = fmt; 54 | ici.extent.width = (uint32_t)depth_img_cfg.width; 55 | ici.extent.height = (uint32_t)depth_img_cfg.height; 56 | ici.extent.depth = 1; 57 | ici.mipLevels = 1; 58 | ici.arrayLayers = 1; 59 | ici.samples = VK_SAMPLE_COUNT_1_BIT; 60 | ici.tiling = VK_IMAGE_TILING_OPTIMAL; 61 | ici.usage = usage; 62 | ici.sharingMode = VK_SHARING_MODE_EXCLUSIVE; 63 | ici.initialLayout = layout; 64 | 65 | bool is_tile_mem = depth_img_cfg.usage & L_DEPTH_IMAGE_USAGE_TILE_MEMORY_BIT; 66 | sys::ImageRef img; 67 | VmaAllocationCreateInfo aci{}; 68 | VkResult res = VK_ERROR_OUT_OF_DEVICE_MEMORY; 69 | if (is_tile_mem) { 70 | aci.usage = VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED; 71 | img = sys::Image::create(*ctxt_->allocator, &ici, &aci); 72 | } 73 | if (res != VK_SUCCESS) { 74 | if (is_tile_mem) { 75 | L_WARN("tile-memory is unsupported, fall back to regular memory"); 76 | } 77 | aci.usage = VMA_MEMORY_USAGE_GPU_ONLY; 78 | img = sys::Image::create(*ctxt_->allocator, &ici, &aci); 79 | } 80 | 81 | VkImageViewCreateInfo ivci{}; 82 | ivci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; 83 | ivci.image = img->img; 84 | ivci.viewType = VK_IMAGE_VIEW_TYPE_2D; 85 | ivci.format = fmt; 86 | ivci.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; 87 | ivci.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; 88 | ivci.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; 89 | ivci.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; 90 | ivci.subresourceRange.aspectMask = 91 | (fmt::get_fmt_depth_nbit(depth_img_cfg.depth_format) > 0 92 | ? VK_IMAGE_ASPECT_DEPTH_BIT 93 | : 0) | 94 | (fmt::get_fmt_stencil_nbit(depth_img_cfg.depth_format) > 0 95 | ? VK_IMAGE_ASPECT_STENCIL_BIT 96 | : 0); 97 | ivci.subresourceRange.baseArrayLayer = 0; 98 | ivci.subresourceRange.layerCount = 1; 99 | ivci.subresourceRange.baseMipLevel = 0; 100 | ivci.subresourceRange.levelCount = 1; 101 | 102 | sys::ImageViewRef img_view = sys::ImageView::create(*ctxt_->dev, &ivci); 103 | 104 | DepthImageDynamicDetail dyn_detail{}; 105 | dyn_detail.layout = layout; 106 | dyn_detail.access = 0; 107 | dyn_detail.stage = VK_PIPELINE_STAGE_HOST_BIT; 108 | 109 | DepthImageInfo info{}; 110 | info.label = depth_img_cfg.label; 111 | info.width = depth_img_cfg.width; 112 | info.height = depth_img_cfg.height; 113 | info.depth_format = depth_img_cfg.depth_format; 114 | info.usage = depth_img_cfg.usage; 115 | 116 | VulkanDepthImageRef out = 117 | std::make_shared(ctxt_, std::move(info)); 118 | out->img = std::move(img); 119 | out->img_view = std::move(img_view); 120 | out->dyn_detail = std::move(dyn_detail); 121 | 122 | L_DEBUG("created depth image '", depth_img_cfg.label, "'"); 123 | 124 | return out; 125 | } 126 | VulkanDepthImage::VulkanDepthImage( 127 | VulkanContextRef ctxt, 128 | DepthImageInfo&& info 129 | ) : 130 | DepthImage(std::move(info)), ctxt(ctxt) {} 131 | VulkanDepthImage::~VulkanDepthImage() { 132 | if (img) { 133 | L_DEBUG("destroyed depth image '", info.label, "'"); 134 | } 135 | } 136 | 137 | } // namespace vk 138 | } // namespace liong 139 | -------------------------------------------------------------------------------- /src/gft/vk/vk-image.cpp: -------------------------------------------------------------------------------- 1 | #include "sys.hpp" 2 | #include "gft/assert.hpp" 3 | #include "gft/util.hpp" 4 | #include "gft/log.hpp" 5 | #include "gft/vk/vk-image.hpp" 6 | 7 | namespace liong { 8 | namespace vk { 9 | 10 | VulkanImage::VulkanImage(VulkanContextRef ctxt, ImageInfo&& info) : 11 | Image(std::move(info)), ctxt(ctxt) {} 12 | 13 | ImageRef VulkanImage::create( 14 | const ContextRef& ctxt, 15 | const ImageConfig& img_cfg 16 | ) { 17 | VulkanContextRef ctxt_ = VulkanContext::from_hal(ctxt); 18 | 19 | VkFormat fmt = format2vk(img_cfg.format, img_cfg.color_space); 20 | VkImageUsageFlags usage = 0; 21 | SubmitType init_submit_ty = L_SUBMIT_TYPE_ANY; 22 | 23 | if (img_cfg.usage & L_IMAGE_USAGE_TRANSFER_SRC_BIT) { 24 | usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; 25 | init_submit_ty = L_SUBMIT_TYPE_ANY; 26 | } 27 | if (img_cfg.usage & L_IMAGE_USAGE_TRANSFER_DST_BIT) { 28 | usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; 29 | init_submit_ty = L_SUBMIT_TYPE_ANY; 30 | } 31 | if (img_cfg.usage & L_IMAGE_USAGE_SAMPLED_BIT) { 32 | usage |= VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; 33 | init_submit_ty = L_SUBMIT_TYPE_ANY; 34 | } 35 | if (img_cfg.usage & L_IMAGE_USAGE_STORAGE_BIT) { 36 | usage |= VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | 37 | VK_IMAGE_USAGE_TRANSFER_DST_BIT; 38 | init_submit_ty = L_SUBMIT_TYPE_ANY; 39 | } 40 | // KEEP THIS AFTER ANY SUBMIT TYPES. 41 | if (img_cfg.usage & L_IMAGE_USAGE_ATTACHMENT_BIT) { 42 | usage |= 43 | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; 44 | init_submit_ty = L_SUBMIT_TYPE_GRAPHICS; 45 | } 46 | if (img_cfg.usage & L_IMAGE_USAGE_SUBPASS_DATA_BIT) { 47 | usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT | 48 | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT; 49 | init_submit_ty = L_SUBMIT_TYPE_GRAPHICS; 50 | } 51 | 52 | VkImageType img_ty; 53 | VkImageViewType img_view_ty; 54 | if (img_cfg.depth != 0) { 55 | img_ty = VK_IMAGE_TYPE_3D; 56 | img_view_ty = VK_IMAGE_VIEW_TYPE_3D; 57 | } else if (img_cfg.height != 0) { 58 | img_ty = VK_IMAGE_TYPE_2D; 59 | img_view_ty = VK_IMAGE_VIEW_TYPE_2D; 60 | } else { 61 | img_ty = VK_IMAGE_TYPE_1D; 62 | img_view_ty = VK_IMAGE_VIEW_TYPE_1D; 63 | } 64 | 65 | // Check whether the device support our use case. 66 | VkImageFormatProperties ifp; 67 | VK_ASSERT << vkGetPhysicalDeviceImageFormatProperties( 68 | ctxt_->physdev(), fmt, img_ty, VK_IMAGE_TILING_OPTIMAL, usage, 0, &ifp 69 | ); 70 | 71 | VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; 72 | 73 | VkImageCreateInfo ici{}; 74 | ici.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; 75 | ici.imageType = img_ty; 76 | ici.format = fmt; 77 | ici.extent.width = img_cfg.width; 78 | ici.extent.height = img_cfg.height == 0 ? 1 : img_cfg.height; 79 | ici.extent.depth = img_cfg.depth == 0 ? 1 : img_cfg.depth; 80 | ici.mipLevels = 1; 81 | ici.arrayLayers = 1; 82 | ici.samples = VK_SAMPLE_COUNT_1_BIT; 83 | ici.tiling = VK_IMAGE_TILING_OPTIMAL; 84 | ici.usage = usage; 85 | ici.sharingMode = VK_SHARING_MODE_EXCLUSIVE; 86 | ici.initialLayout = layout; 87 | 88 | bool is_tile_mem = img_cfg.usage & L_IMAGE_USAGE_TILE_MEMORY_BIT; 89 | sys::ImageRef img = nullptr; 90 | VmaAllocationCreateInfo aci{}; 91 | VkResult res = VK_ERROR_OUT_OF_DEVICE_MEMORY; 92 | if (is_tile_mem) { 93 | aci.usage = VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED; 94 | img = sys::Image::create(*ctxt_->allocator, &ici, &aci); 95 | } 96 | if (res != VK_SUCCESS) { 97 | if (is_tile_mem) { 98 | L_WARN("tile-memory is unsupported, fall back to regular memory"); 99 | } 100 | aci.usage = VMA_MEMORY_USAGE_GPU_ONLY; 101 | img = sys::Image::create(*ctxt_->allocator, &ici, &aci); 102 | } 103 | 104 | VkImageViewCreateInfo ivci{}; 105 | ivci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; 106 | ivci.image = img->img; 107 | ivci.viewType = img_view_ty; 108 | ivci.format = fmt; 109 | ivci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 110 | ivci.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; 111 | ivci.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; 112 | 113 | sys::ImageViewRef img_view = sys::ImageView::create(ctxt_->dev->dev, &ivci); 114 | 115 | ImageDynamicDetail dyn_detail{}; 116 | dyn_detail.layout = layout; 117 | dyn_detail.access = 0; 118 | dyn_detail.stage = VK_PIPELINE_STAGE_HOST_BIT; 119 | 120 | ImageInfo info{}; 121 | info.label = img_cfg.label; 122 | info.width = img_cfg.width; 123 | info.height = img_cfg.height; 124 | info.depth = img_cfg.depth; 125 | info.format = img_cfg.format; 126 | info.color_space = img_cfg.color_space; 127 | info.usage = img_cfg.usage; 128 | 129 | VulkanImageRef out = std::make_shared(ctxt_, std::move(info)); 130 | out->img = std::move(img); 131 | out->img_view = std::move(img_view); 132 | out->dyn_detail = std::move(dyn_detail); 133 | 134 | L_DEBUG("created image '", img_cfg.label, "'"); 135 | 136 | return out; 137 | } 138 | VulkanImage::~VulkanImage() { 139 | if (img) { 140 | L_DEBUG("destroyed image '", info.label, "'"); 141 | } 142 | } 143 | 144 | } // namespace vk 145 | } // namespace liong -------------------------------------------------------------------------------- /src/gft/vk/vk-instance.cpp: -------------------------------------------------------------------------------- 1 | #include "sys.hpp" 2 | #include "gft/util.hpp" 3 | #include "gft/log.hpp" 4 | #include "gft/vk/vk-instance.hpp" 5 | #include "gft/vk/vk-context.hpp" 6 | 7 | namespace liong { 8 | namespace vk { 9 | 10 | void desc_physdev_prop( 11 | std::stringstream& ss, 12 | const VkPhysicalDeviceProperties& prop 13 | ) { 14 | const char* dev_ty_lit; 15 | switch (prop.deviceType) { 16 | case VK_PHYSICAL_DEVICE_TYPE_OTHER: 17 | dev_ty_lit = "Other"; 18 | break; 19 | case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: 20 | dev_ty_lit = "Integrated GPU"; 21 | break; 22 | case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: 23 | dev_ty_lit = "Discrete GPU"; 24 | break; 25 | case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: 26 | dev_ty_lit = "Virtual GPU"; 27 | break; 28 | case VK_PHYSICAL_DEVICE_TYPE_CPU: 29 | dev_ty_lit = "CPU"; 30 | break; 31 | default: 32 | dev_ty_lit = "Unknown"; 33 | break; 34 | } 35 | ss << util::format( 36 | prop.deviceName, 37 | " (", 38 | dev_ty_lit, 39 | ", ", 40 | VK_VERSION_MAJOR(prop.apiVersion), 41 | ".", 42 | VK_VERSION_MINOR(prop.apiVersion), 43 | ")" 44 | ); 45 | } 46 | void desc_physdev_mem_prop( 47 | std::stringstream& ss, 48 | VkPhysicalDeviceMemoryProperties mem_prop 49 | ) { 50 | for (size_t i = 0; i < mem_prop.memoryHeapCount; ++i) { 51 | const VkMemoryHeap& heap = mem_prop.memoryHeaps[i]; 52 | static const std::array flag_lits{ 53 | "DEVICE_LOCAL", 54 | }; 55 | std::vector flags{}; 56 | for (uint32_t j = 0; j < sizeof(heap.flags) * 8; ++j) { 57 | if (((heap.flags >> j) & 1) == 0) { 58 | continue; 59 | } 60 | if (j < flag_lits.size()) { 61 | flags.emplace_back(flag_lits[j]); 62 | } else { 63 | flags.emplace_back(util::format("(1 << ", j, ")")); 64 | } 65 | } 66 | std::string all_flags = flags.empty() ? "0" : util::join(" | ", flags); 67 | ss << std::endl << " memory heap #" << i << ": " << all_flags; 68 | } 69 | for (size_t i = 0; i < mem_prop.memoryTypeCount; ++i) { 70 | const VkMemoryType& ty = mem_prop.memoryTypes[i]; 71 | static const std::array flag_lits{ 72 | "DEVICE_LOCAL", 73 | "HOST_VISIBLE", 74 | "HOST_COHERENT", 75 | "HOST_CACHED", 76 | "LAZILY_ALLOCATED", 77 | "PROTECTED", 78 | }; 79 | std::vector flags{}; 80 | for (uint32_t j = 0; j < sizeof(ty.propertyFlags) * 8; ++j) { 81 | if (((ty.propertyFlags >> j) & 1) == 0) { 82 | continue; 83 | } 84 | if (j < flag_lits.size()) { 85 | flags.emplace_back(flag_lits[j]); 86 | } else { 87 | flags.emplace_back(util::format("(1 << ", j, ")")); 88 | } 89 | } 90 | std::string all_flags = flags.empty() ? "0" : util::join(" | ", flags); 91 | ss << std::endl << " memory type #" << i << " on heap #" << ty.heapIndex << ": " 92 | << all_flags; 93 | } 94 | } 95 | 96 | InstancePhysicalDeviceDetail make_physdev_detail(VkPhysicalDevice physdev) { 97 | auto prop = sys::get_physdev_prop(physdev); 98 | auto mem_prop = sys::get_physdev_mem_prop(physdev); 99 | 100 | std::stringstream ss{}; 101 | desc_physdev_prop(ss, prop); 102 | desc_physdev_mem_prop(ss, mem_prop); 103 | 104 | InstancePhysicalDeviceDetail out{}; 105 | out.physdev = physdev; 106 | out.prop = prop; 107 | out.mem_prop = mem_prop; 108 | out.feat = sys::get_physdev_feat(physdev); 109 | out.qfam_props = sys::collect_qfam_props(physdev); 110 | out.ext_props = sys::collect_physdev_ext_props(physdev); 111 | out.desc = ss.str(); 112 | return out; 113 | }; 114 | 115 | std::vector collect_physdev_details( 116 | VkInstance inst 117 | ) { 118 | std::vector physdevs = sys::collect_physdevs(inst); 119 | std::vector out{}; 120 | out.reserve(physdevs.size()); 121 | for (const auto& physdev : physdevs) { 122 | out.emplace_back(make_physdev_detail(physdev)); 123 | } 124 | return out; 125 | } 126 | 127 | VulkanInstanceRef VulkanInstance::create( 128 | uint32_t api_ver, 129 | sys::InstanceRef&& inst 130 | ) { 131 | VulkanInstanceRef out = std::make_shared(); 132 | out->api_ver = api_ver; 133 | out->inst = std::move(inst); 134 | out->physdev_details = collect_physdev_details(inst->inst); 135 | out->is_imported = false; 136 | 137 | L_INFO("vulkan backend initialized with external instance"); 138 | 139 | return out; 140 | } 141 | VulkanInstanceRef VulkanInstance::create(bool debug) { 142 | const uint32_t api_ver = VK_API_VERSION_1_1; 143 | 144 | sys::InstanceRef inst = sys::create_inst(api_ver, debug); 145 | sys::DebugUtilsMessengerRef debug_utils_messenger = nullptr; 146 | if (debug) { 147 | debug_utils_messenger = sys::create_debug_utils_messenger(inst->inst); 148 | } 149 | std::vector physdev_details = 150 | collect_physdev_details(inst->inst); 151 | 152 | VulkanInstanceRef out = std::make_shared(); 153 | out->api_ver = api_ver; 154 | out->inst = std::move(inst); 155 | out->debug_utils_messenger = std::move(debug_utils_messenger); 156 | out->physdev_details = std::move(physdev_details); 157 | out->is_imported = true; 158 | 159 | L_INFO("vulkan backend initialized"); 160 | 161 | return out; 162 | } 163 | VulkanInstance::~VulkanInstance() { 164 | debug_utils_messenger.reset(); 165 | inst.reset(); 166 | } 167 | 168 | void finalize() { 169 | L_INFO("vulkan backend finalized"); 170 | } 171 | std::string VulkanInstance::describe_device(uint32_t device_index) { 172 | return device_index < physdev_details.size() 173 | ? physdev_details.at(device_index).desc 174 | : std::string{}; 175 | } 176 | 177 | ContextRef VulkanInstance::create_context(const ContextConfig& cfg) { 178 | return VulkanContext::create(shared_from_this(), cfg); 179 | } 180 | ContextRef VulkanInstance::create_context(const ContextWindowsConfig& cfg) { 181 | return VulkanContext::create(shared_from_this(), cfg); 182 | } 183 | ContextRef VulkanInstance::create_context(const ContextAndroidConfig& cfg) { 184 | return VulkanContext::create(shared_from_this(), cfg); 185 | } 186 | ContextRef VulkanInstance::create_context(const ContextMetalConfig& cfg) { 187 | return VulkanContext::create(shared_from_this(), cfg); 188 | } 189 | 190 | } // namespace vk 191 | } // namespace liong 192 | -------------------------------------------------------------------------------- /src/gft/vk/vk-render-pass.cpp: -------------------------------------------------------------------------------- 1 | #include "gft/log.hpp" 2 | #include "gft/vk/vk-render-pass.hpp" 3 | #include "gft/vk/vk-invocation.hpp" 4 | #include "gft/vk/vk-task.hpp" 5 | #include "gft/vk/vk-image.hpp" 6 | #include "gft/vk/vk-depth-image.hpp" 7 | 8 | namespace liong { 9 | namespace vk { 10 | 11 | VulkanRenderPass::VulkanRenderPass( 12 | const VulkanContextRef& ctxt, 13 | RenderPassInfo&& info 14 | ) : 15 | RenderPass(std::move(info)), ctxt(ctxt) {} 16 | 17 | VkAttachmentLoadOp _get_load_op(AttachmentAccess attm_access) { 18 | if (attm_access & L_ATTACHMENT_ACCESS_CLEAR_BIT) { 19 | return VK_ATTACHMENT_LOAD_OP_CLEAR; 20 | } 21 | if (attm_access & L_ATTACHMENT_ACCESS_LOAD_BIT) { 22 | return VK_ATTACHMENT_LOAD_OP_LOAD; 23 | } 24 | return VK_ATTACHMENT_LOAD_OP_DONT_CARE; 25 | } 26 | VkAttachmentStoreOp _get_store_op(AttachmentAccess attm_access) { 27 | if (attm_access & L_ATTACHMENT_ACCESS_STORE_BIT) { 28 | return VK_ATTACHMENT_STORE_OP_STORE; 29 | } 30 | return VK_ATTACHMENT_STORE_OP_DONT_CARE; 31 | } 32 | sys::RenderPassRef _create_pass( 33 | const VulkanContext& ctxt, 34 | const std::vector& attm_cfgs 35 | ) { 36 | struct SubpassAttachmentReference { 37 | std::vector color_attm_ref; 38 | bool has_depth_attm; 39 | VkAttachmentReference depth_attm_ref; 40 | }; 41 | 42 | SubpassAttachmentReference sar{}; 43 | std::vector ads; 44 | for (uint32_t i = 0; i < attm_cfgs.size(); ++i) { 45 | const AttachmentConfig& attm_cfg = attm_cfgs.at(i); 46 | uint32_t iattm = i; 47 | 48 | VkAttachmentReference ar{}; 49 | ar.attachment = i; 50 | VkAttachmentDescription ad{}; 51 | ad.samples = VK_SAMPLE_COUNT_1_BIT; 52 | ad.loadOp = _get_load_op(attm_cfg.attm_access); 53 | ad.storeOp = _get_store_op(attm_cfg.attm_access); 54 | switch (attm_cfg.attm_ty) { 55 | case L_ATTACHMENT_TYPE_COLOR: { 56 | ar.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; 57 | ad.format = format2vk(attm_cfg.color_fmt, attm_cfg.cspace); 58 | ad.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; 59 | ad.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; 60 | sar.color_attm_ref.emplace_back(std::move(ar)); 61 | break; 62 | } 63 | case L_ATTACHMENT_TYPE_DEPTH: { 64 | L_ASSERT( 65 | !sar.has_depth_attm, "subpass can only have one depth attachment" 66 | ); 67 | ar.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; 68 | ad.format = depth_format2vk(attm_cfg.depth_fmt); 69 | ad.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; 70 | ad.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; 71 | sar.has_depth_attm = true; 72 | sar.depth_attm_ref = std::move(ar); 73 | break; 74 | } 75 | default: 76 | unreachable(); 77 | } 78 | 79 | ads.emplace_back(std::move(ad)); 80 | } 81 | 82 | // TODO: (penguinliong) Support input attachments. 83 | std::vector sds; 84 | { 85 | VkSubpassDescription sd{}; 86 | sd.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; 87 | sd.inputAttachmentCount = 0; 88 | sd.pInputAttachments = nullptr; 89 | sd.colorAttachmentCount = (uint32_t)sar.color_attm_ref.size(); 90 | sd.pColorAttachments = sar.color_attm_ref.data(); 91 | sd.pResolveAttachments = nullptr; 92 | sd.pDepthStencilAttachment = 93 | sar.has_depth_attm ? &sar.depth_attm_ref : nullptr; 94 | sd.preserveAttachmentCount = 0; 95 | sd.pPreserveAttachments = nullptr; 96 | sds.emplace_back(std::move(sd)); 97 | } 98 | VkRenderPassCreateInfo rpci{}; 99 | rpci.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; 100 | rpci.attachmentCount = (uint32_t)ads.size(); 101 | rpci.pAttachments = ads.data(); 102 | rpci.subpassCount = (uint32_t)sds.size(); 103 | rpci.pSubpasses = sds.data(); 104 | // TODO: (penguinliong) Implement subpass dependency resolution in the future. 105 | rpci.dependencyCount = 0; 106 | rpci.pDependencies = nullptr; 107 | 108 | return sys::RenderPass::create(ctxt.dev->dev, &rpci); 109 | } 110 | 111 | sys::FramebufferRef _create_framebuf( 112 | const VulkanRenderPass& pass, 113 | const std::vector& attms 114 | ) { 115 | L_ASSERT(pass.info.attm_count == attms.size()); 116 | 117 | uint32_t width = pass.info.width; 118 | uint32_t height = pass.info.height; 119 | 120 | std::vector img_views(attms.size()); 121 | for (size_t i = 0; i < attms.size(); ++i) { 122 | const ResourceView& attm = attms.at(i); 123 | switch (attm.rsc_view_ty) { 124 | case L_RESOURCE_VIEW_TYPE_IMAGE: 125 | img_views.at(i) = *VulkanImage::from_hal(attm.img_view.img)->img_view; 126 | break; 127 | case L_RESOURCE_VIEW_TYPE_DEPTH_IMAGE: 128 | img_views.at(i) = 129 | *VulkanDepthImage::from_hal(attm.depth_img_view.depth_img)->img_view; 130 | break; 131 | default: 132 | L_PANIC("unexpected attachment resource view type"); 133 | } 134 | } 135 | 136 | VkFramebufferCreateInfo fci{}; 137 | fci.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; 138 | fci.attachmentCount = img_views.size(); 139 | fci.pAttachments = img_views.data(); 140 | fci.renderPass = pass.pass->pass; 141 | fci.width = width; 142 | fci.height = height; 143 | fci.layers = 1; 144 | 145 | return sys::Framebuffer::create(pass.ctxt->dev->dev, &fci); 146 | } 147 | 148 | RenderPassRef VulkanRenderPass::create( 149 | const ContextRef& ctxt, 150 | const RenderPassConfig& cfg 151 | ) { 152 | VulkanContextRef ctxt_ = VulkanContext::from_hal(ctxt); 153 | 154 | sys::RenderPassRef pass = _create_pass(*ctxt_, cfg.attm_cfgs); 155 | 156 | VkRect2D viewport{}; 157 | viewport.extent.width = cfg.width; 158 | viewport.extent.height = cfg.height; 159 | 160 | std::vector clear_values{}; 161 | clear_values.resize(cfg.attm_cfgs.size()); 162 | for (size_t i = 0; i < cfg.attm_cfgs.size(); ++i) { 163 | auto& clear_value = clear_values[i]; 164 | switch (cfg.attm_cfgs[i].attm_ty) { 165 | case L_ATTACHMENT_TYPE_COLOR: 166 | clear_value.color.float32[0] = 0.0f; 167 | clear_value.color.float32[0] = 0.0f; 168 | clear_value.color.float32[0] = 0.0f; 169 | clear_value.color.float32[0] = 0.0f; 170 | break; 171 | case L_ATTACHMENT_TYPE_DEPTH: 172 | clear_value.depthStencil.depth = 1.0f; 173 | clear_value.depthStencil.stencil = 0; 174 | break; 175 | default: 176 | panic(); 177 | } 178 | } 179 | 180 | RenderPassInfo info{}; 181 | info.label = cfg.label; 182 | info.width = cfg.width; 183 | info.height = cfg.height; 184 | info.attm_count = cfg.attm_cfgs.size(); 185 | 186 | VulkanRenderPassRef out = 187 | std::make_shared(ctxt_, std::move(info)); 188 | out->ctxt = ctxt_; 189 | out->pass = std::move(pass); 190 | out->clear_values = clear_values; 191 | 192 | L_DEBUG("created render pass '", cfg.label, "'"); 193 | 194 | return std::static_pointer_cast(out); 195 | } 196 | VulkanRenderPass::~VulkanRenderPass() { 197 | if (pass) { 198 | L_DEBUG("destroyed render pass '", info.label, "'"); 199 | } 200 | } 201 | 202 | FramebufferKey FramebufferKey::create( 203 | const VulkanRenderPass& pass, 204 | const std::vector& rsc_views 205 | ) { 206 | std::stringstream ss; 207 | ss << *pass.pass; 208 | for (const auto& rsc_view : rsc_views) { 209 | switch (rsc_view.rsc_view_ty) { 210 | case L_RESOURCE_VIEW_TYPE_IMAGE: { 211 | VkImageView img_view = 212 | VulkanImage::from_hal(rsc_view.img_view.img)->img_view->img_view; 213 | ss << "," << img_view; 214 | break; 215 | } 216 | case L_RESOURCE_VIEW_TYPE_DEPTH_IMAGE: { 217 | VkImageView img_view = 218 | VulkanDepthImage::from_hal(rsc_view.depth_img_view.depth_img) 219 | ->img_view->img_view; 220 | ss << "," << img_view; 221 | break; 222 | } 223 | default: 224 | L_PANIC("unsupported resource type as an attachment"); 225 | } 226 | } 227 | return {ss.str()}; 228 | } 229 | 230 | FramebufferPoolItem VulkanRenderPass::acquire_framebuf( 231 | const std::vector& attms 232 | ) { 233 | FramebufferKey key = FramebufferKey::create(*this, attms); 234 | if (framebuf_pool.has_free_item(key)) { 235 | return framebuf_pool.acquire(std::move(key)); 236 | } else { 237 | return framebuf_pool.create(std::move(key), _create_framebuf(*this, attms)); 238 | } 239 | } 240 | 241 | TaskRef VulkanRenderPass::create_graphics_task(const GraphicsTaskConfig& cfg) { 242 | return VulkanTask::create(shared_from_this(), cfg); 243 | } 244 | 245 | InvocationRef VulkanRenderPass::create_render_pass_invocation( 246 | const RenderPassInvocationConfig& cfg 247 | ) { 248 | return VulkanInvocation::create(shared_from_this(), cfg); 249 | } 250 | 251 | } // namespace vk 252 | } // namespace liong 253 | -------------------------------------------------------------------------------- /src/gft/vk/vk-swapchain.cpp: -------------------------------------------------------------------------------- 1 | #include "sys.hpp" 2 | #include "gft/assert.hpp" 3 | #include "gft/util.hpp" 4 | #include "gft/log.hpp" 5 | #include "gft/vk/vk-swapchain.hpp" 6 | #include "gft/vk/vk-invocation.hpp" 7 | 8 | namespace liong { 9 | namespace vk { 10 | 11 | fmt::Format _get_valid_format( 12 | const VulkanContext& ctxt, 13 | const SwapchainConfig& cfg 14 | ) { 15 | uint32_t nsurf_fmt = 0; 16 | VK_ASSERT << vkGetPhysicalDeviceSurfaceFormatsKHR( 17 | ctxt.physdev(), ctxt.surf->surf, &nsurf_fmt, nullptr 18 | ); 19 | std::vector surf_fmts; 20 | surf_fmts.resize(nsurf_fmt); 21 | VK_ASSERT << vkGetPhysicalDeviceSurfaceFormatsKHR( 22 | ctxt.physdev(), ctxt.surf->surf, &nsurf_fmt, surf_fmts.data() 23 | ); 24 | 25 | VkColorSpaceKHR cspace = color_space2vk(cfg.color_space); 26 | VkFormat fmt = VK_FORMAT_UNDEFINED; 27 | fmt::Format selected_fmt2 = fmt::L_FORMAT_UNDEFINED; 28 | for (fmt::Format fmt2 : cfg.allowed_formats) { 29 | VkFormat candidate_fmt = format2vk(fmt2, fmt::L_COLOR_SPACE_LINEAR); 30 | 31 | bool found = false; 32 | for (const VkSurfaceFormatKHR& surf_fmt : surf_fmts) { 33 | if (surf_fmt.format == candidate_fmt && surf_fmt.colorSpace == cspace) { 34 | fmt = candidate_fmt; 35 | selected_fmt2 = fmt2; 36 | found = true; 37 | break; 38 | } 39 | } 40 | 41 | if (found) { 42 | break; 43 | } 44 | } 45 | L_ASSERT( 46 | fmt != VK_FORMAT_UNDEFINED, 47 | "surface format is not supported by the underlying platform" 48 | ); 49 | 50 | return selected_fmt2; 51 | } 52 | 53 | uint32_t _get_valid_image_count( 54 | const VulkanContext& ctxt, 55 | const SwapchainConfig& cfg 56 | ) { 57 | VkSurfaceCapabilitiesKHR sc{}; 58 | VK_ASSERT << vkGetPhysicalDeviceSurfaceCapabilitiesKHR( 59 | ctxt.physdev(), ctxt.surf->surf, &sc 60 | ); 61 | 62 | uint32_t nimg = cfg.image_count; 63 | if (nimg < sc.minImageCount || nimg > sc.maxImageCount) { 64 | uint32_t nimg2 = 65 | std::max(std::min(nimg, sc.maxImageCount), sc.minImageCount); 66 | L_WARN( 67 | "physical device cannot afford ", 68 | nimg, 69 | " swapchain images, " 70 | "fallback to ", 71 | nimg2 72 | ); 73 | nimg = nimg2; 74 | } 75 | 76 | return nimg; 77 | } 78 | 79 | std::unique_ptr _create_swapchain_dyn_detail( 80 | const VulkanSwapchain& swapchain 81 | ) { 82 | VkSurfaceCapabilitiesKHR sc{}; 83 | VK_ASSERT << vkGetPhysicalDeviceSurfaceCapabilitiesKHR( 84 | swapchain.ctxt->physdev(), swapchain.ctxt->surf->surf, &sc 85 | ); 86 | 87 | uint32_t width = sc.currentExtent.width; 88 | uint32_t height = sc.currentExtent.height; 89 | L_DEBUG("current surface image size is (", width, ", ", height, ")"); 90 | 91 | std::unique_ptr dyn_detail = 92 | std::make_unique(); 93 | dyn_detail->width = width; 94 | dyn_detail->height = height; 95 | dyn_detail->img_idx = nullptr; 96 | dyn_detail->imgs = {}; 97 | 98 | return dyn_detail; 99 | } 100 | 101 | sys::SwapchainRef _create_swapchain(const VulkanSwapchain& swapchain) { 102 | L_ASSERT(swapchain.ctxt->surf != nullptr); 103 | 104 | VkSwapchainKHR old_swapchain = VK_NULL_HANDLE; 105 | if (swapchain.swapchain != nullptr) { 106 | old_swapchain = *swapchain.swapchain; 107 | } 108 | 109 | VkSwapchainCreateInfoKHR sci{}; 110 | sci.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; 111 | sci.surface = swapchain.ctxt->surf->surf; 112 | sci.oldSwapchain = old_swapchain; 113 | sci.minImageCount = swapchain.info.image_count; 114 | sci.imageFormat = 115 | format2vk(swapchain.info.format, swapchain.info.color_space); 116 | sci.imageColorSpace = color_space2vk(swapchain.info.color_space); 117 | sci.imageExtent.width = swapchain.dyn_detail->width; 118 | sci.imageExtent.height = swapchain.dyn_detail->height; 119 | sci.imageArrayLayers = 1; 120 | sci.imageUsage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | 121 | VK_IMAGE_USAGE_STORAGE_BIT | 122 | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; 123 | sci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; 124 | sci.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; 125 | sci.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; 126 | sci.presentMode = VK_PRESENT_MODE_FIFO_KHR; 127 | sci.clipped = VK_TRUE; 128 | 129 | sys::SwapchainRef out = sys::Swapchain::create(*swapchain.ctxt->dev, &sci); 130 | 131 | if (old_swapchain != VK_NULL_HANDLE) { 132 | vkDestroySwapchainKHR( 133 | swapchain.ctxt->dev->dev, *swapchain.swapchain, nullptr 134 | ); 135 | } 136 | 137 | return out; 138 | } 139 | 140 | void _collect_swapchain_images( 141 | const VulkanSwapchain& swapchain, 142 | SwapchainDynamicDetail& dyn_detail 143 | ) { 144 | const SwapchainInfo& info = swapchain.info; 145 | const sys::DeviceRef& dev = swapchain.ctxt->dev; 146 | 147 | // Collect swapchain images. 148 | uint32_t image_count = 0; 149 | VK_ASSERT << vkGetSwapchainImagesKHR(dev->dev, *swapchain.swapchain, &image_count, nullptr); 150 | std::vector imgs(image_count); 151 | VK_ASSERT << vkGetSwapchainImagesKHR(dev->dev, *swapchain.swapchain, &image_count, imgs.data()); 152 | 153 | std::vector swapchain_imgs{}; 154 | for (uint32_t i = 0; i < image_count; ++i) { 155 | VkImage img = imgs[i]; 156 | 157 | VkImageViewCreateInfo ivci{}; 158 | ivci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; 159 | ivci.image = img; 160 | ivci.viewType = VK_IMAGE_VIEW_TYPE_2D; 161 | ivci.format = format2vk(info.format, info.color_space); 162 | ivci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 163 | ivci.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; 164 | ivci.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; 165 | 166 | sys::ImageViewRef img_view = sys::ImageView::create(dev->dev, &ivci); 167 | 168 | ImageInfo img_info{}; 169 | img_info.label = util::format(info.label, " #", i); 170 | img_info.width = dyn_detail.width; 171 | img_info.height = dyn_detail.height; 172 | img_info.depth = 1; 173 | img_info.format = info.format; 174 | img_info.color_space = info.color_space; 175 | img_info.usage = L_IMAGE_USAGE_ATTACHMENT_BIT | L_IMAGE_USAGE_PRESENT_BIT; 176 | 177 | VulkanImageRef out = 178 | std::make_shared(swapchain.ctxt, std::move(img_info)); 179 | out->img = std::make_shared( 180 | *swapchain.ctxt->allocator, img, nullptr, false 181 | ); 182 | out->img_view = std::move(img_view); 183 | out->dyn_detail.stage = VK_PIPELINE_STAGE_HOST_BIT; 184 | out->dyn_detail.layout = VK_IMAGE_LAYOUT_UNDEFINED; 185 | out->dyn_detail.access = 0; 186 | 187 | swapchain_imgs.emplace_back(std::move(out)); 188 | } 189 | 190 | dyn_detail.imgs = std::move(swapchain_imgs); 191 | } 192 | void _acquire_swapchain_img(VulkanSwapchain& swapchain) { 193 | const VulkanContext& ctxt = *swapchain.ctxt; 194 | SwapchainDynamicDetail& dyn_detail = *swapchain.dyn_detail; 195 | dyn_detail.img_idx = std::make_unique(~0u); 196 | 197 | VkFenceCreateInfo fci{}; 198 | fci.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; 199 | 200 | VkFence fence; 201 | // FIXME: (penguinliong) This is slow. 202 | VK_ASSERT << vkCreateFence(ctxt.dev->dev, &fci, nullptr, &fence); 203 | 204 | VkResult acq_res = vkAcquireNextImageKHR( 205 | ctxt.dev->dev, 206 | *swapchain.swapchain, 207 | SPIN_INTERVAL, 208 | VK_NULL_HANDLE, 209 | fence, 210 | &*dyn_detail.img_idx 211 | ); 212 | L_ASSERT(acq_res >= 0, "failed to initiate swapchain image acquisition"); 213 | 214 | // Ensure the first image is acquired. It shouldn't take long. 215 | VK_ASSERT << vkWaitForFences( 216 | ctxt.dev->dev, 1, &fence, VK_TRUE, SPIN_INTERVAL 217 | ); 218 | 219 | vkDestroyFence(ctxt.dev->dev, fence, nullptr); 220 | } 221 | 222 | void _recreate_swapchain(VulkanSwapchain& swapchain) { 223 | swapchain.dyn_detail = _create_swapchain_dyn_detail(swapchain); 224 | swapchain.swapchain = _create_swapchain(swapchain); 225 | _collect_swapchain_images(swapchain, *swapchain.dyn_detail); 226 | _acquire_swapchain_img(swapchain); 227 | 228 | L_DEBUG("created swapchain '", swapchain.info.label, "'"); 229 | } 230 | SwapchainRef VulkanSwapchain::create( 231 | const ContextRef& ctxt, 232 | const SwapchainConfig& cfg 233 | ) { 234 | VulkanContextRef ctxt_ = VulkanContext::from_hal(ctxt); 235 | 236 | fmt::Format format = _get_valid_format(*ctxt_, cfg); 237 | uint32_t image_count = _get_valid_image_count(*ctxt_, cfg); 238 | 239 | SwapchainInfo info{}; 240 | info.label = cfg.label; 241 | info.image_count = image_count; 242 | info.format = format; 243 | info.color_space = cfg.color_space; 244 | 245 | VulkanSwapchainRef out = 246 | std::make_shared(ctxt_, std::move(info)); 247 | out->swapchain = VK_NULL_HANDLE; 248 | out->dyn_detail = nullptr; 249 | 250 | out->recreate(); 251 | 252 | return out; 253 | } 254 | 255 | VulkanSwapchain::VulkanSwapchain(VulkanContextRef ctxt, SwapchainInfo&& info) : 256 | Swapchain(std::move(info)), ctxt(ctxt) {} 257 | VulkanSwapchain::~VulkanSwapchain() { 258 | if (swapchain) { 259 | L_DEBUG("destroyed swapchain '", info.label, "'"); 260 | } 261 | } 262 | 263 | ImageRef VulkanSwapchain::get_current_image() { 264 | L_ASSERT( 265 | dyn_detail != nullptr, 266 | "swapchain recreation is required; call `acquire_swapchain_img` " 267 | "first" 268 | ); 269 | 270 | L_ASSERT( 271 | *dyn_detail->img_idx != ~0u, 272 | "swapchain has not acquired an image for this frame" 273 | ); 274 | 275 | ImageRef out = dyn_detail->imgs[*dyn_detail->img_idx]; 276 | return out; 277 | } 278 | uint32_t VulkanSwapchain::get_width() const { 279 | L_ASSERT( 280 | dyn_detail != nullptr, 281 | "swapchain recreation is required; call `acquire_swapchain_img` " 282 | "first" 283 | ); 284 | return dyn_detail->width; 285 | } 286 | uint32_t VulkanSwapchain::get_height() const { 287 | L_ASSERT( 288 | dyn_detail != nullptr, 289 | "swapchain recreation is required; call `acquire_swapchain_img` " 290 | "first" 291 | ); 292 | return dyn_detail->height; 293 | } 294 | 295 | void VulkanSwapchain::recreate() { 296 | _recreate_swapchain(*this); 297 | } 298 | 299 | InvocationRef VulkanSwapchain::create_present_invocation( 300 | const PresentInvocationConfig& cfg 301 | ) { 302 | return VulkanInvocation::create(shared_from_this(), cfg); 303 | } 304 | 305 | } // namespace vk 306 | } // namespace liong 307 | -------------------------------------------------------------------------------- /src/gft/vk/vk-task.cpp: -------------------------------------------------------------------------------- 1 | #include "sys.hpp" 2 | #include "gft/log.hpp" 3 | #include "gft/vk/vk-task.hpp" 4 | #include "gft/vk/vk-invocation.hpp" 5 | 6 | namespace liong { 7 | namespace vk { 8 | 9 | VulkanTask::VulkanTask(const ContextRef& ctxt, TaskInfo&& info) : 10 | Task(std::move(info)), ctxt(VulkanContext::from_hal(ctxt)) {} 11 | VulkanTask::VulkanTask(const RenderPassRef& pass, TaskInfo&& info) : 12 | Task(std::move(info)), 13 | pass(VulkanRenderPass::from_hal(pass)), 14 | ctxt(VulkanRenderPass::from_hal(pass)->ctxt) {} 15 | 16 | TaskRef VulkanTask::create( 17 | const ContextRef& ctxt, 18 | const ComputeTaskConfig& cfg 19 | ) { 20 | L_ASSERT( 21 | cfg.workgrp_size.x * cfg.workgrp_size.y * cfg.workgrp_size.z != 0, 22 | "workgroup size cannot be zero" 23 | ); 24 | 25 | const VulkanContextRef& ctxt_ = VulkanContext::from_hal(ctxt); 26 | 27 | std::vector desc_pool_sizes; 28 | sys::DescriptorSetLayoutRef desc_set_layout = 29 | ctxt_->get_desc_set_layout(cfg.rsc_tys); 30 | sys::PipelineLayoutRef pipe_layout = 31 | sys::create_pipe_layout(*ctxt_->dev, desc_set_layout->desc_set_layout); 32 | sys::ShaderModuleRef shader_mod = sys::create_shader_mod( 33 | *ctxt_->dev, (const uint32_t*)cfg.code, cfg.code_size 34 | ); 35 | 36 | // Specialize to set local group size. 37 | VkSpecializationMapEntry spec_map_entries[] = { 38 | VkSpecializationMapEntry{0, 0 * sizeof(int32_t), sizeof(int32_t)}, 39 | VkSpecializationMapEntry{1, 1 * sizeof(int32_t), sizeof(int32_t)}, 40 | VkSpecializationMapEntry{2, 2 * sizeof(int32_t), sizeof(int32_t)}, 41 | }; 42 | VkSpecializationInfo spec_info{}; 43 | spec_info.pData = &cfg.workgrp_size; 44 | spec_info.dataSize = sizeof(cfg.workgrp_size); 45 | spec_info.mapEntryCount = 3; 46 | spec_info.pMapEntries = spec_map_entries; 47 | 48 | VkPipelineShaderStageCreateInfo pssci{}; 49 | pssci.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; 50 | pssci.pName = cfg.entry_name.c_str(); 51 | pssci.stage = VK_SHADER_STAGE_COMPUTE_BIT; 52 | pssci.module = *shader_mod; 53 | pssci.pSpecializationInfo = &spec_info; 54 | 55 | sys::PipelineRef pipe = 56 | sys::create_comp_pipe(*ctxt_->dev, pipe_layout->pipe_layout, pssci); 57 | 58 | TaskResourceDetail rsc_detail{}; 59 | rsc_detail.pipe_layout = std::move(pipe_layout); 60 | rsc_detail.rsc_tys = cfg.rsc_tys; 61 | 62 | TaskInfo info{}; 63 | info.label = cfg.label; 64 | info.submit_ty = L_SUBMIT_TYPE_COMPUTE; 65 | 66 | VulkanTaskRef out = std::make_shared(ctxt_, std::move(info)); 67 | out->ctxt = ctxt_; 68 | out->pass = nullptr; 69 | out->pipe = std::move(pipe); 70 | out->workgrp_size = cfg.workgrp_size; 71 | out->rsc_detail = std::move(rsc_detail); 72 | 73 | L_DEBUG("created compute task '", cfg.label, "'"); 74 | 75 | return out; 76 | } 77 | 78 | TaskRef VulkanTask::create( 79 | const RenderPassRef& pass, 80 | const GraphicsTaskConfig& cfg 81 | ) { 82 | const VulkanRenderPassRef& pass_ = VulkanRenderPass::from_hal(pass); 83 | const VulkanContextRef& ctxt = pass_->ctxt; 84 | 85 | std::vector desc_pool_sizes; 86 | sys::DescriptorSetLayoutRef desc_set_layout = 87 | ctxt->get_desc_set_layout(cfg.rsc_tys); 88 | 89 | sys::PipelineLayoutRef pipe_layout = 90 | sys::create_pipe_layout(*ctxt->dev, desc_set_layout->desc_set_layout); 91 | sys::ShaderModuleRef vert_shader_mod = sys::create_shader_mod( 92 | *ctxt->dev, (const uint32_t*)cfg.vert_code, cfg.vert_code_size 93 | ); 94 | sys::ShaderModuleRef frag_shader_mod = sys::create_shader_mod( 95 | *ctxt->dev, (const uint32_t*)cfg.frag_code, cfg.frag_code_size 96 | ); 97 | 98 | VkPipelineInputAssemblyStateCreateInfo piasci{}; 99 | piasci.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; 100 | switch (cfg.topo) { 101 | case L_TOPOLOGY_POINT: 102 | piasci.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; 103 | break; 104 | case L_TOPOLOGY_LINE: 105 | piasci.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; 106 | break; 107 | case L_TOPOLOGY_TRIANGLE: 108 | case L_TOPOLOGY_TRIANGLE_WIREFRAME: 109 | piasci.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; 110 | break; 111 | default: 112 | panic("unexpected topology (", cfg.topo, ")"); 113 | } 114 | piasci.primitiveRestartEnable = VK_FALSE; 115 | 116 | VkPipelineRasterizationStateCreateInfo prsci{}; 117 | prsci.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; 118 | prsci.cullMode = VK_CULL_MODE_NONE; 119 | prsci.frontFace = VK_FRONT_FACE_CLOCKWISE; 120 | switch (cfg.topo) { 121 | case L_TOPOLOGY_TRIANGLE_WIREFRAME: 122 | prsci.polygonMode = VK_POLYGON_MODE_LINE; 123 | break; 124 | default: 125 | prsci.polygonMode = VK_POLYGON_MODE_FILL; 126 | break; 127 | } 128 | prsci.lineWidth = 1.0f; 129 | 130 | std::array psscis{}; 131 | { 132 | VkPipelineShaderStageCreateInfo& pssci = psscis[0]; 133 | pssci.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; 134 | pssci.pName = cfg.vert_entry_name.c_str(); 135 | pssci.stage = VK_SHADER_STAGE_VERTEX_BIT; 136 | pssci.module = *vert_shader_mod; 137 | } 138 | { 139 | VkPipelineShaderStageCreateInfo& pssci = psscis[1]; 140 | pssci.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; 141 | pssci.pName = cfg.frag_entry_name.c_str(); 142 | pssci.stage = VK_SHADER_STAGE_FRAGMENT_BIT; 143 | pssci.module = *frag_shader_mod; 144 | } 145 | 146 | sys::PipelineRef pipe = sys::create_graph_pipe( 147 | *ctxt->dev, 148 | pipe_layout->pipe_layout, 149 | *pass_->pass, 150 | pass_->info.width, 151 | pass->info.height, 152 | piasci, 153 | prsci, 154 | psscis 155 | ); 156 | 157 | TaskResourceDetail rsc_detail{}; 158 | rsc_detail.pipe_layout = std::move(pipe_layout); 159 | rsc_detail.rsc_tys = cfg.rsc_tys; 160 | 161 | TaskInfo info{}; 162 | info.label = cfg.label; 163 | info.submit_ty = L_SUBMIT_TYPE_GRAPHICS; 164 | 165 | VulkanTaskRef out = std::make_shared(pass_, std::move(info)); 166 | out->ctxt = ctxt; 167 | out->pass = pass_; 168 | out->pipe = std::move(pipe); 169 | out->workgrp_size = {}; 170 | out->rsc_detail = std::move(rsc_detail); 171 | 172 | L_DEBUG("created graphics task '", cfg.label, "'"); 173 | 174 | return out; 175 | } 176 | VulkanTask::~VulkanTask() { 177 | if (pipe) { 178 | L_DEBUG("destroyed task '", info.label, "'"); 179 | } 180 | } 181 | 182 | InvocationRef VulkanTask::create_graphics_invocation( 183 | const GraphicsInvocationConfig& cfg 184 | ) { 185 | return VulkanInvocation::create(shared_from_this(), cfg); 186 | } 187 | InvocationRef VulkanTask::create_compute_invocation( 188 | const ComputeInvocationConfig& cfg 189 | ) { 190 | return VulkanInvocation::create(shared_from_this(), cfg); 191 | } 192 | 193 | } // namespace vk 194 | } // namespace liong 195 | -------------------------------------------------------------------------------- /src/gft/vk/vk-transaction.cpp: -------------------------------------------------------------------------------- 1 | #include "gft/vk/vk-transaction.hpp" 2 | #include "gft/log.hpp" 3 | 4 | namespace liong { 5 | namespace vk { 6 | 7 | VulkanTransaction::VulkanTransaction( 8 | VulkanContextRef ctxt, 9 | TransactionInfo&& info 10 | ) : 11 | Transaction(std::move(info)), ctxt(ctxt) {} 12 | 13 | TransactionRef VulkanTransaction::create( 14 | const InvocationRef& invoke, 15 | const TransactionConfig& cfg 16 | ) { 17 | const VulkanInvocationRef& invoke_ = VulkanInvocation::from_hal(invoke); 18 | const VulkanContextRef& ctxt = VulkanContext::from_hal(invoke_->ctxt); 19 | 20 | TransactionLike transact(ctxt, VK_COMMAND_BUFFER_LEVEL_PRIMARY); 21 | util::Timer timer{}; 22 | timer.tic(); 23 | invoke_->record(transact); 24 | timer.toc(); 25 | 26 | TransactionInfo info{}; 27 | info.label = cfg.label; 28 | 29 | VulkanTransactionRef out = 30 | std::make_shared(ctxt, std::move(info)); 31 | out->submit_details = std::move(transact.submit_details); 32 | out->fences = std::move(transact.fences); 33 | L_DEBUG( 34 | "created and submitted transaction for execution, command recording took ", 35 | timer.us(), "us" 36 | ); 37 | return out; 38 | } 39 | VulkanTransaction::~VulkanTransaction() { 40 | if (!is_waited) { 41 | L_WARN( 42 | "destroying transaction '", info.label, "' before it is done, waiting " 43 | "for it to finish (it's better you wait it explicit on your own)" 44 | ); 45 | wait(); 46 | } 47 | if (fences.size() > 0) { 48 | L_DEBUG("destroyed transaction"); 49 | } 50 | } 51 | bool VulkanTransaction::is_done() { 52 | return is_waited; 53 | } 54 | void VulkanTransaction::wait() { 55 | if (is_waited) { return; } 56 | 57 | std::vector fences2(fences.size()); 58 | for (size_t i = 0; i < fences.size(); ++i) { 59 | fences2.at(i) = fences.at(i)->fence; 60 | } 61 | 62 | util::Timer wait_timer{}; 63 | wait_timer.tic(); 64 | for (VkResult err;;) { 65 | err = vkWaitForFences( 66 | *ctxt->dev, fences2.size(), fences2.data(), VK_TRUE, SPIN_INTERVAL 67 | ); 68 | if (err == VK_TIMEOUT) { 69 | // L_WARN("timeout after 3000ns"); 70 | } else { 71 | VK_ASSERT << err; 72 | break; 73 | } 74 | } 75 | wait_timer.toc(); 76 | 77 | for (auto& submit_detail : submit_details) { 78 | L_ASSERT(submit_detail.is_submitted); 79 | submit_detail.cmdbuf.reset(); 80 | 81 | VK_ASSERT << vkResetCommandPool( 82 | *ctxt->dev, submit_detail.cmd_pool.inner->value->cmd_pool, 0 83 | ); 84 | 85 | submit_detail.cmd_pool.release(); 86 | } 87 | 88 | L_DEBUG("command drain returned after ", wait_timer.us(), "us since the wait " 89 | "started (spin interval = ", SPIN_INTERVAL / 1000.0, "us)"); 90 | is_waited = true; 91 | } 92 | 93 | } // namespace vk 94 | } // namespace liong 95 | --------------------------------------------------------------------------------