diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..bad1769 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,20 @@ +name: 🛠️ CI + +on: [pull_request] + +jobs: + clang-format: + uses: ./.github/workflows/clang-format-deploy.yml + secrets: inherit + + windows: + uses: ./.github/workflows/windows.yml + secrets: inherit + + linux: + uses: ./.github/workflows/linux.yml + secrets: inherit + + mac-armv8: + uses: ./.github/workflows/mac.yml + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/clang-format-deploy.yml b/.github/workflows/clang-format-deploy.yml index ac9f391..c28b442 100644 --- a/.github/workflows/clang-format-deploy.yml +++ b/.github/workflows/clang-format-deploy.yml @@ -1,18 +1,16 @@ -name: clang-format Check -on: [pull_request] +name: clang-format +on: [workflow_call] + jobs: formatting-check: name: Formatting Check runs-on: ubuntu-latest steps: - - - name: 🌐 Downloading .clang-format from TheAtlasEngine - run: wget https://github.com/engine3d-dev/TheAtlasEngine/blob/main/.clang-format -O .clang-format - - uses: actions/checkout@v4 - name: Run clang-format style check for C/C++/Protobuf programs. uses: jidicula/clang-format-action@v4.14.0 with: - clang-format-version: 19 + clang-format-version: 20 check-path: '.' + include-regex: '.*\.(cpp|cppm)$' fallback-style: 'LLVM' # optional diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index e971a69..7f3bd0c 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -1,25 +1,33 @@ -name: C++ Linter +name: cpp-linter on: [pull_request] + jobs: - linux-build: - name: "Linter" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 + cpp-linter: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 - - uses: cpp-linter/cpp-linter-action@main - id: linter - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - style: '.clang-format' # Use .clang-format config file - version: 19 # Using clang-tidy19 - tidy-checks: '.clang-tidy' # Use .clang-tidy config file - # only 'update' a single comment in a pull request thread. - thread-comments: ${{ github.event_name == 'pull_request' && 'update' }} + - uses: cpp-linter/cpp-linter-action@v2 + id: linter + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + style: '' # Disables clang-format so it strictly runs clang-tidy + # tidy-checks: '.clang-tidy' + extensions: 'cpp,cppm,h,hpp' # common C++ file extensions + # files-changed-only: true + thread-comments: ${{ github.event_name == 'pull_request' && 'update' }} + # Ensure clang-tidy knows how to parse modern C++ headers/modules without a compile_commands.json + extra-args: '-Wall -Wextra -Werror -std=c++23 --config-file=.clang-tidy' - - name: Fail fast?! - if: steps.linter.outputs.checks-failed > 0 - run: exit 1 + - name: Failed fast?! + # Does a check if any of the given clang-tidy checks failed! + if: steps.linter.outputs.checks-failed != '0' + run: | + echo "Failed clang-tidy linters check" + echo "Total violations reported: ${{ steps.linter.outputs.checks-failed }}" + exit 1 \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 4c8e012..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,15 +0,0 @@ -# Breaking change was removing the bit timing sections from hal::can::settings, -# now it is just baud_rate. -name: 🚀 Deploy Version - -on: - workflow_dispatch: - -jobs: - engine3d: - uses: engine3d-dev/ci/.github/workflows/deploy.yml@main - with: - # version: ${{ github.ref_name }} - arch: x86_64 - os: Linux - secrets: inherit diff --git a/.github/workflows/deploy_all.yml b/.github/workflows/deploy_all.yml new file mode 100644 index 0000000..b027cfc --- /dev/null +++ b/.github/workflows/deploy_all.yml @@ -0,0 +1,23 @@ + +name: 🚀 Deployment + +# Trigger deployment CI when PRs get merged to main (production branch) +on: + pull_request: + types: [closed] + branches: + - main + workflow_call: + +jobs: + windows: + uses: ./.github/workflows/deploy_windows.yml + secrets: inherit + + linux: + uses: ./.github/workflows/deploy_linux.yml + secrets: inherit + + mac-armv8: + uses: ./.github/workflows/deploy_mac.yml + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/deploy_linux.yml b/.github/workflows/deploy_linux.yml index d53e951..b4963bf 100644 --- a/.github/workflows/deploy_linux.yml +++ b/.github/workflows/deploy_linux.yml @@ -1,10 +1,6 @@ -name: Deploy to Linux +name: Ubuntu Deployment -on: - pull_request: - types: [closed] - branches: - - main # Only trigger for PRs merged into main +on: [workflow_call] jobs: linux_x86_64: @@ -63,4 +59,4 @@ jobs: - name: Uploading vulkan-cpp to engine3d-conan remote repositories shell: pwsh - run: conan upload vulkan-cpp/5.0 --r=engine3d-conan --confirm \ No newline at end of file + run: conan upload vulkan-cpp/6.0 --r=engine3d-conan --confirm \ No newline at end of file diff --git a/.github/workflows/deploy_mac.yml b/.github/workflows/deploy_mac.yml index 072fa2c..39ba0ae 100644 --- a/.github/workflows/deploy_mac.yml +++ b/.github/workflows/deploy_mac.yml @@ -1,10 +1,6 @@ -name: Deploy to MacOS Armv8 +name: MacOS Armv8 Deployment -on: - pull_request: - types: [closed] - branches: - - main # Only trigger for PRs merged into main +on: [workflow_call] jobs: macos_armv8: @@ -55,4 +51,4 @@ jobs: - name: Uploading vulkan-cpp to engine3d-conan remote repositories shell: pwsh - run: conan upload vulkan-cpp/5.0 --r=engine3d-conan --confirm \ No newline at end of file + run: conan upload vulkan-cpp/6.0 --r=engine3d-conan --confirm \ No newline at end of file diff --git a/.github/workflows/deploy_windows.yml b/.github/workflows/deploy_windows.yml index 60b6bfc..1ccac9f 100644 --- a/.github/workflows/deploy_windows.yml +++ b/.github/workflows/deploy_windows.yml @@ -1,10 +1,7 @@ -name: Deploy to Windows +name: Windows Deployment -on: - pull_request: - types: [closed] - branches: - - main # Only trigger for PRs merged into main + +on: [workflow_call] jobs: windows_x86_64: @@ -55,4 +52,4 @@ jobs: - name: Uploading vulkan-cpp to engine3d-conan remote repositories shell: pwsh - run: conan upload vulkan-cpp/5.0 --r=engine3d-conan --confirm \ No newline at end of file + run: conan upload vulkan-cpp/6.0 --r=engine3d-conan --confirm \ No newline at end of file diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 11d337b..bbb3af9 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -1,10 +1,11 @@ -name: Linux Platform Build +name: Linux + +# workflow_call to have linux.yml be able to be invoked by ci.yml +on: [workflow_call] -on: [pull_request] jobs: linux-build: - name: "Linux-Build" runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -46,5 +47,4 @@ jobs: run: conan atlas create . -s build_type=Debug - name: Creating MinSizeRel build for vulkan-cpp - run: conan atlas create . -s build_type=MinSizeRel - + run: conan atlas create . -s build_type=MinSizeRel \ No newline at end of file diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 4f18537..3bbe4d3 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -1,10 +1,10 @@ -name: Macos Platform Build +name: Macos Armv8 -on: [pull_request] +# workflow_call to have mac.yml be able to be invoked by ci.yml +on: [workflow_call] jobs: macos-build: - name: "M1 Mac Build" runs-on: macos-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/platform-config.yml b/.github/workflows/platform-config.yml new file mode 100644 index 0000000..53cc206 --- /dev/null +++ b/.github/workflows/platform-config.yml @@ -0,0 +1,226 @@ +name: Platform Configuration + +on: + workflow_call: + outputs: + cache-key-windows: + description: "Cached Key for the Windows Environment" + value: ${{ jobs.windows-setup.outputs.job-cache-key }} + # cache-key-linux: + # description: "Cached Key for the Ubuntu Environment" + # value: ${{ jobs.ubuntu-setup.outputs.cache-key }} + # cache-key-mac: + # description: "Cached Key for the Apple Silicon Armv8 Environment" + # value: ${{ jobs.armv8-setup.outputs.cache-key }} + +jobs: + windows-setup: + runs-on: windows-latest + # outputs: + # cache-key: ${{ steps.cache-info.outputs.key }} + outputs: + job-cache-key: ${{ steps.cache-info.outputs.key }} + steps: + - uses: actions/checkout@v4 + + - name: Generate Cache Key Info + id: cache-info + shell: pwsh + run: echo "key=windows-setup-${{ runner.os }}-${{ github.run_id }}" >> $ENV:GITHUB_OUTPUT + + - name: Check Cache + id: cache-env + uses: actions/cache@v4 + with: + path: | + ~\AppData\Local\Packages\PythonSoftwareFoundation* + ~\AppData\Local\Programs\Python* + ~\.conan2 + ${{ github.workspace }}\vulkan_sdk + key: ${{ steps.cache-info.outputs.key }} + restore-keys: | + windows-setup-${{ runner.os }}- + + - name: Install Vulkan SDK + if: steps.cache-env.outputs.cache-hit != 'true' + uses: humbletim/setup-vulkan-sdk@v1.2.1 + with: + vulkan-query-version: 1.4.304.1 + vulkan-components: Vulkan-Headers, Vulkan-Loader + destination: ${{ github.workspace }}\vulkan_sdk + + - name: Pip installing conan + if: steps.cache-env.outputs.cache-hit != 'true' + shell: pwsh + run: | + python -m pip install --upgrade pip + pip install conan + # Find out exactly where conan.exe was installed and write it to a marker file + $ConanPath = (Get-Command conan.exe -ErrorAction SilentlyContinue).Source + if (-not $ConanPath) { + $ConanPath = (Get-ChildItem -Path ~\AppData\Local -Filter conan.exe -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1).FullName + } + $ConanDir = Split-Path -Path $ConanPath -Parent + New-Item -ItemType Directory -Force -Path .github\artifacts + Out-File -FilePath .github\artifacts\conan_path.txt -InputObject $ConanDir + + - name: Setting up Conan configuration + if: steps.cache-env.outputs.cache-hit != 'true' + shell: pwsh + run: | + $ConanPath = Get-Content -Path .github\artifacts\conan_path.txt + & "$ConanPath\conan" config install https://github.com/engine3d-dev/conan-config.git + & "$ConanPath\conan" atlas setup + +# jobs: +# windows-setup: +# runs-on: windows-latest +# outputs: +# cache-key: ${{ steps.cache-info.outputs.key }} +# steps: +# - uses: actions/checkout@v4 + +# # Windows runners use PowerShell (pwsh) by default, using the $ENV:GITHUB_OUTPUT syntax +# - name: Generate Cache Key Info +# id: cache-info +# shell: pwsh +# run: echo "key=windows-setup-${{ runner.os }}-${{ github.run_id }}" >> $ENV:GITHUB_OUTPUT + +# - name: Check Cache +# id: cache-env +# uses: actions/cache@v4 +# with: +# path: | +# AppData\Local\pip\Cache +# .conan2 +# vulkan_sdk +# key: ${{ steps.cache-info.outputs.key }} +# restore-keys: | +# windows-setup-${{ runner.os }}- + +# - name: Installing Choco & Git +# if: steps.cache-env.outputs.cache-hit != 'true' +# shell: pwsh +# run: | +# Set-ExecutionPolicy Bypass -Scope Process -Force +# [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 +# iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) +# choco install git -y + +# - name: Install Vulkan SDK +# if: steps.cache-env.outputs.cache-hit != 'true' +# uses: humbletim/setup-vulkan-sdk@v1.2.1 +# with: +# vulkan-query-version: 1.4.304.1 +# vulkan-components: Vulkan-Headers, Vulkan-Loader +# destination: ${{ github.workspace }}\vulkan_sdk + +# - name: Pip installing conan +# if: steps.cache-env.outputs.cache-hit != 'true' +# shell: pwsh +# run: pip install conan + +# - name: Setting up Conan configuration +# if: steps.cache-env.outputs.cache-hit != 'true' +# shell: pwsh +# run: | +# conan config install https://github.com/engine3d-dev/conan-config.git +# conan atlas setup + # ubuntu-setup: + # runs-on: ubuntu-latest + # outputs: + # cache-key: ${{ steps.cache-info.outputs.key }} + # steps: + # - uses: actions/checkout@v4 + + # # Note: We append a hardcoded version or file hash to trigger rebuilds when configs change + # - name: Generate Cache Key Info + # id: cache-info + # run: echo "key=ubuntu-setup-${{ runner.os }}-${{ github.run_id }}" >> $GITHUB_OUTPUT + + # - name: Check Cache + # id: cache-env + # uses: actions/cache@v4 + # with: + # path: | + # ~/.local/share/pipx + # ~/.conan2 + # ${{ github.workspace }}/vulkan_sdk + # key: ${{ steps.cache-info.outputs.key }} + # restore-keys: | + # ubuntu-setup-${{ runner.os }}- + + # - name: Install System Prerequisites + # if: steps.cache-env.outputs.cache-hit != 'true' + # run: | + # sudo apt-get update + # sudo apt-get install -y libc++-20-dev libc++abi-20-dev pipx software-properties-common + # sudo add-apt-repository ppa:deadsnakes/ppa -y + + # - name: Install Vulkan SDK + # if: steps.cache-env.outputs.cache-hit != 'true' + # uses: humbletim/setup-vulkan-sdk@v1.2.1 + # with: + # vulkan-query-version: 1.4.304.1 + # vulkan-components: Vulkan-Headers, Vulkan-Loader + # destination: ${{ github.workspace }}/vulkan_sdk + + # - name: Install and Configure Conan + # if: steps.cache-env.outputs.cache-hit != 'true' + # run: | + # pipx install "conan>=2.18.1" + # export PATH="$HOME/.local/bin:$PATH" + # conan config install https://github.com/engine3d-dev/conan-config.git + # conan atlas setup + + # armv8-setup: + # runs-on: macos-latest + # outputs: + # cache-key: ${{ steps.cache-info.outputs.key }} + # steps: + # - uses: actions/checkout@v4 + + # - name: Generate Cache Key Info + # id: cache-info + # run: echo "key=armv8-setup-${{ runner.os }}-${{ github.run_id }}" >> $GITHUB_OUTPUT + + # - name: Check Cache + # id: cache-env + # uses: actions/cache@v4 + # with: + # path: | + # ~/.local/share/pipx + # ~/.conan2 + # ${{ github.workspace }}/vulkan_sdk + # key: ${{ steps.cache-info.outputs.key }} + # restore-keys: | + # armv8-setup-${{ runner.os }}- + + # - name: Install Homebrew & Core Tooling + # if: steps.cache-env.outputs.cache-hit != 'true' + # run: | + # /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + # brew install python pipx + + # - name: Install Vulkan SDK + # if: steps.cache-env.outputs.cache-hit != 'true' + # uses: humbletim/setup-vulkan-sdk@v1.2.1 + # with: + # vulkan-query-version: 1.4.304.1 + # vulkan-components: Vulkan-Headers, Vulkan-Loader + # destination: ${{ github.workspace }}/vulkan_sdk + + # - name: Installing conan & clang-tidy + # if: steps.cache-env.outputs.cache-hit != 'true' + # run: | + # pipx install "conan>=2.18.2" + # pipx upgrade conan + # sudo ln -s $(brew --prefix llvm)/bin/clang-tidy /usr/local/bin/ + # /usr/sbin/softwareupdate --install-rosetta --agree-to-license + + # - name: Setting up Conan configuration + # if: steps.cache-env.outputs.cache-hit != 'true' + # run: | + # export PATH="$HOME/.local/bin:$PATH" + # conan config install https://github.com/engine3d-dev/conan-config.git + # conan atlas setup \ No newline at end of file diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 864d1ef..f48b736 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -1,10 +1,11 @@ -name: Windows Platform Build +name: Windows + +# workflow_call to have linux.yml be able to be invoked by ci.yml +on: [workflow_call] -on: [pull_request, workflow_dispatch] jobs: windows-build: - name: "Windows-Build" runs-on: windows-latest steps: - uses: actions/checkout@v4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 8debc50..40fe5d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 4.0) # Generate compile commands for anyone using our libraries. set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_COLOR_DIAGNOSTICS ON) +set(CMAKE_CXX_SCAN_FOR_MODULES ON) project(vulkan-cpp LANGUAGES CXX) @@ -15,13 +16,11 @@ static_library( glfw3 Vulkan glm - stb LINK_PACKAGES glfw Vulkan::Vulkan glm::glm - stb::stb ) target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23) @@ -31,6 +30,7 @@ target_sources(${PROJECT_NAME} PUBLIC TYPE CXX_MODULES FILES vulkan-cpp/vk.cppm + vulkan-cpp/feature_extensions.cppm vulkan-cpp/types.cppm vulkan-cpp/utilities.cppm vulkan-cpp/instance.cppm @@ -46,14 +46,16 @@ target_sources(${PROJECT_NAME} PUBLIC vulkan-cpp/sample_image.cppm vulkan-cpp/shader_resource.cppm vulkan-cpp/pipeline.cppm - vulkan-cpp/buffer_streams.cppm + vulkan-cpp/buffer.cppm vulkan-cpp/vertex_buffer.cppm - vulkan-cpp/buffer_streams16.cppm - vulkan-cpp/buffer_streams32.cppm + vulkan-cpp/buffer16.cppm + vulkan-cpp/buffer32.cppm vulkan-cpp/index_buffer.cppm vulkan-cpp/uniform_buffer.cppm vulkan-cpp/descriptor_resource.cppm vulkan-cpp/texture.cppm + vulkan-cpp/dyn/buffer.cppm + vulkan-cpp/image.cppm ) install( diff --git a/README.md b/README.md index 8bca643..8b39749 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,50 @@ # Vulkan-CPP -Modern abstraction layer for Vulkan using C++23 to simplify and modernize development for graphical applications. +Custom Vulkan abstraction layer with native C++20 modules support using LLVM. -![NOTE] -> vulkan-cpp assumes you have some knowledge of computer graphics and API's such as with OpenGL or Direct3D +[![✅CI](https://github.com/engine3d-dev/vulkan-cpp/actions/workflows/ci.yml/badge.svg)](https://github.com/engine3d-dev/vulkan-cpp/actions/workflows/ci.yml) +[![GitHub stars](https://img.shields.io/github/stars/engine3d-dev/vulkan-cpp.svg)](https://github.com/engine3d-dev/vulkan-cpp/stargazers) +[![GitHub forks](https://img.shields.io/github/forks/engine3d-dev/vulkan-cpp.svg)](https://github.com/engine3d-dev/vulkan-cpp/network) +[![GitHub issues](https://img.shields.io/github/issues/engine3d-dev/vulkan-cpp.svg)](https://github.com/engine3d-dev/vulkan-cpp/issues) -# Why another abstraction around Vulkan? +> [!TIP] +> vulkan-cpp assumes you have some knowledge of graphics APIs such as OpenGL or Direct3D -I chose to make this vulkan abstraction for developing graphics applications using Vulkan much simpler. This involved providing ways for specifying operations that had quite a bit of boilerplate being done in raw Vulkan. +## Getting Started -Examples of these are renderpass attachments and setting up descriptor set handles. These take up extroadinary amount of code to implement. Though there are not always a one size fit all situation. +Before buidling the demos, make sure to check the [getting started](https://engine3d-dev.github.io/0.1/getting_started) page beforehand. +## Building Demos -## How to Build +These demos are isolated and attempt at following the Vulkan tutorial, specifically to using the vulkan-cpp APIs. Building the demo is quite easy, assuming you already setup the development environment from the getting started page linked above. -For building our projects we use Conan, the C++ package manager to manage our dependencies and build vulkan-cpp demos. +Here is how to build any of the demos: -Required to start at the [getting started](https://engine3d-dev.github.io/0.1/getting_started) page for setting up the development environment. +> [!NOTE] +> `-s build_type=Debug` to build in debug mode -## Example Demos +```bash +conan atlas build demos/ -s build_type=Debug +``` -The demos are meant to reflect closely enough to the Vulkan tutorial documentation site. Where they show you how to learn how to use Vulkan. +Example for building demo 6: -These demos are supposed to enable you in learning more about how vulkan-cpp works, and how you can effectively build a renderer using the current API's of vulkan-cpp. +```bash +conan atlas build demos/6-graphics-pipeline -s build_type=Debug +``` -## Shader Samples +## Running the Demos -These are shader samples used by the specific demos that you see referenced below. Which demo utilizes those specific shader samples. +The build directory will be located in the demo that you built. -* sample 1 -- used by demo 6 and 7 -* sample 2 -- used by demo 8 -* sample 3 -- used by demo 9 (descriptors + camera uniforms) -* sample 4 -- used by demo 10 (textures) \ No newline at end of file +Executable path will be as the following: + +```bash +./demo//build/Debug/ +``` + +For demo 6 this is where the executable is located: + +```bash +./demo/6-graphics-pipeline/build/Debug/graphics-pipeline +``` \ No newline at end of file diff --git a/asset_samples/skybox/monkstown_castle_4k.hdr b/asset_samples/skybox/monkstown_castle_4k.hdr new file mode 100644 index 0000000..06622bd Binary files /dev/null and b/asset_samples/skybox/monkstown_castle_4k.hdr differ diff --git a/conanfile.py b/conanfile.py index e17b42b..62eaff1 100644 --- a/conanfile.py +++ b/conanfile.py @@ -9,7 +9,7 @@ class VulkanCppRecipe(ConanFile): name = "vulkan-cpp" - version = "5.0" + version = "6.0" license = "Apache-2.0" url = "https://github.com/engine3d-dev/vulkan-cpp" homepage = "https://github.com/engine3d-dev/vulkan-cpp" @@ -27,7 +27,6 @@ def build_requirements(self): def requirements(self): self.requires("glfw/3.4") self.requires("glm/1.0.1") - self.requires("stb/cci.20230920") def layout(self): cmake_layout(self) diff --git a/demos/1-instance/application.cpp b/demos/1-instance/application.cpp index 3125687..0b59d90 100644 --- a/demos/1-instance/application.cpp +++ b/demos/1-instance/application.cpp @@ -110,6 +110,6 @@ main() { } glfwDestroyWindow(window); - api_instance.destroy(); + api_instance.destruct(); return 0; } diff --git a/demos/1-instance/conanfile.py b/demos/1-instance/conanfile.py index 708fb66..eb977f5 100644 --- a/demos/1-instance/conanfile.py +++ b/demos/1-instance/conanfile.py @@ -22,7 +22,7 @@ def requirements(self): self.requires("glm/1.0.1") self.requires("stb/cci.20230920") self.requires("tinyobjloader/2.0.0-rc10") - self.requires("vulkan-cpp/5.0") + self.requires("vulkan-cpp/6.0") def build(self): cmake = CMake(self) diff --git a/demos/10-textures/CMakeLists.txt b/demos/10-textures/CMakeLists.txt index c0d93d5..0d74170 100644 --- a/demos/10-textures/CMakeLists.txt +++ b/demos/10-textures/CMakeLists.txt @@ -13,6 +13,7 @@ build_application( stb LINK_PACKAGES + stb::stb vulkan-cpp Vulkan::Vulkan ) \ No newline at end of file diff --git a/demos/10-textures/application.cpp b/demos/10-textures/application.cpp index 83e6538..6e99072 100644 --- a/demos/10-textures/application.cpp +++ b/demos/10-textures/application.cpp @@ -14,7 +14,6 @@ #include #include #include -import vk; #include #define GLM_FORCE_RADIANS @@ -22,6 +21,14 @@ import vk; #include #include #include +#include + +#ifndef STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#include +#endif + +import vk; static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback( @@ -66,6 +73,80 @@ struct material_uniform { glm::vec4 color; }; +/** + * @brief STBI-specific implementation of the vk::image interface + */ +class stb_image : public vk::image { +public: + stb_image() = delete; + + stb_image(std::string_view p_path, vk::texture_params p_params) { + image_load(p_path, p_params); + } + + ~stb_image() = default; + +protected: + bool image_load(std::string_view p_path, + vk::texture_params p_params) override { + int w = 0; + int h = 0; + int channels = 0; + + stbi_uc* image_pixel_data = + stbi_load(p_path.data(), &w, &h, &channels, STBI_rgb_alpha); + + if (!image_pixel_data) { + return false; + } + + const VkFormat texture_format = + static_cast(vk::format::r8g8b8a8_unorm); + int bytes_per_pixel = vk::bytes_per_texture_format(texture_format); + + m_extent = { + .width = static_cast(w), + .height = static_cast(h), + }; + + // Retrieving total size of bytes of the dimensions of the image and + // accounting for pixels of the image + uint32_t size_bytes = + m_extent.width * m_extent.height * bytes_per_pixel; + + // Retrieving total image size to the count of the image layers + uint32_t size = size_bytes * p_params.layer_count; + + vk::image_params image_options = { + .extent = m_extent, + .format = texture_format, + .memory_mask = p_params.memory_mask, + .usage = + vk::image_usage::transfer_dst_bit | vk::image_usage::sampled_bit, + .mip_levels = p_params.mip_levels, + .layer_count = p_params.layer_count, + }; + + m_bytes.reserve(size); + std::span bytes_view = + std::span(image_pixel_data, size); + + m_bytes.assign(bytes_view.begin(), bytes_view.end()); + + stbi_image_free(image_pixel_data); + + return true; + } + + std::span image_read() const override { return m_bytes; } + + vk::image_extent image_extent() const override { return m_extent; } + +private: + vk::image_extent m_extent{}; + std::vector m_bytes{}; +}; + int main() { //! @note Just added the some test code to test the conan-starter setup code @@ -117,19 +198,9 @@ main() { // 1. Setting up vk instance vk::instance api_instance(config, debug_callback_info); - if (api_instance.alive()) { - std::println("\napi_instance alive and initiated!!!"); - } - - vk::physical_enumeration enumerate_devices{ - .device_type = vk::physical_gpu::discrete, - }; - -#if defined(__APPLE__) - enumerate_devices.device_type = vk::physical_gpu::integrated; -#endif - - vk::physical_device physical_device(api_instance, enumerate_devices); + std::expected physical_device_expected = + api_instance.enumerate_physical_device(vk::physical_gpu::integrated); + vk::physical_device physical_device = physical_device_expected.value(); // selecting depth format std::array format_support = { @@ -141,19 +212,16 @@ main() { // We provide a selection of format support that we want to check is // supported on current hardware device. VkFormat depth_format = - vk::select_depth_format(physical_device, format_support); - - vk::queue_indices queue_indices = physical_device.family_indices(); - std::println("Graphics Queue Family Index = {}", queue_indices.graphics); - std::println("Compute Queue Family Index = {}", queue_indices.compute); - std::println("Transfer Queue Family Index = {}", queue_indices.transfer); + physical_device.request_depth_format(format_support); // setting up logical device std::array priorities = { 0.f }; #if defined(__APPLE__) - std::array extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, - "VK_KHR_portability_subset" }; + std::array extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + "VK_KHR_portability_subset", + }; #else std::array extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; #endif @@ -167,21 +235,14 @@ main() { vk::device logical_device(physical_device, logical_device_params); vk::surface window_surface(api_instance, window); - std::println("Starting implementation of the swapchain!!!"); vk::surface_params surface_properties = - vk::enumerate_surface(physical_device, window_surface); - - if (surface_properties.format.format != VK_FORMAT_UNDEFINED) { - std::println("Surface Format.format is not undefined!!!"); - } + physical_device.request_surface(window_surface); vk::swapchain_params enumerate_swapchain_settings = { - .width = (uint32_t)width, - .height = (uint32_t)height, - .present_index = - physical_device.family_indices() - .graphics, // presentation index just uses the graphics index + .width = static_cast(width), + .height = static_cast(height), + .present_index = 0, }; vk::swapchain main_swapchain(logical_device, window_surface, @@ -194,7 +255,6 @@ main() { // Creating Images std::vector swapchain_images(image_count); - std::vector swapchain_depth_images(image_count); VkExtent2D swapchain_extent = surface_properties.capabilities.currentExtent; @@ -206,29 +266,16 @@ main() { .extent = { .width = swapchain_extent.width, .height = swapchain_extent.height }, .format = surface_properties.format.format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), .aspect = vk::image_aspect_flags::color_bit, - .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .usage = vk::image_usage::color_attachment_bit, .mip_levels = 1, .layer_count = 1, - .phsyical_memory_properties = physical_device.memory_properties(), }; swapchain_images[i] = vk::sample_image(logical_device, images[i], swapchain_image_config); - - // Creating Depth Images for depth buffering - vk::image_params image_config = { - .extent = { .width = swapchain_extent.width, - .height = swapchain_extent.height }, - .format = depth_format, - .aspect = vk::image_aspect_flags::depth_bit, - .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, - .mip_levels = 1, - .layer_count = 1, - .phsyical_memory_properties = physical_device.memory_properties(), - }; - swapchain_depth_images[i] = - vk::sample_image(logical_device, image_config); } // setting up command buffers @@ -236,7 +283,7 @@ main() { for (size_t i = 0; i < swapchain_command_buffers.size(); i++) { vk::command_params settings = { .levels = vk::command_levels::primary, - .queue_index = enumerate_swapchain_settings.present_index, + .queue_index = 0, .flags = vk::command_pool_flags::reset, }; @@ -247,7 +294,7 @@ main() { // setting up renderpass // setting up attachments for the renderpass - std::array renderpass_attachments = { + std::array renderpass_attachments = { vk::attachment{ .format = surface_properties.format.format, .layout = vk::image_layout::color_optimal, @@ -259,29 +306,14 @@ main() { .initial_layout = vk::image_layout::undefined, .final_layout = vk::image_layout::present_src_khr, }, - vk::attachment{ - .format = depth_format, - .layout = vk::image_layout::depth_stencil_optimal, - .samples = vk::sample_bit::count_1, - .load = vk::attachment_load::clear, - .store = vk::attachment_store::dont_care, - .stencil_load = vk::attachment_load::clear, - .stencil_store = vk::attachment_store::dont_care, - .initial_layout = vk::image_layout::undefined, - .final_layout = vk::image_layout::depth_stencil_read_only_optimal, - }, }; vk::renderpass main_renderpass(logical_device, renderpass_attachments); - std::println("renderpass created!!!"); - // Setting up swapchain framebuffers std::vector swapchain_framebuffers(image_count); for (uint32_t i = 0; i < swapchain_framebuffers.size(); i++) { - // image_view_attachments.push_back(swapchain_images[i].view); - // image_view_attachments.push_back(swapchain_depth_images[i].view); // NOTE: This must match the amount of attachments the renderpass also // has to match the image_view attachment for per-framebuffers as well @@ -289,8 +321,7 @@ main() { // ensure this is the case Since you have an image for color attachment // and another image for the depth atttachment to specify std::array - image_view_attachments = { swapchain_images[i].image_view(), - swapchain_depth_images[i].image_view() }; + image_view_attachments = { swapchain_images[i].image_view() }; vk::framebuffer_params framebuffer_info = { .renderpass = main_renderpass, @@ -301,9 +332,6 @@ main() { vk::framebuffer(logical_device, framebuffer_info); } - std::println("Created VkFramebuffer's with size = {}", - swapchain_framebuffers.size()); - // setting up presentation queue to display commands to the screen vk::queue_params enumerate_present_queue{ .family = 0, @@ -315,29 +343,35 @@ main() { // gets set with the renderpass std::array color = { 0.f, 0.5f, 0.5f, 1.f }; - std::println("Start implementing graphics pipeline!!!"); - // Now creating a vulkan graphics pipeline for the shader loading std::array shader_sources = { - vk::shader_source{ .filename = "shader_samples/sample4/test.vert.spv", - .stage = vk::shader_stage::vertex }, - vk::shader_source{ .filename = "shader_samples/sample4/test.frag.spv", - .stage = vk::shader_stage::fragment }, + vk::shader_source{ + .filename = "shader_samples/sample4/test.vert.spv", + .stage = vk::shader_stage::vertex, + }, + vk::shader_source{ + .filename = "shader_samples/sample4/test.frag.spv", + .stage = vk::shader_stage::fragment, + }, }; // Setting up vertex attributes in the test shaders std::array attribute_entries = { - vk::vertex_attribute_entry{ .location = 0, - .format = vk::format::rg32_sfloat, - .stride = - offsetof(vk::vertex_input, position) }, - vk::vertex_attribute_entry{ .location = 1, - .format = vk::format::rgb32_sfloat, - .stride = - offsetof(vk::vertex_input, color) }, - vk::vertex_attribute_entry{ .location = 2, - .format = vk::format::rg32_sfloat, - .stride = offsetof(vk::vertex_input, uv) } + vk::vertex_attribute_entry{ + .location = 0, + .format = vk::format::rg32_sfloat, + .stride = offsetof(vk::vertex_input, position), + }, + vk::vertex_attribute_entry{ + .location = 1, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, color), + }, + vk::vertex_attribute_entry{ + .location = 2, + .format = vk::format::rg32_sfloat, + .stride = offsetof(vk::vertex_input, uv), + } }; std::array attributes = { @@ -360,15 +394,11 @@ main() { vk::shader_resource geometry_resource(logical_device, shader_info); geometry_resource.vertex_attributes(attributes); - if (geometry_resource.is_valid()) { - std::println("geometry resource is valid!"); - } - // Setting up descriptor sets for graphics pipeline std::vector entries = { vk::descriptor_entry{ // specifies "layout (set = 0, binding = 0) uniform GlobalUbo" - .type = vk::buffer::uniform, + .type = vk::descriptor_type::uniform, .binding_point = { .binding = 0, .stage = vk::shader_stage::vertex, @@ -377,7 +407,7 @@ main() { }, vk::descriptor_entry{ // layout (set = 0, binding = 1) uniform sampler2D - .type = vk::buffer::combined_image_sampler, + .type = vk::descriptor_type::combined_image_sampler, .binding_point = { .binding = 1, .stage = vk::shader_stage::fragment, @@ -385,7 +415,7 @@ main() { .descriptor_count = 1, } }; - // uint32_t image_count = image_count; + vk::descriptor_layout set0_layout = { .slot = 0, // indicate that this is descriptor set 0 .max_sets = image_count, // max of descriptor sets able to allocate @@ -394,13 +424,6 @@ main() { vk::descriptor_resource set0_resource(logical_device, set0_layout); std::array layouts = { set0_resource.layout() }; - /* - This get_pipeline_configuration can work as an easy way for specfying - the vulkan configurations as an ease of setting things up - // TODO: Probably provide a shorthand - which could work as this: - vk::pipeline_settings pipeline_configuration = - vk::get_pipeline_configuration(main_renderpass, geometry_resource); - */ std::array color_blend_attachments = { vk::color_blend_attachment_state{}, }; @@ -422,93 +445,125 @@ main() { }; vk::pipeline main_graphics_pipeline(logical_device, pipeline_configuration); - if (main_graphics_pipeline.alive()) { - std::println("Main graphics pipeline alive() = {}", - main_graphics_pipeline.alive()); - } - // Setting up vertex buffer std::array vertices = { - vk::vertex_input{ .position = { -0.5f, -0.5f, 0.f }, - .color = { 1.0f, 0.0f, 0.0f }, - .normals = { 0.f, 0.f, 0.f }, - .uv = { 1.0f, 0.0f } }, - vk::vertex_input{ .position = { 0.5f, -0.5f, 0.f }, - .color = { 0.0f, 1.0f, 0.0f }, - .normals = { 0.f, 0.f, 0.f }, - .uv = { 0.0f, 0.0f } }, - vk::vertex_input{ .position = { 0.5f, 0.5f, 0.f }, - .color = { 0.0f, 0.0f, 1.0f }, - .normals = { 0.f, 0.f, 0.f }, - .uv = { 0.0f, 1.0f } }, - vk::vertex_input{ .position = { -0.5f, 0.5f, 0.f }, - .color = { 1.0f, 1.0f, 1.0f }, - .normals = { 0.f, 0.f, 0.f }, - .uv = { 1.0f, 1.0f } } + vk::vertex_input{ + .position = { -0.5f, -0.5f, 0.f }, + .color = { 1.0f, 0.0f, 0.0f }, + .normals = { 0.f, 0.f, 0.f }, + .uv = { 1.0f, 0.0f }, + }, + vk::vertex_input{ + .position = { 0.5f, -0.5f, 0.f }, + .color = { 0.0f, 1.0f, 0.0f }, + .normals = { 0.f, 0.f, 0.f }, + .uv = { 0.0f, 0.0f }, + }, + vk::vertex_input{ + .position = { 0.5f, 0.5f, 0.f }, + .color = { 0.0f, 0.0f, 1.0f }, + .normals = { 0.f, 0.f, 0.f }, + .uv = { 0.0f, 1.0f }, + }, + vk::vertex_input{ + .position = { -0.5f, 0.5f, 0.f }, + .color = { 1.0f, 1.0f, 1.0f }, + .normals = { 0.f, 0.f, 0.f }, + .uv = { 1.0f, 1.0f }, + } }; - vk::vertex_params vertex_info = { - .phsyical_memory_properties = physical_device.memory_properties(), - .vertices = vertices, + //! @brief Creating vertex/index buffers with host visibility flags + const auto property_flags = + static_cast(vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit); + + vk::buffer_parameters vertex_params = { + .memory_mask = physical_device.memory_properties(property_flags), + .property_flags = vk::memory_property::device_local_bit, + .usage = vk::buffer_usage::transfer_dst_bit | + vk::buffer_usage::vertex_buffer_bit, }; - vk::vertex_buffer test_vbo(logical_device, vertex_info); - std::println("vertex_buffer.alive() = {}", test_vbo.alive()); + vk::vertex_buffer test_vbo(logical_device, vertices, vertex_params); std::array indices = { 0, 1, 2, 2, 3, 0 }; - vk::index_params index_info = { - .phsyical_memory_properties = physical_device.memory_properties(), - .indices = indices, + vk::buffer_parameters index_params = { + .memory_mask = physical_device.memory_properties(property_flags), + .property_flags = static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit), + .usage = vk::buffer_usage::index_buffer_bit, }; - vk::index_buffer test_ibo(logical_device, index_info); - std::println("index_buffer.alive() = {}", test_ibo.alive()); + vk::index_buffer test_ibo(logical_device, indices, index_params); // Setting up descriptor sets for handling uniforms - vk::uniform_params test_ubo_info = { .phsyical_memory_properties = - physical_device.memory_properties(), - .size_bytes = sizeof(global_uniform) }; - vk::uniform_buffer test_ubo = - vk::uniform_buffer(logical_device, test_ubo_info); - - std::array uniforms0 = { vk::write_buffer{ - .buffer = test_ubo, .offset = 0, .range = test_ubo.size_bytes() } }; + vk::buffer_parameters uniform_params = { + .memory_mask = + physical_device.memory_properties(static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit)), + .usage = vk::buffer_usage::uniform_buffer_bit, + }; + vk::uniform_buffer test_ubo = vk::uniform_buffer( + logical_device, sizeof(global_uniform), uniform_params); + + std::array uniforms0 = { + vk::write_buffer{ + .buffer = test_ubo, + .offset = 0, + .range = static_cast(test_ubo.size_bytes()), + }, + }; std::array uniforms = { vk::write_buffer_descriptor{ .dst_binding = 0, .uniforms = uniforms0 } }; // Loading a texture -- for testing - vk::texture_info config_texture = { - .phsyical_memory_properties = physical_device.memory_properties(), - .filepath = - std::filesystem::path("asset_samples/container_diffuse.png"), + vk::texture_params config_texture = { + .memory_mask = + physical_device.memory_properties(static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit)), }; - vk::texture texture1(logical_device, config_texture); - std::println("texture1.valid = {}", texture1.loaded()); + stb_image img = + stb_image("asset_samples/container_diffuse.png", config_texture); + vk::texture texture1(logical_device, &img, config_texture); // Moving update call here because now we add textures to set0 - vk::uniform_params material_ubfo_info = { - .phsyical_memory_properties = physical_device.memory_properties(), - .size_bytes = sizeof(material_uniform) + vk::buffer_parameters material_ubfo_info = { + .memory_mask = + physical_device.memory_properties(static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit)), + .usage = vk::buffer_usage::uniform_buffer_bit, + }; + vk::uniform_buffer material_ubo = vk::uniform_buffer( + logical_device, sizeof(material_uniform), material_ubfo_info); + + std::array set1_uniforms0 = { + vk::write_buffer{ + .buffer = material_ubo, + .offset = 0, + .range = static_cast(material_ubo.size_bytes()), + }, }; - vk::uniform_buffer material_ubo = - vk::uniform_buffer(logical_device, material_ubfo_info); - - std::array set1_uniforms0 = { vk::write_buffer{ - .buffer = material_ubo, - .offset = 0, - .range = material_ubo.size_bytes() } }; std::array uniforms_set1 = { - vk::write_buffer_descriptor{ .dst_binding = 0, - .uniforms = set1_uniforms0 } + vk::write_buffer_descriptor{ + .dst_binding = 0, + .uniforms = set1_uniforms0, + }, }; - std::array set1_samplers = { vk::write_image{ - .sampler = texture1.image().sampler(), - .view = texture1.image().image_view(), - .layout = vk::image_layout::shader_read_only_optimal, - } }; + std::array set1_samplers = { + vk::write_image{ + .sampler = texture1.image().sampler(), + .view = texture1.image().image_view(), + .layout = vk::image_layout::shader_read_only_optimal, + }, + }; std::array sample_images = { vk::write_image_descriptor{ .dst_binding = 1, @@ -527,21 +582,27 @@ main() { // renderpass begin/end must be within a recording command buffer vk::renderpass_begin_params begin_renderpass = { - .current_command = current, .extent = swapchain_extent, .current_framebuffer = swapchain_framebuffers[current_frame], .color = color, .subpass = vk::subpass_contents::inline_bit }; - main_renderpass.begin(begin_renderpass); + main_renderpass.begin(current, begin_renderpass); // Binding a graphics pipeline -- before drawing stuff // Inside of this graphics pipeline bind, is where you want to do the // drawing stuff to main_graphics_pipeline.bind(current); - test_vbo.bind(current); - test_ibo.bind(current); + // test_vbo.bind(current); + // test_ibo.bind(current); + const VkBuffer vertex = test_vbo; + uint64_t offset = 0; + current.bind_vertex_buffers(std::span(&vertex, 1), + std::span(&offset, 1)); + if (!indices.empty()) { + current.bind_index_buffers32(test_ibo); + } static auto start_time = std::chrono::high_resolution_clock::now(); @@ -565,16 +626,19 @@ main() { 10.0f) }; ubo.proj[1][1] *= -1; - test_ubo.update(&ubo); - - // Before we can send stuff to the GPU, since we already updated the - // descriptor set 0 beforehand, we must bind that descriptor resource - // before making any of the draw calls Something to note: You cannot - // update descriptor sets in the process of a current-recording command - // buffers or else that becomes undefined behavior - set0_resource.bind(current, main_graphics_pipeline.layout()); + + test_ubo.transfer( + std::span(&ubo, 1)); + + std::array descriptors = { + set0_resource, + }; + + current.bind_descriptors(main_graphics_pipeline.layout(), + VK_PIPELINE_BIND_POINT_GRAPHICS, + descriptors); + // Drawing-call to render actual triangle to the screen - // vkCmdDraw(current, 3, 1, 0, 0); vkCmdDrawIndexed( current, static_cast(indices.size()), 1, 0, 0, 0); @@ -592,39 +656,34 @@ main() { // }); (???) // this to ensure they are cleaned up in the proper order logical_device.wait(); - main_swapchain.destroy(); + main_swapchain.destruct(); - texture1.destroy(); - set0_resource.destroy(); - test_ubo.destroy(); - material_ubo.destroy(); - test_ibo.destroy(); - test_vbo.destroy(); + texture1.destruct(); + set0_resource.destruct(); + test_ubo.destruct(); + material_ubo.destruct(); + test_ibo.destruct(); + test_vbo.destruct(); for (auto& command : swapchain_command_buffers) { - command.destroy(); + command.destruct(); } for (auto& fb : swapchain_framebuffers) { - fb.destroy(); + fb.destruct(); } for (auto& image : swapchain_images) { - image.destroy(); - } - - for (auto& depth_img : swapchain_depth_images) { - depth_img.destroy(); + image.destruct(); } - main_graphics_pipeline.destroy(); - geometry_resource.destroy(); - main_renderpass.destroy(); - presentation_queue.destroy(); + main_graphics_pipeline.destruct(); + geometry_resource.destruct(); + main_renderpass.destruct(); + presentation_queue.destruct(); - logical_device.destroy(); - window_surface.destroy(); + logical_device.destruct(); + window_surface.destruct(); glfwDestroyWindow(window); - api_instance.destroy(); return 0; } \ No newline at end of file diff --git a/demos/10-textures/conanfile.py b/demos/10-textures/conanfile.py index 708fb66..eb977f5 100644 --- a/demos/10-textures/conanfile.py +++ b/demos/10-textures/conanfile.py @@ -22,7 +22,7 @@ def requirements(self): self.requires("glm/1.0.1") self.requires("stb/cci.20230920") self.requires("tinyobjloader/2.0.0-rc10") - self.requires("vulkan-cpp/5.0") + self.requires("vulkan-cpp/6.0") def build(self): cmake = CMake(self) diff --git a/demos/11-depth-buffering/CMakeLists.txt b/demos/11-depth-buffering/CMakeLists.txt index be80e23..f905274 100644 --- a/demos/11-depth-buffering/CMakeLists.txt +++ b/demos/11-depth-buffering/CMakeLists.txt @@ -13,6 +13,7 @@ build_application( stb LINK_PACKAGES + stb::stb vulkan-cpp Vulkan::Vulkan ) \ No newline at end of file diff --git a/demos/11-depth-buffering/application.cpp b/demos/11-depth-buffering/application.cpp index 30887b8..4e04dfb 100644 --- a/demos/11-depth-buffering/application.cpp +++ b/demos/11-depth-buffering/application.cpp @@ -14,12 +14,19 @@ #include #include #include -import vk; #include #define GLM_FORCE_RADIANS #include #include +#include + +#ifndef STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#include +#endif + +import vk; static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback( @@ -60,6 +67,80 @@ struct global_uniform { glm::mat4 proj; }; +/** + * @brief STBI-specific implementation of the vk::image interface + */ +class stb_image : public vk::image { +public: + stb_image() = delete; + + stb_image(std::string_view p_path, vk::texture_params p_params) { + image_load(p_path, p_params); + } + + ~stb_image() = default; + +protected: + bool image_load(std::string_view p_path, + vk::texture_params p_params) override { + int w = 0; + int h = 0; + int channels = 0; + + stbi_uc* image_pixel_data = + stbi_load(p_path.data(), &w, &h, &channels, STBI_rgb_alpha); + + if (!image_pixel_data) { + return false; + } + + const VkFormat texture_format = + static_cast(vk::format::r8g8b8a8_unorm); + int bytes_per_pixel = vk::bytes_per_texture_format(texture_format); + + m_extent = { + .width = static_cast(w), + .height = static_cast(h), + }; + + // Retrieving total size of bytes of the dimensions of the image and + // accounting for pixels of the image + uint32_t size_bytes = + m_extent.width * m_extent.height * bytes_per_pixel; + + // Retrieving total image size to the count of the image layers + uint32_t size = size_bytes * p_params.layer_count; + + vk::image_params image_options = { + .extent = m_extent, + .format = texture_format, + .memory_mask = p_params.memory_mask, + .usage = + vk::image_usage::transfer_dst_bit | vk::image_usage::sampled_bit, + .mip_levels = p_params.mip_levels, + .layer_count = p_params.layer_count, + }; + + m_bytes.reserve(size); + std::span bytes_view = + std::span(image_pixel_data, size); + + m_bytes.assign(bytes_view.begin(), bytes_view.end()); + + stbi_image_free(image_pixel_data); + + return true; + } + + std::span image_read() const override { return m_bytes; } + + vk::image_extent image_extent() const override { return m_extent; } + +private: + vk::image_extent m_extent{}; + std::vector m_bytes{}; +}; + int main() { //! @note Just added the some test code to test the conan-starter setup code @@ -111,19 +192,9 @@ main() { // 1. Setting up vk instance vk::instance api_instance(config, debug_callback_info); - if (api_instance.alive()) { - std::println("\napi_instance alive and initiated!!!"); - } - - vk::physical_enumeration enumerate_devices{ - .device_type = vk::physical_gpu::discrete, - }; - -#if defined(__APPLE__) - enumerate_devices.device_type = vk::physical_gpu::integrated; -#endif - - vk::physical_device physical_device(api_instance, enumerate_devices); + std::expected physical_device_expected = + api_instance.enumerate_physical_device(vk::physical_gpu::integrated); + vk::physical_device physical_device = physical_device_expected.value(); // selecting depth format std::array format_support = { @@ -135,19 +206,16 @@ main() { // We provide a selection of format support that we want to check is // supported on current hardware device. VkFormat depth_format = - vk::select_depth_format(physical_device, format_support); - - vk::queue_indices queue_indices = physical_device.family_indices(); - std::println("Graphics Queue Family Index = {}", queue_indices.graphics); - std::println("Compute Queue Family Index = {}", queue_indices.compute); - std::println("Transfer Queue Family Index = {}", queue_indices.transfer); + physical_device.request_depth_format(format_support); // setting up logical device std::array priorities = { 0.f }; #if defined(__APPLE__) - std::array extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, - "VK_KHR_portability_subset" }; + std::array extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + "VK_KHR_portability_subset", + }; #else std::array extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; #endif @@ -161,21 +229,14 @@ main() { vk::device logical_device(physical_device, logical_device_params); vk::surface window_surface(api_instance, window); - std::println("Starting implementation of the swapchain!!!"); vk::surface_params surface_properties = - vk::enumerate_surface(physical_device, window_surface); - - if (surface_properties.format.format != VK_FORMAT_UNDEFINED) { - std::println("Surface Format.format is not undefined!!!"); - } + physical_device.request_surface(window_surface); vk::swapchain_params enumerate_swapchain_settings = { - .width = (uint32_t)width, - .height = (uint32_t)height, - .present_index = - physical_device.family_indices() - .graphics, // presentation index just uses the graphics index + .width = static_cast(width), + .height = static_cast(height), + .present_index = 0, }; vk::swapchain main_swapchain(logical_device, window_surface, @@ -195,15 +256,17 @@ main() { // Setting up the images for (uint32_t i = 0; i < swapchain_images.size(); i++) { vk::image_params swapchain_image_config = { - .extent = { .width = swapchain_extent.width, - .height = swapchain_extent.height }, + .extent = { + .width = swapchain_extent.width, + .height = swapchain_extent.height, + }, .format = surface_properties.format.format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), .aspect = vk::image_aspect_flags::color_bit, - .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .usage = vk::image_usage::color_attachment_bit, .mip_levels = 1, .layer_count = 1, - // .physical_device = physical_device - .phsyical_memory_properties = physical_device.memory_properties(), }; swapchain_images[i] = @@ -211,14 +274,17 @@ main() { // Creating Images for depth buffering vk::image_params image_config = { - .extent = { .width = swapchain_extent.width, - .height = swapchain_extent.height }, + .extent = { + .width = swapchain_extent.width, + .height = swapchain_extent.height, + }, .format = depth_format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), .aspect = vk::image_aspect_flags::depth_bit, - .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + .usage = vk::image_usage::depth_stencil_bit, .mip_levels = 1, .layer_count = 1, - .phsyical_memory_properties = physical_device.memory_properties(), }; swapchain_depth_images[i] = vk::sample_image(logical_device, image_config); @@ -237,8 +303,6 @@ main() { vk::command_buffer(logical_device, settings); } - // setting up renderpass - // setting up attachments for the renderpass std::array renderpass_attachments = { vk::attachment{ @@ -267,14 +331,9 @@ main() { vk::renderpass main_renderpass(logical_device, renderpass_attachments); - std::println("renderpass created!!!"); - // Setting up swapchain framebuffers - std::vector swapchain_framebuffers(image_count); for (uint32_t i = 0; i < swapchain_framebuffers.size(); i++) { - // image_view_attachments.push_back(swapchain_images[i].view); - // image_view_attachments.push_back(swapchain_depth_images[i].view); // NOTE: This must match the amount of attachments the renderpass also // has to match the image_view attachment for per-framebuffers as well @@ -294,9 +353,6 @@ main() { vk::framebuffer(logical_device, framebuffer_info); } - std::println("Created VkFramebuffer's with size = {}", - swapchain_framebuffers.size()); - // setting up presentation queue to display commands to the screen vk::queue_params enumerate_present_queue{ .family = 0, @@ -308,33 +364,40 @@ main() { // gets set with the renderpass std::array color = { 0.f, 0.5f, 0.5f, 1.f }; - std::println("Start implementing graphics pipeline!!!"); - // Now creating a vulkan graphics pipeline for the shader loading std::array shader_sources = { - vk::shader_source{ .filename = "shader_samples/sample4/test.vert.spv", - .stage = vk::shader_stage::vertex }, - vk::shader_source{ .filename = "shader_samples/sample4/test.frag.spv", - .stage = vk::shader_stage::fragment }, + vk::shader_source{ + .filename = "shader_samples/sample4/test.vert.spv", + .stage = vk::shader_stage::vertex, + }, + vk::shader_source{ + .filename = "shader_samples/sample4/test.frag.spv", + .stage = vk::shader_stage::fragment, + }, }; // Setting up vertex attributes in the test shaders std::array attribute_entries = { - vk::vertex_attribute_entry{ .location = 0, - .format = vk::format::rgb32_sfloat, - .stride = - offsetof(vk::vertex_input, position) }, - vk::vertex_attribute_entry{ .location = 1, - .format = vk::format::rgb32_sfloat, - .stride = - offsetof(vk::vertex_input, color) }, - vk::vertex_attribute_entry{ .location = 2, - .format = vk::format::rg32_sfloat, - .stride = offsetof(vk::vertex_input, uv) }, - vk::vertex_attribute_entry{ .location = 3, - .format = vk::format::rgb32_sfloat, - .stride = - offsetof(vk::vertex_input, normals) } + vk::vertex_attribute_entry{ + .location = 0, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, position), + }, + vk::vertex_attribute_entry{ + .location = 1, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, color), + }, + vk::vertex_attribute_entry{ + .location = 2, + .format = vk::format::rg32_sfloat, + .stride = offsetof(vk::vertex_input, uv), + }, + vk::vertex_attribute_entry{ + .location = 3, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, normals), + }, }; std::array attributes = { @@ -357,15 +420,11 @@ main() { vk::shader_resource geometry_resource(logical_device, shader_info); geometry_resource.vertex_attributes(attributes); - if (geometry_resource.is_valid()) { - std::println("geometry resource is valid!"); - } - // Setting up descriptor sets for graphics pipeline std::vector entries = { vk::descriptor_entry{ // specifies "layout (set = 0, binding = 0) uniform GlobalUbo" - .type = vk::buffer::uniform, + .type = vk::descriptor_type::uniform, .binding_point = { .binding = 0, .stage = vk::shader_stage::vertex, @@ -374,7 +433,7 @@ main() { }, vk::descriptor_entry{ // layout (set = 0, binding = 1) uniform sampler2D - .type = vk::buffer::combined_image_sampler, + .type = vk::descriptor_type::combined_image_sampler, .binding_point = { .binding = 1, .stage = vk::shader_stage::fragment, @@ -382,7 +441,7 @@ main() { .descriptor_count = 1, } }; - // uint32_t image_count = image_count; + vk::descriptor_layout set0_layout = { .slot = 0, // indicate that this is descriptor set 0 .max_sets = image_count, // max of descriptor sets able to allocate @@ -413,79 +472,100 @@ main() { }; vk::pipeline main_graphics_pipeline(logical_device, pipeline_configuration); - if (main_graphics_pipeline.alive()) { - std::println("Main graphics pipeline alive() = {}", - main_graphics_pipeline.alive()); - } - // Setting up vertex buffer std::array vertices = { - vk::vertex_input{ .position = { -0.5f, -0.5f, 0.f }, - .color = { 1.0f, 0.0f, 0.0f }, - .normals = { 1.0f, 0.0f, 0.f }, - .uv = { 1.0f, 0.0f } }, - vk::vertex_input{ .position = { 0.5f, -0.5f, 0.f }, - .color = { 0.0f, 1.0f, 0.0f }, - .normals = { 0.0f, 0.0f, 0.f }, - .uv = { 0.0f, 0.0f } }, - vk::vertex_input{ .position = { 0.5f, 0.5f, 0.f }, - .color = { 0.0f, 0.0f, 1.0f }, - .normals = { 0.0f, 1.0f, 0.f }, - .uv = { 0.0f, 1.0f } }, - vk::vertex_input{ .position = { -0.5f, 0.5f, 0.f }, - .color = { 1.0f, 1.0f, 1.0f }, - .normals = { 1.0f, 1.0f, 0.f }, - .uv = { 1.0f, 1.0f } }, - - vk::vertex_input{ .position = { -0.5f, -0.5f, -0.5f }, - .color = { 1.0f, 0.0f, 0.0f }, - .normals = { 0.0f, 0.0f, 0.f }, - .uv = { 1.0f, 0.0f } }, - vk::vertex_input{ .position = { 0.5f, -0.5f, -0.5f }, - .color = { 0.0f, 1.0f, 0.0f }, - .normals = { 1.0f, 0.0f, 0.f }, - .uv = { 0.0f, 0.0f } }, - vk::vertex_input{ .position = { 0.5f, 0.5f, -0.5f }, - .color = { 0.0f, 0.0f, 1.0f }, - .normals = { 1.0f, 1.0f, 0.f }, - .uv = { 0.0f, 1.0f } }, - vk::vertex_input{ .position = { -0.5f, 0.5f, -0.5f }, - .color = { 1.0f, 1.0f, 1.0f }, - .normals = { 0.0f, 1.0f, 0.f }, - .uv = { 1.0f, 1.0f } } + vk::vertex_input{ + .position = { -0.5f, -0.5f, 0.f }, + .color = { 1.0f, 0.0f, 0.0f }, + .normals = { 1.0f, 0.0f, 0.f }, + .uv = { 1.0f, 0.0f }, + }, + vk::vertex_input{ + .position = { 0.5f, -0.5f, 0.f }, + .color = { 0.0f, 1.0f, 0.0f }, + .normals = { 0.0f, 0.0f, 0.f }, + .uv = { 0.0f, 0.0f }, + }, + vk::vertex_input{ + .position = { 0.5f, 0.5f, 0.f }, + .color = { 0.0f, 0.0f, 1.0f }, + .normals = { 0.0f, 1.0f, 0.f }, + .uv = { 0.0f, 1.0f }, + }, + vk::vertex_input{ + .position = { -0.5f, 0.5f, 0.f }, + .color = { 1.0f, 1.0f, 1.0f }, + .normals = { 1.0f, 1.0f, 0.f }, + .uv = { 1.0f, 1.0f }, + }, + + vk::vertex_input{ + .position = { -0.5f, -0.5f, -0.5f }, + .color = { 1.0f, 0.0f, 0.0f }, + .normals = { 0.0f, 0.0f, 0.f }, + .uv = { 1.0f, 0.0f }, + }, + vk::vertex_input{ + .position = { 0.5f, -0.5f, -0.5f }, + .color = { 0.0f, 1.0f, 0.0f }, + .normals = { 1.0f, 0.0f, 0.f }, + .uv = { 0.0f, 0.0f }, + }, + vk::vertex_input{ + .position = { 0.5f, 0.5f, -0.5f }, + .color = { 0.0f, 0.0f, 1.0f }, + .normals = { 1.0f, 1.0f, 0.f }, + .uv = { 0.0f, 1.0f }, + }, + vk::vertex_input{ + .position = { -0.5f, 0.5f, -0.5f }, + .color = { 1.0f, 1.0f, 1.0f }, + .normals = { 0.0f, 1.0f, 0.f }, + .uv = { 1.0f, 1.0f }, + } }; - vk::vertex_params vertex_info = { - .phsyical_memory_properties = physical_device.memory_properties(), - .vertices = vertices, + + //! @brief Setting host visibility property flags + const auto property_flags = + static_cast(vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit); + + // Creating vertex buffers + vk::buffer_parameters vertex_params = { + .memory_mask = physical_device.memory_properties(property_flags), + .property_flags = vk::memory_property::device_local_bit, + .usage = vk::buffer_usage::transfer_dst_bit | + vk::buffer_usage::vertex_buffer_bit, }; - vk::vertex_buffer test_vbo(logical_device, vertex_info); - std::println("vertex_buffer.alive() = {}", test_vbo.alive()); + vk::vertex_buffer test_vbo(logical_device, vertices, vertex_params); + // Creating index buffers std::array indices = { 0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4 }; + vk::buffer_parameters index_params = { + .memory_mask = physical_device.memory_properties(property_flags), + .property_flags = vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit, + .usage = vk::buffer_usage::index_buffer_bit, + }; + vk::index_buffer test_ibo(logical_device, indices, index_params); - vk::index_params index_info = { - .phsyical_memory_properties = physical_device.memory_properties(), - .indices = indices, + vk::buffer_parameters uniform_params = { + .memory_mask = physical_device.memory_properties( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit), + .usage = vk::buffer_usage::uniform_buffer_bit, + }; + + vk::uniform_buffer test_ubo = vk::uniform_buffer( + logical_device, sizeof(global_uniform), uniform_params); + + std::array uniforms0 = { + vk::write_buffer{ + .buffer = test_ubo, + .offset = 0, + .range = static_cast(test_ubo.size_bytes()), + }, }; - vk::index_buffer test_ibo(logical_device, index_info); - std::println("index_buffer.alive() = {}", test_ibo.alive()); - - // Dummy uniform struct just for testing if update works when mapping some - // data camera_ubo global_ubo = {}; test_ubo.update(&global_ubo); - - // Setting up descriptor sets for handling uniforms - vk::uniform_params test_ubo_info = { .phsyical_memory_properties = - physical_device.memory_properties(), - .size_bytes = sizeof(global_uniform) }; - vk::uniform_buffer test_ubo = - vk::uniform_buffer(logical_device, test_ubo_info); - std::println("uniform_buffer.alive() = {}", test_ubo.alive()); - - std::array uniforms0 = { vk::write_buffer{ - .buffer = test_ubo, - .offset = 0, - .range = test_ubo.size_bytes(), - } }; std::array uniforms = { vk::write_buffer_descriptor{ @@ -494,21 +574,25 @@ main() { } }; - // Loading a texture -- for testing - vk::texture_info config_texture = { - .phsyical_memory_properties = physical_device.memory_properties(), - .filepath = - std::filesystem::path("asset_samples/container_diffuse.png") + // Loading a texture + vk::texture_params config_texture = { + .memory_mask = + physical_device.memory_properties(static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit)), }; - vk::texture texture1(logical_device, config_texture); - std::println("texture1.valid = {}", texture1.loaded()); + stb_image img = + stb_image("asset_samples/container_diffuse.png", config_texture); + vk::texture texture1(logical_device, &img, config_texture); - std::array samplers = { vk::write_image{ - .sampler = texture1.image().sampler(), - .view = texture1.image().image_view(), - .layout = vk::image_layout::shader_read_only_optimal, - } }; + std::array samplers = { + vk::write_image{ + .sampler = texture1.image().sampler(), + .view = texture1.image().image_view(), + .layout = vk::image_layout::shader_read_only_optimal, + }, + }; // Moving update call here because now we add textures to set0 std::array sample_images = { @@ -527,21 +611,30 @@ main() { // renderpass begin/end must be within a recording command buffer vk::renderpass_begin_params begin_renderpass = { - .current_command = current, .extent = swapchain_extent, .current_framebuffer = swapchain_framebuffers[current_frame], .color = color, .subpass = vk::subpass_contents::inline_bit }; - main_renderpass.begin(begin_renderpass); + main_renderpass.begin(current, begin_renderpass); // Binding a graphics pipeline -- before drawing stuff // Inside of this graphics pipeline bind, is where you want to do the // drawing stuff to main_graphics_pipeline.bind(current); - test_vbo.bind(current); - test_ibo.bind(current); + const VkBuffer vertex = test_vbo; + uint64_t offset = 0; + + current.bind_vertex_buffers(std::span(&vertex, 1), + std::span(&offset, 1)); + + if (!indices.empty()) { + current.bind_index_buffers32(test_ibo); + } + + // test_vbo.bind(current); + // test_ibo.bind(current); static auto start_time = std::chrono::high_resolution_clock::now(); @@ -565,14 +658,22 @@ main() { 10.0f) }; ubo.proj[1][1] *= -1; - test_ubo.update(&ubo); + // test_ubo.update(&ubo); + test_ubo.transfer( + std::span(&ubo, 1)); // Before we can send stuff to the GPU, since we already updated the // descriptor set 0 beforehand, we must bind that descriptor resource // before making any of the draw calls Something to note: You cannot // update descriptor sets in the process of a current-recording command // buffers or else that becomes undefined behavior - set0_resource.bind(current, main_graphics_pipeline.layout()); + // set0_resource.bind(current, main_graphics_pipeline.layout()); + std::array descriptors = { + set0_resource, + }; + current.bind_descriptors(main_graphics_pipeline.layout(), + VK_PIPELINE_BIND_POINT_GRAPHICS, + descriptors); // Drawing-call to render actual triangle to the screen // vkCmdDraw(current, 3, 1, 0, 0); @@ -593,38 +694,37 @@ main() { // }); (???) // this to ensure they are cleaned up in the proper order logical_device.wait(); - main_swapchain.destroy(); + main_swapchain.destruct(); - texture1.destroy(); - set0_resource.destroy(); - test_ubo.destroy(); - test_ibo.destroy(); - test_vbo.destroy(); + texture1.destruct(); + set0_resource.destruct(); + test_ubo.destruct(); + test_ibo.destruct(); + test_vbo.destruct(); for (auto& command : swapchain_command_buffers) { - command.destroy(); + command.destruct(); } for (auto& fb : swapchain_framebuffers) { - fb.destroy(); + fb.destruct(); } for (auto& image : swapchain_images) { - image.destroy(); + image.destruct(); } for (auto& depth_img : swapchain_depth_images) { - depth_img.destroy(); + depth_img.destruct(); } - main_graphics_pipeline.destroy(); - geometry_resource.destroy(); - main_renderpass.destroy(); - presentation_queue.destroy(); + main_graphics_pipeline.destruct(); + geometry_resource.destruct(); + main_renderpass.destruct(); + presentation_queue.destruct(); - logical_device.destroy(); - window_surface.destroy(); + logical_device.destruct(); + window_surface.destruct(); glfwDestroyWindow(window); - api_instance.destroy(); return 0; } \ No newline at end of file diff --git a/demos/11-depth-buffering/conanfile.py b/demos/11-depth-buffering/conanfile.py index 708fb66..eb977f5 100644 --- a/demos/11-depth-buffering/conanfile.py +++ b/demos/11-depth-buffering/conanfile.py @@ -22,7 +22,7 @@ def requirements(self): self.requires("glm/1.0.1") self.requires("stb/cci.20230920") self.requires("tinyobjloader/2.0.0-rc10") - self.requires("vulkan-cpp/5.0") + self.requires("vulkan-cpp/6.0") def build(self): cmake = CMake(self) diff --git a/demos/12-loading-models/CMakeLists.txt b/demos/12-loading-models/CMakeLists.txt index a0f1d47..e89a9ad 100644 --- a/demos/12-loading-models/CMakeLists.txt +++ b/demos/12-loading-models/CMakeLists.txt @@ -14,6 +14,7 @@ build_application( tinyobjloader LINK_PACKAGES + stb::stb vulkan-cpp tinyobjloader Vulkan::Vulkan diff --git a/demos/12-loading-models/application.cpp b/demos/12-loading-models/application.cpp index e83eef5..c65f07c 100644 --- a/demos/12-loading-models/application.cpp +++ b/demos/12-loading-models/application.cpp @@ -14,7 +14,6 @@ #include #include #include -import vk; #include #define GLM_FORCE_RADIANS @@ -24,6 +23,14 @@ import vk; #include #include +#include + +#ifndef STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#include +#endif + +import vk; static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback( @@ -144,48 +151,68 @@ class obj_model { indices.push_back(unique_vertices[vertex]); } } - vk::vertex_params vertex_info = { .phsyical_memory_properties = - p_physical.memory_properties(), - .vertices = vertices }; - - vk::index_params index_info = { .phsyical_memory_properties = - p_physical.memory_properties(), - .indices = indices }; - m_vertex_buffer = vk::vertex_buffer(p_device, vertex_info); - m_index_buffer = vk::index_buffer(p_device, index_info); + + m_has_indices = (indices.size() > 0) ? true : false; + + if (m_has_indices) { + m_indices_size = indices.size(); + } + m_indices_size = vertices.size(); + m_indices_size = indices.size(); + + //! @brief Creating vertex/index buffers with host visibility flags + const auto property_flags = static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit); + + vk::buffer_parameters vertex_params = { + .memory_mask = p_physical.memory_properties(property_flags), + .property_flags = vk::memory_property::device_local_bit, + .usage = vk::buffer_usage::transfer_dst_bit | + vk::buffer_usage::vertex_buffer_bit, + }; + + vk::buffer_parameters index_params = { + .memory_mask = p_physical.memory_properties(property_flags), + .property_flags = static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit), + .usage = vk::buffer_usage::index_buffer_bit, + }; + + m_vertex_buffer = vk::vertex_buffer(p_device, vertices, vertex_params); + m_index_buffer = vk::index_buffer(p_device, indices, index_params); m_is_loaded = true; } [[nodiscard]] bool loaded() const { return m_is_loaded; } - void bind(const VkCommandBuffer& p_command) { - m_vertex_buffer.bind(p_command); - if (m_index_buffer.size() > 0) { - m_index_buffer.bind(p_command); - } - } + [[nodiscard]] VkBuffer vertex_handle() const { return m_vertex_buffer; } + + [[nodiscard]] VkBuffer index_handle() const { return m_index_buffer; } + + [[nodiscard]] bool has_indices() const { return m_has_indices; } + + [[nodiscard]] uint32_t indices_size() const { return m_indices_size; } void draw(const VkCommandBuffer& p_command) { - if (m_index_buffer.size() > 0) { - vkCmdDrawIndexed(p_command, - static_cast(m_index_buffer.size()), - 1, - 0, - 0, - 0); + if (m_has_indices) { + vkCmdDrawIndexed(p_command, m_indices_size, 1, 0, 0, 0); } else { - vkCmdDraw(p_command, m_vertex_buffer.size(), 1, 0, 0); + vkCmdDraw(p_command, m_indices_size, 1, 0, 0); } } - void destroy() { - m_vertex_buffer.destroy(); - m_index_buffer.destroy(); + void destruct() { + m_vertex_buffer.destruct(); + m_index_buffer.destruct(); } private: bool m_is_loaded = false; + bool m_has_indices = false; + uint32_t m_indices_size = 0; vk::vertex_buffer m_vertex_buffer{}; vk::index_buffer m_index_buffer{}; }; @@ -213,6 +240,80 @@ get_instance_extensions() { return extension_names; } +/** + * @brief STBI-specific implementation of the vk::image interface + */ +class stb_image : public vk::image { +public: + stb_image() = delete; + + stb_image(std::string_view p_path, vk::texture_params p_params) { + image_load(p_path, p_params); + } + + ~stb_image() = default; + +protected: + bool image_load(std::string_view p_path, + vk::texture_params p_params) override { + int w = 0; + int h = 0; + int channels = 0; + + stbi_uc* image_pixel_data = + stbi_load(p_path.data(), &w, &h, &channels, STBI_rgb_alpha); + + if (!image_pixel_data) { + return false; + } + + const VkFormat texture_format = + static_cast(vk::format::r8g8b8a8_unorm); + int bytes_per_pixel = vk::bytes_per_texture_format(texture_format); + + m_extent = { + .width = static_cast(w), + .height = static_cast(h), + }; + + // Retrieving total size of bytes of the dimensions of the image and + // accounting for pixels of the image + uint32_t size_bytes = + m_extent.width * m_extent.height * bytes_per_pixel; + + // Retrieving total image size to the count of the image layers + uint32_t size = size_bytes * p_params.layer_count; + + vk::image_params image_options = { + .extent = m_extent, + .format = texture_format, + .memory_mask = p_params.memory_mask, + .usage = + vk::image_usage::transfer_dst_bit | vk::image_usage::sampled_bit, + .mip_levels = p_params.mip_levels, + .layer_count = p_params.layer_count, + }; + + m_bytes.reserve(size); + std::span bytes_view = + std::span(image_pixel_data, size); + + m_bytes.assign(bytes_view.begin(), bytes_view.end()); + + stbi_image_free(image_pixel_data); + + return true; + } + + std::span image_read() const override { return m_bytes; } + + vk::image_extent image_extent() const override { return m_extent; } + +private: + vk::image_extent m_extent{}; + std::vector m_bytes{}; +}; + int main() { //! @note Just added the some test code to test the conan-starter setup code @@ -264,20 +365,9 @@ main() { // Setting up vk instance vk::instance api_instance(config, debug_callback_info); - if (api_instance.alive()) { - std::println("\napi_instance alive and initiated!!!"); - } - - vk::physical_enumeration enumerate_devices{ - .device_type = vk::physical_gpu::discrete, - }; - - // Specifically set for the mac m1 series platform -#if defined(__APPLE__) - enumerate_devices.device_type = vk::physical_gpu::integrated; -#endif - - vk::physical_device physical_device(api_instance, enumerate_devices); + std::expected physical_device_expected = + api_instance.enumerate_physical_device(vk::physical_gpu::integrated); + vk::physical_device physical_device = physical_device_expected.value(); // selecting depth format std::array format_support = { @@ -288,20 +378,19 @@ main() { // We provide a selection of format support that we want to check is // supported on current hardware device. + // VkFormat depth_format = + // vk::select_depth_format(physical_device, format_support); VkFormat depth_format = - vk::select_depth_format(physical_device, format_support); - - vk::queue_indices queue_indices = physical_device.family_indices(); - std::println("Graphics Queue Family Index = {}", queue_indices.graphics); - std::println("Compute Queue Family Index = {}", queue_indices.compute); - std::println("Transfer Queue Family Index = {}", queue_indices.transfer); + physical_device.request_depth_format(format_support); // setting up logical device std::array priorities = { 0.f }; #if defined(__APPLE__) - std::array extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, - "VK_KHR_portability_subset" }; + std::array extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + "VK_KHR_portability_subset", + }; #else std::array extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; #endif @@ -315,21 +404,14 @@ main() { vk::device logical_device(physical_device, logical_device_params); vk::surface window_surface(api_instance, window); - std::println("Starting implementation of the swapchain!!!"); vk::surface_params surface_properties = - vk::enumerate_surface(physical_device, window_surface); - - if (surface_properties.format.format != VK_FORMAT_UNDEFINED) { - std::println("Surface Format.format is not undefined!!!"); - } + physical_device.request_surface(window_surface); vk::swapchain_params enumerate_swapchain_settings = { .width = (uint32_t)width, .height = (uint32_t)height, - .present_index = - physical_device.family_indices() - .graphics, // presentation index just uses the graphics index + .present_index = 0, }; vk::swapchain main_swapchain(logical_device, window_surface, @@ -351,14 +433,17 @@ main() { uint32_t mip_levels = 1; for (uint32_t i = 0; i < swapchain_images.size(); i++) { vk::image_params swapchain_image_config = { - .extent = { .width = swapchain_extent.width, - .height = swapchain_extent.height }, + .extent = { + .width = swapchain_extent.width, + .height = swapchain_extent.height, + }, .format = surface_properties.format.format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), .aspect = vk::image_aspect_flags::color_bit, - .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .usage = vk::image_usage::color_attachment_bit, .mip_levels = 1, .layer_count = 1, - .phsyical_memory_properties = physical_device.memory_properties(), }; swapchain_images[i] = @@ -366,14 +451,17 @@ main() { // Creating Images for depth buffering vk::image_params image_config = { - .extent = { .width = swapchain_extent.width, - .height = swapchain_extent.height }, + .extent = { + .width = swapchain_extent.width, + .height = swapchain_extent.height, + }, .format = depth_format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), .aspect = vk::image_aspect_flags::depth_bit, - .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + .usage = vk::image_usage::depth_stencil_bit, .mip_levels = 1, .layer_count = 1, - .phsyical_memory_properties = physical_device.memory_properties(), }; swapchain_depth_images[i] = vk::sample_image(logical_device, image_config); @@ -384,7 +472,7 @@ main() { for (size_t i = 0; i < swapchain_command_buffers.size(); i++) { vk::command_params settings = { .levels = vk::command_levels::primary, - .queue_index = enumerate_swapchain_settings.present_index, + .queue_index = 0, .flags = vk::command_pool_flags::reset, }; @@ -422,8 +510,6 @@ main() { vk::renderpass main_renderpass(logical_device, renderpass_attachments); - std::println("renderpass created!!!"); - // Setting up swapchain framebuffers std::vector swapchain_framebuffers(image_count); @@ -447,9 +533,6 @@ main() { vk::framebuffer(logical_device, framebuffer_info); } - std::println("Created VkFramebuffer's with size = {}", - swapchain_framebuffers.size()); - // setting up presentation queue to display commands to the screen vk::queue_params enumerate_present_queue{ .family = 0, @@ -461,14 +544,16 @@ main() { // gets set with the renderpass std::array color = { 0.f, 0.5f, 0.5f, 1.f }; - // std::println("Start implementing graphics pipeline!!!"); - // Now creating a vulkan graphics pipeline for the shader loading std::array shader_sources = { - vk::shader_source{ .filename = "shader_samples/sample5/test.vert.spv", - .stage = vk::shader_stage::vertex }, - vk::shader_source{ .filename = "shader_samples/sample5/test.frag.spv", - .stage = vk::shader_stage::fragment }, + vk::shader_source{ + .filename = "shader_samples/sample5/test.vert.spv", + .stage = vk::shader_stage::vertex, + }, + vk::shader_source{ + .filename = "shader_samples/sample5/test.frag.spv", + .stage = vk::shader_stage::fragment, + }, }; // Setting up vertex attributes in the test shaders @@ -514,35 +599,49 @@ main() { vk::shader_resource geometry_resource(logical_device, shader_info); geometry_resource.vertex_attributes(attributes); - // Setting up descriptor sets for graphics pipeline + // Set 0: For Uniform BUffers (or global scene data) std::vector entries = { vk::descriptor_entry{ // specifies "layout (set = 0, binding = 0) uniform GlobalUbo" - .type = vk::buffer::uniform, + .type = vk::descriptor_type::uniform, .binding_point = { .binding = 0, .stage = vk::shader_stage::vertex, }, .descriptor_count = 1, }, + }; + vk::descriptor_layout set0_layout = { + .slot = 0, // indicate specific descriptor slot 0 + .max_sets = image_count, // max descriptors to allocate + .entries = entries, // descriptor layout entries description + }; + vk::descriptor_resource set0_resource(logical_device, set0_layout); + + // Set 1 = For Textures + std::vector entries_set1 = { vk::descriptor_entry{ - // layout (set = 0, binding = 1) uniform sampler2D - .type = vk::buffer::combined_image_sampler, + // layout (set = 1, binding = 0) uniform sampler2D + .type = vk::descriptor_type::combined_image_sampler, .binding_point = { - .binding = 1, + .binding = 0, .stage = vk::shader_stage::fragment, }, .descriptor_count = 1, } }; - vk::descriptor_layout set0_layout = { - .slot = 0, // indicate specific descriptor slot 0 + vk::descriptor_layout set1_layout = { + .slot = 1, // indicate specific descriptor slot 0 .max_sets = image_count, // max descriptors to allocate - .entries = entries, // descriptor layout entries description + .entries = entries_set1, // descriptor layout entries description }; - vk::descriptor_resource set0_resource(logical_device, set0_layout); - std::array layouts = { set0_resource.layout() }; + vk::descriptor_resource set1_resource(logical_device, set1_layout); + + std::array layouts = { + set0_resource.layout(), + set1_resource.layout(), + }; std::array color_blend_attachments = { vk::color_blend_attachment_state{}, @@ -570,26 +669,40 @@ main() { logical_device, physical_device); - // Setting up descriptor sets for handling uniforms - vk::uniform_params test_ubo_info = { .phsyical_memory_properties = - physical_device.memory_properties(), - .size_bytes = sizeof(global_uniform) }; - vk::uniform_buffer test_ubo = - vk::uniform_buffer(logical_device, test_ubo_info); - // std::println("uniform_buffer.alive() = {}", test_ubo.alive()); + // Loading a texture - std::array uniforms0 = { vk::write_buffer{ - .buffer = test_ubo, .offset = 0, .range = test_ubo.size_bytes() } }; + vk::buffer_parameters uniform_params = { + .memory_mask = + physical_device.memory_properties(static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit)), + .usage = vk::buffer_usage::uniform_buffer_bit, + }; + vk::uniform_buffer test_ubo = vk::uniform_buffer( + logical_device, sizeof(global_uniform), uniform_params); + + std::array uniforms0 = { + vk::write_buffer{ + .buffer = test_ubo, + .offset = 0, + .range = static_cast(test_ubo.size_bytes()), + }, + }; std::array uniforms = { - vk::write_buffer_descriptor{ .dst_binding = 0, .uniforms = uniforms0 } + vk::write_buffer_descriptor{ + .dst_binding = 0, + .uniforms = uniforms0, + }, }; - // Loading a texture - vk::texture_info config_texture = { - .phsyical_memory_properties = physical_device.memory_properties(), - .filepath = std::filesystem::path("asset_samples/viking_room.png") + vk::texture_params config_texture = { + .memory_mask = physical_device.memory_properties( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit), }; - vk::texture texture1(logical_device, config_texture); + + stb_image img = stb_image("asset_samples/viking_room.png", config_texture); + vk::texture texture1(logical_device, &img, config_texture); std::array samplers = { vk::write_image{ @@ -600,13 +713,15 @@ main() { }; // Specify image descriptor images/samplers to the descriptor - std::array sample_images = { + std::array set1_samples = { vk::write_image_descriptor{ - .dst_binding = 1, + .dst_binding = 0, .sample_images = samplers, } }; - set0_resource.update(uniforms, sample_images); + set0_resource.update(uniforms); + + set1_resource.update({}, set1_samples); while (!glfwWindowShouldClose(window)) { glfwPollEvents(); @@ -618,21 +733,28 @@ main() { // renderpass begin/end must be within a recording command buffer vk::renderpass_begin_params begin_renderpass = { - .current_command = current, .extent = swapchain_extent, .current_framebuffer = swapchain_framebuffers[current_frame], .color = color, .subpass = vk::subpass_contents::inline_bit }; - main_renderpass.begin(begin_renderpass); + main_renderpass.begin(current, begin_renderpass); // Binding a graphics pipeline -- before drawing stuff // Inside of this graphics pipeline bind, is where you want to do the // drawing stuff to main_graphics_pipeline.bind(current); - // Must be binded before descriptor resource gets binded - test_model.bind(current); + // test_model.bind(current); + // std::array vertex = { test_model.vertex_handle() }; + const VkBuffer vertex = test_model.vertex_handle(); + uint64_t offset = 0; + current.bind_vertex_buffers(std::span(&vertex, 1), + std::span(&offset, 1)); + + if (test_model.has_indices()) { + current.bind_index_buffers32(test_model.index_handle()); + } static auto start_time = std::chrono::high_resolution_clock::now(); @@ -656,14 +778,25 @@ main() { 10.0f) }; ubo.proj[1][1] *= -1; - test_ubo.update(&ubo); + + test_ubo.transfer( + std::span(&ubo, 1)); // Before we can send stuff to the GPU, since we already updated the // descriptor set 0 beforehand, we must bind that descriptor resource // before making any of the draw calls Something to note: You cannot // update descriptor sets in the process of a current-recording command // buffers or else that becomes undefined behavior - set0_resource.bind(current, main_graphics_pipeline.layout()); + // set0_resource.bind(current, main_graphics_pipeline.layout()); + + std::array descriptors = { + set0_resource, + set1_resource, + }; + + current.bind_descriptors(main_graphics_pipeline.layout(), + VK_PIPELINE_BIND_POINT_GRAPHICS, + descriptors); // Drawing-call to render actual triangle to the screen // vkCmdDrawIndexed(current, static_cast(indices.size()), 1, @@ -682,37 +815,37 @@ main() { // this to ensure they are cleaned up in the proper order logical_device.wait(); - main_swapchain.destroy(); + main_swapchain.destruct(); - texture1.destroy(); - set0_resource.destroy(); - test_ubo.destroy(); - test_model.destroy(); + texture1.destruct(); + set0_resource.destruct(); + set1_resource.destruct(); + test_ubo.destruct(); + test_model.destruct(); for (auto& command : swapchain_command_buffers) { - command.destroy(); + command.destruct(); } for (auto& fb : swapchain_framebuffers) { - fb.destroy(); + fb.destruct(); } for (auto& image : swapchain_images) { - image.destroy(); + image.destruct(); } for (auto& depth_img : swapchain_depth_images) { - depth_img.destroy(); + depth_img.destruct(); } - main_graphics_pipeline.destroy(); - geometry_resource.destroy(); - main_renderpass.destroy(); - presentation_queue.destroy(); + main_graphics_pipeline.destruct(); + geometry_resource.destruct(); + main_renderpass.destruct(); + presentation_queue.destruct(); - logical_device.destroy(); - window_surface.destroy(); + logical_device.destruct(); + window_surface.destruct(); glfwDestroyWindow(window); - api_instance.destroy(); return 0; } \ No newline at end of file diff --git a/demos/12-loading-models/conanfile.py b/demos/12-loading-models/conanfile.py index 708fb66..eb977f5 100644 --- a/demos/12-loading-models/conanfile.py +++ b/demos/12-loading-models/conanfile.py @@ -22,7 +22,7 @@ def requirements(self): self.requires("glm/1.0.1") self.requires("stb/cci.20230920") self.requires("tinyobjloader/2.0.0-rc10") - self.requires("vulkan-cpp/5.0") + self.requires("vulkan-cpp/6.0") def build(self): cmake = CMake(self) diff --git a/demos/13-skybox/CMakeLists.txt b/demos/13-skybox/CMakeLists.txt index e8842d9..6a44aa1 100644 --- a/demos/13-skybox/CMakeLists.txt +++ b/demos/13-skybox/CMakeLists.txt @@ -6,13 +6,25 @@ build_application( application.cpp PACKAGES + vulkan-cpp Vulkan - VulkanHeaders - # vulkan-cpp + glfw3 + glm + stb + tinyobjloader LINK_PACKAGES - Vulkan::Vulkan - vulkan-headers::vulkan-headers - # vulkan-cpp::vulkan-cpp + stb::stb vulkan-cpp + tinyobjloader + Vulkan::Vulkan +) + +target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23) + +target_sources(${PROJECT_NAME} PUBLIC + FILE_SET CXX_MODULES + TYPE CXX_MODULES + FILES + environment_map.cppm ) \ No newline at end of file diff --git a/demos/13-skybox/application.cpp b/demos/13-skybox/application.cpp index a2eb2de..1e37459 100644 --- a/demos/13-skybox/application.cpp +++ b/demos/13-skybox/application.cpp @@ -1,28 +1,19 @@ +#define GLFW_INCLUDE_VULKAN +#if _WIN32 +#define VK_USE_PLATFORM_WIN32_KHR +#include +#define GLFW_EXPOSE_NATIVE_WIN32 +#include +#include +#else +#include +#include +#endif + #include #include - -// This is required to select the correct extension for specific platform -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include +#include +#include #include #define GLM_FORCE_RADIANS @@ -30,12 +21,16 @@ #include #define GLM_ENABLE_EXPERIMENTAL #include +#include -// loading tinyobjloader library here #include +#ifndef STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#include +#endif -#include -#include +import vk; +import environment_map; static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback( @@ -48,36 +43,25 @@ debug_callback( } std::vector -initialize_instance_extensions() { +get_instance_extensions() { std::vector extension_names; + uint32_t extension_count = 0; + const char** required_extensions = + glfwGetRequiredInstanceExtensions(&extension_count); - extension_names.emplace_back(VK_KHR_SURFACE_EXTENSION_NAME); + for (uint32_t i = 0; i < extension_count; i++) { + std::println("Required Extension = {}", required_extensions[i]); + extension_names.emplace_back(required_extensions[i]); + } - // An additional surface extension needs to be loaded. This extension is - // platform-specific so needs to be selected based on the platform the - // example is going to be deployed to. Preprocessor directives are used - // here to select the correct platform. -#ifdef VK_USE_PLATFORM_WIN32_KHR - extension_names.emplace_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); -#endif -#ifdef VK_USE_PLATFORM_XLIB_KHR - extensionNames.emplace_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); -#endif -#ifdef VK_USE_PLATFORM_XCB_KHR - extensionNames.emplace_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME); -#endif -#ifdef VK_USE_PLATFORM_ANDROID_KHR - extensionNames.emplace_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); -#endif -#ifdef VK_USE_PLATFORM_WAYLAND_KHR - extensionNames.emplace_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); -#endif -#ifdef VK_USE_PLATFORM_MACOS_MVK - extensionNames.emplace_back(VK_MVK_MACOS_SURFACE_EXTENSION_NAME); -#endif -#ifdef USE_PLATFORM_NULLWS - extensionNames.emplace_back(VK_KHR_DISPLAY_EXTENSION_NAME); + extension_names.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + +#if defined(__APPLE__) + extension_names.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + extension_names.emplace_back( + VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); #endif + return extension_names; } @@ -87,174 +71,6 @@ struct global_uniform { glm::mat4 proj; }; -struct skybox_camera_data { - glm::vec4 forward; - glm::vec4 right; - glm::vec4 up; -}; - -template -void -hash_combine(size_t& seed, const T& v, const Rest&... rest) { - seed ^= std::hash()(v) + 0x9e3779b9 + (seed << 6) + (seed << 2); - (hash_combine(seed, rest), ...); -} - -namespace std { - - template<> - struct hash { - size_t operator()(const vk::vertex_input& vertex) const { - size_t seed = 0; - hash_combine( - seed, vertex.position, vertex.color, vertex.normals, vertex.uv); - return seed; - } - }; -} - -// This is how we are going to load a .obj model for this demo -// Example of how you might want to have your own classes for loading -// geometry-meshes -class obj_model { -public: - obj_model() = default; - obj_model(const std::filesystem::path& p_filename, - const VkDevice& p_device, - const vk::physical_device& p_physical) { - tinyobj::attrib_t attrib; - std::vector shapes; - std::vector materials; - std::string warn, err; - - //! @note If we return the constructor then we can check if the mesh - //! loaded successfully - //! @note We also receive hints if the loading is successful! - //! @note Return default constructor automatically returns false means - //! that mesh will return the boolean as false because it wasnt - //! successful - if (!tinyobj::LoadObj(&attrib, - &shapes, - &materials, - &warn, - &err, - p_filename.string().c_str())) { - std::println("Could not load model from path {}", - p_filename.string()); - m_is_loaded = false; - return; - } - - std::vector vertices; - std::vector indices; - std::unordered_map unique_vertices{}; - - for (const auto& shape : shapes) { - for (const auto& index : shape.mesh.indices) { - vk::vertex_input vertex{}; - - // vertices.push_back(vertex); - if (!unique_vertices.contains(vertex)) { - unique_vertices[vertex] = - static_cast(vertices.size()); - vertices.push_back(vertex); - } - - if (index.vertex_index >= 0) { - vertex.position = { - attrib.vertices[3 * index.vertex_index + 0], - attrib.vertices[3 * index.vertex_index + 1], - attrib.vertices[3 * index.vertex_index + 2] - }; - - vertex.color = { - attrib.colors[3 * index.vertex_index + 0], - attrib.colors[3 * index.vertex_index + 1], - attrib.colors[3 * index.vertex_index + 2] - }; - } - - if (index.normal_index >= 0) { - vertex.normals = { - attrib.normals[3 * index.normal_index + 0], - attrib.normals[3 * index.normal_index + 1], - attrib.normals[3 * index.normal_index + 2] - }; - } - - if (index.texcoord_index >= 0) { - vertex.uv = { - attrib.texcoords[2 * index.texcoord_index + 0], - 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] - }; - } - - if (!unique_vertices.contains(vertex)) { - unique_vertices[vertex] = - static_cast(vertices.size()); - vertices.push_back(vertex); - } - - indices.push_back(unique_vertices[vertex]); - } - } - vk::vertex_params vertex_info = { .phsyical_memory_properties = - p_physical.memory_properties(), - .vertices = vertices }; - - vk::index_params index_info = { .phsyical_memory_properties = - p_physical.memory_properties(), - .indices = indices }; - m_vertex_buffer = vk::vertex_buffer(p_device, vertex_info); - m_index_buffer = vk::index_buffer(p_device, index_info); - m_is_loaded = true; - } - - [[nodiscard]] bool loaded() const { return m_is_loaded; } - - void bind(const VkCommandBuffer& p_command) { - m_vertex_buffer.bind(p_command); - if (m_index_buffer.size() > 0) { - m_index_buffer.bind(p_command); - } - } - - void draw(const VkCommandBuffer& p_command) { - if (m_index_buffer.size() > 0) { - vkCmdDrawIndexed(p_command, - static_cast(m_index_buffer.size()), - 1, - 0, - 0, - 0); - } - else { - vkCmdDraw(p_command, m_vertex_buffer.size(), 1, 0, 0); - } - } - - void destroy() { - m_vertex_buffer.destroy(); - m_index_buffer.destroy(); - } - -private: - bool m_is_loaded = false; - vk::vertex_buffer m_vertex_buffer{}; - vk::index_buffer m_index_buffer{}; -}; - -// template -// void write(const VkDevice& p_device, const vk::buffer_handle& p_buffer, const -// std::array& p_in_buffer) { -// } - -// void write_array(const VkDevice& p_device, const vk::buffer_handle& p_buffer) -// { -// std::array buffer_to_write; -// write<256>(p_device, p_buffer, buffer_to_write); -// } - int main() { //! @note Just added the some test code to test the conan-starter setup code @@ -273,7 +89,7 @@ main() { int width = 800; int height = 600; - std::string title = "Skybox Example"; + std::string title = "Hello Window"; GLFWwindow* window = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr); @@ -284,16 +100,11 @@ main() { }; // setting up extensions - std::vector global_extensions = - initialize_instance_extensions(); + std::vector global_extensions = get_instance_extensions(); vk::debug_message_utility debug_callback_info = { - // .severity essentially takes in vk::message::verbose, - // vk::message::warning, vk::message::error .severity = vk::message::verbose | vk::message::warning | vk::message::error, - // .message_type essentially takes in vk::debug. Like: - // vk::debug::general, vk::debug::validation, vk::debug::performance .message_type = vk::debug::general | vk::debug::validation | vk::debug::performance, .callback = debug_callback @@ -308,30 +119,15 @@ main() { global_extensions // .extensions also takes in std::span }; - // 1. Setting up vk instance + // Setting up vk instance vk::instance api_instance(config, debug_callback_info); - if (api_instance.alive()) { - std::println("\napi_instance alive and initiated!!!"); - } - - // TODO: Implement this as a way to setup physical devices - // vk::enumerate_physical_devices(vk::instance) -> returns - // std::span - - // setting up physical device - // TODO: Probably enforce the use of - // vk::enumerate_physical_device({.device_type = - // vk::physical_gpu::discrete}) - vk::physical_enumeration enumerate_devices{ .device_type = - vk::physical_gpu::discrete }; - vk::physical_device physical_device(api_instance, enumerate_devices); + std::expected physical_device_expected = + api_instance.enumerate_physical_device(vk::physical_gpu::integrated); + vk::physical_device physical_device = physical_device_expected.value(); // selecting depth format std::array format_support = { - // VK_FORMAT_D32_SFLOAT, - // VK_FORMAT_D32_SFLOAT_S8_UINT, - // VK_FORMAT_D24_UNORM_S8_UINT, vk::format::d32_sfloat, vk::format::d32_sfloat_s8_uint, vk::format::d24_unorm_s8_uint @@ -340,16 +136,20 @@ main() { // We provide a selection of format support that we want to check is // supported on current hardware device. VkFormat depth_format = - vk::select_depth_format(physical_device, format_support); - - vk::queue_indices queue_indices = physical_device.family_indices(); - std::println("Graphics Queue Family Index = {}", queue_indices.graphics); - std::println("Compute Queue Family Index = {}", queue_indices.compute); - std::println("Transfer Queue Family Index = {}", queue_indices.transfer); + physical_device.request_depth_format(format_support); // setting up logical device std::array priorities = { 0.f }; + +#if defined(__APPLE__) + std::array extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + "VK_KHR_portability_subset", + }; +#else std::array extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; +#endif + vk::device_params logical_device_params = { .queue_priorities = priorities, .extensions = extensions, @@ -359,21 +159,14 @@ main() { vk::device logical_device(physical_device, logical_device_params); vk::surface window_surface(api_instance, window); - std::println("Starting implementation of the swapchain!!!"); vk::surface_params surface_properties = - vk::enumerate_surface(physical_device, window_surface); - - if (surface_properties.format.format != VK_FORMAT_UNDEFINED) { - std::println("Surface Format.format is not undefined!!!"); - } + physical_device.request_surface(window_surface); vk::swapchain_params enumerate_swapchain_settings = { - .width = (uint32_t)width, - .height = (uint32_t)height, - .present_index = - physical_device.family_indices() - .graphics, // presentation index just uses the graphics index + .width = static_cast(width), + .height = static_cast(height), + .present_index = 0, }; vk::swapchain main_swapchain(logical_device, window_surface, @@ -381,19 +174,8 @@ main() { surface_properties); // querying swapchain images - // TODO: Make the images and framebuffers contained within the vk::swapchain - // Considering if you have two display they will prob have their own set of - // images to display to the two separate screens - uint32_t image_count = 0; - vkGetSwapchainImagesKHR(logical_device, - main_swapchain, - &image_count, - nullptr); // used to get the amount of images - std::vector images(image_count); - vkGetSwapchainImagesKHR(logical_device, - main_swapchain, - &image_count, - images.data()); // used to store in the images + std::span images = main_swapchain.get_images(); + uint32_t image_count = static_cast(images.size()); // Creating Images std::vector swapchain_images(image_count); @@ -402,17 +184,20 @@ main() { VkExtent2D swapchain_extent = surface_properties.capabilities.currentExtent; // Setting up the images + uint32_t layer_count = 1; + uint32_t mip_levels = 1; for (uint32_t i = 0; i < swapchain_images.size(); i++) { - vk::image_params swapchain_image_config = { - .extent = { .width = swapchain_extent.width, - .height = swapchain_extent.height }, + .extent = { + .width = swapchain_extent.width, + .height = swapchain_extent.height, + }, .format = surface_properties.format.format, + .memory_mask = physical_device.memory_properties(vk::memory_property::device_local_bit), .aspect = vk::image_aspect_flags::color_bit, - .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .usage = vk::image_usage::color_attachment_bit, .mip_levels = 1, .layer_count = 1, - .phsyical_memory_properties = physical_device.memory_properties(), }; swapchain_images[i] = @@ -420,14 +205,17 @@ main() { // Creating Images for depth buffering vk::image_params image_config = { - .extent = { .width = swapchain_extent.width, - .height = swapchain_extent.height }, + .extent = { + .width = swapchain_extent.width, + .height = swapchain_extent.height, + }, .format = depth_format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), .aspect = vk::image_aspect_flags::depth_bit, - .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + .usage = vk::image_usage::depth_stencil_bit, .mip_levels = 1, .layer_count = 1, - .phsyical_memory_properties = physical_device.memory_properties(), }; swapchain_depth_images[i] = vk::sample_image(logical_device, image_config); @@ -446,44 +234,40 @@ main() { vk::command_buffer(logical_device, settings); } - // setting up renderpass - // setting up attachments for the renderpass std::array renderpass_attachments = { + // color attachment vk::attachment{ .format = surface_properties.format.format, .layout = vk::image_layout::color_optimal, .samples = vk::sample_bit::count_1, .load = vk::attachment_load::clear, - .store = vk::attachment_store::dont_care, - .stencil_load = vk::attachment_load::clear, + .store = vk::attachment_store::store, + .stencil_load = vk::attachment_load::dont_care, .stencil_store = vk::attachment_store::dont_care, .initial_layout = vk::image_layout::undefined, .final_layout = vk::image_layout::present_src_khr, }, + // depth attachment vk::attachment{ .format = depth_format, .layout = vk::image_layout::depth_stencil_optimal, .samples = vk::sample_bit::count_1, .load = vk::attachment_load::clear, .store = vk::attachment_store::dont_care, - .stencil_load = vk::attachment_load::clear, + .stencil_load = vk::attachment_load::dont_care, .stencil_store = vk::attachment_store::dont_care, .initial_layout = vk::image_layout::undefined, - .final_layout = vk::image_layout::present_src_khr, + .final_layout = vk::image_layout::depth_stencil_read_only_optimal, }, }; vk::renderpass main_renderpass(logical_device, renderpass_attachments); - std::println("renderpass created!!!"); - // Setting up swapchain framebuffers std::vector swapchain_framebuffers(image_count); for (uint32_t i = 0; i < swapchain_framebuffers.size(); i++) { - // image_view_attachments.push_back(swapchain_images[i].view); - // image_view_attachments.push_back(swapchain_depth_images[i].view); // NOTE: This must match the amount of attachments the renderpass also // has to match the image_view attachment for per-framebuffers as well @@ -503,9 +287,6 @@ main() { vk::framebuffer(logical_device, framebuffer_info); } - std::println("Created VkFramebuffer's with size = {}", - swapchain_framebuffers.size()); - // setting up presentation queue to display commands to the screen vk::queue_params enumerate_present_queue{ .family = 0, @@ -517,284 +298,16 @@ main() { // gets set with the renderpass std::array color = { 0.f, 0.5f, 0.5f, 1.f }; - std::println("Start implementing graphics pipeline!!!"); - - // Now creating a vulkan graphics pipeline for the shader loading - std::array shader_sources = { - vk::shader_source{ .filename = "shader_samples/sample6/test.vert.spv", - .stage = vk::shader_stage::vertex }, - vk::shader_source{ .filename = "shader_samples/sample6/test.frag.spv", - .stage = vk::shader_stage::fragment }, - }; - - // Setting up vertex attributes in the test shaders - std::array attribute_entries = { - vk::vertex_attribute_entry{ .location = 0, - .format = vk::format::rgb32_sfloat, - .stride = - offsetof(vk::vertex_input, position) }, - vk::vertex_attribute_entry{ .location = 1, - .format = vk::format::rgb32_sfloat, - .stride = - offsetof(vk::vertex_input, color) }, - vk::vertex_attribute_entry{ .location = 2, - .format = vk::format::rg32_sfloat, - .stride = offsetof(vk::vertex_input, uv) }, - vk::vertex_attribute_entry{ .location = 3, - .format = vk::format::rgb32_sfloat, - .stride = - offsetof(vk::vertex_input, normals) } - }; + environment_map skybox = environment_map( + logical_device, + std::filesystem::path("asset_samples/skybox/monkstown_castle_4k.hdr"), + physical_device, + main_renderpass); - std::array attributes = { - vk::vertex_attribute{ - // layout (set = 0, binding = 0) - .binding = 0, - .entries = attribute_entries, - .stride = sizeof(vk::vertex_input), - .input_rate = vk::input_rate::vertex, - }, - }; - - // To render triangle, we do not need to set any vertex attributes - vk::shader_resource_info shader_info = { - .sources = shader_sources, - .vertex_attributes = - attributes // this is to explicitly set to none, but also dont need to - // set this at all regardless - }; - vk::shader_resource geometry_resource(logical_device, shader_info); - geometry_resource.vertex_attributes(attributes); - - if (geometry_resource.is_valid()) { - std::println("geometry resource is valid!"); - } - - // Setting up descriptor sets for graphics pipeline - std::vector entries = { - vk::descriptor_entry{ - // specifies "layout (set = 0, binding = 0) uniform GlobalUbo" - .type = vk::buffer::uniform, - .binding_point = { - .binding = 0, - .stage = vk::shader_stage::vertex, - }, - .descriptor_count = 1, - }, - vk::descriptor_entry{ - // layout (set = 0, binding = 1) uniform sampler2D - .type = vk::buffer::combined_image_sampler, - .binding_point = { - .binding = 1, - .stage = vk::shader_stage::fragment, - }, - .descriptor_count = 1, - } - }; - // uint32_t image_count = image_count; - vk::descriptor_layout set0_layout = { - .slot = 0, // represents as set 0 - .max_sets = image_count, // max of descriptor sets able to allocate - .entries = entries, // specifies pool sizes and descriptor layout - }; - vk::descriptor_resource set0_resource(logical_device, set0_layout); - - std::array layouts = { set0_resource.layout() }; - - /* - // This get_pipeline_configuration can work as an easy way for - specfying the vulkan configurations as an ease of setting things up - // TODO: Probably provide a shorthand - which could work as this: - vk::pipeline_settings pipeline_configuration = - vk::get_pipeline_configuration(main_renderpass, geometry_resource); - */ - vk::pipeline_settings pipeline_configuration = { - .renderpass = main_renderpass, - .shader_modules = geometry_resource.handles(), - .vertex_attributes = geometry_resource.vertex_attributes(), - .vertex_bind_attributes = geometry_resource.vertex_bind_attributes(), - .descriptor_layouts = layouts - }; - vk::pipeline main_graphics_pipeline(logical_device, pipeline_configuration); - - if (main_graphics_pipeline.alive()) { - std::println("Main graphics pipeline alive() = {}", - main_graphics_pipeline.alive()); - } - - // Loading mesh - obj_model test_model(std::filesystem::path("asset_samples/viking_room.obj"), - logical_device, - physical_device); - - std::println("Obj Model Load Status = {}", test_model.loaded()); - - // Setting up descriptor sets for handling uniforms - vk::uniform_params test_ubo_info = { .phsyical_memory_properties = - physical_device.memory_properties(), - .size_bytes = sizeof(global_uniform) }; - vk::uniform_buffer test_ubo = - vk::uniform_buffer(logical_device, test_ubo_info); - std::println("uniform_buffer.alive() = {}", test_ubo.alive()); - - std::array uniforms0 = { vk::write_buffer{ - .buffer = test_ubo, .offset = 0, .range = test_ubo.size_bytes() } }; - - std::array uniforms = { - vk::write_buffer_descriptor{ - .dst_binding = 0, - .uniforms = uniforms0, - } - }; - - // Loading a texture -- for testing - vk::texture_info config_texture = { - .phsyical_memory_properties = physical_device.memory_properties(), - .filepath = std::filesystem::path("asset_samples/viking_room.png") - }; - vk::texture texture1(logical_device, config_texture); - - std::println("texture1.valid = {}", texture1.loaded()); - - // Moving update call here because now we add textures to set0 - std::array samplers = { vk::write_image{ - .sampler = texture1.image().sampler(), - .view = texture1.image().image_view(), - .layout = vk::image_layout::shader_read_only_optimal, - } }; - std::array sample_images = { - vk::write_image_descriptor{ - .dst_binding = 1, - .sample_images = samplers, - } - }; - set0_resource.update(uniforms, sample_images); - - // ---------------------------------------- - // Creating Skybox Resources - // 1. Creating Skybox Camera Uniform - // 2. Loading Skybox Shader Resources - // 3. Loading Skybox Descriptor for GPU Resource Lookup - // 4. Create Graphics Pipeline with those three information up front - // ---------------------------------------- - - // 1. loading uniforms for skybox - vk::uniform_params skybox_ubo_info = { - .phsyical_memory_properties = physical_device.memory_properties(), - .size_bytes = sizeof(skybox_camera_data) - }; - vk::uniform_buffer skybox_ubo = - vk::uniform_buffer(logical_device, skybox_ubo_info); - - std::println("skybox_ubo.alive() = {}", skybox_ubo.alive()); - - // loading in skybox shaders, vertex attributes - std::array skybox_shader_sources = { - vk::shader_source{ .filename = "shader_samples/sample6/test.vert.spv", - .stage = vk::shader_stage::vertex }, - vk::shader_source{ .filename = "shader_samples/sample6/test.frag.spv", - .stage = vk::shader_stage::fragment }, - }; - - // Setting up vertex attributes in the test shaders - // To render triangle, we do not need to set any vertex attributes - vk::shader_resource_info skybox_shader_info = { - .sources = skybox_shader_sources, - }; - vk::shader_resource skybox_resource(logical_device, skybox_shader_info); - - // for skybox no vertex attributes needed - // geometry_resource.vertex_attributes(skybox_vertex_attributes); - - // Creating skybox descriptors - std::vector skybox_descriptor_entries = { - vk::descriptor_entry{ - // specifies "layout (set = 0, binding = 0) uniform CameraData" in skybox.vert shader - .type = vk::buffer::uniform, - .binding_point = { - .binding = 0, - .stage = vk::shader_stage::vertex, - }, - .descriptor_count = 1, - }, - vk::descriptor_entry{ - // specifies "layout (set = 0, binding = 0) uniform CameraData" in skybox.vert shader - .type = vk::buffer::combined_image_sampler, - .binding_point = { - .binding = 1, - .stage = vk::shader_stage::fragment, - }, - .descriptor_count = 1, - }, - }; - - // in skybox shader, this descriptor set is for set 0 in the skybox shader - vk::descriptor_layout skybox_layout = { - .slot = 0, // indicate that this is descriptor set 1 - .max_sets = image_count, // max of descriptor sets able to allocate - .entries = skybox_descriptor_entries, // specifies pool sizes and - // descriptor layout - }; - - // descriptor for skybox-specific resources on the GPU - vk::descriptor_resource skybox_descriptor(logical_device, skybox_layout); - - // Pass the layouts to the skybox graphics pipeline - std::array skybox_layouts = { - skybox_descriptor.layout() - }; - - // Creating skybox graphics pipeline - vk::pipeline_settings skybox_pipeline_configuration = { - .renderpass = main_renderpass, - .shader_modules = geometry_resource.handles(), - .vertex_attributes = geometry_resource.vertex_attributes(), - .vertex_bind_attributes = geometry_resource.vertex_bind_attributes(), - .descriptor_layouts = skybox_layouts - }; - - // skybox renderpass and graphics pipeline specification - - // separate render operation for the skybox - vk::renderpass skybox_renderpass; - - // separate graphics pipeline for loading the skybox shaders - vk::pipeline skybox_graphics_pipeline(logical_device, - skybox_pipeline_configuration); - - // Loading Skybox - - std::array faces = { - "asset_samples/skybox/front.jpg", "asset_samples/skybox/back.jpg", - "asset_samples/skybox/top.jpg", "asset_samples/skybox/bottom.jpg", - "asset_samples/skybox/right.jpg", "asset_samples/skybox/left.jpg" - }; - - // std::array skybox_textures; - // for(size_t i = 0; i < faces.size(); i++) { - // vk::texture_info skybox_texture_info = { - // .physical = physical_device, - // .filepath = std::filesystem::path(faces[i]), - // }; - // skybox_textures[i] = vk::texture(logical_device, - // skybox_texture_info); - - // if(skybox_textures[i].loaded()) { - // std::println("Skybox Texture[{}] {} loaded!", i, faces[i]); - // } - // else { - // std::println("Skybox Texture {} not loaded!!!", faces[i]); - // } - // } - - // vk::skybox_texture_info skybox_properties = { - // .physical_handle = physical_device, - // .faces = faces, - // }; - // vk::skybox_texture skybox_textures(logical_device, skybox_properties); - - // vk::buffer_handle test_buffer; - // write_array(logical_device, test_buffer); + float field_of_view = 45.f; + glm::vec3 position = { 3.5f, 4.90f, 36.40f }; + glm::vec3 scale{ 1.f }; + glm::vec2 plane = { 0.1f, 5000.f }; while (!glfwWindowShouldClose(window)) { glfwPollEvents(); @@ -806,22 +319,16 @@ main() { // renderpass begin/end must be within a recording command buffer vk::renderpass_begin_params begin_renderpass = { - .current_command = current, .extent = swapchain_extent, .current_framebuffer = swapchain_framebuffers[current_frame], .color = color, .subpass = vk::subpass_contents::inline_bit }; - main_renderpass.begin(begin_renderpass); + main_renderpass.begin(current, begin_renderpass); // Binding a graphics pipeline -- before drawing stuff // Inside of this graphics pipeline bind, is where you want to do the // drawing stuff to - main_graphics_pipeline.bind(current); - - // Must be binded before descriptor resource gets binded - test_model.bind(current); - static auto start_time = std::chrono::high_resolution_clock::now(); auto current_time = std::chrono::high_resolution_clock::now(); @@ -829,43 +336,39 @@ main() { current_time - start_time) .count(); - // We set the uniforms and then we offload that to the GPU - global_uniform ubo = { - .model = glm::rotate(glm::mat4(1.0f), - time * glm::radians(90.0f), - glm::vec3(0.0f, 0.0f, 1.0f)), - .view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), - glm::vec3(0.0f, 0.0f, 0.0f), - glm::vec3(0.0f, 0.0f, 1.0f)), - .proj = glm::perspective(glm::radians(45.0f), - (float)swapchain_extent.width / - (float)swapchain_extent.height, - 0.1f, - 10.0f) - }; + if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) { + position.z += 1.f; + } + if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) { + position.x += 1.f; + } + if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) { + position.z -= 1.f; + } + if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) { + position.x -= 1.f; + } + + global_uniform ubo = {}; + ubo.proj = glm::mat4(1.f); + ubo.proj = glm::perspective( + glm::radians(field_of_view), + static_cast(swapchain_extent.width / swapchain_extent.height), + plane.x, + plane.y); ubo.proj[1][1] *= -1; - test_ubo.update(&ubo); - - // Last thing that we set is going to be the skybox - // This is setting the skybox uniform within the skybox.vert shader - skybox_camera_data skybox_uniform_data = { - .forward = { 1.f, 0.f, 0.f, 0.f }, - .right = { 0.f, -1.f, 0.f, 0.f }, - .up = { 0.f, 0.f, 1.f, 0.f }, - }; - skybox_ubo.update(&skybox_uniform_data); - // Before we can send stuff to the GPU, since we already updated the - // descriptor set 0 beforehand, we must bind that descriptor resource - // before making any of the draw calls Something to note: You cannot - // update descriptor sets in the process of a current-recording command - // buffers or else that becomes undefined behavior - set0_resource.bind(current, main_graphics_pipeline.layout()); + ubo.view = glm::mat4(1.f); + ubo.view = glm::translate(ubo.view, position); + ubo.view = glm::inverse(ubo.view); + + skybox_uniform sky_ubo = { + .proj_view = ubo.proj * glm::mat4(glm::mat3(ubo.view)), + }; + skybox.update_uniform(sky_ubo); - // Drawing-call to render actual triangle to the screen - // vkCmdDrawIndexed(current, static_cast(indices.size()), 1, - // 0, 0, 0); - test_model.draw(current); + skybox.bind(current); + skybox.draw(current); main_renderpass.end(current); current.end(); @@ -876,54 +379,32 @@ main() { presentation_queue.present_frame(current_frame); } - // TODO: Make the cleanup much saner. For now we are cleaning it up like - // Potentially bring back submit_resource_free([this](){ .. free stuff .. - // }); (???) // this to ensure they are cleaned up in the proper order logical_device.wait(); - main_swapchain.destroy(); - - // for(auto& skybox_texture : skybox_textures) { - // if(skybox_texture.loaded()) { - // skybox_texture.destroy(); - // } - // } - - skybox_ubo.destroy(); - skybox_descriptor.destroy(); - skybox_resource.destroy(); - skybox_graphics_pipeline.destroy(); - // skybox_textures.destroy(); - - texture1.destroy(); - set0_resource.destroy(); - test_ubo.destroy(); - test_model.destroy(); + main_swapchain.destruct(); for (auto& command : swapchain_command_buffers) { - command.destroy(); + command.destruct(); } for (auto& fb : swapchain_framebuffers) { - fb.destroy(); + fb.destruct(); } for (auto& image : swapchain_images) { - image.destroy(); + image.destruct(); } for (auto& depth_img : swapchain_depth_images) { - depth_img.destroy(); + depth_img.destruct(); } - main_graphics_pipeline.destroy(); - geometry_resource.destroy(); - main_renderpass.destroy(); - presentation_queue.destroy(); + skybox.destruct(); + main_renderpass.destruct(); + presentation_queue.destruct(); - logical_device.destroy(); - window_surface.destroy(); + logical_device.destruct(); + window_surface.destruct(); glfwDestroyWindow(window); - api_instance.destroy(); return 0; } \ No newline at end of file diff --git a/demos/13-skybox/conanfile.py b/demos/13-skybox/conanfile.py index 708fb66..eb977f5 100644 --- a/demos/13-skybox/conanfile.py +++ b/demos/13-skybox/conanfile.py @@ -22,7 +22,7 @@ def requirements(self): self.requires("glm/1.0.1") self.requires("stb/cci.20230920") self.requires("tinyobjloader/2.0.0-rc10") - self.requires("vulkan-cpp/5.0") + self.requires("vulkan-cpp/6.0") def build(self): cmake = CMake(self) diff --git a/demos/13-skybox/environment_map.cppm b/demos/13-skybox/environment_map.cppm new file mode 100644 index 0000000..ada7444 --- /dev/null +++ b/demos/13-skybox/environment_map.cppm @@ -0,0 +1,593 @@ +module; + +#include +#include +#include +#include +#include +#include +#include + +#define GLFW_INCLUDE_VULKAN +#if _WIN32 +#define VK_USE_PLATFORM_WIN32_KHR +#include +#define GLFW_EXPOSE_NATIVE_WIN32 +#include +#include +#else +#include +#include +#endif +#include + +#define GLM_FORCE_RADIANS +#include +#include +#define GLM_ENABLE_EXPERIMENTAL +#include +#include +#include + +export module environment_map; +import vk; + +export struct skybox_uniform { + glm::mat4 proj_view; +}; + +export class environment_map { +public: + environment_map() = default; + + environment_map(const VkDevice& p_device, + const std::filesystem::path& p_filename, + vk::physical_device& p_physical_device, + VkRenderPass p_renderpass) + : m_device(p_device) + , m_physical_device(&p_physical_device) { + create_hdr_skybox(p_filename); + + create_skybox_pipeline(p_renderpass); + } + + // ~environment_map() { + // destruct(); + // } + + void create_hdr_skybox(const std::filesystem::path& p_filename) { + + stbi_set_flip_vertically_on_load(true); + int w, h, channels; + float* pixels = stbi_loadf( + p_filename.string().c_str(), &w, &h, &channels, STBI_rgb_alpha); + + if (!pixels) { + throw std::runtime_error("Failed to load HDR image at: " + + p_filename.string()); + } + + const uint32_t width = static_cast(w); + const uint32_t height = static_cast(h); + + VkFormat texture_format = VK_FORMAT_R32G32B32A32_SFLOAT; + const uint64_t bytes_per_pixel_channel = 16; // float are 4 bytes + const uint64_t total_size_bytes = + static_cast(width * height * bytes_per_pixel_channel); + const uint64_t image_size = total_size_bytes; + + // Creating staging buffer + vk::buffer_parameters staging_buffer_params = { + .memory_mask = m_physical_device->memory_properties( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit), + .property_flags = static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit), + .usage = vk::buffer_usage::transfer_src_bit, + }; + + vk::buffer staging_buffer = vk::buffer( + m_device, static_cast(image_size), staging_buffer_params); + + // Creating image handle to storing the HDR + vk::image_params skybox_image_params = { + .extent = { .width = width, .height = height, }, + .format = texture_format, + .property = vk::memory_property::device_local_bit, + .memory_mask = m_physical_device->memory_properties(vk::memory_property::device_local_bit), + .aspect = vk::image_aspect_flags::color_bit, + .usage = vk::image_usage::transfer_dst_bit | vk::image_usage::sampled_bit, + .mip_levels = 1, + .layer_count = 1, + }; + m_skybox_image = vk::sample_image(m_device, skybox_image_params); + + // Transferring data from the CPU + // void* data = nullptr; + // vkMapMemory(m_device, staging_memory, 0, total_size_bytes, 0, + // &data); std::memcpy(data, pixels, + // static_cast(total_size_bytes)); vkUnmapMemory(m_device, + // staging_memory); + std::span pixels_data( + reinterpret_cast(pixels), image_size); + staging_buffer.transfer(pixels_data); + // staging_buffer.write(pixels_data); + + // Free CPU pixels immediately after staging copy + stbi_image_free(pixels); + + // 6. Record and Execute Upload + vk::command_params upload_params = { + .levels = vk::command_levels::primary, + .queue_index = 0, // Graphics Queue + .flags = vk::command_pool_flags::reset, + }; + vk::command_buffer upload_cmd(m_device, upload_params); + + upload_cmd.begin(vk::command_usage::one_time_submit); + + // Begin Memory Barrier: Undefined to TRANSFER_DST + m_skybox_image.memory_barrier(upload_cmd, + texture_format, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + std::array region_copies = { + vk::buffer_image_copy{ + .image_offset = { .width = 0, .height = 0, .depth = 0, }, + .image_extent = skybox_image_params.extent, + }, + }; + staging_buffer.copy_to_image(upload_cmd, m_skybox_image, region_copies); + + // Begin Memory Barrier: TRANSFER_DST to SHADER_READ_ONLY + m_skybox_image.memory_barrier(upload_cmd, + texture_format, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + upload_cmd.end(); + + VkQueue graphics_queue; + vkGetDeviceQueue(m_device, 0, 0, &graphics_queue); + + VkCommandBuffer raw_cmd = upload_cmd; + VkSubmitInfo submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .commandBufferCount = 1, + .pCommandBuffers = &raw_cmd, + }; + + vkQueueSubmit(graphics_queue, 1, &submit_info, nullptr); + vkQueueWaitIdle(graphics_queue); + + upload_cmd.destruct(); + staging_buffer.destruct(); + stbi_set_flip_vertically_on_load(false); + } + + void create_buffers() { + + std::vector vertices = { + // Front Face + vk::vertex_input{ { -1.0f, 1.0f, -1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { -1.0f, -1.0f, -1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { 1.0f, -1.0f, -1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { 1.0f, -1.0f, -1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { 1.0f, 1.0f, -1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { -1.0f, 1.0f, -1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + + // Left Face + vk::vertex_input{ { -1.0f, -1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { -1.0f, -1.0f, -1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { -1.0f, 1.0f, -1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { -1.0f, 1.0f, -1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { -1.0f, 1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { -1.0f, -1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + + // Right Face + vk::vertex_input{ { 1.0f, -1.0f, -1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { 1.0f, -1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { 1.0f, 1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { 1.0f, 1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { 1.0f, 1.0f, -1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { 1.0f, -1.0f, -1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + + // Back Face + vk::vertex_input{ { -1.0f, -1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { -1.0f, 1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { 1.0f, 1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { 1.0f, 1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { 1.0f, -1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { -1.0f, -1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + + // Top Face + vk::vertex_input{ { -1.0f, 1.0f, -1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { 1.0f, 1.0f, -1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { 1.0f, 1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { 1.0f, 1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { -1.0f, 1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { -1.0f, 1.0f, -1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + + // Bottom Face + vk::vertex_input{ { -1.0f, -1.0f, -1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { -1.0f, -1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { 1.0f, -1.0f, -1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { 1.0f, -1.0f, -1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { -1.0f, -1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }, + vk::vertex_input{ { 1.0f, -1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } } + }; + + vk::buffer_parameters vertex_params = { + .memory_mask = m_physical_device->memory_properties( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit), + .property_flags = vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit, + .usage = vk::buffer_usage::transfer_dst_bit | + vk::buffer_usage::vertex_buffer_bit, + }; + m_skybox_vbo_size = vertices.size(); + m_skybox_vbo = vk::vertex_buffer(m_device, vertices, vertex_params); + } + + void create_skybox_pipeline(const VkRenderPass& p_renderpass) { + create_buffers(); + std::array attribute_entries = { + vk::vertex_attribute_entry{ + .location = 0, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, position), + }, + vk::vertex_attribute_entry{ + .location = 1, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, color), + }, + vk::vertex_attribute_entry{ + .location = 2, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, normals), + }, + vk::vertex_attribute_entry{ + .location = 3, + .format = vk::format::rg32_sfloat, + .stride = offsetof(vk::vertex_input, uv), + } + }; + std::array attribute = { + vk::vertex_attribute{ + // layout (set = 0, binding = 0) + .binding = 0, + .entries = attribute_entries, + .stride = sizeof(vk::vertex_input), + .input_rate = vk::input_rate::vertex, + }, + }; + + const std::array sources = { + vk::shader_source{ + .filename = "shader_samples/sample7-skybox/skybox.vert.spv", + .stage = vk::shader_stage::vertex, + }, + vk::shader_source{ + .filename = "shader_samples/sample7-skybox/skybox.frag.spv", + .stage = vk::shader_stage::fragment, + }, + }; + + vk::shader_resource_info shader_info = { + .sources = sources, + }; + m_skybox_shaders = vk::shader_resource(m_device, shader_info); + m_skybox_shaders.vertex_attributes(attribute); + + // set=0 binding=0 UBO: mat4 VP + // vk::uniform_params ubo_params = { + // .phsyical_memory_properties = p_memory_properties, + // .debug_name = "skybox_ubo", + // .vkSetDebugUtilsObjectNameEXT = nullptr, + // }; + // const uint32_t property = + // static_cast(vk::memory_property::host_visible_bit) | + // static_cast(vk::memory_property::host_cached_bit); + vk::buffer_parameters uniform_params = { + // .memory_mask = p_memory_mask, + // .memory_mask = + // m_physical_device->memory_properties(static_cast(property)), + .memory_mask = m_physical_device->memory_properties( + vk::memory_property::host_cached_bit), + .usage = vk::buffer_usage::uniform_buffer_bit, + }; + m_skybox_ubo = + vk::uniform_buffer(m_device, sizeof(skybox_uniform), uniform_params); + // vk::uniform_buffer(m_device, sizeof(skybox_uniform), ubo_params); + + skybox_uniform identity = { .proj_view = glm::mat4(1.0f) }; + identity.proj_view[1][1] *= -1; + std::span bytes(reinterpret_cast(&identity), + 1); + + // set=0 bindings: + // - binding 0: UBO (vertex) + // - binding 1: samplerCube (fragment) + std::array entries = { + vk::descriptor_entry{ + .type = vk::descriptor_type::uniform, + .binding_point = + vk::descriptor_binding_point{ + .binding = 0, .stage = vk::shader_stage::vertex }, + .descriptor_count = 1, + }, + vk::descriptor_entry{ + .type = vk::descriptor_type::combined_image_sampler, + .binding_point = + vk::descriptor_binding_point{ + .binding = 1, .stage = vk::shader_stage::fragment }, + .descriptor_count = 1, + }, + }; + + vk::descriptor_layout desc_layout = { + .slot = 0, + .max_sets = 1, + .entries = entries, + }; + m_skybox_descriptors = vk::descriptor_resource(m_device, desc_layout); + + const std::array ubo_writes = { + vk::write_buffer{ .buffer = static_cast(m_skybox_ubo), + .offset = 0, + .range = + static_cast(sizeof(skybox_uniform)) }, + }; + const vk::write_buffer_descriptor ubo_write_desc = { + .dst_binding = 0, + .uniforms = ubo_writes, + }; + + const std::array image_writes = { + vk::write_image{ + .sampler = m_skybox_image.sampler(), + .view = m_skybox_image.image_view(), + .layout = vk::image_layout::shader_read_only_optimal, + }, + }; + const vk::write_image_descriptor image_write_desc = { + .dst_binding = 1, + .sample_images = image_writes, + }; + + m_skybox_descriptors.update(std::span(&ubo_write_desc, 1), + std::span(&image_write_desc, 1)); + + const std::array layouts = { + m_skybox_descriptors.layout(), + }; + + const std::array + blend_attachments = { + vk::color_blend_attachment_state{ .blend_enabled = false }, + }; + vk::color_blend_state blend_state = { + .logic_op_enable = false, + .logical_op = vk::logical_op::copy, + .attachments = blend_attachments, + .blend_constants = {}, + }; + + std::array dyn = { + vk::dynamic_state::viewport, + vk::dynamic_state::scissor, + }; + + // pipeline expects a non-const span + std::array pipeline_layouts = layouts; + + vk::pipeline_params pipe_info = { + .renderpass = p_renderpass, + .shader_modules = m_skybox_shaders.handles(), + .vertex_attributes = + m_skybox_shaders.vertex_attributes(), // no vertex input + .vertex_bind_attributes = + m_skybox_shaders.vertex_bind_attributes(), // no vertex input + .descriptor_layouts = pipeline_layouts, + .input_assembly = + vk::input_assembly_state{ + .topology = vk::primitive_topology::triangle_list, + .primitive_restart_enable = false, + }, + .viewport = + vk::viewport_state{ .viewport_count = 1, .scissor_count = 1 }, + .rasterization = + vk::rasterization_state{ + .polygon_mode = vk::polygon_mode::fill, + .cull_mode = vk::cull_mode::front_bit, + // .cull_mode = vk::cull_mode::none, + // .front_face = vk::front_face::counter_clockwise, + .front_face = vk::front_face::clockwise, + .line_width = 1.f, + }, + .multisample = vk::multisample_state{}, + .color_blend = blend_state, + .depth_stencil_enabled = true, + .depth_stencil = + vk::depth_stencil_state{ + .depth_test_enable = true, + .depth_write_enable = false, + .depth_compare_op = vk::compare_op::less_or_equal, + .depth_bounds_test_enable = false, + .stencil_test_enable = false, + }, + .dynamic_states = dyn, + }; + + m_skybox_pipeline = + std::make_optional(m_device, pipe_info); + } + + void update_uniform(const skybox_uniform& p_ubo) { + m_skybox_ubo.transfer(std::span(&p_ubo, 1)); + } + + void bind(vk::command_buffer p_current) { + m_skybox_pipeline->bind(p_current); + std::array descriptors = { m_skybox_descriptors }; + p_current.bind_descriptors(m_skybox_pipeline->layout(), + VK_PIPELINE_BIND_POINT_GRAPHICS, + descriptors); + // m_skybox_vbo.bind(p_current); + + std::array skybox_buffers = { m_skybox_vbo }; + uint64_t offset = 0; + p_current.bind_vertex_buffers(skybox_buffers, + std::span(&offset, 1)); + } + + void draw(const VkCommandBuffer& p_current) { + // bind(p_current); + // vkCmdDraw(p_current, m_skybox_vbo.size(), 1, 0, 0); + vkCmdDraw(p_current, m_skybox_vbo_size, 1, 0, 0); + // vkCmdDrawIndexed(p_current, 36, 1, 0, 0, 0); + } + + //! @brief Retreving the sample image of the environment map. + [[nodiscard]] vk::sample_image image() const { return m_skybox_image; } + + void destruct() { + + m_skybox_image.destruct(); + if (m_skybox_pipeline->alive()) { + m_skybox_pipeline->destruct(); + } + m_skybox_descriptors.destruct(); + m_skybox_ubo.destruct(); + m_skybox_shaders.destruct(); + m_skybox_vbo.destruct(); + } + + //! TODO: Logic for converting the HDR image handles to a skybox + //! samplerCube + void process_to_cubemap() {} + +private: + VkDevice m_device = nullptr; + vk::physical_device* m_physical_device = nullptr; + vk::sample_image m_skybox_image; + + vk::shader_resource m_skybox_shaders{}; + vk::uniform_buffer m_skybox_ubo{}; + vk::descriptor_resource m_skybox_descriptors{}; + std::optional m_skybox_pipeline{}; + vk::vertex_buffer m_skybox_vbo; + uint64_t m_skybox_vbo_size = 0; +}; \ No newline at end of file diff --git a/demos/14-imgui/CMakeLists.txt b/demos/14-imgui/CMakeLists.txt new file mode 100644 index 0000000..e89a9ad --- /dev/null +++ b/demos/14-imgui/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.27) +project(model-loading CXX) + +build_application( + SOURCES + application.cpp + + PACKAGES + vulkan-cpp + Vulkan + glfw3 + glm + stb + tinyobjloader + + LINK_PACKAGES + stb::stb + vulkan-cpp + tinyobjloader + Vulkan::Vulkan +) \ No newline at end of file diff --git a/demos/14-imgui/application.cpp b/demos/14-imgui/application.cpp new file mode 100644 index 0000000..e447748 --- /dev/null +++ b/demos/14-imgui/application.cpp @@ -0,0 +1,850 @@ +#define GLFW_INCLUDE_VULKAN +#if _WIN32 +#define VK_USE_PLATFORM_WIN32_KHR +#include +#define GLFW_EXPOSE_NATIVE_WIN32 +#include +#include +#else +#include +#include +#endif + +#include +#include +#include +#include +#include + +#include +#define GLM_FORCE_RADIANS +#include +#include +#define GLM_ENABLE_EXPERIMENTAL +#include + +#include + +#ifndef STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#include +#endif + +import vk; + +static VKAPI_ATTR VkBool32 VKAPI_CALL +debug_callback( + [[maybe_unused]] VkDebugUtilsMessageSeverityFlagBitsEXT p_message_severity, + [[maybe_unused]] VkDebugUtilsMessageTypeFlagsEXT p_message_type, + const VkDebugUtilsMessengerCallbackDataEXT* p_callback_data, + [[maybe_unused]] void* p_user_data) { + std::print("validation layer:\t\t{}\n\n", p_callback_data->pMessage); + return false; +} + +struct global_uniform { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +template +void +hash_combine(size_t& seed, const T& v, const Rest&... rest) { + seed ^= std::hash()(v) + 0x9e3779b9 + (seed << 6) + (seed << 2); + (hash_combine(seed, rest), ...); +} + +namespace std { + + template<> + struct hash { + size_t operator()(const vk::vertex_input& vertex) const { + size_t seed = 0; + hash_combine( + seed, vertex.position, vertex.color, vertex.normals, vertex.uv); + return seed; + } + }; +} + +// Part of this demo for loading a 3D .obj model +class obj_model { +public: + obj_model() = default; + obj_model(const std::filesystem::path& p_filename, + const VkDevice& p_device, + const vk::physical_device& p_physical) { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + + //! @note If we return the constructor then we can check if the mesh + //! loaded successfully + //! @note We also receive hints if the loading is successful! + //! @note Return default constructor automatically returns false means + //! that mesh will return the boolean as false because it wasnt + //! successful + if (!tinyobj::LoadObj(&attrib, + &shapes, + &materials, + &warn, + &err, + p_filename.string().c_str())) { + std::println("Could not load model from path {}", + p_filename.string()); + m_is_loaded = false; + return; + } + + std::vector vertices; + std::vector indices; + std::unordered_map unique_vertices{}; + + for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + vk::vertex_input vertex{}; + + // vertices.push_back(vertex); + if (!unique_vertices.contains(vertex)) { + unique_vertices[vertex] = + static_cast(vertices.size()); + vertices.push_back(vertex); + } + + if (index.vertex_index >= 0) { + vertex.position = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2] + }; + + vertex.color = { + attrib.colors[3 * index.vertex_index + 0], + attrib.colors[3 * index.vertex_index + 1], + attrib.colors[3 * index.vertex_index + 2] + }; + } + + if (index.normal_index >= 0) { + vertex.normals = { + attrib.normals[3 * index.normal_index + 0], + attrib.normals[3 * index.normal_index + 1], + attrib.normals[3 * index.normal_index + 2] + }; + } + + if (index.texcoord_index >= 0) { + vertex.uv = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] + }; + } + + if (!unique_vertices.contains(vertex)) { + unique_vertices[vertex] = + static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(unique_vertices[vertex]); + } + } + + m_has_indices = (indices.size() > 0) ? true : false; + + if (m_has_indices) { + m_indices_size = indices.size(); + } + m_indices_size = vertices.size(); + m_indices_size = indices.size(); + //! @brief Creating vertex/index buffers with host visibility flags + const auto property_flags = static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit); + + vk::buffer_parameters vertex_params = { + .memory_mask = p_physical.memory_properties(property_flags), + .property_flags = vk::memory_property::device_local_bit, + .usage = vk::buffer_usage::transfer_dst_bit | + vk::buffer_usage::vertex_buffer_bit, + }; + + vk::buffer_parameters index_params = { + .memory_mask = p_physical.memory_properties(property_flags), + .property_flags = static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit), + .usage = vk::buffer_usage::index_buffer_bit, + }; + + m_vertex_buffer = vk::vertex_buffer(p_device, vertices, vertex_params); + m_index_buffer = vk::index_buffer(p_device, indices, index_params); + m_is_loaded = true; + } + + [[nodiscard]] bool loaded() const { return m_is_loaded; } + + [[nodiscard]] VkBuffer vertex_handle() const { return m_vertex_buffer; } + + [[nodiscard]] VkBuffer index_handle() const { return m_index_buffer; } + + [[nodiscard]] bool has_indices() const { return m_has_indices; } + + [[nodiscard]] uint32_t indices_size() const { return m_indices_size; } + + void draw(const VkCommandBuffer& p_command) { + if (m_has_indices) { + vkCmdDrawIndexed(p_command, m_indices_size, 1, 0, 0, 0); + } + else { + vkCmdDraw(p_command, m_indices_size, 1, 0, 0); + } + } + + void destruct() { + m_vertex_buffer.destruct(); + m_index_buffer.destruct(); + } + +private: + bool m_is_loaded = false; + bool m_has_indices = false; + uint32_t m_indices_size = 0; + vk::vertex_buffer m_vertex_buffer{}; + vk::index_buffer m_index_buffer{}; +}; + +std::vector +get_instance_extensions() { + std::vector extension_names; + uint32_t extension_count = 0; + const char** required_extensions = + glfwGetRequiredInstanceExtensions(&extension_count); + + for (uint32_t i = 0; i < extension_count; i++) { + std::println("Required Extension = {}", required_extensions[i]); + extension_names.emplace_back(required_extensions[i]); + } + + extension_names.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + +#if defined(__APPLE__) + extension_names.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + extension_names.emplace_back( + VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); +#endif + + return extension_names; +} + +/** + * @brief STBI-specific implementation of the vk::image interface + */ +class stb_image : public vk::image { +public: + stb_image() = delete; + + stb_image(std::string_view p_path, vk::texture_params p_params) { + image_load(p_path, p_params); + } + + ~stb_image() = default; + +protected: + bool image_load(std::string_view p_path, + vk::texture_params p_params) override { + int w = 0; + int h = 0; + int channels = 0; + + stbi_uc* image_pixel_data = + stbi_load(p_path.data(), &w, &h, &channels, STBI_rgb_alpha); + + if (!image_pixel_data) { + return false; + } + + const VkFormat texture_format = + static_cast(vk::format::r8g8b8a8_unorm); + int bytes_per_pixel = vk::bytes_per_texture_format(texture_format); + + m_extent = { + .width = static_cast(w), + .height = static_cast(h), + }; + + // Retrieving total size of bytes of the dimensions of the image and + // accounting for pixels of the image + uint32_t size_bytes = + m_extent.width * m_extent.height * bytes_per_pixel; + + // Retrieving total image size to the count of the image layers + uint32_t size = size_bytes * p_params.layer_count; + + vk::image_params image_options = { + .extent = m_extent, + .format = texture_format, + .memory_mask = p_params.memory_mask, + .usage = + vk::image_usage::transfer_dst_bit | vk::image_usage::sampled_bit, + .mip_levels = p_params.mip_levels, + .layer_count = p_params.layer_count, + }; + + m_bytes.reserve(size); + std::span bytes_view = + std::span(image_pixel_data, size); + + m_bytes.assign(bytes_view.begin(), bytes_view.end()); + + stbi_image_free(image_pixel_data); + + return true; + } + + std::span image_read() const override { return m_bytes; } + + vk::image_extent image_extent() const override { return m_extent; } + +private: + vk::image_extent m_extent{}; + std::vector m_bytes{}; +}; + +int +main() { + //! @note Just added the some test code to test the conan-starter setup code + if (!glfwInit()) { + std::println("glfwInit could not be initialized!"); + return -1; + } + + if (!glfwVulkanSupported()) { + std::println("GLFW: Vulkan is not supported!"); + return -1; + } + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + int width = 800; + int height = 600; + std::string title = "Hello Window"; + GLFWwindow* window = + glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr); + + glfwMakeContextCurrent(window); + + std::array validation_layers = { + "VK_LAYER_KHRONOS_validation", + }; + + // setting up extensions + std::vector global_extensions = get_instance_extensions(); + + vk::debug_message_utility debug_callback_info = { + .severity = + vk::message::verbose | vk::message::warning | vk::message::error, + .message_type = + vk::debug::general | vk::debug::validation | vk::debug::performance, + .callback = debug_callback + }; + + vk::application_params config = { + .name = "vulkan instance", + .version = vk::api_version::vk_1_3, // specify to using vulkan 1.3 + .validations = + validation_layers, // .validation takes in a std::span + .extensions = + global_extensions // .extensions also takes in std::span + }; + + // Setting up vk instance + vk::instance api_instance(config, debug_callback_info); + + if (api_instance.alive()) { + std::println("\napi_instance alive and initiated!!!"); + } + + std::expected physical_device_expected = + api_instance.enumerate_physical_device(vk::physical_gpu::integrated); + vk::physical_device physical_device = physical_device_expected.value(); + + // selecting depth format + std::array format_support = { + vk::format::d32_sfloat, + vk::format::d32_sfloat_s8_uint, + vk::format::d24_unorm_s8_uint + }; + + // We provide a selection of format support that we want to check is + // supported on current hardware device. + VkFormat depth_format = + physical_device.request_depth_format(format_support); + + // setting up logical device + std::array priorities = { 0.f }; + +#if defined(__APPLE__) + std::array extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + "VK_KHR_portability_subset", + }; +#else + std::array extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; +#endif + + vk::device_params logical_device_params = { + .queue_priorities = priorities, + .extensions = extensions, + .queue_family_index = 0, + }; + + vk::device logical_device(physical_device, logical_device_params); + + vk::surface window_surface(api_instance, window); + + vk::surface_params surface_properties = + physical_device.request_surface(window_surface); + + vk::swapchain_params enumerate_swapchain_settings = { + .width = static_cast(width), + .height = static_cast(height), + .present_index = 0, + }; + vk::swapchain main_swapchain(logical_device, + window_surface, + enumerate_swapchain_settings, + surface_properties); + + // querying swapchain images + std::span images = main_swapchain.get_images(); + uint32_t image_count = static_cast(images.size()); + + // Creating Images + std::vector swapchain_images(image_count); + std::vector swapchain_depth_images(image_count); + + VkExtent2D swapchain_extent = surface_properties.capabilities.currentExtent; + + // Setting up the images + uint32_t layer_count = 1; + uint32_t mip_levels = 1; + for (uint32_t i = 0; i < swapchain_images.size(); i++) { + vk::image_params swapchain_image_config = { + .extent = { .width = swapchain_extent.width, + .height = swapchain_extent.height, }, + .format = surface_properties.format.format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), + .aspect = vk::image_aspect_flags::color_bit, + .usage = vk::image_usage::color_attachment_bit, + .mip_levels = 1, + .layer_count = 1, + }; + swapchain_images[i] = + vk::sample_image(logical_device, images[i], swapchain_image_config); + + // Creating Images for depth buffering + vk::image_params image_config = { + .extent = { + .width = swapchain_extent.width, + .height = swapchain_extent.height, + }, + .format = depth_format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), + .aspect = vk::image_aspect_flags::depth_bit, + .usage = vk::image_usage::depth_stencil_bit, + .mip_levels = 1, + .layer_count = 1, + }; + swapchain_depth_images[i] = + vk::sample_image(logical_device, image_config); + } + + // setting up command buffers + std::vector swapchain_command_buffers(image_count); + for (size_t i = 0; i < swapchain_command_buffers.size(); i++) { + vk::command_params settings = { + .levels = vk::command_levels::primary, + .queue_index = enumerate_swapchain_settings.present_index, + .flags = vk::command_pool_flags::reset, + }; + + swapchain_command_buffers[i] = + vk::command_buffer(logical_device, settings); + } + + // setting up attachments for the renderpass + std::array renderpass_attachments = { + // color attachment + vk::attachment{ + .format = surface_properties.format.format, + .layout = vk::image_layout::color_optimal, + .samples = vk::sample_bit::count_1, + .load = vk::attachment_load::clear, + .store = vk::attachment_store::store, + .stencil_load = vk::attachment_load::dont_care, + .stencil_store = vk::attachment_store::dont_care, + .initial_layout = vk::image_layout::undefined, + .final_layout = vk::image_layout::present_src_khr, + }, + // depth attachment + vk::attachment{ + .format = depth_format, + .layout = vk::image_layout::depth_stencil_optimal, + .samples = vk::sample_bit::count_1, + .load = vk::attachment_load::clear, + .store = vk::attachment_store::dont_care, + .stencil_load = vk::attachment_load::dont_care, + .stencil_store = vk::attachment_store::dont_care, + .initial_layout = vk::image_layout::undefined, + .final_layout = vk::image_layout::depth_stencil_read_only_optimal, + }, + }; + + vk::renderpass main_renderpass(logical_device, renderpass_attachments); + + // Setting up swapchain framebuffers + + std::vector swapchain_framebuffers(image_count); + for (uint32_t i = 0; i < swapchain_framebuffers.size(); i++) { + + // NOTE: This must match the amount of attachments the renderpass also + // has to match the image_view attachment for per-framebuffers as well + // I just set the size to whatever the renderpass attachment size are to + // ensure this is the case Since you have an image for color attachment + // and another image for the depth atttachment to specify + std::array + image_view_attachments = { swapchain_images[i].image_view(), + swapchain_depth_images[i].image_view() }; + + vk::framebuffer_params framebuffer_info = { + .renderpass = main_renderpass, + .views = image_view_attachments, + .extent = swapchain_extent + }; + swapchain_framebuffers[i] = + vk::framebuffer(logical_device, framebuffer_info); + } + + // setting up presentation queue to display commands to the screen + vk::queue_params enumerate_present_queue{ + .family = 0, + .index = 0, + }; + vk::device_present_queue presentation_queue( + logical_device, main_swapchain, enumerate_present_queue); + + // gets set with the renderpass + std::array color = { 0.f, 0.5f, 0.5f, 1.f }; + + // std::println("Start implementing graphics pipeline!!!"); + + // Now creating a vulkan graphics pipeline for the shader loading + std::array shader_sources = { + vk::shader_source{ + .filename = "shader_samples/sample5/test.vert.spv", + .stage = vk::shader_stage::vertex, + }, + vk::shader_source{ + .filename = "shader_samples/sample5/test.frag.spv", + .stage = vk::shader_stage::fragment, + }, + }; + + // Setting up vertex attributes in the test shaders + std::array attribute_entries = { + vk::vertex_attribute_entry{ + .location = 0, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, position), + }, + vk::vertex_attribute_entry{ + .location = 1, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, color), + }, + vk::vertex_attribute_entry{ + .location = 2, + .format = vk::format::rg32_sfloat, + .stride = offsetof(vk::vertex_input, uv), + }, + vk::vertex_attribute_entry{ + .location = 3, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, normals), + } + }; + + std::array attributes = { + vk::vertex_attribute{ + // layout (set = 0, binding = 0) + .binding = 0, + .entries = attribute_entries, + .stride = sizeof(vk::vertex_input), + .input_rate = vk::input_rate::vertex, + }, + }; + + // To render triangle, we do not need to set any vertex attributes + vk::shader_resource_info shader_info = { + .sources = shader_sources, + .vertex_attributes = + attributes // NOT NEEDED: Specifying vertex attributes + }; + vk::shader_resource geometry_resource(logical_device, shader_info); + geometry_resource.vertex_attributes(attributes); + + // Set 0: For Uniform BUffers (or global scene data) + std::vector entries = { + vk::descriptor_entry{ + // specifies "layout (set = 0, binding = 0) uniform GlobalUbo" + .type = vk::descriptor_type::uniform, + .binding_point = { + .binding = 0, + .stage = vk::shader_stage::vertex, + }, + .descriptor_count = 1, + }, + }; + vk::descriptor_layout set0_layout = { + .slot = 0, // indicate specific descriptor slot 0 + .max_sets = image_count, // max descriptors to allocate + .entries = entries, // descriptor layout entries description + }; + vk::descriptor_resource set0_resource(logical_device, set0_layout); + + // Set 1 = For Textures + std::vector entries_set1 = { + vk::descriptor_entry{ + // layout (set = 1, binding = 0) uniform sampler2D + .type = vk::descriptor_type::combined_image_sampler, + .binding_point = { + .binding = 0, + .stage = vk::shader_stage::fragment, + }, + .descriptor_count = 1, + } + }; + vk::descriptor_layout set1_layout = { + .slot = 1, // indicate specific descriptor slot 0 + .max_sets = image_count, // max descriptors to allocate + .entries = entries_set1, // descriptor layout entries description + }; + + vk::descriptor_resource set1_resource(logical_device, set1_layout); + + std::array layouts = { + set0_resource.layout(), + set1_resource.layout(), + }; + + std::array color_blend_attachments = { + vk::color_blend_attachment_state{}, + }; + + std::array dynamic_states = { + vk::dynamic_state::viewport, vk::dynamic_state::scissor + }; + vk::pipeline_params pipeline_configuration = { + .renderpass = main_renderpass, + .shader_modules = geometry_resource.handles(), + .vertex_attributes = geometry_resource.vertex_attributes(), + .vertex_bind_attributes = geometry_resource.vertex_bind_attributes(), + .descriptor_layouts = layouts, + .color_blend = { + .attachments = color_blend_attachments, + }, + .depth_stencil_enabled = true, + .dynamic_states = dynamic_states, + }; + vk::pipeline main_graphics_pipeline(logical_device, pipeline_configuration); + + // Loading mesh + obj_model test_model(std::filesystem::path("asset_samples/viking_room.obj"), + logical_device, + physical_device); + + // Setting up descriptor sets for handling uniforms + vk::buffer_parameters uniform_params = { + .memory_mask = + physical_device.memory_properties(static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit)), + .usage = vk::buffer_usage::uniform_buffer_bit | + vk::buffer_usage::shader_device_address_bit, + .allocate_flags = vk::memory_allocate_flags::device_address_bit_khr, + }; + vk::uniform_buffer test_ubo = vk::uniform_buffer( + logical_device, sizeof(global_uniform), uniform_params); + // std::println("uniform_buffer.alive() = {}", test_ubo.alive()); + + std::array uniforms0 = { + vk::write_buffer{ + .buffer = test_ubo, + .offset = 0, + .range = static_cast(test_ubo.size_bytes()), + }, + }; + std::array uniforms = { + vk::write_buffer_descriptor{ + .dst_binding = 0, + .uniforms = uniforms0, + }, + }; + + // Loading a texture + vk::texture_params config_texture = { + .memory_mask = physical_device.memory_properties( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit), + }; + + stb_image img = stb_image("asset_samples/viking_room.png", config_texture); + vk::texture texture1(logical_device, &img, config_texture); + + std::array samplers = { + vk::write_image{ + .sampler = texture1.image().sampler(), + .view = texture1.image().image_view(), + .layout = vk::image_layout::shader_read_only_optimal, + }, + }; + + // Specify image descriptor images/samplers to the descriptor + std::array set1_samples = { + vk::write_image_descriptor{ + .dst_binding = 0, + .sample_images = samplers, + } + }; + set0_resource.update(uniforms); + + set1_resource.update({}, set1_samples); + + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + + uint32_t current_frame = presentation_queue.acquire_next_image(); + vk::command_buffer current = swapchain_command_buffers[current_frame]; + + current.begin(vk::command_usage::simulatneous_use_bit); + + // renderpass begin/end must be within a recording command buffer + vk::renderpass_begin_params begin_renderpass = { + .extent = swapchain_extent, + .current_framebuffer = swapchain_framebuffers[current_frame], + .color = color, + .subpass = vk::subpass_contents::inline_bit + }; + main_renderpass.begin(current, begin_renderpass); + + // Binding a graphics pipeline -- before drawing stuff + // Inside of this graphics pipeline bind, is where you want to do the + // drawing stuff to + main_graphics_pipeline.bind(current); + + const VkBuffer vertex = test_model.vertex_handle(); + uint64_t offset = 0; + current.bind_vertex_buffers(std::span(&vertex, 1), + std::span(&offset, 1)); + + if (test_model.has_indices()) { + current.bind_index_buffers32(test_model.index_handle()); + } + + static auto start_time = std::chrono::high_resolution_clock::now(); + + auto current_time = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration( + current_time - start_time) + .count(); + + // We set the uniforms and then we offload that to the GPU + global_uniform ubo = { + .model = glm::rotate(glm::mat4(1.0f), + time * glm::radians(90.0f), + glm::vec3(0.0f, 0.0f, 1.0f)), + .view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), + glm::vec3(0.0f, 0.0f, 0.0f), + glm::vec3(0.0f, 0.0f, 1.0f)), + .proj = glm::perspective(glm::radians(45.0f), + (float)swapchain_extent.width / + (float)swapchain_extent.height, + 0.1f, + 10.0f) + }; + ubo.proj[1][1] *= -1; + + std::array ubo_arr = { ubo }; + test_ubo.transfer(ubo_arr); + + // Before we can send stuff to the GPU, since we already updated the + // descriptor set 0 beforehand, we must bind that descriptor resource + // before making any of the draw calls Something to note: You cannot + // update descriptor sets in the process of a current-recording command + // buffers or else that becomes undefined behavior + // set0_resource.bind(current, main_graphics_pipeline.layout()); + + std::array descriptors = { set0_resource, + set1_resource }; + + current.bind_descriptors(main_graphics_pipeline.layout(), + VK_PIPELINE_BIND_POINT_GRAPHICS, + descriptors); + + // Drawing-call to render actual triangle to the screen + // vkCmdDrawIndexed(current, static_cast(indices.size()), 1, + // 0, 0, 0); + test_model.draw(current); + + main_renderpass.end(current); + current.end(); + + // Submitting and then presenting to the screen + std::array commands = { current }; + // presentation_queue.submit_async(current); + presentation_queue.submit_async(commands); + presentation_queue.present_frame(current_frame); + } + + // this to ensure they are cleaned up in the proper order + logical_device.wait(); + main_swapchain.destruct(); + + texture1.destruct(); + set0_resource.destruct(); + set1_resource.destruct(); + test_ubo.destruct(); + test_model.destruct(); + + for (auto& command : swapchain_command_buffers) { + command.destruct(); + } + + for (auto& fb : swapchain_framebuffers) { + fb.destruct(); + } + + for (auto& image : swapchain_images) { + image.destruct(); + } + + for (auto& depth_img : swapchain_depth_images) { + depth_img.destruct(); + } + + main_graphics_pipeline.destruct(); + geometry_resource.destruct(); + main_renderpass.destruct(); + presentation_queue.destruct(); + + logical_device.destruct(); + window_surface.destruct(); + glfwDestroyWindow(window); + return 0; +} \ No newline at end of file diff --git a/demos/14-imgui/conanfile.py b/demos/14-imgui/conanfile.py new file mode 100644 index 0000000..eb977f5 --- /dev/null +++ b/demos/14-imgui/conanfile.py @@ -0,0 +1,37 @@ +from conan import ConanFile +from conan.tools.cmake import CMake, cmake_layout + +class Demo(ConanFile): + name = "game-demo" + version = "1.0" + settings = "os", "compiler", "build_type", "arch" + generators = "CMakeDeps", "CMakeToolchain" + export_source = "CMakeLists.txt", "application.cpp" + + # Putting all of your build-related dependencies here + def build_requirements(self): + self.tool_requires("cmake/[^4.0.0]") + self.tool_requires("ninja/[^1.3.0]") + self.tool_requires("engine3d-cmake-utils/4.0") + + # Putting all of your packages here + # To build engine3d/1.0 locally do the following: + # conan create . --name=engine3d --version=0.1.0 --user=local --channel=12345 + def requirements(self): + self.requires("glfw/3.4") + self.requires("glm/1.0.1") + self.requires("stb/cci.20230920") + self.requires("tinyobjloader/2.0.0-rc10") + self.requires("vulkan-cpp/6.0") + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() + + def layout(self): + cmake_layout(self) \ No newline at end of file diff --git a/demos/15-dynamic-rendering/CMakeLists.txt b/demos/15-dynamic-rendering/CMakeLists.txt new file mode 100644 index 0000000..c60be78 --- /dev/null +++ b/demos/15-dynamic-rendering/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 4.0) +project(dynamic-rendering CXX) + +build_application( + SOURCES + application.cpp + + PACKAGES + vulkan-cpp + Vulkan + glfw3 + glm + stb + tinyobjloader + + LINK_PACKAGES + vulkan-cpp + tinyobjloader + Vulkan::Vulkan +) \ No newline at end of file diff --git a/demos/15-dynamic-rendering/README.md b/demos/15-dynamic-rendering/README.md new file mode 100644 index 0000000..dca1d6e --- /dev/null +++ b/demos/15-dynamic-rendering/README.md @@ -0,0 +1,332 @@ +# Demo 15 -- Dynamic Rendering + +This demo shows how to get dynamic rendering setup. + + +## Setting the Physical Device Features + +Before using dynamic rendering, you need to set the dynamic rendering feature to be enabled. vulkan-cpp now has support for enabling this feature. + +This introduces a struct called `vk::device_features` which performs the chaining operation at compile-time to automatically assign the `.pNext` parameter when you add them into the initializer list as shown below. + +```C++ +vk::device_features device_features{ + vk::dynamic_rendering_feature{ { + .dynamicRendering = true, + } }, +}; +``` + +Then `vk::device_features` provides you a way to get access to the internal pointer to the entire chain of the pNext pointer itself through an API call `.data()`. + +When constructing a logical device, here is how you can pass in that pointer. + +```C++ + + +vk::device_params logical_params = { + // setting logical device to the enabled features + .features = device_features.data(), + .... +}; + +vk::device logical_device(physical, logical_params); +``` + +## Previously + +Originally before dynamic rendering, you would have to create a renderpass handle and framebuffers handles: + +```C++ +std::array renderpass_attachments = { + // color attachment + vk::attachment{ + .format = surface_properties.format.format, + .layout = vk::image_layout::color_optimal, + .samples = vk::sample_bit::count_1, + .load = vk::attachment_load::clear, + .store = vk::attachment_store::store, + .stencil_load = vk::attachment_load::dont_care, + .stencil_store = vk::attachment_store::dont_care, + .initial_layout = vk::image_layout::undefined, + .final_layout = vk::image_layout::present_src_khr, + }, + // depth attachment + vk::attachment{ + .format = depth_format, + .layout = vk::image_layout::depth_stencil_optimal, + .samples = vk::sample_bit::count_1, + .load = vk::attachment_load::clear, + .store = vk::attachment_store::dont_care, + .stencil_load = vk::attachment_load::dont_care, + .stencil_store = vk::attachment_store::dont_care, + .initial_layout = vk::image_layout::undefined, + .final_layout = vk::image_layout::depth_stencil_read_only_optimal, + }, +}; + +vk::renderpass main_renderpass(logical_device, renderpass_attachments); + +std::vector swapchain_framebuffers(image_count); +for (uint32_t i = 0; i < swapchain_framebuffers.size(); i++) { + std::array + image_view_attachments = { swapchain_images[i].image_view(), + swapchain_depth_images[i].image_view() }; + + vk::framebuffer_params framebuffer_info = { + .renderpass = main_renderpass, + .views = image_view_attachments, + .extent = swapchain_extent + }; + swapchain_framebuffers[i] = + vk::framebuffer(logical_device, framebuffer_info); +} +``` + + + +Then following by `vkCmdBeGinRenderPass` and `vkCmdEndRenderPass` calls to start rendering within that renderpass instance. +Which would look like this: + +```C++ +vk::renderpass_begin_params begin_renderpass = { + .extent = swapchain_extent, + .current_framebuffer = swapchain_framebuffers[current_frame], + .color = color, + .subpass = vk::subpass_contents::inline_bit +}; +main_renderpass.begin(current, begin_renderpass); + +main_renderpass.end(); +``` + +## With Dynamic Rendering + +Now with Dynamic Rendering. You no longer need to keep track and create framebuffers and renderpass handles at all. + +You can completely remove the creation of the renderpass and framebuffer handles. Replace `vk::attachment` with `vk::rendering_attachment`. + + +## Preparing Graphics Pipeline + +Before getting dynamic rendering to work, you must also set a few parameters for the graphics pipeline to set the `VkPipelineRenderingCreateInfo`. + +This rendering create info parameters are used to specify attachment information the graphics pipeline should expect to receive. + +```C++ +vk::pipeline_params pipeline_configuration = { + .use_render_pipeline = true, // Dynamic Rendering + .color_attachment_formats = std::span(&format, 1), // Dynamic Rendering + .depth_format = static_cast(depth_format), // Dynamic Rendering + .stencil_format = static_cast(depth_format), // Dynamic Rendering + .renderpass = nullptr, + .shader_modules = geometry_resource.handles(), + .vertex_attributes = geometry_resource.vertex_attributes(), + .vertex_bind_attributes = geometry_resource.vertex_bind_attributes(), + .color_blend = { + .attachments = color_blend_attachments, + }, + .depth_stencil_enabled = true, + .dynamic_states = dynamic_states, +}; +vk::pipeline main_graphics_pipeline(logical_device, pipeline_configuration); +``` + + +## Setting up Rendering Attachments +Here are what the rendering attachments look like for dynamic rendering: + +```C++ +vk::rendering_attachment color_render_attachment = { + .image_view = swapchain_images[current_frame].image_view(), + .layout = vk::image_layout::color_optimal, + .resolve_mode = vk::resolved_mode_flags::none, + .resolve_image_view = nullptr, + .resolve_image_layout = vk::image_layout::undefined, + .load = vk::attachment_load::clear, + .store = vk::attachment_store::store, + .clear_values = clear_color +}; + +vk::rendering_attachment depth_stencil_attachment = { + .image_view = swapchain_depth_images[current_frame].image_view(), + .layout = vk::image_layout::depth_stencil_optimal, + .resolve_mode = vk::resolved_mode_flags::none, + .resolve_image_view = nullptr, + .resolve_image_layout = vk::image_layout::undefined, + .load = vk::attachment_load::clear, + .store = vk::attachment_store::store, + .depth_values = depth_value +}; +``` + + +## Calling vkCmdBeginRendering/vkCmdEndRendering +Then instead of calling `vkCmdBeginRenderPass` you call `vkCmdBeginRendering` instead. Which will look quite similar to calling the BeginRenderPass API. + + + +```C++ +vk::rendering_begin_parameters begin_params = { + .render_area = { { 0, 0 }, + { + swapchain_extent.width, + swapchain_extent.height, + }, }, + .layer_count = 1, + .color_attachments = std::span( + &color_render_attachment, 1), + .depth_attachment = depth_stencil_attachment, + .stencil_attachment = depth_stencil_attachment, +}; + +vk::viewport_params viewport = { + .x = 0.0f, + .y = 0.0f, + .width = static_cast(swapchain_extent.width), + .height = static_cast(swapchain_extent.height), + .min_depth = 0.0f, + .max_depth = 1.0f, +}; +current.set_viewport( + 0, 1, std::span(&viewport, 1)); + +vk::scissor_params scissor = { + .offset = { 0, 0 }, + .extent = swapchain_extent, +}; + +current.set_scissor( + 0, 1, std::span(&scissor, 1)); + +current.begin_rendering(begin_params); + +// always follow up with `.end_rendering()` +current.end_rendering(); +``` + + +## Image Layout Transitions + +One of the things renderpasses do for you is automatically handling layout transitions. With dynamic rendering this is not automatically handled for you. + +So, you have to perform image memory barriers before you perform the `.begin_rendering` call. + +Before the rendering attachments, do the following: + +```C++ +swapchain_images[current_frame].memory_barrier( + current, + surface_properties.format.format, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + +// Because dynamic rendering does not automatically handle layout transitions +// These memory barriers set the color and depth images for the output +swapchain_depth_images[current_frame].memory_barrier( + current, + depth_format, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT); +``` + +Then after `.end_rendering` follow-up with this memory barrier call. + +```C++ +swapchain_images[current_frame].memory_barrier( + current, + surface_properties.format.format, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); +``` + +## Final Result + +This is what it should look like altogether. + +```C++ +swapchain_images[current_frame].memory_barrier( + current, + surface_properties.format.format, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + +// Because dynamic rendering does not automatically handle layout transitions +// These memory barriers set the color and depth images for the output +swapchain_depth_images[current_frame].memory_barrier( + current, + depth_format, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT); + +vk::rendering_attachment color_render_attachment = { + .image_view = swapchain_images[current_frame].image_view(), + .layout = vk::image_layout::color_optimal, + .resolve_mode = vk::resolved_mode_flags::none, + .resolve_image_view = nullptr, + .resolve_image_layout = vk::image_layout::undefined, + .load = vk::attachment_load::clear, + .store = vk::attachment_store::store, + .clear_values = clear_color +}; + +vk::rendering_attachment depth_stencil_attachment = { + .image_view = swapchain_depth_images[current_frame].image_view(), + .layout = vk::image_layout::depth_stencil_optimal, + .resolve_mode = vk::resolved_mode_flags::none, + .resolve_image_view = nullptr, + .resolve_image_layout = vk::image_layout::undefined, + .load = vk::attachment_load::clear, + .store = vk::attachment_store::store, + .depth_values = depth_value +}; + +vk::rendering_begin_parameters begin_params = { + .render_area = { { 0, 0 }, + { + swapchain_extent.width, + swapchain_extent.height, + }, }, + .layer_count = 1, + .color_attachments = std::span( + &color_render_attachment, 1), + .depth_attachment = depth_stencil_attachment, + .stencil_attachment = depth_stencil_attachment, +}; + +vk::viewport_params viewport = { + .x = 0.0f, + .y = 0.0f, + .width = static_cast(swapchain_extent.width), + .height = static_cast(swapchain_extent.height), + .min_depth = 0.0f, + .max_depth = 1.0f, +}; +current.set_viewport( + 0, 1, std::span(&viewport, 1)); + +vk::scissor_params scissor = { + .offset = { 0, 0 }, + .extent = swapchain_extent, +}; + +current.set_scissor( + 0, 1, std::span(&scissor, 1)); + +current.begin_rendering(begin_params); + +main_graphics_pipeline.bind(current); +vkCmdDraw(current, 3, 1, 0, 0); + +current.end_rendering(); + +swapchain_images[current_frame].memory_barrier( + current, + surface_properties.format.format, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); +``` + \ No newline at end of file diff --git a/demos/15-dynamic-rendering/application.cpp b/demos/15-dynamic-rendering/application.cpp new file mode 100644 index 0000000..b05ec4a --- /dev/null +++ b/demos/15-dynamic-rendering/application.cpp @@ -0,0 +1,604 @@ +#define GLFW_INCLUDE_VULKAN +#if _WIN32 +#define VK_USE_PLATFORM_WIN32_KHR +#include +#define GLFW_EXPOSE_NATIVE_WIN32 +#include +#include +#else +#include +#include +#endif + +#include +#include +#include +#include +#include + +#include +#define GLM_FORCE_RADIANS +#include +#include +#define GLM_ENABLE_EXPERIMENTAL +#include + +#include + +import vk; + +static VKAPI_ATTR VkBool32 VKAPI_CALL +debug_callback( + [[maybe_unused]] VkDebugUtilsMessageSeverityFlagBitsEXT p_message_severity, + [[maybe_unused]] VkDebugUtilsMessageTypeFlagsEXT p_message_type, + const VkDebugUtilsMessengerCallbackDataEXT* p_callback_data, + [[maybe_unused]] void* p_user_data) { + std::print("validation layer:\t\t{}\n\n", p_callback_data->pMessage); + return false; +} + +struct global_uniform { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +template +void +hash_combine(size_t& seed, const T& v, const Rest&... rest) { + seed ^= std::hash()(v) + 0x9e3779b9 + (seed << 6) + (seed << 2); + (hash_combine(seed, rest), ...); +} + +namespace std { + + template<> + struct hash { + size_t operator()(const vk::vertex_input& vertex) const { + size_t seed = 0; + hash_combine( + seed, vertex.position, vertex.color, vertex.normals, vertex.uv); + return seed; + } + }; +} + +// Part of this demo for loading a 3D .obj model +class obj_model { +public: + obj_model() = default; + obj_model(const std::filesystem::path& p_filename, + const VkDevice& p_device, + const vk::physical_device& p_physical) { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + + //! @note If we return the constructor then we can check if the mesh + //! loaded successfully + //! @note We also receive hints if the loading is successful! + //! @note Return default constructor automatically returns false means + //! that mesh will return the boolean as false because it wasnt + //! successful + if (!tinyobj::LoadObj(&attrib, + &shapes, + &materials, + &warn, + &err, + p_filename.string().c_str())) { + std::println("Could not load model from path {}", + p_filename.string()); + m_is_loaded = false; + return; + } + + std::vector vertices; + std::vector indices; + std::unordered_map unique_vertices{}; + + for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + vk::vertex_input vertex{}; + + // vertices.push_back(vertex); + if (!unique_vertices.contains(vertex)) { + unique_vertices[vertex] = + static_cast(vertices.size()); + vertices.push_back(vertex); + } + + if (index.vertex_index >= 0) { + vertex.position = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2] + }; + + vertex.color = { + attrib.colors[3 * index.vertex_index + 0], + attrib.colors[3 * index.vertex_index + 1], + attrib.colors[3 * index.vertex_index + 2] + }; + } + + if (index.normal_index >= 0) { + vertex.normals = { + attrib.normals[3 * index.normal_index + 0], + attrib.normals[3 * index.normal_index + 1], + attrib.normals[3 * index.normal_index + 2] + }; + } + + if (index.texcoord_index >= 0) { + vertex.uv = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] + }; + } + + if (!unique_vertices.contains(vertex)) { + unique_vertices[vertex] = + static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(unique_vertices[vertex]); + } + } + + m_has_indices = (indices.size() > 0) ? true : false; + + if (m_has_indices) { + m_indices_size = indices.size(); + } + m_indices_size = vertices.size(); + m_indices_size = indices.size(); + + //! @brief Creating vertex/index buffers with host visibility flags + const auto property_flags = static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit); + + vk::buffer_parameters vertex_params = { + .memory_mask = p_physical.memory_properties(property_flags), + .property_flags = vk::memory_property::device_local_bit, + .usage = vk::buffer_usage::transfer_dst_bit | + vk::buffer_usage::vertex_buffer_bit, + }; + + vk::buffer_parameters index_params = { + .memory_mask = p_physical.memory_properties(property_flags), + .property_flags = static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit), + .usage = vk::buffer_usage::index_buffer_bit, + }; + + m_vertex_buffer = vk::vertex_buffer(p_device, vertices, vertex_params); + m_index_buffer = vk::index_buffer(p_device, indices, index_params); + m_is_loaded = true; + } + + [[nodiscard]] bool loaded() const { return m_is_loaded; } + + [[nodiscard]] VkBuffer vertex_handle() const { return m_vertex_buffer; } + + [[nodiscard]] VkBuffer index_handle() const { return m_index_buffer; } + + [[nodiscard]] bool has_indices() const { return m_has_indices; } + + [[nodiscard]] uint32_t indices_size() const { return m_indices_size; } + + void draw(const VkCommandBuffer& p_command) { + if (m_has_indices) { + vkCmdDrawIndexed(p_command, m_indices_size, 1, 0, 0, 0); + } + else { + vkCmdDraw(p_command, m_indices_size, 1, 0, 0); + } + } + + void destruct() { + m_vertex_buffer.destruct(); + m_index_buffer.destruct(); + } + +private: + bool m_is_loaded = false; + bool m_has_indices = false; + uint32_t m_indices_size = 0; + vk::vertex_buffer m_vertex_buffer{}; + vk::index_buffer m_index_buffer{}; +}; + +std::vector +get_instance_extensions() { + std::vector extension_names; + uint32_t extension_count = 0; + const char** required_extensions = + glfwGetRequiredInstanceExtensions(&extension_count); + + for (uint32_t i = 0; i < extension_count; i++) { + std::println("Required Extension = {}", required_extensions[i]); + extension_names.emplace_back(required_extensions[i]); + } + + extension_names.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + +#if defined(__APPLE__) + extension_names.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + extension_names.emplace_back( + VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); +#endif + + return extension_names; +} + +int +main() { + //! @note Just added the some test code to test the conan-starter setup code + if (!glfwInit()) { + std::print("glfwInit could not be initialized!\n"); + return -1; + } + + if (!glfwVulkanSupported()) { + std::print("GLFW: Vulkan is not supported!"); + return -1; + } + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + int width = 800; + int height = 600; + std::string title = "Hello Window"; + GLFWwindow* window = + glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr); + + glfwMakeContextCurrent(window); + + std::array validation_layers = { + "VK_LAYER_KHRONOS_validation", + }; + + // setting up extensions + std::vector global_extensions = get_instance_extensions(); + + vk::debug_message_utility debug_callback_info = { + // .severity essentially takes in vk::message::verbose, + // vk::message::warning, vk::message::error + .severity = + vk::message::verbose | vk::message::warning | vk::message::error, + // .message_type essentially takes in vk::debug. Like: + // vk::debug::general, vk::debug::validation, vk::debug::performance + .message_type = + vk::debug::general | vk::debug::validation | vk::debug::performance, + .callback = debug_callback + }; + + vk::application_params config = { + .name = "vulkan instance", + .version = vk::api_version::vk_1_3, // specify to using vulkan 1.3 + .validations = + validation_layers, // .validation takes in a std::span + .extensions = + global_extensions // .extensions also takes in std::span + }; + + // 1. Setting up vk instance + vk::instance api_instance(config, debug_callback_info); + + if (api_instance.alive()) { + std::println("\napi_instance alive and initiated!!!"); + } + + // setting up physical device + std::expected physical_device_expected = + api_instance.enumerate_physical_device(vk::physical_gpu::integrated); + vk::physical_device physical_device = physical_device_expected.value(); + + // setting up logical device + std::array priorities = { 0.f }; + +#if defined(__APPLE__) + std::array extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + "VK_KHR_portability_subset", + }; +#else + std::array extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + }; +#endif + + std::array format_support = { + vk::format::d32_sfloat, + vk::format::d32_sfloat_s8_uint, + vk::format::d24_unorm_s8_uint + }; + + // We provide a selection of format support that we want to check is + // supported on current hardware device. + VkFormat depth_format = + physical_device.request_depth_format(format_support); + + vk::device_features device_features{ + vk::dynamic_rendering_feature{ { + .dynamicRendering = true, + } }, + }; + + vk::device_params logical_device_params = { + .features = device_features.data(), + .queue_priorities = priorities, + .extensions = extensions, + .queue_family_index = 0, + }; + + vk::device logical_device(physical_device, logical_device_params); + + vk::surface window_surface(api_instance, window); + + vk::surface_params surface_properties = + physical_device.request_surface(window_surface); + + vk::swapchain_params enumerate_swapchain_settings = { + .width = static_cast(width), + .height = static_cast(height), + .present_index = 0, + }; + vk::swapchain main_swapchain(logical_device, + window_surface, + enumerate_swapchain_settings, + surface_properties); + + // querying presentable images + std::span images = main_swapchain.get_images(); + uint32_t image_count = static_cast(images.size()); + + // Creating Images + std::vector swapchain_images(image_count); + std::vector swapchain_depth_images(image_count); + + VkExtent2D swapchain_extent = surface_properties.capabilities.currentExtent; + + // Setting up the images + uint32_t layer_count = 1; + uint32_t mip_levels = 1; + for (uint32_t i = 0; i < swapchain_images.size(); i++) { + vk::image_params swapchain_image_config = { + .extent = { .width = swapchain_extent.width, + .height = swapchain_extent.height }, + .format = surface_properties.format.format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), + .aspect = vk::image_aspect_flags::color_bit, + .usage = vk::image_usage::color_attachment_bit, + .mip_levels = 1, + .layer_count = 1, + }; + + swapchain_images[i] = + vk::sample_image(logical_device, images[i], swapchain_image_config); + + vk::image_params image_config = { + .extent = { + .width = swapchain_extent.width, + .height = swapchain_extent.height, + }, + .format = depth_format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), + .aspect = vk::image_aspect_flags::depth_bit, + .usage = vk::image_usage::depth_stencil_bit, + .mip_levels = 1, + .layer_count = 1, + }; + swapchain_depth_images[i] = + vk::sample_image(logical_device, image_config); + } + + // setting up command buffers + std::vector swapchain_command_buffers(image_count); + for (size_t i = 0; i < swapchain_command_buffers.size(); i++) { + vk::command_params settings = { + .levels = vk::command_levels::primary, + .queue_index = 0, + .flags = vk::command_pool_flags::reset, + }; + + swapchain_command_buffers[i] = + vk::command_buffer(logical_device, settings); + } + + // setting up presentation queue to display commands to the screen + vk::queue_params enumerate_present_queue{ + .family = 0, + .index = 0, + }; + vk::device_present_queue presentation_queue( + logical_device, main_swapchain, enumerate_present_queue); + + // gets set with the renderpass + std::array color = { 0.f, 0.5f, 0.5f, 1.f }; + + // Loading graphics pipeline + std::array shader_sources = { + vk::shader_source{ + .filename = "shader_samples/sample1/test.vert.spv", + .stage = vk::shader_stage::vertex, + }, + vk::shader_source{ + .filename = "shader_samples/sample1/test.frag.spv", + .stage = vk::shader_stage::fragment, + }, + }; + + // To render triangle, we do not need to set any vertex attributes + vk::shader_resource_info shader_info = { + .sources = shader_sources, + .vertex_attributes = {} // this is to explicitly set to none, but also + // dont need to set this at all regardless + }; + vk::shader_resource geometry_resource(logical_device, shader_info); + + std::array color_blend_attachments = { + vk::color_blend_attachment_state{}, + }; + + std::array dynamic_states = { + vk::dynamic_state::viewport, vk::dynamic_state::scissor + }; + + uint32_t format = static_cast(surface_properties.format.format); + vk::pipeline_params pipeline_configuration = { + .use_render_pipeline = true, + .color_attachment_formats = std::span(&format, 1), + .depth_format = static_cast(depth_format), + .stencil_format = static_cast(depth_format), + .renderpass = nullptr, + .shader_modules = geometry_resource.handles(), + .vertex_attributes = geometry_resource.vertex_attributes(), + .vertex_bind_attributes = geometry_resource.vertex_bind_attributes(), + .color_blend = { + .attachments = color_blend_attachments, + }, + .depth_stencil_enabled = true, + .dynamic_states = dynamic_states, + }; + vk::pipeline main_graphics_pipeline(logical_device, pipeline_configuration); + + VkClearValue clear_color = { + { 0.f, 0.5f, 0.5f, 1.f }, + }; + + VkClearValue depth_value = { + .depthStencil = { .depth = 1.f, .stencil = 0 }, + }; + + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + + uint32_t current_frame = presentation_queue.acquire_next_image(); + vk::command_buffer current = swapchain_command_buffers[current_frame]; + + current.begin(vk::command_usage::simulatneous_use_bit); + + swapchain_images[current_frame].memory_barrier( + current, + surface_properties.format.format, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + // Because dynamic rendering does not automatically handle layout + // transitions These memory barriers set the color and depth images for + // the output + swapchain_depth_images[current_frame].memory_barrier( + current, + depth_format, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT); + + vk::rendering_attachment color_render_attachment = { + .image_view = swapchain_images[current_frame].image_view(), + .layout = vk::image_layout::color_optimal, + .resolve_mode = vk::resolved_mode_flags::none, + .resolve_image_view = nullptr, + .resolve_image_layout = vk::image_layout::undefined, + .load = vk::attachment_load::clear, + .store = vk::attachment_store::store, + .clear_values = clear_color + }; + + vk::rendering_attachment depth_stencil_attachment = { + .image_view = swapchain_depth_images[current_frame].image_view(), + .layout = vk::image_layout::depth_stencil_optimal, + .resolve_mode = vk::resolved_mode_flags::none, + .resolve_image_view = nullptr, + .resolve_image_layout = vk::image_layout::undefined, + .load = vk::attachment_load::clear, + .store = vk::attachment_store::store, + .depth_values = depth_value + }; + + vk::rendering_begin_parameters begin_params = { + .render_area = { { 0, 0 }, + { + swapchain_extent.width, + swapchain_extent.height, + }, }, + .layer_count = 1, + .color_attachments = std::span( + &color_render_attachment, 1), + .depth_attachment = depth_stencil_attachment, + .stencil_attachment = depth_stencil_attachment, + }; + + vk::viewport_params viewport = { + .x = 0.0f, + .y = 0.0f, + .width = static_cast(swapchain_extent.width), + .height = static_cast(swapchain_extent.height), + .min_depth = 0.0f, + .max_depth = 1.0f, + }; + current.set_viewport( + 0, 1, std::span(&viewport, 1)); + + vk::scissor_params scissor = { + .offset = { 0, 0 }, + .extent = swapchain_extent, + }; + + current.set_scissor( + 0, 1, std::span(&scissor, 1)); + + current.begin_rendering(begin_params); + + main_graphics_pipeline.bind(current); + vkCmdDraw(current, 3, 1, 0, 0); + + current.end_rendering(); + + swapchain_images[current_frame].memory_barrier( + current, + surface_properties.format.format, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + + current.end(); + + // Submitting and then presenting to the screen + std::array commands = { current }; + presentation_queue.submit_async(commands); + presentation_queue.present_frame(current_frame); + } + + logical_device.wait(); + main_swapchain.destruct(); + + geometry_resource.destruct(); + main_graphics_pipeline.destruct(); + + for (auto& command : swapchain_command_buffers) { + command.destruct(); + } + + for (auto& image : swapchain_images) { + image.destruct(); + } + + for (auto& image : swapchain_depth_images) { + image.destruct(); + } + + presentation_queue.destruct(); + + logical_device.destruct(); + window_surface.destruct(); + glfwDestroyWindow(window); + return 0; +} diff --git a/demos/15-dynamic-rendering/conanfile.py b/demos/15-dynamic-rendering/conanfile.py new file mode 100644 index 0000000..eb977f5 --- /dev/null +++ b/demos/15-dynamic-rendering/conanfile.py @@ -0,0 +1,37 @@ +from conan import ConanFile +from conan.tools.cmake import CMake, cmake_layout + +class Demo(ConanFile): + name = "game-demo" + version = "1.0" + settings = "os", "compiler", "build_type", "arch" + generators = "CMakeDeps", "CMakeToolchain" + export_source = "CMakeLists.txt", "application.cpp" + + # Putting all of your build-related dependencies here + def build_requirements(self): + self.tool_requires("cmake/[^4.0.0]") + self.tool_requires("ninja/[^1.3.0]") + self.tool_requires("engine3d-cmake-utils/4.0") + + # Putting all of your packages here + # To build engine3d/1.0 locally do the following: + # conan create . --name=engine3d --version=0.1.0 --user=local --channel=12345 + def requirements(self): + self.requires("glfw/3.4") + self.requires("glm/1.0.1") + self.requires("stb/cci.20230920") + self.requires("tinyobjloader/2.0.0-rc10") + self.requires("vulkan-cpp/6.0") + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() + + def layout(self): + cmake_layout(self) \ No newline at end of file diff --git a/demos/16-descriptor-indexing/CMakeLists.txt b/demos/16-descriptor-indexing/CMakeLists.txt new file mode 100644 index 0000000..2d64cab --- /dev/null +++ b/demos/16-descriptor-indexing/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 4.0) +project(descriptor-indexing CXX) + +build_application( + SOURCES + application.cpp + + PACKAGES + vulkan-cpp + Vulkan + glfw3 + glm + stb + tinyobjloader + + LINK_PACKAGES + stb::stb + vulkan-cpp + tinyobjloader + Vulkan::Vulkan +) \ No newline at end of file diff --git a/demos/16-descriptor-indexing/README.md b/demos/16-descriptor-indexing/README.md new file mode 100644 index 0000000..c618560 --- /dev/null +++ b/demos/16-descriptor-indexing/README.md @@ -0,0 +1,188 @@ +# Demo 16: Descriptor Indexing + + +In this demo, I show how to get descriptor indexing to work properly using vulkan-cpp's wrappers. + + +## Enabling Descriptor Indexing Features + +To enable the descriptor indexing features, you must add onto the `vk::device_features` initialization here, as follows. + +```C++ + +vk::device_features device_features{ + vk::dynamic_rendering_feature{ { + .dynamicRendering = true, + } }, + + // ADD This: Enabling descriptor indexing + vk::descriptor_indexing_feature{ { + .descriptorBindingPartiallyBound = true, + .runtimeDescriptorArray = true, + .descriptorBindingVariableDescriptorCount = true, + .descriptorBindingSampledImageUpdateAfterBind = true, + .shaderSampledImageArrayNonUniformIndexing = true, + } }, +}; +``` + +## Configuring Descriptor Flags + +After enabling the descriptor indexing features. You must set flags to your descriptor binding layouts. In vulkan-cpp, these are referred to as `vk::descriptor_entry`. + +Specifically these flags: +* `vk::descriptor_bind_flags::partially_bound_bit` +* `vk::descriptor_bind_flags::variable_descriptor_count_bit` +* `vk::descriptor_bind_flags::update_after_bind` + +As you can see below, I add these flags onto this specific binding point entry of this specific resource. + +These flags are a way to indicate this specific layout binding may have a max amount of descriptor (resources) that may be utilized at this specific binding point in the shader. + +```C++ +std::vector entries_set1 = { + vk::descriptor_entry{ + // layout (set = 1, binding = 0) uniform sampler2D + .type = vk::descriptor_type::combined_image_sampler, + .binding_point = { + .binding = 0, + .stage = vk::shader_stage::fragment, + }, + .descriptor_count = 1, + .flags = vk::descriptor_bind_flags::partially_bound_bit | + vk::descriptor_bind_flags::variable_descriptor_count_bit | + vk::descriptor_bind_flags::update_after_bind, + } +}; +``` + +## Configuring Descriptor Layout + +After the descriptor entryr has its flags set. You are going to need to set the descriptor layout `.flag` parameters to `vk::descriptor_layout_flags::update_after_bind_pool`. + +This is a third parameter part of the `vk::descriptor_resource` constructor, since this is only ever needed to be set if you plan to use descriptor indexing to keep support for both traditional and modern approaches. + +You also have to set the max descriptors so Vulkan knows how much slots that may be in-used. In this demno, I only specify `1`. Though if you had, lets say 10. You would set max descriptors to `10`. + +Here is how you'd set the flag in the constructor: + +```C++ +uint32_t max_descriptor = 1; +vk::descriptor_layout set1_layout = { + .slot = 1, + .max_sets = image_count, + .entries = entries_set1, + .descriptor_counts = std::span(&max_descriptor, 1), +}; + +vk::descriptor_resource set1_resource(logical_device, set1_layout, vk::descriptor_layout_flags::update_after_bind_pool); +``` + +## Update Descriptors + +In this demo, you can leave the descriptors to be updated the same way it has been previously since we only ever use a single texture. + +Where we update them like this: + +```C++ +vk::texture_params config_texture = { + .memory_mask = + physical_device.memory_properties(static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit)), +}; +vk::texture texture1(logical_device, + std::filesystem::path("asset_samples/viking_room.png"), + config_texture); + +std::array samplers = { + vk::write_image{ + .sampler = texture1.image().sampler(), + .view = texture1.image().image_view(), + .layout = vk::image_layout::shader_read_only_optimal, + }, +}; + +// Specify image descriptor images/samplers to the descriptor +std::array set1_samples = { + vk::write_image_descriptor{ + .dst_binding = 0, + .sample_images = samplers, + } +}; + +set1_resource.update({}, set1_samples); +``` + +## Push Constants + +Then for accessibility to be able to use the textures specifically, we create a push_constant data below to use for our index data to retrieve specific textures. + +```C++ +struct push_constant_data { + uint32_t texture_index=0; +}; +``` + +## Configuring `vk::pipeline` for Push Constant + +Make sure to configur the graphics pipeline to ensure it will handle the push constants correctly. + +Below is a demonstration of the only parameters needed for specifying the parameters for sending over push constants. + +```C++ + +// ADD THIS: Used for specifying which accessibility in shader stages to access push constants data +uint32_t format = static_cast(surface_properties.format.format); +uint32_t vertex_mask = static_cast(vk::shader_stage::vertex); +uint32_t fragment_mask = static_cast(vk::shader_stage::fragment); +uint32_t stage_mask = vertex_mask | fragment_mask; +vk::shader_stage stage = static_cast(stage_mask); + +// ADD THIS +vk::push_constant_range range = { + .stage = stage, + .offset = 0, + .range = sizeof(push_constant_data), +}; +vk::pipeline_params pipeline_configuration = { + .use_render_pipeline = true, + .color_attachment_formats = std::span(&format, 1), + .depth_format = static_cast(depth_format), + .stencil_format = static_cast(depth_format), + .renderpass = nullptr, + .shader_modules = geometry_resource.handles(), + .vertex_attributes = geometry_resource.vertex_attributes(), + .vertex_bind_attributes = geometry_resource.vertex_bind_attributes(), + .descriptor_layouts = layouts, + .color_blend = { + .attachments = color_blend_attachments, + }, + .depth_stencil_enabled = true, + .dynamic_states = dynamic_states, + + // ADD THIS + .push_constants = std::span(&range, 1), +}; +``` + + +## Transferring Push Constant Data + +Now, that we have done the hard part of doing the boilerplate. You can then send over your texture index to access the texture and you should receive the same result when you do + +In here, you will see in the mainloop where, I call `vk::pipeline::push_constant` API to transfer over the push constant data over to the shader. + +```C++ +push_constant_data push = { + .texture_index = 0, +}; +main_graphics_pipeline.push_constant( + current, push, stage, 0, sizeof(push_constant_data)); +``` + + +## Thats it! + +As soon, you transfer over the push constants. You should still see the same viking room demo from demo 12. Where I use that same demo to demonstrate using descriptor indexing to fetch the viking room texture for the 3D mesh. + diff --git a/demos/16-descriptor-indexing/application.cpp b/demos/16-descriptor-indexing/application.cpp new file mode 100644 index 0000000..078d220 --- /dev/null +++ b/demos/16-descriptor-indexing/application.cpp @@ -0,0 +1,908 @@ +#define GLFW_INCLUDE_VULKAN +#if _WIN32 +#define VK_USE_PLATFORM_WIN32_KHR +#include +#define GLFW_EXPOSE_NATIVE_WIN32 +#include +#include +#else +#include +#include +#endif + +#include +#include +#include +#include +#include + +#include +#define GLM_FORCE_RADIANS +#include +#include +#define GLM_ENABLE_EXPERIMENTAL +#include + +#ifndef STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#include +#endif + +#include +import vk; + +static VKAPI_ATTR VkBool32 VKAPI_CALL +debug_callback( + [[maybe_unused]] VkDebugUtilsMessageSeverityFlagBitsEXT p_message_severity, + [[maybe_unused]] VkDebugUtilsMessageTypeFlagsEXT p_message_type, + const VkDebugUtilsMessengerCallbackDataEXT* p_callback_data, + [[maybe_unused]] void* p_user_data) { + std::print("validation layer:\t\t{}\n\n", p_callback_data->pMessage); + return false; +} + +struct global_uniform { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +template +void +hash_combine(size_t& seed, const T& v, const Rest&... rest) { + seed ^= std::hash()(v) + 0x9e3779b9 + (seed << 6) + (seed << 2); + (hash_combine(seed, rest), ...); +} + +namespace std { + + template<> + struct hash { + size_t operator()(const vk::vertex_input& vertex) const { + size_t seed = 0; + hash_combine( + seed, vertex.position, vertex.color, vertex.normals, vertex.uv); + return seed; + } + }; +} + +// Part of this demo for loading a 3D .obj model +class obj_model { +public: + obj_model() = default; + obj_model(const std::filesystem::path& p_filename, + const VkDevice& p_device, + const vk::physical_device& p_physical) { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + + //! @note If we return the constructor then we can check if the mesh + //! loaded successfully + //! @note We also receive hints if the loading is successful! + //! @note Return default constructor automatically returns false means + //! that mesh will return the boolean as false because it wasnt + //! successful + if (!tinyobj::LoadObj(&attrib, + &shapes, + &materials, + &warn, + &err, + p_filename.string().c_str())) { + std::println("Could not load model from path {}", + p_filename.string()); + m_is_loaded = false; + return; + } + + std::vector vertices; + std::vector indices; + std::unordered_map unique_vertices{}; + + for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + vk::vertex_input vertex{}; + + // vertices.push_back(vertex); + if (!unique_vertices.contains(vertex)) { + unique_vertices[vertex] = + static_cast(vertices.size()); + vertices.push_back(vertex); + } + + if (index.vertex_index >= 0) { + vertex.position = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2] + }; + + vertex.color = { + attrib.colors[3 * index.vertex_index + 0], + attrib.colors[3 * index.vertex_index + 1], + attrib.colors[3 * index.vertex_index + 2] + }; + } + + if (index.normal_index >= 0) { + vertex.normals = { + attrib.normals[3 * index.normal_index + 0], + attrib.normals[3 * index.normal_index + 1], + attrib.normals[3 * index.normal_index + 2] + }; + } + + if (index.texcoord_index >= 0) { + vertex.uv = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] + }; + } + + if (!unique_vertices.contains(vertex)) { + unique_vertices[vertex] = + static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(unique_vertices[vertex]); + } + } + + m_has_indices = (indices.size() > 0) ? true : false; + + if (m_has_indices) { + m_indices_size = indices.size(); + } + m_indices_size = vertices.size(); + m_indices_size = indices.size(); + + //! @brief Creating vertex/index buffers with host visibility flags + const auto property_flags = static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit); + + vk::buffer_parameters vertex_params = { + .memory_mask = p_physical.memory_properties(property_flags), + .property_flags = vk::memory_property::device_local_bit, + .usage = vk::buffer_usage::transfer_dst_bit | + vk::buffer_usage::vertex_buffer_bit, + }; + + vk::buffer_parameters index_params = { + .memory_mask = p_physical.memory_properties(property_flags), + .property_flags = static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit), + .usage = vk::buffer_usage::index_buffer_bit, + }; + + m_vertex_buffer = vk::vertex_buffer(p_device, vertices, vertex_params); + m_index_buffer = vk::index_buffer(p_device, indices, index_params); + m_is_loaded = true; + } + + [[nodiscard]] bool loaded() const { return m_is_loaded; } + + [[nodiscard]] VkBuffer vertex_handle() const { return m_vertex_buffer; } + + [[nodiscard]] VkBuffer index_handle() const { return m_index_buffer; } + + [[nodiscard]] bool has_indices() const { return m_has_indices; } + + [[nodiscard]] uint32_t indices_size() const { return m_indices_size; } + + void draw(const VkCommandBuffer& p_command) { + if (m_has_indices) { + vkCmdDrawIndexed(p_command, m_indices_size, 1, 0, 0, 0); + } + else { + vkCmdDraw(p_command, m_indices_size, 1, 0, 0); + } + } + + void destruct() { + m_vertex_buffer.destruct(); + m_index_buffer.destruct(); + } + +private: + bool m_is_loaded = false; + bool m_has_indices = false; + uint32_t m_indices_size = 0; + vk::vertex_buffer m_vertex_buffer{}; + vk::index_buffer m_index_buffer{}; +}; + +std::vector +get_instance_extensions() { + std::vector extension_names; + uint32_t extension_count = 0; + const char** required_extensions = + glfwGetRequiredInstanceExtensions(&extension_count); + + for (uint32_t i = 0; i < extension_count; i++) { + std::println("Required Extension = {}", required_extensions[i]); + extension_names.emplace_back(required_extensions[i]); + } + + extension_names.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + +#if defined(__APPLE__) + extension_names.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + extension_names.emplace_back( + VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); +#endif + + return extension_names; +} + +struct push_constant_data { + uint32_t texture_index = 0; +}; + +/** + * @brief STBI-specific implementation of the vk::image interface + */ +class stb_image : public vk::image { +public: + stb_image() = delete; + + stb_image(std::string_view p_path, vk::texture_params p_params) { + image_load(p_path, p_params); + } + + ~stb_image() = default; + +protected: + bool image_load(std::string_view p_path, + vk::texture_params p_params) override { + int w = 0; + int h = 0; + int channels = 0; + + stbi_uc* image_pixel_data = + stbi_load(p_path.data(), &w, &h, &channels, STBI_rgb_alpha); + + if (!image_pixel_data) { + return false; + } + + const VkFormat texture_format = + static_cast(vk::format::r8g8b8a8_unorm); + int bytes_per_pixel = vk::bytes_per_texture_format(texture_format); + + m_extent = { + .width = static_cast(w), + .height = static_cast(h), + }; + + // Retrieving total size of bytes of the dimensions of the image and + // accounting for pixels of the image + uint32_t size_bytes = + m_extent.width * m_extent.height * bytes_per_pixel; + + // Retrieving total image size to the count of the image layers + uint32_t size = size_bytes * p_params.layer_count; + + vk::image_params image_options = { + .extent = m_extent, + .format = texture_format, + .memory_mask = p_params.memory_mask, + .usage = + vk::image_usage::transfer_dst_bit | vk::image_usage::sampled_bit, + .mip_levels = p_params.mip_levels, + .layer_count = p_params.layer_count, + }; + + m_bytes.reserve(size); + std::span bytes_view = + std::span(image_pixel_data, size); + + m_bytes.assign(bytes_view.begin(), bytes_view.end()); + + stbi_image_free(image_pixel_data); + + return true; + } + + std::span image_read() const override { return m_bytes; } + + vk::image_extent image_extent() const override { return m_extent; } + +private: + vk::image_extent m_extent{}; + std::vector m_bytes{}; +}; + +int +main() { + //! @note Just added the some test code to test the conan-starter setup code + if (!glfwInit()) { + std::print("glfwInit could not be initialized!\n"); + return -1; + } + + if (!glfwVulkanSupported()) { + std::print("GLFW: Vulkan is not supported!"); + return -1; + } + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + int width = 800; + int height = 600; + std::string title = "Hello Window"; + GLFWwindow* window = + glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr); + + glfwMakeContextCurrent(window); + + std::array validation_layers = { + "VK_LAYER_KHRONOS_validation", + }; + + // setting up extensions + std::vector global_extensions = get_instance_extensions(); + + vk::debug_message_utility debug_callback_info = { + // .severity essentially takes in vk::message::verbose, + // vk::message::warning, vk::message::error + .severity = + vk::message::verbose | vk::message::warning | vk::message::error, + // .message_type essentially takes in vk::debug. Like: + // vk::debug::general, vk::debug::validation, vk::debug::performance + .message_type = + vk::debug::general | vk::debug::validation | vk::debug::performance, + .callback = debug_callback + }; + + vk::application_params config = { + .name = "vulkan instance", + .version = vk::api_version::vk_1_3, // specify to using vulkan 1.3 + .validations = + validation_layers, // .validation takes in a std::span + .extensions = + global_extensions // .extensions also takes in std::span + }; + + // 1. Setting up vk instance + vk::instance api_instance(config, debug_callback_info); + + // setting up physical device + std::expected physical_device_expected = + api_instance.enumerate_physical_device(vk::physical_gpu::integrated); + vk::physical_device physical_device = physical_device_expected.value(); + + // setting up logical device + std::array priorities = { 0.f }; + +#if defined(__APPLE__) + std::array extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + "VK_KHR_portability_subset", + }; +#else + std::array extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + }; +#endif + + std::array format_support = { + vk::format::d32_sfloat, + vk::format::d32_sfloat_s8_uint, + vk::format::d24_unorm_s8_uint + }; + + // We provide a selection of format support that we want to check is + // supported on current hardware device. + VkFormat depth_format = + physical_device.request_depth_format(format_support); + + vk::device_features device_features{ + vk::dynamic_rendering_feature{ { + .dynamicRendering = true, + } }, + vk::descriptor_indexing_feature{ { + .shaderSampledImageArrayNonUniformIndexing = true, + .descriptorBindingSampledImageUpdateAfterBind = true, + .descriptorBindingPartiallyBound = true, + .descriptorBindingVariableDescriptorCount = true, + .runtimeDescriptorArray = true, + } }, + }; + + vk::device_params logical_device_params = { + .features = device_features.data(), + .queue_priorities = priorities, + .extensions = extensions, + .queue_family_index = 0, + }; + + vk::device logical_device(physical_device, logical_device_params); + + vk::surface window_surface(api_instance, window); + + vk::surface_params surface_properties = + physical_device.request_surface(window_surface); + + vk::swapchain_params enumerate_swapchain_settings = { + .width = static_cast(width), + .height = static_cast(height), + .present_index = 0, + }; + vk::swapchain main_swapchain(logical_device, + window_surface, + enumerate_swapchain_settings, + surface_properties); + + // querying presentable images + std::span images = main_swapchain.get_images(); + uint32_t image_count = static_cast(images.size()); + + // Creating Images + std::vector swapchain_images(image_count); + std::vector swapchain_depth_images(image_count); + + VkExtent2D swapchain_extent = surface_properties.capabilities.currentExtent; + + // Setting up the images + uint32_t layer_count = 1; + uint32_t mip_levels = 1; + for (uint32_t i = 0; i < swapchain_images.size(); i++) { + vk::image_params swapchain_image_config = { + .extent = { .width = swapchain_extent.width, + .height = swapchain_extent.height }, + .format = surface_properties.format.format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), + .aspect = vk::image_aspect_flags::color_bit, + .usage = vk::image_usage::color_attachment_bit, + .mip_levels = 1, + .layer_count = 1, + }; + + swapchain_images[i] = + vk::sample_image(logical_device, images[i], swapchain_image_config); + + vk::image_params image_config = { + .extent = { + .width = swapchain_extent.width, + .height = swapchain_extent.height, + }, + .format = depth_format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), + .aspect = vk::image_aspect_flags::depth_bit, + .usage = vk::image_usage::depth_stencil_bit, + .mip_levels = 1, + .layer_count = 1, + }; + swapchain_depth_images[i] = + vk::sample_image(logical_device, image_config); + } + + // setting up command buffers + std::vector swapchain_command_buffers(image_count); + for (size_t i = 0; i < swapchain_command_buffers.size(); i++) { + vk::command_params settings = { + .levels = vk::command_levels::primary, + .queue_index = 0, + .flags = vk::command_pool_flags::reset, + }; + + swapchain_command_buffers[i] = + vk::command_buffer(logical_device, settings); + } + + // setting up presentation queue to display commands to the screen + vk::queue_params enumerate_present_queue{ + .family = 0, + .index = 0, + }; + vk::device_present_queue presentation_queue( + logical_device, main_swapchain, enumerate_present_queue); + + // gets set with the renderpass + std::array color = { 0.f, 0.5f, 0.5f, 1.f }; + + // Loading graphics pipeline + std::array shader_sources = { + vk::shader_source{ + .filename = + "shader_samples/sample8-descriptor-indexing/test.vert.spv", + .stage = vk::shader_stage::vertex, + }, + vk::shader_source{ + .filename = + "shader_samples/sample8-descriptor-indexing/test.frag.spv", + .stage = vk::shader_stage::fragment, + }, + }; + + // To render triangle, we do not need to set any vertex attributes + vk::shader_resource_info shader_info = { + .sources = shader_sources, + .vertex_attributes = {} // this is to explicitly set to none, but also + // dont need to set this at all regardless + }; + vk::shader_resource geometry_resource(logical_device, shader_info); + + // Setting up vertex attributes in the test shaders + std::array attribute_entries = { + vk::vertex_attribute_entry{ + .location = 0, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, position), + }, + vk::vertex_attribute_entry{ + .location = 1, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, color), + }, + vk::vertex_attribute_entry{ + .location = 2, + .format = vk::format::rg32_sfloat, + .stride = offsetof(vk::vertex_input, uv), + }, + vk::vertex_attribute_entry{ + .location = 3, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, normals), + } + }; + + std::array attributes = { + vk::vertex_attribute{ + // layout (set = 0, binding = 0) + .binding = 0, + .entries = attribute_entries, + .stride = sizeof(vk::vertex_input), + .input_rate = vk::input_rate::vertex, + }, + }; + geometry_resource.vertex_attributes(attributes); + + // Set 0: For Uniform BUffers (or global scene data) + std::vector entries = { + vk::descriptor_entry{ + // specifies "layout (set = 0, binding = 0) uniform GlobalUbo" + .type = vk::descriptor_type::uniform, + .binding_point = { + .binding = 0, + .stage = vk::shader_stage::vertex, + }, + .descriptor_count = 1, + }, + }; + vk::descriptor_layout set0_layout = { + .slot = 0, // indicate specific descriptor slot 0 + .max_sets = image_count, // max descriptors to allocate + .entries = entries, // descriptor layout entries description + }; + vk::descriptor_resource set0_resource(logical_device, set0_layout); + + // Set 1 = For Textures + std::vector entries_set1 = { + vk::descriptor_entry{ + // layout (set = 1, binding = 0) uniform sampler2D textures[]; + .type = vk::descriptor_type::combined_image_sampler, + .binding_point = { + .binding = 0, + .stage = vk::shader_stage::fragment, + }, + .descriptor_count = 1, + .flags = vk::descriptor_bind_flags::partially_bound_bit | + vk::descriptor_bind_flags::variable_descriptor_count_bit | + vk::descriptor_bind_flags::update_after_bind, + } + }; + + uint32_t max_descriptor = 1; + vk::descriptor_layout set1_layout = { + .slot = 1, // indicate specific descriptor slot 0 + .max_sets = image_count, // max descriptors to allocate + .entries = entries_set1, // descriptor layout entries description + .descriptor_counts = std::span(&max_descriptor, 1), + }; + + vk::descriptor_resource set1_resource( + logical_device, + set1_layout, + vk::descriptor_layout_flags::update_after_bind_pool); + + std::array layouts = { + set0_resource.layout(), + set1_resource.layout(), + }; + + std::array color_blend_attachments = { + vk::color_blend_attachment_state{}, + }; + + std::array dynamic_states = { + vk::dynamic_state::viewport, vk::dynamic_state::scissor + }; + + uint32_t format = static_cast(surface_properties.format.format); + uint32_t vertex_mask = static_cast(vk::shader_stage::vertex); + uint32_t fragment_mask = static_cast(vk::shader_stage::fragment); + uint32_t stage_mask = vertex_mask | fragment_mask; + vk::shader_stage stage = static_cast(stage_mask); + vk::push_constant_range range = { + .stage = stage, + .offset = 0, + .range = sizeof(push_constant_data), + }; + vk::pipeline_params pipeline_configuration = { + .use_render_pipeline = true, + .color_attachment_formats = std::span(&format, 1), + .depth_format = static_cast(depth_format), + .stencil_format = static_cast(depth_format), + .renderpass = nullptr, + .shader_modules = geometry_resource.handles(), + .vertex_attributes = geometry_resource.vertex_attributes(), + .vertex_bind_attributes = geometry_resource.vertex_bind_attributes(), + .descriptor_layouts = layouts, + .color_blend = { + .attachments = color_blend_attachments, + }, + .depth_stencil_enabled = true, + .dynamic_states = dynamic_states, + .push_constants = std::span(&range, 1), + }; + vk::pipeline main_graphics_pipeline(logical_device, pipeline_configuration); + + obj_model test_model(std::filesystem::path("asset_samples/viking_room.obj"), + logical_device, + physical_device); + + vk::buffer_parameters uniform_params = { + .memory_mask = + physical_device.memory_properties(static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit)), + .usage = vk::buffer_usage::uniform_buffer_bit, + }; + vk::uniform_buffer test_ubo = vk::uniform_buffer( + logical_device, sizeof(global_uniform), uniform_params); + + std::array uniforms0 = { + vk::write_buffer{ + .buffer = test_ubo, + .offset = 0, + .range = static_cast(test_ubo.size_bytes()), + }, + }; + std::array uniforms = { + vk::write_buffer_descriptor{ + .dst_binding = 0, + .uniforms = uniforms0, + }, + }; + + vk::texture_params config_texture = { + .memory_mask = physical_device.memory_properties( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit), + }; + + stb_image img = stb_image("asset_samples/viking_room.png", config_texture); + vk::texture texture1(logical_device, &img, config_texture); + + std::array samplers = { + vk::write_image{ + .sampler = texture1.image().sampler(), + .view = texture1.image().image_view(), + .layout = vk::image_layout::shader_read_only_optimal, + }, + }; + + // Specify image descriptor images/samplers to the descriptor + std::array set1_samples = { + vk::write_image_descriptor{ + .dst_binding = 0, + .sample_images = samplers, + } + }; + set0_resource.update(uniforms); + + set1_resource.update({}, set1_samples); + + VkClearValue clear_color = { + { 0.f, 0.5f, 0.5f, 1.f }, + }; + + VkClearValue depth_value = { + .depthStencil = { .depth = 1.f, .stencil = 0 }, + }; + + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + + uint32_t current_frame = presentation_queue.acquire_next_image(); + vk::command_buffer current = swapchain_command_buffers[current_frame]; + + current.begin(vk::command_usage::simulatneous_use_bit); + + swapchain_images[current_frame].memory_barrier( + current, + surface_properties.format.format, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + // Because dynamic rendering does not automatically handle layout + // transitions These memory barriers set the color and depth images for + // the output + swapchain_depth_images[current_frame].memory_barrier( + current, + depth_format, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT); + + vk::rendering_attachment color_render_attachment = { + .image_view = swapchain_images[current_frame].image_view(), + .layout = vk::image_layout::color_optimal, + .resolve_mode = vk::resolved_mode_flags::none, + .resolve_image_view = nullptr, + .resolve_image_layout = vk::image_layout::undefined, + .load = vk::attachment_load::clear, + .store = vk::attachment_store::store, + .clear_values = clear_color + }; + + vk::rendering_attachment depth_stencil_attachment = { + .image_view = swapchain_depth_images[current_frame].image_view(), + .layout = vk::image_layout::depth_stencil_optimal, + .resolve_mode = vk::resolved_mode_flags::none, + .resolve_image_view = nullptr, + .resolve_image_layout = vk::image_layout::undefined, + .load = vk::attachment_load::clear, + .store = vk::attachment_store::store, + .depth_values = depth_value + }; + + vk::rendering_begin_parameters begin_params = { + .render_area = { { 0, 0 }, + { + swapchain_extent.width, + swapchain_extent.height, + }, }, + .layer_count = 1, + .color_attachments = std::span( + &color_render_attachment, 1), + .depth_attachment = depth_stencil_attachment, + .stencil_attachment = depth_stencil_attachment, + }; + + vk::viewport_params viewport = { + .x = 0.0f, + .y = 0.0f, + .width = static_cast(swapchain_extent.width), + .height = static_cast(swapchain_extent.height), + .min_depth = 0.0f, + .max_depth = 1.0f, + }; + current.set_viewport( + 0, 1, std::span(&viewport, 1)); + + vk::scissor_params scissor = { + .offset = { 0, 0 }, + .extent = swapchain_extent, + }; + + current.set_scissor( + 0, 1, std::span(&scissor, 1)); + + current.begin_rendering(begin_params); + + main_graphics_pipeline.bind(current); + + const VkBuffer vertex = test_model.vertex_handle(); + uint64_t offset = 0; + current.bind_vertex_buffers(std::span(&vertex, 1), + std::span(&offset, 1)); + + if (test_model.has_indices()) { + current.bind_index_buffers32(test_model.index_handle()); + } + + static auto start_time = std::chrono::high_resolution_clock::now(); + + auto current_time = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration( + current_time - start_time) + .count(); + + push_constant_data push = { + .texture_index = 0, + }; + main_graphics_pipeline.push_constant( + current, push, stage, 0); + + // We set the uniforms and then we offload that to the GPU + global_uniform ubo = { + .model = glm::rotate(glm::mat4(1.0f), + time * glm::radians(90.0f), + glm::vec3(0.0f, 0.0f, 1.0f)), + .view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), + glm::vec3(0.0f, 0.0f, 0.0f), + glm::vec3(0.0f, 0.0f, 1.0f)), + .proj = glm::perspective(glm::radians(45.0f), + (float)swapchain_extent.width / + (float)swapchain_extent.height, + 0.1f, + 10.0f) + }; + ubo.proj[1][1] *= -1; + + test_ubo.transfer( + std::span(&ubo, 1)); + + std::array descriptors = { + set0_resource, + set1_resource, + }; + + current.bind_descriptors(main_graphics_pipeline.layout(), + VK_PIPELINE_BIND_POINT_GRAPHICS, + descriptors); + + // Drawing-call to render actual triangle to the screen + // vkCmdDrawIndexed(current, static_cast(indices.size()), 1, + // 0, 0, 0); + test_model.draw(current); + + // vkCmdDraw(current, 3, 1, 0, 0); + + current.end_rendering(); + + swapchain_images[current_frame].memory_barrier( + current, + surface_properties.format.format, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + + current.end(); + + // Submitting and then presenting to the screen + std::array commands = { current }; + presentation_queue.submit_async(commands); + presentation_queue.present_frame(current_frame); + } + + logical_device.wait(); + main_swapchain.destruct(); + + texture1.destruct(); + set0_resource.destruct(); + set1_resource.destruct(); + test_ubo.destruct(); + test_model.destruct(); + + geometry_resource.destruct(); + main_graphics_pipeline.destruct(); + + for (auto& command : swapchain_command_buffers) { + command.destruct(); + } + + for (auto& image : swapchain_images) { + image.destruct(); + } + + for (auto& image : swapchain_depth_images) { + image.destruct(); + } + + presentation_queue.destruct(); + + logical_device.destruct(); + window_surface.destruct(); + glfwDestroyWindow(window); + return 0; +} diff --git a/demos/16-descriptor-indexing/conanfile.py b/demos/16-descriptor-indexing/conanfile.py new file mode 100644 index 0000000..eb977f5 --- /dev/null +++ b/demos/16-descriptor-indexing/conanfile.py @@ -0,0 +1,37 @@ +from conan import ConanFile +from conan.tools.cmake import CMake, cmake_layout + +class Demo(ConanFile): + name = "game-demo" + version = "1.0" + settings = "os", "compiler", "build_type", "arch" + generators = "CMakeDeps", "CMakeToolchain" + export_source = "CMakeLists.txt", "application.cpp" + + # Putting all of your build-related dependencies here + def build_requirements(self): + self.tool_requires("cmake/[^4.0.0]") + self.tool_requires("ninja/[^1.3.0]") + self.tool_requires("engine3d-cmake-utils/4.0") + + # Putting all of your packages here + # To build engine3d/1.0 locally do the following: + # conan create . --name=engine3d --version=0.1.0 --user=local --channel=12345 + def requirements(self): + self.requires("glfw/3.4") + self.requires("glm/1.0.1") + self.requires("stb/cci.20230920") + self.requires("tinyobjloader/2.0.0-rc10") + self.requires("vulkan-cpp/6.0") + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() + + def layout(self): + cmake_layout(self) \ No newline at end of file diff --git a/demos/17-buffer-device-address/CMakeLists.txt b/demos/17-buffer-device-address/CMakeLists.txt new file mode 100644 index 0000000..b7fefe3 --- /dev/null +++ b/demos/17-buffer-device-address/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 4.0) +project(buffer-device-address CXX) + +build_application( + SOURCES + application.cpp + + PACKAGES + vulkan-cpp + Vulkan + glfw3 + glm + stb + tinyobjloader + + LINK_PACKAGES + stb::stb + vulkan-cpp + tinyobjloader + Vulkan::Vulkan +) \ No newline at end of file diff --git a/demos/17-buffer-device-address/README.md b/demos/17-buffer-device-address/README.md new file mode 100644 index 0000000..7fe6fce --- /dev/null +++ b/demos/17-buffer-device-address/README.md @@ -0,0 +1,164 @@ +# Demo 17: Buffer Device Address + + +In this demo 17, will show how to get buffer device addresses working using vulkan-cpp APIs. + +## Enabling Buffer Device Addresses + +To enable buffer device addresses, add onto the `vk::device_features` initialization here: + +```C++ +vk::device_features device_features{ + vk::dynamic_rendering_feature{ { + .dynamicRendering = true, + } }, + vk::descriptor_indexing_feature{ { + .shaderSampledImageArrayNonUniformIndexing = true, + .descriptorBindingSampledImageUpdateAfterBind = true, + .descriptorBindingPartiallyBound = true, + .descriptorBindingVariableDescriptorCount = true, + .runtimeDescriptorArray = true, + } }, + + // NEW: Add buffer device address feature here + vk::buffer_device_address{ { + .bufferDeviceAddress = true, + } }, +}; +``` + +## Replacing `vk::uniform_buffer` with `vk::dyn::buffer` + +The `vk::dyn::buffer` is meant to be a buffer abstraction similar to `vk::buffer` with an additional API to getting the buffers device address from the buffer. + +Which also does performs an automated configuration to for buffer device address. + +The initialization for the test_ubo variable is quite similar. + +```C++ + +vk::buffer_parameters uniform_params = { + .memory_mask = + physical_device.memory_properties(static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit)), + .usage = vk::buffer_usage::uniform_buffer_bit | + vk::buffer_usage::shader_device_address_bit, + .allocate_flags = vk::memory_allocate_flags::device_address_bit_khr, +}; + +// NEW: Change this from vk::uniform_buffer to vk::dyn::buffer +vk::dyn::buffer test_ubo = + vk::dyn::buffer(logical_device, sizeof(global_uniform), uniform_params); +``` + +## Getting the Device Address + +In the mainloop, you can retrieve the buffer device address through the `.get_device_address` API. + + +When getting the `ubo_addr`, you pass this into the push constant to send in the address to retrieve that specific buffer to access. + +```C++ + +while (!glfwWindowShouldClose(window)) { + // ... + // Performing some operation + global_uniform ubo = { + .model = glm::rotate(glm::mat4(1.0f), + time * glm::radians(90.0f), + glm::vec3(0.0f, 0.0f, 1.0f)), + .view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), + glm::vec3(0.0f, 0.0f, 0.0f), + glm::vec3(0.0f, 0.0f, 1.0f)), + .proj = glm::perspective(glm::radians(45.0f), + (float)swapchain_extent.width / + (float)swapchain_extent.height, + 0.1f, + 10.0f) + }; + ubo.proj[1][1] *= -1; + + test_ubo.transfer(std::span(&ubo, 1)); + + // NEW: ADD THIS LINE + const uint64_t ubo_addr = test_ubo.get_device_address(); + + push_constant_data push = { + .texture_index = 0, + + // NEW: Set device address here + .global_ubo_addr = ubo_addr, + }; + main_graphics_pipeline.push_constant( + current, push, stage, 0, sizeof(push_constant_data)); + + // ... +} + +``` + +## Changes to the shader + +### `vertex` shader + +In the vertex shader, we originally had used `layout(set = 0, binding = 0)` to contain a descriptor set that allows us to write our buffer. + +Now, that we are using a buffer device address, we can change that layout to being `layout(buffer_reference, std140)`. + +Here is what `layout(buffer_reference, std140)`: + +- `buffer_reference`: is to tell the resource lookup that the layout of this uniform is not going to be lookedup as a descriptor binding rather as a reference to this buffer. +- `std140`: Just set what the alignment rules are in how these uniform data can be aligned. + +```C++ +#version 450 + +#extension GL_EXT_buffer_reference : require + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inColor; +layout(location = 2) in vec2 inTexCoords; +layout(location = 3) in vec3 inNormals; + +layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec2 fragTexCoords; +layout(location = 2) out vec3 fragNormals; +layout(location = 3) out flat int fragTexIndex; + +// NEW: Replace layout(set=0, binding=1) +layout(buffer_reference, std140) buffer readonly UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +}; + +layout(push_constant) uniform Constants { + int texture_index; + + // NEW: Pass uint64_t from the push_constant_data and this + // specifies what piece of data to access from buffer device address previously stored + UniformBufferObject global_ubo_address; +} push_const; + + + +void main() { + + // NEW: Add this here to retrieve the global uniform buffer data + UniformBufferObject ubo = push_const.global_ubo_address; + + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); + fragColor = inColor; + fragTexCoords = inTexCoords; + fragNormals = inNormals; + fragTexIndex = push_const.texture_index; +} +``` + +## That is it! + +Configuring buffer device address and using this Vulkan feature to access your uniform buffers are now much easier through the use of buffer device address. + +This will reduce the need to have to manage an entire descriptor sets for specifically buffers, and to just provide the address for the shader to directly access the buffer already stored in memory the GPU already has access. + diff --git a/demos/17-buffer-device-address/application.cpp b/demos/17-buffer-device-address/application.cpp new file mode 100644 index 0000000..8d78a8c --- /dev/null +++ b/demos/17-buffer-device-address/application.cpp @@ -0,0 +1,883 @@ +#define GLFW_INCLUDE_VULKAN +#if _WIN32 +#define VK_USE_PLATFORM_WIN32_KHR +#include +#define GLFW_EXPOSE_NATIVE_WIN32 +#include +#include +#else +#include +#include +#endif + +#include +#include +#include +#include +import vk; + +#include +#define GLM_FORCE_RADIANS +#include +#include +#define GLM_ENABLE_EXPERIMENTAL +#include +#include +#include + +#include + +#ifndef STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#include +#endif + +static VKAPI_ATTR VkBool32 VKAPI_CALL +debug_callback( + [[maybe_unused]] VkDebugUtilsMessageSeverityFlagBitsEXT p_message_severity, + [[maybe_unused]] VkDebugUtilsMessageTypeFlagsEXT p_message_type, + const VkDebugUtilsMessengerCallbackDataEXT* p_callback_data, + [[maybe_unused]] void* p_user_data) { + + std::print("validation layer:\t\t{}\n\n", p_callback_data->pMessage); + return false; +} + +struct global_uniform { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +template +void +hash_combine(size_t& seed, const T& v, const Rest&... rest) { + seed ^= std::hash()(v) + 0x9e3779b9 + (seed << 6) + (seed << 2); + (hash_combine(seed, rest), ...); +} + +namespace std { + + template<> + struct hash { + size_t operator()(const vk::vertex_input& vertex) const { + size_t seed = 0; + hash_combine( + seed, vertex.position, vertex.color, vertex.normals, vertex.uv); + return seed; + } + }; +} + +// Part of this demo for loading a 3D .obj model +class obj_model { +public: + obj_model() = default; + obj_model(const std::filesystem::path& p_filename, + const VkDevice& p_device, + const vk::physical_device& p_physical) { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + + //! @note If we return the constructor then we can check if the mesh + //! loaded successfully + //! @note We also receive hints if the loading is successful! + //! @note Return default constructor automatically returns false means + //! that mesh will return the boolean as false because it wasnt + //! successful + if (!tinyobj::LoadObj(&attrib, + &shapes, + &materials, + &warn, + &err, + p_filename.string().c_str())) { + std::println("Could not load model from path {}", + p_filename.string()); + m_is_loaded = false; + return; + } + + std::vector vertices; + std::vector indices; + std::unordered_map unique_vertices{}; + + for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + vk::vertex_input vertex{}; + + // vertices.push_back(vertex); + if (!unique_vertices.contains(vertex)) { + unique_vertices[vertex] = + static_cast(vertices.size()); + vertices.push_back(vertex); + } + + if (index.vertex_index >= 0) { + vertex.position = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2] + }; + + vertex.color = { + attrib.colors[3 * index.vertex_index + 0], + attrib.colors[3 * index.vertex_index + 1], + attrib.colors[3 * index.vertex_index + 2] + }; + } + + if (index.normal_index >= 0) { + vertex.normals = { + attrib.normals[3 * index.normal_index + 0], + attrib.normals[3 * index.normal_index + 1], + attrib.normals[3 * index.normal_index + 2] + }; + } + + if (index.texcoord_index >= 0) { + vertex.uv = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] + }; + } + + if (!unique_vertices.contains(vertex)) { + unique_vertices[vertex] = + static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(unique_vertices[vertex]); + } + } + + m_has_indices = (indices.size() > 0) ? true : false; + + if (m_has_indices) { + m_indices_size = indices.size(); + } + m_indices_size = vertices.size(); + m_indices_size = indices.size(); + + //! @brief Creating vertex/index buffers with host visibility flags + const auto property_flags = static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit); + + vk::buffer_parameters vertex_params = { + .memory_mask = p_physical.memory_properties(property_flags), + .property_flags = vk::memory_property::device_local_bit, + .usage = vk::buffer_usage::transfer_dst_bit | + vk::buffer_usage::vertex_buffer_bit, + }; + + vk::buffer_parameters index_params = { + .memory_mask = p_physical.memory_properties(property_flags), + .property_flags = static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit), + .usage = vk::buffer_usage::index_buffer_bit, + }; + + m_vertex_buffer = vk::vertex_buffer(p_device, vertices, vertex_params); + m_index_buffer = vk::index_buffer(p_device, indices, index_params); + m_is_loaded = true; + } + + [[nodiscard]] bool loaded() const { return m_is_loaded; } + + [[nodiscard]] VkBuffer vertex_handle() const { return m_vertex_buffer; } + + [[nodiscard]] VkBuffer index_handle() const { return m_index_buffer; } + + [[nodiscard]] bool has_indices() const { return m_has_indices; } + + [[nodiscard]] uint32_t indices_size() const { return m_indices_size; } + + void draw(const VkCommandBuffer& p_command) { + if (m_has_indices) { + vkCmdDrawIndexed(p_command, m_indices_size, 1, 0, 0, 0); + } + else { + vkCmdDraw(p_command, m_indices_size, 1, 0, 0); + } + } + + void destruct() { + m_vertex_buffer.destruct(); + m_index_buffer.destruct(); + } + +private: + bool m_is_loaded = false; + bool m_has_indices = false; + uint32_t m_indices_size = 0; + vk::vertex_buffer m_vertex_buffer{}; + vk::index_buffer m_index_buffer{}; +}; + +std::vector +get_instance_extensions() { + std::vector extension_names; + uint32_t extension_count = 0; + const char** required_extensions = + glfwGetRequiredInstanceExtensions(&extension_count); + + for (uint32_t i = 0; i < extension_count; i++) { + std::println("Required Extension = {}", required_extensions[i]); + extension_names.emplace_back(required_extensions[i]); + } + + extension_names.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + +#if defined(__APPLE__) + extension_names.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + extension_names.emplace_back( + VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); +#endif + + return extension_names; +} + +struct push_constant_data { + uint32_t texture_index = 0; + uint64_t global_ubo_addr = 0; +}; + +/** + * @brief STBI-specific implementation of the vk::image interface + */ +class stb_image : public vk::image { +public: + stb_image() = delete; + + stb_image(std::string_view p_path, vk::texture_params p_params) { + image_load(p_path, p_params); + } + + ~stb_image() = default; + +protected: + bool image_load(std::string_view p_path, + vk::texture_params p_params) override { + int w = 0; + int h = 0; + int channels = 0; + + stbi_uc* image_pixel_data = + stbi_load(p_path.data(), &w, &h, &channels, STBI_rgb_alpha); + + if (!image_pixel_data) { + return false; + } + + const VkFormat texture_format = + static_cast(vk::format::r8g8b8a8_unorm); + int bytes_per_pixel = vk::bytes_per_texture_format(texture_format); + + m_extent = { + .width = static_cast(w), + .height = static_cast(h), + }; + + // Retrieving total size of bytes of the dimensions of the image and + // accounting for pixels of the image + uint32_t size_bytes = + m_extent.width * m_extent.height * bytes_per_pixel; + + // Retrieving total image size to the count of the image layers + uint32_t size = size_bytes * p_params.layer_count; + + vk::image_params image_options = { + .extent = m_extent, + .format = texture_format, + .memory_mask = p_params.memory_mask, + .usage = + vk::image_usage::transfer_dst_bit | vk::image_usage::sampled_bit, + .mip_levels = p_params.mip_levels, + .layer_count = p_params.layer_count, + }; + + m_bytes.reserve(size); + std::span bytes_view = + std::span(image_pixel_data, size); + + m_bytes.assign(bytes_view.begin(), bytes_view.end()); + + stbi_image_free(image_pixel_data); + + return true; + } + + std::span image_read() const override { return m_bytes; } + + vk::image_extent image_extent() const override { return m_extent; } + +private: + vk::image_extent m_extent{}; + std::vector m_bytes{}; +}; + +int +main() { + //! @note Just added the some test code to test the conan-starter setup code + if (!glfwInit()) { + std::print("glfwInit could not be initialized!\n"); + return -1; + } + + if (!glfwVulkanSupported()) { + std::print("GLFW: Vulkan is not supported!"); + return -1; + } + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + int width = 800; + int height = 600; + std::string title = "Hello Window"; + GLFWwindow* window = + glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr); + + glfwMakeContextCurrent(window); + + std::array validation_layers = { + "VK_LAYER_KHRONOS_validation", + }; + + // setting up extensions + std::vector global_extensions = get_instance_extensions(); + + vk::debug_message_utility debug_callback_info = { + // .severity essentially takes in vk::message::verbose, + // vk::message::warning, vk::message::error + .severity = + vk::message::verbose | vk::message::warning | vk::message::error, + // .message_type essentially takes in vk::debug. Like: + // vk::debug::general, vk::debug::validation, vk::debug::performance + .message_type = + vk::debug::general | vk::debug::validation | vk::debug::performance, + .callback = debug_callback + }; + + vk::application_params config = { + .name = "vulkan instance", + .version = vk::api_version::vk_1_3, // specify to using vulkan 1.3 + .validations = + validation_layers, // .validation takes in a std::span + .extensions = + global_extensions // .extensions also takes in std::span + }; + + // 1. Setting up vk instance + vk::instance api_instance(config, debug_callback_info); + + if (api_instance.alive()) { + std::println("\napi_instance alive and initiated!!!"); + } + + // setting up physical device + + std::expected physical_device_expected = + api_instance.enumerate_physical_device(vk::physical_gpu::integrated); + vk::physical_device physical_device = physical_device_expected.value(); + + // setting up logical device + std::array priorities = { 0.f }; + +#if defined(__APPLE__) + std::array extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + "VK_KHR_portability_subset", + }; +#else + std::array extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + }; +#endif + + std::array format_support = { + vk::format::d32_sfloat, + vk::format::d32_sfloat_s8_uint, + vk::format::d24_unorm_s8_uint + }; + + // We provide a selection of format support that we want to check is + // supported on current hardware device. + VkFormat depth_format = + physical_device.request_depth_format(format_support); + + vk::device_features device_features{ + vk::dynamic_rendering_feature{ { + .dynamicRendering = true, + } }, + vk::descriptor_indexing_feature{ { + .shaderSampledImageArrayNonUniformIndexing = true, + .descriptorBindingSampledImageUpdateAfterBind = true, + .descriptorBindingPartiallyBound = true, + .descriptorBindingVariableDescriptorCount = true, + .runtimeDescriptorArray = true, + } }, + vk::buffer_device_address{ { + .bufferDeviceAddress = true, + } }, + }; + + vk::device_params logical_device_params = { + .features = device_features.data(), + .queue_priorities = priorities, + .extensions = extensions, + // .queue_family_index = queue_indices.graphics, + .queue_family_index = 0, + }; + + vk::device logical_device(physical_device, logical_device_params); + + vk::surface window_surface(api_instance, window); + + vk::surface_params surface_properties = + physical_device.request_surface(window_surface); + + vk::swapchain_params enumerate_swapchain_settings = { + .width = static_cast(width), + .height = static_cast(height), + .present_index = 0, + }; + + vk::swapchain main_swapchain(logical_device, + window_surface, + enumerate_swapchain_settings, + surface_properties); + + if (!main_swapchain.alive()) { + std::println("main_swapchain is nullptr!!!"); + return -1; + } + + // querying presentable images + std::span images = main_swapchain.get_images(); + uint32_t image_count = static_cast(images.size()); + + // Creating Images + std::vector swapchain_images(image_count); + std::vector swapchain_depth_images(image_count); + + VkExtent2D swapchain_extent = surface_properties.capabilities.currentExtent; + + // Setting up the images + uint32_t layer_count = 1; + uint32_t mip_levels = 1; + for (uint32_t i = 0; i < swapchain_images.size(); i++) { + vk::image_params swapchain_image_config = { + .extent = { .width = swapchain_extent.width, + .height = swapchain_extent.height, }, + .format = surface_properties.format.format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), + .aspect = vk::image_aspect_flags::color_bit, + .usage = vk::image_usage::color_attachment_bit, + .mip_levels = 1, + .layer_count = 1, + }; + + swapchain_images[i] = + vk::sample_image(logical_device, images[i], swapchain_image_config); + + vk::image_params image_config = { + .extent = { + .width = swapchain_extent.width, + .height = swapchain_extent.height, + }, + .format = depth_format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), + .aspect = vk::image_aspect_flags::depth_bit, + .usage = vk::image_usage::depth_stencil_bit, + .mip_levels = 1, + .layer_count = 1, + }; + swapchain_depth_images[i] = + vk::sample_image(logical_device, image_config); + } + + // setting up command buffers + std::vector swapchain_command_buffers(image_count); + for (size_t i = 0; i < swapchain_command_buffers.size(); i++) { + vk::command_params settings = { + .levels = vk::command_levels::primary, + .queue_index = 0, + .flags = vk::command_pool_flags::reset, + }; + + swapchain_command_buffers[i] = + vk::command_buffer(logical_device, settings); + } + + // setting up presentation queue to display commands to the screen + vk::queue_params enumerate_present_queue{ + .family = 0, + .index = 0, + }; + vk::device_present_queue presentation_queue( + logical_device, main_swapchain, enumerate_present_queue); + + // gets set with the renderpass + std::array color = { 0.f, 0.5f, 0.5f, 1.f }; + + // Loading graphics pipeline + std::array shader_sources = { + vk::shader_source{ + .filename = + "shader_samples/sample9-buffer-device-address/test.vert.spv", + .stage = vk::shader_stage::vertex, + }, + vk::shader_source{ + .filename = + "shader_samples/sample9-buffer-device-address/test.frag.spv", + .stage = vk::shader_stage::fragment, + }, + }; + + // To render triangle, we do not need to set any vertex attributes + vk::shader_resource_info shader_info = { + .sources = shader_sources, + .vertex_attributes = {} // this is to explicitly set to none, but also + // dont need to set this at all regardless + }; + vk::shader_resource geometry_resource(logical_device, shader_info); + + // Setting up vertex attributes in the test shaders + std::array attribute_entries = { + vk::vertex_attribute_entry{ + .location = 0, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, position), + }, + vk::vertex_attribute_entry{ + .location = 1, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, color), + }, + vk::vertex_attribute_entry{ + .location = 2, + .format = vk::format::rg32_sfloat, + .stride = offsetof(vk::vertex_input, uv), + }, + vk::vertex_attribute_entry{ + .location = 3, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, normals), + } + }; + + std::array attributes = { + vk::vertex_attribute{ + .binding = 0, + .entries = attribute_entries, + .stride = sizeof(vk::vertex_input), + .input_rate = vk::input_rate::vertex, + }, + }; + geometry_resource.vertex_attributes(attributes); + + std::vector entries_set1 = { + vk::descriptor_entry{ + // layout (set = 0, binding = 1) uniform sampler2D textures[]; + .type = vk::descriptor_type::combined_image_sampler, + .binding_point = { + .binding = 1, + .stage = vk::shader_stage::fragment, + }, + .descriptor_count = 1, + .flags = vk::descriptor_bind_flags::partially_bound_bit | + vk::descriptor_bind_flags::variable_descriptor_count_bit | + vk::descriptor_bind_flags::update_after_bind, + } + }; + + uint32_t max_descriptor = 1; + vk::descriptor_layout set1_layout = { + .slot = 0, // indicate specific descriptor slot 0 + .max_sets = image_count, // max descriptors to allocate + .entries = entries_set1, // descriptor layout entries description + .descriptor_counts = std::span(&max_descriptor, 1), + }; + + vk::descriptor_resource set1_resource( + logical_device, + set1_layout, + vk::descriptor_layout_flags::update_after_bind_pool); + + std::array layouts = { + set1_resource.layout(), + }; + + std::array color_blend_attachments = { + vk::color_blend_attachment_state{}, + }; + + std::array dynamic_states = { + vk::dynamic_state::viewport, vk::dynamic_state::scissor + }; + + uint32_t format = static_cast(surface_properties.format.format); + uint32_t vertex_mask = static_cast(vk::shader_stage::vertex); + uint32_t fragment_mask = static_cast(vk::shader_stage::fragment); + uint32_t stage_mask = vertex_mask | fragment_mask; + vk::shader_stage stage = static_cast(stage_mask); + vk::push_constant_range range = { + .stage = stage, + .offset = 0, + .range = sizeof(push_constant_data), + }; + vk::pipeline_params pipeline_configuration = { + .use_render_pipeline = true, + .color_attachment_formats = std::span(&format, 1), + .depth_format = static_cast(depth_format), + .stencil_format = static_cast(depth_format), + .renderpass = nullptr, + .shader_modules = geometry_resource.handles(), + .vertex_attributes = geometry_resource.vertex_attributes(), + .vertex_bind_attributes = geometry_resource.vertex_bind_attributes(), + .descriptor_layouts = layouts, + .color_blend = { + .attachments = color_blend_attachments, + }, + .depth_stencil_enabled = true, + .dynamic_states = dynamic_states, + .push_constants = std::span(&range, 1), + }; + vk::pipeline main_graphics_pipeline(logical_device, pipeline_configuration); + + obj_model test_model(std::filesystem::path("asset_samples/viking_room.obj"), + logical_device, + physical_device); + + vk::buffer_parameters uniform_params = { + .memory_mask = + physical_device.memory_properties(static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit)), + .usage = vk::buffer_usage::uniform_buffer_bit | + vk::buffer_usage::shader_device_address_bit, + .allocate_flags = vk::memory_allocate_flags::device_address_bit_khr, + }; + vk::dyn::buffer test_ubo = + vk::dyn::buffer(logical_device, sizeof(global_uniform), uniform_params); + + vk::texture_params config_texture = { + .memory_mask = physical_device.memory_properties( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit), + }; + + stb_image img = stb_image("asset_samples/viking_room.png", config_texture); + vk::texture texture1(logical_device, &img, config_texture); + + // Setting the texture sampler/image view to descriptor resource + std::array samplers = { + vk::write_image{ + .sampler = texture1.image().sampler(), + .view = texture1.image().image_view(), + .layout = vk::image_layout::shader_read_only_optimal, + }, + }; + + std::array set1_samples = { + vk::write_image_descriptor{ + .dst_binding = 1, + .sample_images = samplers, + } + }; + + set1_resource.update({}, set1_samples); + + VkClearValue clear_color = { + { 0.f, 0.5f, 0.5f, 1.f }, + }; + + VkClearValue depth_value = { + .depthStencil = { .depth = 1.f, .stencil = 0 }, + }; + + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + + uint32_t current_frame = presentation_queue.acquire_next_image(); + vk::command_buffer current = swapchain_command_buffers[current_frame]; + + current.begin(vk::command_usage::simulatneous_use_bit); + + swapchain_images[current_frame].memory_barrier( + current, + surface_properties.format.format, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + // Because dynamic rendering does not automatically handle layout + // transitions These memory barriers set the color and depth images for + // the output + swapchain_depth_images[current_frame].memory_barrier( + current, + depth_format, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT); + + vk::rendering_attachment color_render_attachment = { + .image_view = swapchain_images[current_frame].image_view(), + .layout = vk::image_layout::color_optimal, + .resolve_mode = vk::resolved_mode_flags::none, + .resolve_image_view = nullptr, + .resolve_image_layout = vk::image_layout::undefined, + .load = vk::attachment_load::clear, + .store = vk::attachment_store::store, + .clear_values = clear_color + }; + + vk::rendering_attachment depth_stencil_attachment = { + .image_view = swapchain_depth_images[current_frame].image_view(), + .layout = vk::image_layout::depth_stencil_optimal, + .resolve_mode = vk::resolved_mode_flags::none, + .resolve_image_view = nullptr, + .resolve_image_layout = vk::image_layout::undefined, + .load = vk::attachment_load::clear, + .store = vk::attachment_store::store, + .depth_values = depth_value + }; + + vk::rendering_begin_parameters begin_params = { + .render_area = { { 0, 0 }, + { + swapchain_extent.width, + swapchain_extent.height, + }, }, + .layer_count = 1, + .color_attachments = std::span( + &color_render_attachment, 1), + .depth_attachment = depth_stencil_attachment, + .stencil_attachment = depth_stencil_attachment, + }; + + vk::viewport_params viewport = { + .x = 0.0f, + .y = 0.0f, + .width = static_cast(swapchain_extent.width), + .height = static_cast(swapchain_extent.height), + .min_depth = 0.0f, + .max_depth = 1.0f, + }; + current.set_viewport( + 0, 1, std::span(&viewport, 1)); + + vk::scissor_params scissor = { + .offset = { 0, 0 }, + .extent = swapchain_extent, + }; + + current.set_scissor( + 0, 1, std::span(&scissor, 1)); + + current.begin_rendering(begin_params); + + main_graphics_pipeline.bind(current); + + const VkBuffer vertex = test_model.vertex_handle(); + uint64_t offset = 0; + current.bind_vertex_buffers(std::span(&vertex, 1), + std::span(&offset, 1)); + + if (test_model.has_indices()) { + current.bind_index_buffers32(test_model.index_handle()); + } + + static auto start_time = std::chrono::high_resolution_clock::now(); + + auto current_time = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration( + current_time - start_time) + .count(); + + // We set the uniforms and then we offload that to the GPU + global_uniform ubo = { + .model = glm::rotate(glm::mat4(1.0f), + time * glm::radians(90.0f), + glm::vec3(0.0f, 0.0f, 1.0f)), + .view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), + glm::vec3(0.0f, 0.0f, 0.0f), + glm::vec3(0.0f, 0.0f, 1.0f)), + .proj = glm::perspective(glm::radians(45.0f), + (float)swapchain_extent.width / + (float)swapchain_extent.height, + 0.1f, + 10.0f) + }; + ubo.proj[1][1] *= -1; + + test_ubo.transfer( + std::span(&ubo, 1)); + + const uint64_t ubo_address = test_ubo.get_device_address(); + push_constant_data push = { + .texture_index = 0, + .global_ubo_addr = ubo_address, + }; + main_graphics_pipeline.push_constant( + current, push, stage, 0); + + const VkDescriptorSet set1 = set1_resource; + current.bind_descriptors(main_graphics_pipeline.layout(), + VK_PIPELINE_BIND_POINT_GRAPHICS, + std::span(&set1, 1)); + + test_model.draw(current); + + current.end_rendering(); + + swapchain_images[current_frame].memory_barrier( + current, + surface_properties.format.format, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + + current.end(); + + // Submitting and then presenting to the screen + std::array commands = { current }; + presentation_queue.submit_async(commands); + presentation_queue.present_frame(current_frame); + } + + logical_device.wait(); + main_swapchain.destruct(); + + texture1.destruct(); + set1_resource.destruct(); + test_ubo.reset(); + test_model.destruct(); + + geometry_resource.destruct(); + main_graphics_pipeline.destruct(); + + for (auto& command : swapchain_command_buffers) { + command.destruct(); + } + + for (auto& image : swapchain_images) { + image.destruct(); + } + + for (auto& image : swapchain_depth_images) { + image.destruct(); + } + + presentation_queue.destruct(); + + logical_device.destruct(); + window_surface.destruct(); + glfwDestroyWindow(window); + return 0; +} diff --git a/demos/17-buffer-device-address/conanfile.py b/demos/17-buffer-device-address/conanfile.py new file mode 100644 index 0000000..5d2a70c --- /dev/null +++ b/demos/17-buffer-device-address/conanfile.py @@ -0,0 +1,35 @@ +from conan import ConanFile +from conan.tools.cmake import CMake, cmake_layout + +class Demo(ConanFile): + name = "game-demo" + version = "1.0" + settings = "os", "compiler", "build_type", "arch" + generators = "CMakeDeps", "CMakeToolchain" + export_source = "CMakeLists.txt", "application.cpp" + + # Putting all of your build-related dependencies here + def build_requirements(self): + self.tool_requires("cmake/[^4.0.0]") + self.tool_requires("ninja/[^1.3.0]") + self.tool_requires("engine3d-cmake-utils/4.0") + + # Setting demo dependencies + def requirements(self): + self.requires("glfw/3.4") + self.requires("glm/1.0.1") + self.requires("stb/cci.20230920") + self.requires("tinyobjloader/2.0.0-rc10") + self.requires("vulkan-cpp/6.0") + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() + + def layout(self): + cmake_layout(self) \ No newline at end of file diff --git a/demos/2-physical-device/application.cpp b/demos/2-physical-device/application.cpp index 64fb99f..278cb3a 100644 --- a/demos/2-physical-device/application.cpp +++ b/demos/2-physical-device/application.cpp @@ -13,6 +13,7 @@ #include #include #include +#include import vk; static VKAPI_ATTR VkBool32 VKAPI_CALL @@ -103,18 +104,18 @@ main() { std::println("\napi_instance alive and initiated!!!"); } - // setting up physical device - vk::physical_enumeration enumerate_devices{ - .device_type = vk::physical_gpu::integrated - }; + // Selecting a specific physical device + std::expected physical_device_expected = + api_instance.enumerate_physical_device(vk::physical_gpu::integrated); + vk::physical_device physical_device = physical_device_expected.value(); - vk::physical_device device(api_instance, enumerate_devices); + std::println("Device name: {}", physical_device.properties().deviceName); while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } glfwDestroyWindow(window); - api_instance.destroy(); + api_instance.destruct(); return 0; } diff --git a/demos/2-physical-device/conanfile.py b/demos/2-physical-device/conanfile.py index 708fb66..eb977f5 100644 --- a/demos/2-physical-device/conanfile.py +++ b/demos/2-physical-device/conanfile.py @@ -22,7 +22,7 @@ def requirements(self): self.requires("glm/1.0.1") self.requires("stb/cci.20230920") self.requires("tinyobjloader/2.0.0-rc10") - self.requires("vulkan-cpp/5.0") + self.requires("vulkan-cpp/6.0") def build(self): cmake = CMake(self) diff --git a/demos/3-logical-device/README.md b/demos/3-logical-device/README.md index 64f58a8..590d3b6 100644 --- a/demos/3-logical-device/README.md +++ b/demos/3-logical-device/README.md @@ -76,7 +76,7 @@ vk::device logical_device(physical_device, logical_device_params); # Last Minute Notes -When you have a logical device. When your application closes, make sure to call `.wait()` and `.destroy()`. +When you have a logical device. When your application closes, make sure to call `.wait()` and `.destruct()`. Vulkan requires that all objects you create must be destroyed, deallocated, or freed before the logical device itself gets destroyed @@ -91,8 +91,8 @@ while(!glfwWindowShouldCLose(window)) { // .wait() calls vkDeviceWaitIdle(device) under the hood logical_device.wait(); -// .destroy() ensure the resources of the logical devices are cleaned up properly -logical_device.destroy(); +// .destruct() ensure the resources of the logical devices are cleaned up properly +logical_device.destruct(); ``` # Thats it! diff --git a/demos/3-logical-device/application.cpp b/demos/3-logical-device/application.cpp index 1631dff..05050cc 100644 --- a/demos/3-logical-device/application.cpp +++ b/demos/3-logical-device/application.cpp @@ -13,6 +13,7 @@ #include #include #include +#include import vk; static VKAPI_ATTR VkBool32 VKAPI_CALL @@ -104,32 +105,9 @@ main() { } // setting up physical device - vk::physical_enumeration enumerate_devices{ - .device_type = vk::physical_gpu::integrated, - }; - vk::physical_device physical_device(api_instance, enumerate_devices); - - // selecting depth format - std::array format_support = { - vk::format::d32_sfloat, - vk::format::d32_sfloat_s8_uint, - vk::format::d24_unorm_s8_uint - }; - - // We provide a selection of format support that we want to check is - // supported on current hardware device. - VkFormat depth_format = - vk::select_depth_format(physical_device, format_support); - - if (depth_format != VK_FORMAT_UNDEFINED) { - std::println("Depth format specifically was able to be found!!!"); - } - - vk::queue_indices queue_indices = physical_device.family_indices(); - std::println("Graphics Queue Family Index = {}", - (int)queue_indices.graphics); - std::println("Compute Queue Family Index = {}", queue_indices.compute); - std::println("Transfer Queue Family Index = {}", queue_indices.transfer); + std::expected physical_device_expected = + api_instance.enumerate_physical_device(vk::physical_gpu::integrated); + vk::physical_device physical_device = physical_device_expected.value(); // setting up logical device std::array priorities = { 0.f }; @@ -140,29 +118,28 @@ main() { std::array extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; #endif - vk::device_params logical_device_params = { + vk::device_params device_params = { .queue_priorities = priorities, .extensions = extensions, .queue_family_index = 0, }; - vk::device logical_device(physical_device, logical_device_params); + vk::device logical_device(physical_device, device_params); // Presentation queue family uses graphics queue vk::queue_params present_queue_enumerate = { .family = 0, - .index = queue_indices.graphics, + .index = 0, }; - vk::device_queue presesnt_queue(logical_device, present_queue_enumerate); + vk::device_queue present_queue(logical_device, present_queue_enumerate); while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } logical_device.wait(); - logical_device.destroy(); + logical_device.destruct(); glfwDestroyWindow(window); - api_instance.destroy(); return 0; } diff --git a/demos/3-logical-device/conanfile.py b/demos/3-logical-device/conanfile.py index 708fb66..eb977f5 100644 --- a/demos/3-logical-device/conanfile.py +++ b/demos/3-logical-device/conanfile.py @@ -22,7 +22,7 @@ def requirements(self): self.requires("glm/1.0.1") self.requires("stb/cci.20230920") self.requires("tinyobjloader/2.0.0-rc10") - self.requires("vulkan-cpp/5.0") + self.requires("vulkan-cpp/6.0") def build(self): cmake = CMake(self) diff --git a/demos/4-surface/application.cpp b/demos/4-surface/application.cpp index 3188b74..049c06d 100644 --- a/demos/4-surface/application.cpp +++ b/demos/4-surface/application.cpp @@ -13,6 +13,7 @@ #include #include #include +#include import vk; static VKAPI_ATTR VkBool32 VKAPI_CALL @@ -103,25 +104,10 @@ main() { std::println("\napi_instance alive and initiated!!!"); } - // setting up physical device - vk::physical_enumeration enumerate_devices{ - .device_type = vk::physical_gpu::integrated, - }; - vk::physical_device physical_device(api_instance, enumerate_devices); - - // selecting depth format - std::array format_support = { - vk::format::d32_sfloat, - vk::format::d32_sfloat_s8_uint, - vk::format::d24_unorm_s8_uint - }; - - // We provide a selection of format support that we want to check is - // supported on current hardware device. - VkFormat depth_format = - vk::select_depth_format(physical_device, format_support); - - vk::queue_indices queue_indices = physical_device.family_indices(); + // Selecting a specific physical device + std::expected physical_device_expected = + api_instance.enumerate_physical_device(vk::physical_gpu::integrated); + vk::physical_device physical_device = physical_device_expected.value(); // setting up logical device std::array priorities = { 0.f }; @@ -146,7 +132,7 @@ main() { // Presentation queue family uses graphics queue vk::queue_params present_queue_enumerate = { .family = 0, - .index = queue_indices.graphics, + .index = 0, }; vk::device_queue presesnt_queue(logical_device, present_queue_enumerate); @@ -155,10 +141,9 @@ main() { } logical_device.wait(); - logical_device.destroy(); + logical_device.destruct(); - window_surface.destroy(); + window_surface.destruct(); glfwDestroyWindow(window); - api_instance.destroy(); return 0; } diff --git a/demos/4-surface/conanfile.py b/demos/4-surface/conanfile.py index 708fb66..eb977f5 100644 --- a/demos/4-surface/conanfile.py +++ b/demos/4-surface/conanfile.py @@ -22,7 +22,7 @@ def requirements(self): self.requires("glm/1.0.1") self.requires("stb/cci.20230920") self.requires("tinyobjloader/2.0.0-rc10") - self.requires("vulkan-cpp/5.0") + self.requires("vulkan-cpp/6.0") def build(self): cmake = CMake(self) diff --git a/demos/5-swapchain/README.md b/demos/5-swapchain/README.md index 3ec4b76..7de0f7e 100644 --- a/demos/5-swapchain/README.md +++ b/demos/5-swapchain/README.md @@ -118,7 +118,7 @@ for(uint32_t i = 0; i < swapchain_images.size(); i++) { .extent = { .width=swapchain_extent.width, .height=swapchain_extent.height }, .format = surface_properties.format.format, .aspect = vk::image_aspect_flags::color_bit, - .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .usage = vk::image_usage::color_attachment_bit, .mip_levels = 1, .layer_count = 1, .phsyical_memory_properties = physical_device.memory_properties(), diff --git a/demos/5-swapchain/application.cpp b/demos/5-swapchain/application.cpp index be6d9a9..fdc244d 100644 --- a/demos/5-swapchain/application.cpp +++ b/demos/5-swapchain/application.cpp @@ -13,6 +13,7 @@ #include #include #include +#include import vk; static VKAPI_ATTR VkBool32 VKAPI_CALL @@ -107,20 +108,10 @@ main() { std::println("\napi_instance alive and initiated!!!"); } - // setting up physical device - vk::physical_enumeration enumerate_devices{ - .device_type = vk::physical_gpu::integrated, - }; - vk::physical_device physical_device(api_instance, enumerate_devices); - - // selecting depth format - std::array format_support = { - vk::format::d32_sfloat, - vk::format::d32_sfloat_s8_uint, - vk::format::d24_unorm_s8_uint - }; - - vk::queue_indices queue_indices = physical_device.family_indices(); + // Selecting a specific physical device + std::expected physical_device_expected = + api_instance.enumerate_physical_device(vk::physical_gpu::integrated); + vk::physical_device physical_device = physical_device_expected.value(); // setting up logical device std::array priorities = { 0.f }; @@ -135,7 +126,7 @@ main() { vk::device_params logical_device_params = { .queue_priorities = priorities, .extensions = extensions, - .queue_family_index = queue_indices.graphics, + .queue_family_index = 0, }; vk::device logical_device(physical_device, logical_device_params); @@ -143,14 +134,12 @@ main() { vk::surface window_surface(api_instance, window); vk::surface_params surface_properties = - vk::enumerate_surface(physical_device, window_surface); + physical_device.request_surface(window_surface); vk::swapchain_params enumerate_swapchain_settings = { .width = static_cast(width), .height = static_cast(height), - .present_index = - physical_device.family_indices() - .graphics, // presentation index just uses the graphics index + .present_index = 0, }; vk::swapchain main_swapchain(logical_device, window_surface, @@ -174,11 +163,12 @@ main() { .extent = { .width = swapchain_extent.width, .height = swapchain_extent.height }, .format = surface_properties.format.format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), .aspect = vk::image_aspect_flags::color_bit, .usage = vk::image_usage::color_attachment_bit, .mip_levels = 1, .layer_count = 1, - .phsyical_memory_properties = physical_device.memory_properties(), }; swapchain_images[i] = @@ -190,7 +180,7 @@ main() { for (size_t i = 0; i < swapchain_command_buffers.size(); i++) { vk::command_params settings = { .levels = vk::command_levels::primary, - .queue_index = enumerate_swapchain_settings.present_index, + .queue_index = 0, .flags = vk::command_pool_flags::reset, }; @@ -230,9 +220,6 @@ main() { vk::framebuffer(logical_device, framebuffer_info); } - std::println("Created VkFramebuffer's with size = {}", - swapchain_framebuffers.size()); - // setting up presentation queue to display commands to the screen vk::queue_params enumerate_present_queue{ .family = 0, @@ -254,13 +241,12 @@ main() { // renderpass begin/end must be within a recording command buffer vk::renderpass_begin_params begin_renderpass = { - .current_command = current, .extent = swapchain_extent, .current_framebuffer = swapchain_framebuffers[current_frame], .color = color, .subpass = vk::subpass_contents::inline_bit }; - main_renderpass.begin(begin_renderpass); + main_renderpass.begin(current, begin_renderpass); main_renderpass.end(current); current.end(); @@ -276,30 +262,25 @@ main() { // }); (???) // this to ensure they are cleaned up in the proper order logical_device.wait(); - main_swapchain.destroy(); + main_swapchain.destruct(); for (auto& command : swapchain_command_buffers) { - command.destroy(); + command.destruct(); } for (auto& fb : swapchain_framebuffers) { - fb.destroy(); + fb.destruct(); } for (auto& image : swapchain_images) { - image.destroy(); + image.destruct(); } - // for (auto& depth_image : swapchain_depth_images) { - // depth_image.destroy(); - // } - - main_renderpass.destroy(); - presentation_queue.destroy(); + main_renderpass.destruct(); + presentation_queue.destruct(); - logical_device.destroy(); - window_surface.destroy(); + logical_device.destruct(); + window_surface.destruct(); glfwDestroyWindow(window); - api_instance.destroy(); return 0; } diff --git a/demos/5-swapchain/conanfile.py b/demos/5-swapchain/conanfile.py index 708fb66..eb977f5 100644 --- a/demos/5-swapchain/conanfile.py +++ b/demos/5-swapchain/conanfile.py @@ -22,7 +22,7 @@ def requirements(self): self.requires("glm/1.0.1") self.requires("stb/cci.20230920") self.requires("tinyobjloader/2.0.0-rc10") - self.requires("vulkan-cpp/5.0") + self.requires("vulkan-cpp/6.0") def build(self): cmake = CMake(self) diff --git a/demos/6-graphics-pipeline/README.md b/demos/6-graphics-pipeline/README.md index 0132671..c59f055 100644 --- a/demos/6-graphics-pipeline/README.md +++ b/demos/6-graphics-pipeline/README.md @@ -8,11 +8,11 @@ The vulkan tutorial website goes over more in-depth of the responsibility of the # Process of the Graphics Pipeline -This section will briefly touch on the graphics pipeline and the process behind it. +In this section will briefly begin to introduce the graphics pipeline using the vulkan-cpp abstraction, `vk::pipeline` class. -This is the following graphics sequence diagram: +The sequence diagram shows stages for the graphics pipeline: -![NOTE] +> [!NOTE] > Credit to the vulkan web-page that can be seen used in this [link](https://vulkan-tutorial.com/Drawing_a_triangle/Graphics_pipeline_basics/Introduction) @@ -57,48 +57,43 @@ These normalized device coordinates are `homogeneous coordinates` that map the f # Graphics Pipeline Concepts -## Programmable Stages +## How to use `vk::pipeline`? -Diagrams marked green are process in the graphics pipeline you can upload your code to modify its parameters. +Now, in this section will show you how to setup the graphics pipeline using vulkan-cpp parameters to construct the `vk::pipeline` object. -Which involve: -* Offload code to the GPU -* Sending uniform data such as (textures, lighting, ray tracing, etc) -* Process data on multiple GPU cores simultaneously to process objects in parallel. +### What are optional parameters to use? +Noting the geometry and tesselation shader parameters are **optional** to be set if you do not plan to do operations that require you have computations involving the topology of your geometry. -## Optional Stages +Whereas the **fragment** shaders can be disabled in situations such as shadow-mapping generations. -These are stages that can be entirely optional. Optional because they are not required for a variety of different reasons. +# Constructing the actual Graphics Pipeline -Stages involved are: -* `geometry` and `tessellation` can be disabled if you plan to deal with simpler geometry. -* `fragment shaders` can be disabled, if you need depth values only. Useful for `shadow-mapping` generation. - -# Building the Graphics Pipeline - -![NOTE] +> [!NOTE] > `vk::pipeline` implementation is still in-progress as I have not exposed the other stages quite yet. That will be worked in exposing soon. -Previously in demo 5. We focused on how to setup the swapchain, command buffers, images, and framebuffers to make sure we can send some data to the screen. Which was changing the background color to a specific color. +Previously in demo 5 focused in setting up the swapchain, command buffers, images, and framebuffers to ensure we can at least be able to change the background color to a different color. -In this demo, we are going to try and get a triangle drawn to the screen. Which wont focus too much in getting transforms to work, we will focus in specifying the raw vertices to the GPU to draw the triangle. +To indicate the changes we have made are working with no additional errors from the validation layers. -Since the triangle, we will not be applying any transformations. We will specify the positions of the three vertices directly as normalized device coordinates to create the following shape: +Goal for this demo, where we setup the graphics pipeline is to finally get us a triangle to render to the screen. triangle_coordinates_colors -# Loading Shaders +## Loading Shaders using `vk::shader_resource` + +The graphics pipeline handle requires there to be a shader to be loaded with. This will load into the shader module handles using the `vk::shader_resource` abstraction. -In Vulkan before building the graphics pipeline. You need to specify both the source to the shader compiled code and the stage this shader is being compiled as. +The shader resource class helps with loading in the shaders and associating the stage and loading in N amount of handles with the shaders specified. -Since Vulkan does not have a runtime shader compiler that comes with it, you have to compile your glsl shader to spirv binary format. +You should be able to copy shaders, `test.vert` and `test.frag` glsl shaders into your project location. For the demos, which are contained in a folder called `shader_samples/sample1/`. -In this case, you'd compile `shader_samples/sample1/test.vert` to `shader_samples/sample1/test.vert.spv`. Same needs to happen compiling `sample1/test.frag` to `shader_samples/sample1/test.frag.spv` +Here are what each of the shaders contain: -## Vertex Shader +## Vertex Shader (Update) -In the next demo, we will actually use a vertex buffer to store these vertices. For now we will type in the raw vertices in the vertex shader. If you -look at the `shader_samples/sample1/test.vert` shader. As shown below: +In the next and incoming demos, we would typically have these vertices handled via vertex attributes. For getting this triangle to render. The next demo will cover vertex buffers that will show how to get vertex attributes working. + +For now, the vertices will be hardcoded. ```glsl #version 450 @@ -117,12 +112,9 @@ void main() { ## Fragment Shader -In the fragment shader is what forms using the position vertices you pass from the vertex shader. Then fills that area onto the screen with fragments. - -The fragment shader is involved in executing on those fragments to produce color and depth for the framebuffer (or framebuffers). - -A simple fragment shader outputs color gradient. Adding this into your vertex shader. +The fragment shader is what processes the vertices of your 3D geometry, in this case the triangle for this demo. +Here is what the current fragment shader code looks like: ```glsl #version 450 @@ -147,7 +139,7 @@ void main() { } ``` -In the fragment shader, add the following code: +In that same fragment shader, add the following code: ```glsl @@ -170,24 +162,29 @@ Where `layout(location = 0)` modifier specifies index of the framebuffer. The co ## Compiling Shaders -I have mentioned that previously vulkan does not know what to do with your shader source code and expects only the final binary blob that is after compiling the source code. -To compile the shaders to SPIRV-bytecode, you should already have glsl install and be able to reference similarily to your platform-specific file paths. +If you have used OpenGL, it would provide API's to automatically compile your shaders to `.spv`, BUT in Vulkan this responsibility will be on you. the developer. Therefore, before you can load in your shaders. You are going to need to compile your shaders to .spv + +For context, `.spv` is the binary format of the compiled-down shader code. ```bash glslc.exe shader_samples/sample1/test.vert -o shader_samples/sample1/test.vert.spv glslc.exe shader_samples/sample1/test.frag -o shader_samples/sample1/test.frag.spv ``` -## Finalized Setting up Shaders + + +## Setting up `vk::shader_source` -Now, that you have setup the glsl shaders to specify what data it should expect to render the triangle on the GPU. We will now continue to building the graphics pipeline. + -In vulkan-cpp you only need to specify the .spv shader sources and their stages. +Now, the shaders are compiled to `.spv`. In this section, I am going to show how to load the spirv compiled binaries. -Here is how you specify the shader sources: +Each `vk::shader_source` contains the location to the individual binaries along with the stage of the shader they are used for. + +This is important to distinction to know how these shaders are going to be utilized. ```C++ std::array shader_sources = { @@ -202,13 +199,11 @@ std::array shader_sources = { }; ``` -## Creating `vk::shader_resource` - -Before you create the shader_resource, you need to specify the `logical device` and the `vk::shader_resource_info`. +## Initializing the `vk::shader_resource` -The shader resource information are parameters required to be specified for the shader resource such as vertex attributes. +Now, the shader sources are already configured. Now use pass that into `vk::shader_resource_info`. -Here is how to specify the parameters: +For this demo, we will now initialize the `vk::shader_resource`. Firstly passing the `shader_info` parameters to the constructor. Followed by passing the logical device as the first parameter. ```C++ vk::shader_resource_info shader_info = { @@ -221,43 +216,96 @@ vk::shader_resource geometry_resource(logical_device, shader_info); The `vk::shader_resource` does allow you to set the vertex attributes as the following below. Though for the triangle, it will be brought up in the next few demos. +If you have no vertex attributes that needs to be set. Then you can completely ignore invoking this API. + +This is what the API looks like. In later demos, we will cover how you may setup the vertex attributes for `vk::shader_resource`. + ```C++ geoemetry_resource.vertex_attributes(...); ``` -## Creating the Graphics Pipeline +## Configuring the Graphics Pipeline -In vulkan-cpp, it is quite simple to create a graphics pipeline. vulkan-cpp was designed to be quite simple in the workflow for specifying work through Vulkan. This includes specifying the graphics pipeline. +In vulkan-cpp, I tried to consider ways of approaching the design for the graphics pipeline for simplifying steps needed to configure the graphics pipeline. -In the `vk::shader_resource` implementation is supposed to be wrapper for creating the raw `VkShaderModule` handles needed to be specified by the vulkan-cpp graphics pipeline. Specifically `vk::pipeline`. +Parmaters to configure `vk::pipeline` with: +* `VkRenderPass`. +* `span` +* `span` +* `span` -Now, that you have created the shader sources and loaded them into the vulkan shader handles. Here is how you setup the graphics pipeline. +Which are provided to you by the `vk::shader_resource` class. -The `vk::shader_resource` gives you access to the handles created. +### Parameters for `vk::pipeline` -These are handles returned in that order: +When configuring the graphics pipeline, there are an extensive amount of parameters. Which are set to default values that can be changed. -* span -* span -* span +These are the list of parameters that need to be set for the graphics pipeline set to default values. ```C++ -vk::pipeline_settings pipeline_configuration = { +struct pipeline_params { + VkRenderPass renderpass = nullptr; + std::span shader_modules{}; + std::span + vertex_attributes; + std::span + vertex_bind_attributes; + std::span descriptor_layouts; + + input_assembly_state input_assembly; + viewport_state viewport; + rasterization_state rasterization; + multisample_state multisample; + color_blend_state color_blend; + bool depth_stencil_enabled = false; + depth_stencil_state depth_stencil; + std::span dynamic_states = {}; +}; +``` + +For this demo, we are only going to be setting the color blend states and the dynamic states. + +### Color Blend States + +These parameters tell the rasterization (when enabled) how to access any color attachments during rendering operations. + +`vk::color_blend_attachment_state` defaults `.blend_enabled` to true. This can be used to enable alpha blending via transparency. + +### Dynamic States + +Enabling parameters `vk::dynamic_state::viewport` and `vk::dynamic_state::scissor`, this allows the graphics pipeline handles to handle dynamically changing when resizing the window and updating the graphics pipeline handles. + + +```C++ +// Sets up the default color blend attachment state +std::array color_blend_attachments = { + vk::color_blend_attachment_state{}, +}; + +// Setting up graphics pipeline to having a dynamic state +// So we are not needing to invalidate and have the driver be responsible for that. +std::array dynamic_states = { + vk::dynamic_state::viewport, vk::dynamic_state::scissor +}; + +vk::pipeline_params pipeline_params = { .renderpass = main_renderpass, .shader_modules = geometry_resource.handles(), .vertex_attributes = geometry_resource.vertex_attributes(), - .vertex_bind_attributes = geometry_resource.vertex_bind_attributes() + .vertex_bind_attributes = geometry_resource.vertex_bind_attributes(), + .color_blend = { + .attachments = color_blend_attachments, + }, + .dynamic_states = dynamic_states, }; -vk::pipeline main_graphics_pipeline(logical_device, pipeline_configuration); +vk::pipeline main_graphics_pipeline(logical_device, pipeline_params); ``` -# Finally Created the Graphics Pipeline +# Graphics Pipeline Instantiated -As soon the graphics pipeline as been created using `vk::pipeline`. This demo will introduce a few of the `vk::pipeline` API's. +Now the graphics pipeline has been initialized and to check you can print out using the `.alive` API to see if the handle was initialized successfully. As it should return true if it initialized successfully. -`vk::pipeline` gives you a `.bind(const VkCommandBuffer&)` API. This just means making a particular resource available to be used by the GPU. This is a common operation done in Computer Graphics. - -Here is the additional code to add in the mainloop: +Now, let us go ahead and start using the `vk::pipeline` API's, which includes calling the bind and draw call API to render our first triangle ```C++ @@ -266,22 +314,29 @@ while (!glfwWindowShouldClose(window)) { uint32_t current_frame = presentation_queue.acquire_next_image(); vk::command_buffer current = swapchain_command_buffers[current_frame]; - // Binding to make resources available and accessible by the GPU - // Add + // ... + // current.begin(...) + // main_renderpass.begin(current, ...) + + // NEW: Using graphics pipeline bind API to bind resource to the command buffer main_graphics_pipeline.bind(current); - // Draw call to render to the screen - // Add + // NEW: Add draw call here + // vkCmdDraw(VkCommandBuffer, vertexCount, instanceCount, firstVertex, firstInstance) vkCmdDraw(current, 3, 1, 0, 0); - // .... + // main_renderpass.end(current) + // current.end(); + // ... } ``` -# Cleanup +# Post Cleanup -After you have created the graphics pipeline. Do not forget to do the post cleanup after the mainloop call. +After creating the graphics pipeline, be sure to invoke the `.destroy` API to ensure the handles are destroyed properly. + +> [!NOTE] This will be replaced to fully using RAII to automate cleanup for your objects. ```C++ @@ -290,14 +345,16 @@ while (!glfwWindowShouldClose(window)) { } // make sure to destroy child objects of Vulkan before the actual logical device. -main_graphics_pipeline.destroy(); -geometry_resource.destroy(); +main_graphics_pipeline.destruct(); +geometry_resource.destruct(); -logical_device.destroy(); +logical_device.destruct(); ``` # Final Result -As soon those two lines of code have been added. This is what the final result should look like: +If you followed this demo tutorial correctly with no given issue. + +You should be able to see this triangle get rendered: Screenshot 2025-12-06 201645 diff --git a/demos/6-graphics-pipeline/application.cpp b/demos/6-graphics-pipeline/application.cpp index 7ee21e1..25c6262 100644 --- a/demos/6-graphics-pipeline/application.cpp +++ b/demos/6-graphics-pipeline/application.cpp @@ -13,6 +13,9 @@ #include #include #include +#include +#include + import vk; static VKAPI_ATTR VkBool32 VKAPI_CALL @@ -103,16 +106,10 @@ main() { std::println("\napi_instance alive and initiated!!!"); } - // setting up physical device - vk::physical_enumeration enumerate_devices{ - .device_type = vk::physical_gpu::discrete, - }; - -#if defined(__APPLE__) - enumerate_devices.device_type = vk::physical_gpu::integrated; -#endif - - vk::physical_device physical_device(api_instance, enumerate_devices); + // Selecting a specific physical device + std::expected physical_device_expected = + api_instance.enumerate_physical_device(vk::physical_gpu::integrated); + vk::physical_device physical_device = physical_device_expected.value(); // selecting depth format std::array format_support = { @@ -124,12 +121,7 @@ main() { // We provide a selection of format support that we want to check is // supported on current hardware device. VkFormat depth_format = - vk::select_depth_format(physical_device, format_support); - - vk::queue_indices queue_indices = physical_device.family_indices(); - std::println("Graphics Queue Family Index = {}", queue_indices.graphics); - std::println("Compute Queue Family Index = {}", queue_indices.compute); - std::println("Transfer Queue Family Index = {}", queue_indices.transfer); + physical_device.request_depth_format(format_support); // setting up logical device std::array priorities = { 0.f }; @@ -150,21 +142,14 @@ main() { vk::device logical_device(physical_device, logical_device_params); vk::surface window_surface(api_instance, window); - std::println("Starting implementation of the swapchain!!!"); vk::surface_params surface_properties = - vk::enumerate_surface(physical_device, window_surface); - - if (surface_properties.format.format != VK_FORMAT_UNDEFINED) { - std::println("Surface Format.format is not undefined!!!"); - } + physical_device.request_surface(window_surface); vk::swapchain_params enumerate_swapchain_settings = { - .width = (uint32_t)width, - .height = (uint32_t)height, - .present_index = - physical_device.family_indices() - .graphics, // presentation index just uses the graphics index + .width = static_cast(width), + .height = static_cast(height), + .present_index = 0 }; vk::swapchain main_swapchain(logical_device, window_surface, @@ -175,41 +160,29 @@ main() { std::span images = main_swapchain.get_images(); uint32_t image_count = static_cast(images.size()); - // Creating Images + // Creating images from vk::swapchain std::vector swapchain_images(image_count); - std::vector swapchain_depth_images(image_count); VkExtent2D swapchain_extent = surface_properties.capabilities.currentExtent; // Setting up the images for (uint32_t i = 0; i < swapchain_images.size(); i++) { vk::image_params swapchain_image_config = { - .extent = { .width = swapchain_extent.width, - .height = swapchain_extent.height }, + .extent = { + .width = swapchain_extent.width, + .height = swapchain_extent.height, + }, .format = surface_properties.format.format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), .aspect = vk::image_aspect_flags::color_bit, - .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .usage = vk::image_usage::color_attachment_bit, .mip_levels = 1, .layer_count = 1, - .phsyical_memory_properties = physical_device.memory_properties(), }; swapchain_images[i] = vk::sample_image(logical_device, images[i], swapchain_image_config); - - // Creating Depth Images for depth buffering - vk::image_params image_config = { - .extent = { .width = swapchain_extent.width, - .height = swapchain_extent.height }, - .format = depth_format, - .aspect = vk::image_aspect_flags::depth_bit, - .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, - .mip_levels = 1, - .layer_count = 1, - .phsyical_memory_properties = physical_device.memory_properties(), - }; - swapchain_depth_images[i] = - vk::sample_image(logical_device, image_config); } // setting up command buffers @@ -217,7 +190,7 @@ main() { for (size_t i = 0; i < swapchain_command_buffers.size(); i++) { vk::command_params settings = { .levels = vk::command_levels::primary, - .queue_index = enumerate_swapchain_settings.present_index, + .queue_index = 0, .flags = vk::command_pool_flags::reset, }; @@ -225,10 +198,9 @@ main() { vk::command_buffer(logical_device, settings); } - // setting up renderpass - + // Configuring a renderpass // setting up attachments for the renderpass - std::array renderpass_attachments = { + std::array renderpass_attachments = { vk::attachment{ .format = surface_properties.format.format, .layout = vk::image_layout::color_optimal, @@ -240,23 +212,10 @@ main() { .initial_layout = vk::image_layout::undefined, .final_layout = vk::image_layout::present_src_khr, }, - vk::attachment{ - .format = depth_format, - .layout = vk::image_layout::depth_stencil_optimal, - .samples = vk::sample_bit::count_1, - .load = vk::attachment_load::clear, - .store = vk::attachment_store::dont_care, - .stencil_load = vk::attachment_load::clear, - .stencil_store = vk::attachment_store::dont_care, - .initial_layout = vk::image_layout::undefined, - .final_layout = vk::image_layout::depth_stencil_read_only_optimal, - }, }; vk::renderpass main_renderpass(logical_device, renderpass_attachments); - std::println("renderpass created!!!"); - // Setting up swapchain framebuffers std::vector swapchain_framebuffers(image_count); for (uint32_t i = 0; i < swapchain_framebuffers.size(); i++) { @@ -267,8 +226,7 @@ main() { // ensure this is the case Since you have an image for color attachment // and another image for the depth atttachment to specify std::array - image_view_attachments = { swapchain_images[i].image_view(), - swapchain_depth_images[i].image_view() }; + image_view_attachments = { swapchain_images[i].image_view() }; vk::framebuffer_params framebuffer_info = { .renderpass = main_renderpass, @@ -279,9 +237,6 @@ main() { vk::framebuffer(logical_device, framebuffer_info); } - std::println("Created VkFramebuffer's with size = {}", - swapchain_framebuffers.size()); - // setting up presentation queue to display commands to the screen vk::queue_params enumerate_present_queue{ .family = 0, @@ -293,14 +248,16 @@ main() { // gets set with the renderpass std::array color = { 0.f, 0.5f, 0.5f, 1.f }; - std::println("Start implementing graphics pipeline!!!"); - // Now creating a vulkan graphics pipeline for the shader loading std::array shader_sources = { - vk::shader_source{ .filename = "shader_samples/sample1/test.vert.spv", - .stage = vk::shader_stage::vertex }, - vk::shader_source{ .filename = "shader_samples/sample1/test.frag.spv", - .stage = vk::shader_stage::fragment }, + vk::shader_source{ + .filename = "shader_samples/sample1/test.vert.spv", + .stage = vk::shader_stage::vertex, + }, + vk::shader_source{ + .filename = "shader_samples/sample1/test.frag.spv", + .stage = vk::shader_stage::fragment, + }, }; // To render triangle, we do not need to set any vertex attributes @@ -311,10 +268,6 @@ main() { }; vk::shader_resource geometry_resource(logical_device, shader_info); - if (geometry_resource.is_valid()) { - std::println("geometry resource is valid!"); - } - // Setting up descriptor sets for graphics pipeline std::array color_blend_attachments = { vk::color_blend_attachment_state{}, @@ -336,11 +289,6 @@ main() { }; vk::pipeline main_graphics_pipeline(logical_device, pipeline_configuration); - if (main_graphics_pipeline.alive()) { - std::println("Main graphics pipeline alive() = {}", - main_graphics_pipeline.alive()); - } - while (!glfwWindowShouldClose(window)) { glfwPollEvents(); @@ -351,13 +299,12 @@ main() { // renderpass begin/end must be within a recording command buffer vk::renderpass_begin_params begin_renderpass = { - .current_command = current, .extent = swapchain_extent, .current_framebuffer = swapchain_framebuffers[current_frame], .color = color, .subpass = vk::subpass_contents::inline_bit }; - main_renderpass.begin(begin_renderpass); + main_renderpass.begin(current, begin_renderpass); // Binding a graphics pipeline -- before drawing stuff // Inside of this graphics pipeline bind, is where you want to do the @@ -378,32 +325,27 @@ main() { // this to ensure they are cleaned up in the proper order logical_device.wait(); - main_swapchain.destroy(); + main_swapchain.destruct(); for (auto& command : swapchain_command_buffers) { - command.destroy(); + command.destruct(); } for (auto& fb : swapchain_framebuffers) { - fb.destroy(); + fb.destruct(); } for (auto& image : swapchain_images) { - image.destroy(); - } - - for (auto& depth_image : swapchain_depth_images) { - depth_image.destroy(); + image.destruct(); } - main_graphics_pipeline.destroy(); - geometry_resource.destroy(); - main_renderpass.destroy(); - presentation_queue.destroy(); + main_graphics_pipeline.destruct(); + geometry_resource.destruct(); + main_renderpass.destruct(); + presentation_queue.destruct(); - logical_device.destroy(); - window_surface.destroy(); + logical_device.destruct(); + window_surface.destruct(); glfwDestroyWindow(window); - api_instance.destroy(); return 0; } \ No newline at end of file diff --git a/demos/6-graphics-pipeline/conanfile.py b/demos/6-graphics-pipeline/conanfile.py index 708fb66..eb977f5 100644 --- a/demos/6-graphics-pipeline/conanfile.py +++ b/demos/6-graphics-pipeline/conanfile.py @@ -22,7 +22,7 @@ def requirements(self): self.requires("glm/1.0.1") self.requires("stb/cci.20230920") self.requires("tinyobjloader/2.0.0-rc10") - self.requires("vulkan-cpp/5.0") + self.requires("vulkan-cpp/6.0") def build(self): cmake = CMake(self) diff --git a/demos/7-vertex-buffer/application.cpp b/demos/7-vertex-buffer/application.cpp index 7fccc00..61b4d4e 100644 --- a/demos/7-vertex-buffer/application.cpp +++ b/demos/7-vertex-buffer/application.cpp @@ -13,6 +13,8 @@ #include #include #include +#include + import vk; static VKAPI_ATTR VkBool32 VKAPI_CALL @@ -103,33 +105,13 @@ main() { // 1. Setting up vk instance vk::instance api_instance(config, debug_callback_info); - if (api_instance.alive()) { - std::println("\napi_instance alive and initiated!!!"); - } - - // TODO: Implement this as a way to setup physical devices - // vk::enumerate_physical_devices(vk::instance) -> returns - // std::span - - // setting up physical device - // TODO: Probably enforce the use of - // vk::enumerate_physical_device({.device_type = - // vk::physical_gpu::discrete}) - vk::physical_enumeration enumerate_devices{ - .device_type = vk::physical_gpu::discrete, - }; - -#if defined(__APPLE__) - enumerate_devices.device_type = vk::physical_gpu::integrated; -#endif - - vk::physical_device physical_device(api_instance, enumerate_devices); + // Selecting a specific physical device + std::expected physical_device_expected = + api_instance.enumerate_physical_device(vk::physical_gpu::integrated); + vk::physical_device physical_device = physical_device_expected.value(); // selecting depth format std::array format_support = { - // VK_FORMAT_D32_SFLOAT, - // VK_FORMAT_D32_SFLOAT_S8_UINT, - // VK_FORMAT_D24_UNORM_S8_UINT, vk::format::d32_sfloat, vk::format::d32_sfloat_s8_uint, vk::format::d24_unorm_s8_uint @@ -138,19 +120,16 @@ main() { // We provide a selection of format support that we want to check is // supported on current hardware device. VkFormat depth_format = - vk::select_depth_format(physical_device, format_support); - - vk::queue_indices queue_indices = physical_device.family_indices(); - std::println("Graphics Queue Family Index = {}", queue_indices.graphics); - std::println("Compute Queue Family Index = {}", queue_indices.compute); - std::println("Transfer Queue Family Index = {}", queue_indices.transfer); + physical_device.request_depth_format(format_support); // setting up logical device std::array priorities = { 0.f }; #if defined(__APPLE__) - std::array extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, - "VK_KHR_portability_subset" }; + std::array extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + "VK_KHR_portability_subset", + }; #else std::array extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; #endif @@ -164,21 +143,14 @@ main() { vk::device logical_device(physical_device, logical_device_params); vk::surface window_surface(api_instance, window); - std::println("Starting implementation of the swapchain!!!"); vk::surface_params surface_properties = - vk::enumerate_surface(physical_device, window_surface); - - if (surface_properties.format.format != VK_FORMAT_UNDEFINED) { - std::println("Surface Format.format is not undefined!!!"); - } + physical_device.request_surface(window_surface); vk::swapchain_params enumerate_swapchain_settings = { - .width = (uint32_t)width, - .height = (uint32_t)height, - .present_index = - physical_device.family_indices() - .graphics, // presentation index just uses the graphics index + .width = static_cast(width), + .height = static_cast(height), + .present_index = 0, }; vk::swapchain main_swapchain(logical_device, window_surface, @@ -191,7 +163,6 @@ main() { // Creating Images std::vector swapchain_images(image_count); - std::vector swapchain_depth_images(image_count); VkExtent2D swapchain_extent = surface_properties.capabilities.currentExtent; @@ -200,31 +171,21 @@ main() { uint32_t mip_levels = 1; for (uint32_t i = 0; i < swapchain_images.size(); i++) { vk::image_params swapchain_image_config = { - .extent = { .width = swapchain_extent.width, - .height = swapchain_extent.height }, + .extent = { + .width = swapchain_extent.width, + .height = swapchain_extent.height, + }, .format = surface_properties.format.format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), .aspect = vk::image_aspect_flags::color_bit, - .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .usage = vk::image_usage::color_attachment_bit, .mip_levels = 1, .layer_count = 1, - .phsyical_memory_properties = physical_device.memory_properties() }; swapchain_images[i] = vk::sample_image(logical_device, images[i], swapchain_image_config); - - vk::image_params image_config = { - .extent = { .width = swapchain_extent.width, - .height = swapchain_extent.height }, - .format = depth_format, - .aspect = vk::image_aspect_flags::depth_bit, - .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, - .mip_levels = 1, - .layer_count = 1, - .phsyical_memory_properties = physical_device.memory_properties() - }; - swapchain_depth_images[i] = - vk::sample_image(logical_device, image_config); } // setting up command buffers @@ -232,7 +193,7 @@ main() { for (size_t i = 0; i < swapchain_command_buffers.size(); i++) { vk::command_params settings = { .levels = vk::command_levels::primary, - .queue_index = enumerate_swapchain_settings.present_index, + .queue_index = 0, .flags = vk::command_pool_flags::reset, }; @@ -240,10 +201,8 @@ main() { vk::command_buffer(logical_device, settings); } - // setting up renderpass - // setting up attachments for the renderpass - std::array renderpass_attachments = { + std::array renderpass_attachments = { vk::attachment{ .format = surface_properties.format.format, .layout = vk::image_layout::color_optimal, @@ -255,29 +214,13 @@ main() { .initial_layout = vk::image_layout::undefined, .final_layout = vk::image_layout::present_src_khr, }, - vk::attachment{ - .format = depth_format, - .layout = vk::image_layout::depth_stencil_optimal, - .samples = vk::sample_bit::count_1, - .load = vk::attachment_load::clear, - .store = vk::attachment_store::dont_care, - .stencil_load = vk::attachment_load::clear, - .stencil_store = vk::attachment_store::dont_care, - .initial_layout = vk::image_layout::undefined, - .final_layout = vk::image_layout::depth_stencil_read_only_optimal, - }, }; vk::renderpass main_renderpass(logical_device, renderpass_attachments); - std::println("renderpass created!!!"); - // Setting up swapchain framebuffers - std::vector swapchain_framebuffers(image_count); for (uint32_t i = 0; i < swapchain_framebuffers.size(); i++) { - // image_view_attachments.push_back(swapchain_images[i].view); - // image_view_attachments.push_back(swapchain_depth_images[i].view); // NOTE: This must match the amount of attachments the renderpass also // has to match the image_view attachment for per-framebuffers as well @@ -285,8 +228,9 @@ main() { // ensure this is the case Since you have an image for color attachment // and another image for the depth atttachment to specify std::array - image_view_attachments = { swapchain_images[i].image_view(), - swapchain_depth_images[i].image_view() }; + image_view_attachments = { + swapchain_images[i].image_view(), + }; vk::framebuffer_params framebuffer_info = { .renderpass = main_renderpass, @@ -297,9 +241,6 @@ main() { vk::framebuffer(logical_device, framebuffer_info); } - std::println("Created VkFramebuffer's with size = {}", - swapchain_framebuffers.size()); - // setting up presentation queue to display commands to the screen vk::queue_params enumerate_present_queue{ .family = 0, @@ -311,15 +252,17 @@ main() { // gets set with the renderpass std::array color = { 0.f, 0.5f, 0.5f, 1.f }; - std::println("Start implementing graphics pipeline!!!"); - // Now creating a vulkan graphics pipeline for the shader loading // We are using sample1 shaders which is just showing a triangle std::array shader_sources = { - vk::shader_source{ .filename = "shader_samples/sample1/test.vert.spv", - .stage = vk::shader_stage::vertex }, - vk::shader_source{ .filename = "shader_samples/sample1/test.frag.spv", - .stage = vk::shader_stage::fragment }, + vk::shader_source{ + .filename = "shader_samples/sample1/test.vert.spv", + .stage = vk::shader_stage::vertex, + }, + vk::shader_source{ + .filename = "shader_samples/sample1/test.frag.spv", + .stage = vk::shader_stage::fragment, + }, }; // To render triangle, we do not need to set any vertex attributes @@ -330,10 +273,6 @@ main() { }; vk::shader_resource geometry_resource(logical_device, shader_info); - if (geometry_resource.is_valid()) { - std::println("geometry resource is valid!"); - } - std::array color_blend_attachments = { vk::color_blend_attachment_state{}, }; @@ -355,11 +294,6 @@ main() { }; vk::pipeline main_graphics_pipeline(logical_device, pipeline_configuration); - if (main_graphics_pipeline.alive()) { - std::println("Main graphics pipeline alive() = {}", - main_graphics_pipeline.alive()); - } - // Setting up vertex buffer std::array vertices = { vk::vertex_input{ @@ -375,15 +309,17 @@ main() { .uv = { 1.f, 1.f }, } }; - vk::vertex_params vertex_info = { - .phsyical_memory_properties = physical_device.memory_properties(), - .vertices = vertices, - }; - vk::vertex_buffer test_vbo(logical_device, vertex_info); - if (test_vbo.alive()) { - std::println("Vertex Buffer Successfully is valid and working!!!"); - } + const auto property_flags = + static_cast(vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit); + vk::buffer_parameters vertex_params = { + .memory_mask = physical_device.memory_properties(property_flags), + .property_flags = vk::memory_property::device_local_bit, + .usage = vk::buffer_usage::transfer_dst_bit | + vk::buffer_usage::vertex_buffer_bit, + }; + vk::vertex_buffer test_vbo(logical_device, vertices, vertex_params); while (!glfwWindowShouldClose(window)) { glfwPollEvents(); @@ -395,13 +331,12 @@ main() { // renderpass begin/end must be within a recording command buffer vk::renderpass_begin_params begin_renderpass = { - .current_command = current, .extent = swapchain_extent, .current_framebuffer = swapchain_framebuffers[current_frame], .color = color, .subpass = vk::subpass_contents::inline_bit }; - main_renderpass.begin(begin_renderpass); + main_renderpass.begin(current, begin_renderpass); // Binding a graphics pipeline -- before drawing stuff // Inside of this graphics pipeline bind, is where you want to do the @@ -425,34 +360,29 @@ main() { // }); (???) // this to ensure they are cleaned up in the proper order logical_device.wait(); - main_swapchain.destroy(); + main_swapchain.destruct(); - test_vbo.destroy(); + test_vbo.destruct(); for (auto& command : swapchain_command_buffers) { - command.destroy(); + command.destruct(); } for (auto& fb : swapchain_framebuffers) { - fb.destroy(); + fb.destruct(); } for (auto& image : swapchain_images) { - image.destroy(); - } - - for (auto& depth_img : swapchain_depth_images) { - depth_img.destroy(); + image.destruct(); } - main_graphics_pipeline.destroy(); - geometry_resource.destroy(); - main_renderpass.destroy(); - presentation_queue.destroy(); + main_graphics_pipeline.destruct(); + geometry_resource.destruct(); + main_renderpass.destruct(); + presentation_queue.destruct(); - logical_device.destroy(); - window_surface.destroy(); + logical_device.destruct(); + window_surface.destruct(); glfwDestroyWindow(window); - api_instance.destroy(); return 0; } \ No newline at end of file diff --git a/demos/7-vertex-buffer/conanfile.py b/demos/7-vertex-buffer/conanfile.py index 708fb66..eb977f5 100644 --- a/demos/7-vertex-buffer/conanfile.py +++ b/demos/7-vertex-buffer/conanfile.py @@ -22,7 +22,7 @@ def requirements(self): self.requires("glm/1.0.1") self.requires("stb/cci.20230920") self.requires("tinyobjloader/2.0.0-rc10") - self.requires("vulkan-cpp/5.0") + self.requires("vulkan-cpp/6.0") def build(self): cmake = CMake(self) diff --git a/demos/8-index-uniform-buffers/application.cpp b/demos/8-index-uniform-buffers/application.cpp index a73f189..ebc3060 100644 --- a/demos/8-index-uniform-buffers/application.cpp +++ b/demos/8-index-uniform-buffers/application.cpp @@ -11,8 +11,10 @@ #endif #include +#include #include #include +#include import vk; static VKAPI_ATTR VkBool32 VKAPI_CALL @@ -103,27 +105,9 @@ main() { // 1. Setting up vk instance vk::instance api_instance(config, debug_callback_info); - if (api_instance.alive()) { - std::println("\napi_instance alive and initiated!!!"); - } - - // TODO: Implement this as a way to setup physical devices - // vk::enumerate_physical_devices(vk::instance) -> returns - // std::span - - // setting up physical device - // TODO: Probably enforce the use of - // vk::enumerate_physical_device({.device_type = - // vk::physical_gpu::discrete}) - vk::physical_enumeration enumerate_devices{ - .device_type = vk::physical_gpu::discrete, - }; - -#if defined(__APPLE__) - enumerate_devices.device_type = vk::physical_gpu::integrated; -#endif - - vk::physical_device physical_device(api_instance, enumerate_devices); + std::expected physical_device_expected = + api_instance.enumerate_physical_device(vk::physical_gpu::integrated); + vk::physical_device physical_device = physical_device_expected.value(); // selecting depth format std::array format_support = { @@ -135,19 +119,16 @@ main() { // We provide a selection of format support that we want to check is // supported on current hardware device. VkFormat depth_format = - vk::select_depth_format(physical_device, format_support); - - vk::queue_indices queue_indices = physical_device.family_indices(); - std::println("Graphics Queue Family Index = {}", queue_indices.graphics); - std::println("Compute Queue Family Index = {}", queue_indices.compute); - std::println("Transfer Queue Family Index = {}", queue_indices.transfer); + physical_device.request_depth_format(format_support); // setting up logical device std::array priorities = { 0.f }; #if defined(__APPLE__) - std::array extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, - "VK_KHR_portability_subset" }; + std::array extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + "VK_KHR_portability_subset", + }; #else std::array extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; #endif @@ -161,21 +142,14 @@ main() { vk::device logical_device(physical_device, logical_device_params); vk::surface window_surface(api_instance, window); - std::println("Starting implementation of the swapchain!!!"); vk::surface_params surface_properties = - vk::enumerate_surface(physical_device, window_surface); - - if (surface_properties.format.format != VK_FORMAT_UNDEFINED) { - std::println("Surface Format.format is not undefined!!!"); - } + physical_device.request_surface(window_surface); vk::swapchain_params enumerate_swapchain_settings = { - .width = (uint32_t)width, - .height = (uint32_t)height, - .present_index = - physical_device.family_indices() - .graphics, // presentation index just uses the graphics index + .width = static_cast(width), + .height = static_cast(height), + .present_index = 0, }; vk::swapchain main_swapchain(logical_device, window_surface, @@ -188,7 +162,6 @@ main() { // Creating Images std::vector swapchain_images(image_count); - std::vector swapchain_depth_images(image_count); VkExtent2D swapchain_extent = surface_properties.capabilities.currentExtent; @@ -200,30 +173,16 @@ main() { .extent = { .width = swapchain_extent.width, .height = swapchain_extent.height }, .format = surface_properties.format.format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), .aspect = vk::image_aspect_flags::color_bit, - .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .usage = vk::image_usage::color_attachment_bit, .mip_levels = 1, .layer_count = 1, - .phsyical_memory_properties = physical_device.memory_properties() }; swapchain_images[i] = vk::sample_image(logical_device, images[i], swapchain_image_config); - - // Creating Depth Images for depth buffering - vk::image_params image_config = { - .extent = { .width = swapchain_extent.width, - .height = swapchain_extent.height }, - .format = depth_format, - .aspect = vk::image_aspect_flags::depth_bit, - .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, - .mip_levels = 1, - .layer_count = 1, - // .physical_device = physical_device - .phsyical_memory_properties = physical_device.memory_properties() - }; - swapchain_depth_images[i] = - vk::sample_image(logical_device, image_config); } // setting up command buffers @@ -231,7 +190,7 @@ main() { for (size_t i = 0; i < swapchain_command_buffers.size(); i++) { vk::command_params settings = { .levels = vk::command_levels::primary, - .queue_index = enumerate_swapchain_settings.present_index, + .queue_index = 0, .flags = vk::command_pool_flags::reset, }; @@ -239,10 +198,8 @@ main() { vk::command_buffer(logical_device, settings); } - // setting up renderpass - // setting up attachments for the renderpass - std::array renderpass_attachments = { + std::array renderpass_attachments = { vk::attachment{ .format = surface_properties.format.format, .layout = vk::image_layout::color_optimal, @@ -254,38 +211,20 @@ main() { .initial_layout = vk::image_layout::undefined, .final_layout = vk::image_layout::present_src_khr, }, - vk::attachment{ - .format = depth_format, - .layout = vk::image_layout::depth_stencil_optimal, - .samples = vk::sample_bit::count_1, - .load = vk::attachment_load::clear, - .store = vk::attachment_store::dont_care, - .stencil_load = vk::attachment_load::clear, - .stencil_store = vk::attachment_store::dont_care, - .initial_layout = vk::image_layout::undefined, - .final_layout = vk::image_layout::depth_stencil_read_only_optimal, - }, }; vk::renderpass main_renderpass(logical_device, renderpass_attachments); - std::println("renderpass created!!!"); - // Setting up swapchain framebuffers - std::vector swapchain_framebuffers(image_count); for (uint32_t i = 0; i < swapchain_framebuffers.size(); i++) { - // image_view_attachments.push_back(swapchain_images[i].view); - // image_view_attachments.push_back(swapchain_depth_images[i].view); - // NOTE: This must match the amount of attachments the renderpass also // has to match the image_view attachment for per-framebuffers as well // I just set the size to whatever the renderpass attachment size are to // ensure this is the case Since you have an image for color attachment // and another image for the depth atttachment to specify std::array - image_view_attachments = { swapchain_images[i].image_view(), - swapchain_depth_images[i].image_view() }; + image_view_attachments = { swapchain_images[i].image_view() }; vk::framebuffer_params framebuffer_info = { .renderpass = main_renderpass, @@ -296,9 +235,6 @@ main() { vk::framebuffer(logical_device, framebuffer_info); } - std::println("Created VkFramebuffer's with size = {}", - swapchain_framebuffers.size()); - // setting up presentation queue to display commands to the screen vk::queue_params enumerate_present_queue{ .family = 0, @@ -310,26 +246,30 @@ main() { // gets set with the renderpass std::array color = { 0.f, 0.5f, 0.5f, 1.f }; - std::println("Start implementing graphics pipeline!!!"); - - // Now creating a vulkan graphics pipeline for the shader loading + // Specifying which shaders to load into shader sources std::array shader_sources = { - vk::shader_source{ .filename = "shader_samples/sample2/test.vert.spv", - .stage = vk::shader_stage::vertex }, - vk::shader_source{ .filename = "shader_samples/sample2/test.frag.spv", - .stage = vk::shader_stage::fragment }, + vk::shader_source{ + .filename = "shader_samples/sample2/test.vert.spv", + .stage = vk::shader_stage::vertex, + }, + vk::shader_source{ + .filename = "shader_samples/sample2/test.frag.spv", + .stage = vk::shader_stage::fragment, + }, }; // Setting up vertex attributes in the test shaders std::array attribute_entries = { - vk::vertex_attribute_entry{ .location = 0, - .format = vk::format::rg32_sfloat, - .stride = - offsetof(vk::vertex_input, position) }, - vk::vertex_attribute_entry{ .location = 1, - .format = vk::format::rgb32_sfloat, - .stride = - offsetof(vk::vertex_input, color) } + vk::vertex_attribute_entry{ + .location = 0, + .format = vk::format::rg32_sfloat, + .stride = offsetof(vk::vertex_input, position), + }, + vk::vertex_attribute_entry{ + .location = 1, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, color), + } }; std::array attributes = { @@ -352,18 +292,6 @@ main() { vk::shader_resource geometry_resource(logical_device, shader_info); geometry_resource.vertex_attributes(attributes); - if (geometry_resource.is_valid()) { - std::println("geometry resource is valid!"); - } - - /* - // This get_pipeline_configuration can work as an easy way for - specfying the vulkan configurations as an ease of setting things up - // TODO: Probably provide a shorthand - which could work as this: - vk::pipeline_settings pipeline_configuration = - vk::get_pipeline_configuration(main_renderpass, geometry_resource); - */ - std::array color_blend_attachments = { vk::color_blend_attachment_state{}, }; @@ -385,62 +313,59 @@ main() { }; vk::pipeline main_graphics_pipeline(logical_device, pipeline_configuration); - if (main_graphics_pipeline.alive()) { - std::println("Main graphics pipeline alive() = {}", - main_graphics_pipeline.alive()); - } - // Setting up vertex buffer - // std::array vertices = { - // vk::vertex_input{ - // {1.f, 1.f, 1.f}, - // {1.f, 1.f, 1.f}, - // {1.f, 1.f, 1.f}, - // {1.f, 1.f}, - // }, - // vk::vertex_input{ - // {1.f, 1.f, 1.f}, - // {1.f, 1.f, 1.f}, - // {1.f, 1.f, 1.f}, - // {1.f, 1.f}, - // } - // }; std::array vertices = { - vk::vertex_input{ .position = { -0.5f, -0.5f, 0.f }, - .color = { 1.0f, 0.0f, 0.0f } }, - vk::vertex_input{ .position = { 0.5f, -0.5f, 0.f }, - .color = { 0.0f, 1.0f, 0.0f } }, - vk::vertex_input{ .position = { 0.5f, 0.5f, 0.f }, - .color = { 0.0f, 0.0f, 1.0f } }, - vk::vertex_input{ .position = { -0.5f, 0.5f, 0.f }, - .color = { 1.0f, 1.0f, 1.0f } } - }; - // vk::vertex_buffer_info vertex_info = { - // .physical_handle = physical_device, - // .vertices = vertices, - // }; - - vk::vertex_params vertex_info = { - .phsyical_memory_properties = physical_device.memory_properties(), - .vertices = vertices, + vk::vertex_input{ + .position = { -0.5f, -0.5f, 0.f }, + .color = { 1.0f, 0.0f, 0.0f }, + }, + vk::vertex_input{ + .position = { 0.5f, -0.5f, 0.f }, + .color = { 0.0f, 1.0f, 0.0f }, + }, + vk::vertex_input{ + .position = { 0.5f, 0.5f, 0.f }, + .color = { 0.0f, 0.0f, 1.0f }, + }, + vk::vertex_input{ + .position = { -0.5f, 0.5f, 0.f }, + .color = { 1.0f, 1.0f, 1.0f }, + }, }; - vk::vertex_buffer test_vbo(logical_device, vertex_info); - std::println("vertex_buffer.alive() = {}", test_vbo.alive()); - std::array indices = { 0, 1, 2, 2, 3, 0 }; + const auto property_flags = + static_cast(vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit); - vk::index_params index_info = { - .phsyical_memory_properties = physical_device.memory_properties(), - .indices = indices, + // Creating vertex buffers + vk::buffer_parameters vertex_params = { + .memory_mask = physical_device.memory_properties(property_flags), + .property_flags = vk::memory_property::device_local_bit, + .usage = vk::buffer_usage::transfer_dst_bit | + vk::buffer_usage::vertex_buffer_bit, }; - vk::index_buffer test_ibo(logical_device, index_info); - std::println("index_buffer.alive() = {}", test_ibo.alive()); + vk::vertex_buffer test_vbo(logical_device, vertices, vertex_params); - vk::uniform_params ubo_info = { .phsyical_memory_properties = - physical_device.memory_properties(), - .size_bytes = sizeof(vk::vertex_input) }; - vk::uniform_buffer test_ubo(logical_device, ubo_info); - std::println("uniform_buffer.alive() = {}", test_ubo.alive()); + // Creating index buffer + std::array indices = { 0, 1, 2, 2, 3, 0 }; + vk::buffer_parameters index_params = { + .memory_mask = physical_device.memory_properties(property_flags), + .property_flags = static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit), + .usage = vk::buffer_usage::index_buffer_bit, + }; + vk::index_buffer test_ibo(logical_device, indices, index_params); + + vk::buffer_parameters uniform_params = { + .memory_mask = + physical_device.memory_properties(static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit)), + .usage = vk::buffer_usage::uniform_buffer_bit, + }; + vk::uniform_buffer test_ubo( + logical_device, sizeof(vk::vertex_input), uniform_params); while (!glfwWindowShouldClose(window)) { glfwPollEvents(); @@ -452,24 +377,27 @@ main() { // renderpass begin/end must be within a recording command buffer vk::renderpass_begin_params begin_renderpass = { - .current_command = current, .extent = swapchain_extent, .current_framebuffer = swapchain_framebuffers[current_frame], .color = color, .subpass = vk::subpass_contents::inline_bit }; - main_renderpass.begin(begin_renderpass); + main_renderpass.begin(current, begin_renderpass); // Binding a graphics pipeline -- before drawing stuff // Inside of this graphics pipeline bind, is where you want to do the // drawing stuff to main_graphics_pipeline.bind(current); - test_vbo.bind(current); - test_ibo.bind(current); + const VkBuffer vertex = test_vbo; + uint64_t offset = 0; + current.bind_vertex_buffers(std::span(&vertex, 1), + std::span(&offset, 1)); + + if (!indices.empty()) { + current.bind_index_buffers32(test_ibo); + } - // Drawing-call to render actual triangle to the screen - // vkCmdDraw(current, 3, 1, 0, 0); vkCmdDrawIndexed( current, static_cast(indices.size()), 1, 0, 0, 0); @@ -482,41 +410,33 @@ main() { presentation_queue.present_frame(current_frame); } - // TODO: Make the cleanup much saner. For now we are cleaning it up like - // Potentially bring back submit_resource_free([this](){ .. free stuff .. - // }); (???) - // this to ensure they are cleaned up in the proper order + // Performing Vulkan cleanup logical_device.wait(); - main_swapchain.destroy(); + main_swapchain.destruct(); - test_ubo.destroy(); - test_ibo.destroy(); - test_vbo.destroy(); + test_ubo.destruct(); + test_ibo.destruct(); + test_vbo.destruct(); for (auto& command : swapchain_command_buffers) { - command.destroy(); + command.destruct(); } for (auto& fb : swapchain_framebuffers) { - fb.destroy(); + fb.destruct(); } for (auto& image : swapchain_images) { - image.destroy(); - } - - for (auto& depth_img : swapchain_depth_images) { - depth_img.destroy(); + image.destruct(); } - main_graphics_pipeline.destroy(); - geometry_resource.destroy(); - main_renderpass.destroy(); - presentation_queue.destroy(); + main_graphics_pipeline.destruct(); + geometry_resource.destruct(); + main_renderpass.destruct(); + presentation_queue.destruct(); - logical_device.destroy(); - window_surface.destroy(); + logical_device.destruct(); + window_surface.destruct(); glfwDestroyWindow(window); - api_instance.destroy(); return 0; } \ No newline at end of file diff --git a/demos/8-index-uniform-buffers/conanfile.py b/demos/8-index-uniform-buffers/conanfile.py index 708fb66..eb977f5 100644 --- a/demos/8-index-uniform-buffers/conanfile.py +++ b/demos/8-index-uniform-buffers/conanfile.py @@ -22,7 +22,7 @@ def requirements(self): self.requires("glm/1.0.1") self.requires("stb/cci.20230920") self.requires("tinyobjloader/2.0.0-rc10") - self.requires("vulkan-cpp/5.0") + self.requires("vulkan-cpp/6.0") def build(self): cmake = CMake(self) diff --git a/demos/9-uniforms/application.cpp b/demos/9-uniforms/application.cpp index cb8de36..0598bfe 100644 --- a/demos/9-uniforms/application.cpp +++ b/demos/9-uniforms/application.cpp @@ -13,6 +13,7 @@ #include #include #include +#include import vk; #include @@ -107,22 +108,12 @@ main() { global_extensions // .extensions also takes in std::span }; - // 1. Setting up vk instance + // Creating VkInstance vk::instance api_instance(config, debug_callback_info); - if (api_instance.alive()) { - std::println("\napi_instance alive and initiated!!!"); - } - - vk::physical_enumeration enumerate_devices{ - .device_type = vk::physical_gpu::discrete, - }; - -#if defined(__APPLE__) - enumerate_devices.device_type = vk::physical_gpu::integrated; -#endif - - vk::physical_device physical_device(api_instance, enumerate_devices); + std::expected physical_device_expected = + api_instance.enumerate_physical_device(vk::physical_gpu::integrated); + vk::physical_device physical_device = physical_device_expected.value(); // selecting depth format std::array format_support = { @@ -134,19 +125,16 @@ main() { // We provide a selection of format support that we want to check is // supported on current hardware device. VkFormat depth_format = - vk::select_depth_format(physical_device, format_support); - - vk::queue_indices queue_indices = physical_device.family_indices(); - std::println("Graphics Queue Family Index = {}", queue_indices.graphics); - std::println("Compute Queue Family Index = {}", queue_indices.compute); - std::println("Transfer Queue Family Index = {}", queue_indices.transfer); + physical_device.request_depth_format(format_support); // setting up logical device std::array priorities = { 0.f }; #if defined(__APPLE__) - std::array extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, - "VK_KHR_portability_subset" }; + std::array extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + "VK_KHR_portability_subset", + }; #else std::array extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; #endif @@ -160,21 +148,14 @@ main() { vk::device logical_device(physical_device, logical_device_params); vk::surface window_surface(api_instance, window); - std::println("Starting implementation of the swapchain!!!"); vk::surface_params surface_properties = - vk::enumerate_surface(physical_device, window_surface); - - if (surface_properties.format.format != VK_FORMAT_UNDEFINED) { - std::println("Surface Format.format is not undefined!!!"); - } + physical_device.request_surface(window_surface); vk::swapchain_params enumerate_swapchain_settings = { - .width = (uint32_t)width, - .height = (uint32_t)height, - .present_index = - physical_device.family_indices() - .graphics, // presentation index just uses the graphics index + .width = static_cast(width), + .height = static_cast(height), + .present_index = 0, }; vk::swapchain main_swapchain(logical_device, window_surface, @@ -187,7 +168,6 @@ main() { // Creating Images std::vector swapchain_images(image_count); - std::vector swapchain_depth_images(image_count); VkExtent2D swapchain_extent = surface_properties.capabilities.currentExtent; @@ -196,32 +176,21 @@ main() { uint32_t mip_levels = 1; for (uint32_t i = 0; i < swapchain_images.size(); i++) { vk::image_params swapchain_image_config = { - .extent = { .width = swapchain_extent.width, - .height = swapchain_extent.height }, + .extent = { + .width = swapchain_extent.width, + .height = swapchain_extent.height, + }, .format = surface_properties.format.format, + .memory_mask = physical_device.memory_properties( + vk::memory_property::device_local_bit), .aspect = vk::image_aspect_flags::color_bit, - .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .usage = vk::image_usage::color_attachment_bit, .mip_levels = 1, .layer_count = 1, - // .physical_device = physical_device - .phsyical_memory_properties = physical_device.memory_properties() }; swapchain_images[i] = vk::sample_image(logical_device, images[i], swapchain_image_config); - - vk::image_params image_config = { - .extent = { .width = swapchain_extent.width, - .height = swapchain_extent.height }, - .format = depth_format, - .aspect = vk::image_aspect_flags::depth_bit, - .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, - .mip_levels = 1, - .layer_count = 1, - .phsyical_memory_properties = physical_device.memory_properties() - }; - swapchain_depth_images[i] = - vk::sample_image(logical_device, image_config); } // setting up command buffers @@ -229,7 +198,7 @@ main() { for (size_t i = 0; i < swapchain_command_buffers.size(); i++) { vk::command_params settings = { .levels = vk::command_levels::primary, - .queue_index = enumerate_swapchain_settings.present_index, + .queue_index = 0, .flags = vk::command_pool_flags::reset, }; @@ -240,7 +209,7 @@ main() { // setting up renderpass // setting up attachments for the renderpass - std::array renderpass_attachments = { + std::array renderpass_attachments = { vk::attachment{ .format = surface_properties.format.format, .layout = vk::image_layout::color_optimal, @@ -252,23 +221,10 @@ main() { .initial_layout = vk::image_layout::undefined, .final_layout = vk::image_layout::present_src_khr, }, - vk::attachment{ - .format = depth_format, - .layout = vk::image_layout::depth_stencil_optimal, - .samples = vk::sample_bit::count_1, - .load = vk::attachment_load::clear, - .store = vk::attachment_store::dont_care, - .stencil_load = vk::attachment_load::clear, - .stencil_store = vk::attachment_store::dont_care, - .initial_layout = vk::image_layout::undefined, - .final_layout = vk::image_layout::depth_stencil_read_only_optimal, - }, }; vk::renderpass main_renderpass(logical_device, renderpass_attachments); - std::println("renderpass created!!!"); - // Setting up swapchain framebuffers std::vector swapchain_framebuffers(image_count); @@ -280,8 +236,7 @@ main() { // ensure this is the case Since you have an image for color attachment // and another image for the depth atttachment to specify std::array - image_view_attachments = { swapchain_images[i].image_view(), - swapchain_depth_images[i].image_view() }; + image_view_attachments = { swapchain_images[i].image_view() }; vk::framebuffer_params framebuffer_info = { .renderpass = main_renderpass, @@ -292,9 +247,6 @@ main() { vk::framebuffer(logical_device, framebuffer_info); } - std::println("Created VkFramebuffer's with size = {}", - swapchain_framebuffers.size()); - // setting up presentation queue to display commands to the screen vk::queue_params enumerate_present_queue{ .family = 0, @@ -306,26 +258,30 @@ main() { // gets set with the renderpass std::array color = { 0.f, 0.5f, 0.5f, 1.f }; - std::println("Start implementing graphics pipeline!!!"); - // Now creating a vulkan graphics pipeline for the shader loading std::array shader_sources = { - vk::shader_source{ .filename = "shader_samples/sample3/test.vert.spv", - .stage = vk::shader_stage::vertex }, - vk::shader_source{ .filename = "shader_samples/sample3/test.frag.spv", - .stage = vk::shader_stage::fragment }, + vk::shader_source{ + .filename = "shader_samples/sample3/test.vert.spv", + .stage = vk::shader_stage::vertex, + }, + vk::shader_source{ + .filename = "shader_samples/sample3/test.frag.spv", + .stage = vk::shader_stage::fragment, + }, }; // Setting up vertex attributes in the test shaders std::array attribute_entries = { - vk::vertex_attribute_entry{ .location = 0, - .format = vk::format::rg32_sfloat, - .stride = - offsetof(vk::vertex_input, position) }, - vk::vertex_attribute_entry{ .location = 1, - .format = vk::format::rgb32_sfloat, - .stride = - offsetof(vk::vertex_input, color) } + vk::vertex_attribute_entry{ + .location = 0, + .format = vk::format::rg32_sfloat, + .stride = offsetof(vk::vertex_input, position), + }, + vk::vertex_attribute_entry{ + .location = 1, + .format = vk::format::rgb32_sfloat, + .stride = offsetof(vk::vertex_input, color), + }, }; std::array attributes = { @@ -348,15 +304,11 @@ main() { vk::shader_resource geometry_resource(logical_device, shader_info); geometry_resource.vertex_attributes(attributes); - if (geometry_resource.is_valid()) { - std::println("geometry resource is valid!"); - } - // Setting up descriptor entries for descriptor set 0 std::vector entries = { vk::descriptor_entry{ // specifies "layout (set = 0, binding = 0) uniform GlobalUbo" - .type = vk::buffer::uniform, + .type = vk::descriptor_type::uniform, .binding_point = { .binding = 0, .stage = vk::shader_stage::vertex, @@ -371,22 +323,10 @@ main() { .max_sets = image_count, // max of descriptor sets able to allocate .entries = entries, // specifies pool sizes and descriptor layout }; - - // Setting up the actual descriptor set 0 and array to pass into the - // graphics pipeline since graphics pipeline requires the descriptor layouts - // to be known upfront vk::descriptor_resource set0_resource(logical_device, set0_layout); - // Array of descriptor layouts to give the graphics pipeline std::array layouts = { set0_resource.layout() }; - /* - This get_pipeline_configuration can work as an easy way for specfying - the vulkan configurations as an ease of setting things up - // TODO: Probably provide a shorthand - which could work as this: - vk::pipeline_settings pipeline_configuration = - vk::get_pipeline_configuration(main_renderpass, geometry_resource); - */ std::array color_blend_attachments = { vk::color_blend_attachment_state{}, }; @@ -409,56 +349,68 @@ main() { }; vk::pipeline main_graphics_pipeline(logical_device, pipeline_configuration); - if (main_graphics_pipeline.alive()) { - std::println("Main graphics pipeline alive() = {}", - main_graphics_pipeline.alive()); - } - // Setting up vertex buffer std::array vertices = { - vk::vertex_input{ .position = { -0.5f, -0.5f, 0.f }, - .color = { 1.0f, 0.0f, 0.0f } }, - vk::vertex_input{ .position = { 0.5f, -0.5f, 0.f }, - .color = { 0.0f, 1.0f, 0.0f } }, - vk::vertex_input{ .position = { 0.5f, 0.5f, 0.f }, - .color = { 0.0f, 0.0f, 1.0f } }, - vk::vertex_input{ .position = { -0.5f, 0.5f, 0.f }, - .color = { 1.0f, 1.0f, 1.0f } } + vk::vertex_input{ + .position = { -0.5f, -0.5f, 0.f }, + .color = { 1.0f, 0.0f, 0.0f }, + }, + vk::vertex_input{ + .position = { 0.5f, -0.5f, 0.f }, + .color = { 0.0f, 1.0f, 0.0f }, + }, + vk::vertex_input{ + .position = { 0.5f, 0.5f, 0.f }, + .color = { 0.0f, 0.0f, 1.0f }, + }, + vk::vertex_input{ + .position = { -0.5f, 0.5f, 0.f }, + .color = { 1.0f, 1.0f, 1.0f }, + } }; - vk::vertex_params vertex_info = { - // .physical_handle = physical_device, - .phsyical_memory_properties = physical_device.memory_properties(), - .vertices = vertices, + const auto property_flags = + static_cast(vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit); + + // Creating vertex buffer + vk::buffer_parameters vertex_params = { + .memory_mask = physical_device.memory_properties(property_flags), + .property_flags = vk::memory_property::device_local_bit, + .usage = vk::buffer_usage::transfer_dst_bit | + vk::buffer_usage::vertex_buffer_bit, }; - vk::vertex_buffer test_vbo(logical_device, vertex_info); - std::println("vertex_buffer.alive() = {}", test_vbo.alive()); + vk::vertex_buffer test_vbo(logical_device, vertices, vertex_params); + // Creating index buffer std::array indices = { 0, 1, 2, 2, 3, 0 }; - - vk::index_params index_info = { - .phsyical_memory_properties = physical_device.memory_properties(), - .indices = indices, + vk::buffer_parameters index_params = { + .memory_mask = physical_device.memory_properties(property_flags), + .property_flags = static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit), + .usage = vk::buffer_usage::index_buffer_bit, }; - vk::index_buffer test_ibo(logical_device, index_info); - std::println("index_buffer.alive() = {}", test_ibo.alive()); - - // Setting up our uniformss specifications and updating descriptor set 0 -- - // with our global uniform data before we bind This has to be done before we - // bind so the shader resource knows how to look up our data layout and see - // if they match Will get validation layer error messages if the data layout - // does not match (meaning size_bytes doesn't match) - vk::uniform_params test_ubo_info = { // .physical_handle = physical_device, - .phsyical_memory_properties = - physical_device.memory_properties(), - .size_bytes = sizeof(global_uniform) + vk::index_buffer test_ibo(logical_device, indices, index_params); + + // Creating uniform buffer + vk::buffer_parameters uniform_params = { + .memory_mask = + physical_device.memory_properties(static_cast( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit)), + .usage = vk::buffer_usage::uniform_buffer_bit, }; - vk::uniform_buffer test_ubo = - vk::uniform_buffer(logical_device, test_ubo_info); - std::println("uniform_buffer.alive() = {}", test_ubo.alive()); + vk::uniform_buffer test_ubo = vk::uniform_buffer( + logical_device, sizeof(global_uniform), uniform_params); // vk::write_buffer_descriptor - std::array uniforms0 = { vk::write_buffer{ - .buffer = test_ubo, .offset = 0, .range = test_ubo.size_bytes() } }; + std::array uniforms0 = { + vk::write_buffer{ + .buffer = test_ubo, + .offset = 0, + .range = static_cast(test_ubo.size_bytes()), + }, + }; std::array uniforms = { vk::write_buffer_descriptor{ .dst_binding = 0, .uniforms = uniforms0 } @@ -475,21 +427,23 @@ main() { // renderpass begin/end must be within a recording command buffer vk::renderpass_begin_params begin_renderpass = { - .current_command = current, .extent = swapchain_extent, .current_framebuffer = swapchain_framebuffers[current_frame], .color = color, .subpass = vk::subpass_contents::inline_bit }; - main_renderpass.begin(begin_renderpass); + main_renderpass.begin(current, begin_renderpass); - // Binding a graphics pipeline -- before drawing stuff - // Inside of this graphics pipeline bind, is where you want to do the - // drawing stuff to main_graphics_pipeline.bind(current); - test_vbo.bind(current); - test_ibo.bind(current); + const VkBuffer vertex = test_vbo; + uint64_t offset = 0; + current.bind_vertex_buffers(std::span(&vertex, 1), + std::span(&offset, 1)); + + if (!indices.empty()) { + current.bind_index_buffers32(test_ibo); + } static auto start_time = std::chrono::high_resolution_clock::now(); @@ -513,14 +467,21 @@ main() { 10.0f) }; ubo.proj[1][1] *= -1; - test_ubo.update(&ubo); + test_ubo.transfer( + std::span(&ubo, 1)); // Before we can send stuff to the GPU, since we already updated the // descriptor set 0 beforehand, we must bind that descriptor resource // before making any of the draw calls Something to note: You cannot // update descriptor sets in the process of a current-recording command // buffers or else that becomes undefined behavior - set0_resource.bind(current, main_graphics_pipeline.layout()); + // set0_resource.bind(current, main_graphics_pipeline.layout()); + std::array descriptors = { + set0_resource, + }; + current.bind_descriptors(main_graphics_pipeline.layout(), + VK_PIPELINE_BIND_POINT_GRAPHICS, + descriptors); // Drawing-call to render actual triangle to the screen // vkCmdDraw(current, 3, 1, 0, 0); @@ -541,37 +502,32 @@ main() { // }); (???) // this to ensure they are cleaned up in the proper order logical_device.wait(); - main_swapchain.destroy(); + main_swapchain.destruct(); - set0_resource.destroy(); - test_ubo.destroy(); - test_ibo.destroy(); - test_vbo.destroy(); + set0_resource.destruct(); + test_ubo.destruct(); + test_ibo.destruct(); + test_vbo.destruct(); for (auto& command : swapchain_command_buffers) { - command.destroy(); + command.destruct(); } for (auto& fb : swapchain_framebuffers) { - fb.destroy(); + fb.destruct(); } for (auto& image : swapchain_images) { - image.destroy(); - } - - for (auto& depth_img : swapchain_depth_images) { - depth_img.destroy(); + image.destruct(); } - main_graphics_pipeline.destroy(); - geometry_resource.destroy(); - main_renderpass.destroy(); - presentation_queue.destroy(); + main_graphics_pipeline.destruct(); + geometry_resource.destruct(); + main_renderpass.destruct(); + presentation_queue.destruct(); - logical_device.destroy(); - window_surface.destroy(); + logical_device.destruct(); + window_surface.destruct(); glfwDestroyWindow(window); - api_instance.destroy(); return 0; } \ No newline at end of file diff --git a/demos/9-uniforms/conanfile.py b/demos/9-uniforms/conanfile.py index 708fb66..eb977f5 100644 --- a/demos/9-uniforms/conanfile.py +++ b/demos/9-uniforms/conanfile.py @@ -22,7 +22,7 @@ def requirements(self): self.requires("glm/1.0.1") self.requires("stb/cci.20230920") self.requires("tinyobjloader/2.0.0-rc10") - self.requires("vulkan-cpp/5.0") + self.requires("vulkan-cpp/6.0") def build(self): cmake = CMake(self) diff --git a/shader_samples/sample5/test.frag b/shader_samples/sample5/test.frag index 1eab06c..a702d37 100644 --- a/shader_samples/sample5/test.frag +++ b/shader_samples/sample5/test.frag @@ -6,7 +6,7 @@ layout(location = 2) in vec3 fragNormals; layout(location = 0) out vec4 outColor; -layout(set = 0, binding = 1) uniform sampler2D texture_image; +layout(set = 1, binding = 0) uniform sampler2D texture_image; void main() { // outColor = vec4(fragColor, 1.0); diff --git a/shader_samples/sample5/test.frag.spv b/shader_samples/sample5/test.frag.spv index 8cc2250..f25d057 100644 Binary files a/shader_samples/sample5/test.frag.spv and b/shader_samples/sample5/test.frag.spv differ diff --git a/shader_samples/sample5/test.vert.spv b/shader_samples/sample5/test.vert.spv index dbe7dc6..6d951c8 100644 Binary files a/shader_samples/sample5/test.vert.spv and b/shader_samples/sample5/test.vert.spv differ diff --git a/shader_samples/sample7-skybox/skybox.frag b/shader_samples/sample7-skybox/skybox.frag new file mode 100644 index 0000000..ffcc2b8 --- /dev/null +++ b/shader_samples/sample7-skybox/skybox.frag @@ -0,0 +1,56 @@ +// #version 450 + +// layout (location=0) in vec3 TexCoords; + +// layout (location=0) out vec4 out_Color; + +// layout(set = 0, binding = 1) uniform samplerCube cubeSampler; + +// void main() { +// out_Color = texture(cubeSampler, TexCoords); +// } +#version 450 + +layout (location=0) in vec3 TexCoords; // Direction vector from vertex shader + +layout (location=0) out vec4 out_Color; + +layout(set = 0, binding = 1) uniform sampler2D cubeSampler; + +// Constants to convert direction to spherical UVs +const vec2 invAtan = vec2(0.1591, 0.3183); // 1.0 / (2.0 * PI), 1.0 / PI + +vec2 SampleEquirectangular(vec3 v) { + // Normalize the input direction + // We only need to grab the point of the vector + vec3 direction = normalize(v); + + // Calculate spherical coordinates + // atan(direction.z, direction.x) = atan(-PI, PI) + // atan(z, x) returns -PI to PI + // asin(y) returns -PI/2 to PI/2 + + vec2 uv = vec2(atan(direction.z, direction.x), asin(direction.y)); + + // Inverses the uv to be between the [0.0, 1.0] range + uv *= invAtan; + uv += 0.5; + return uv; +} + +void main() { + // Get the 2D UV coordinate from the 3D direction + vec2 uv = SampleEquirectangular(TexCoords); + + // Sample the HDR map + vec3 color = texture(cubeSampler, uv).rgb; + + // Simple Reinhard tone mapping (HDR values can be > 1.0, + // so we must compress them to [0, 1] for the screen) + color = color / (color + vec3(1.0)); + + // Gamma correction + color = pow(color, vec3(1.0/2.2)); + + out_Color = vec4(color, 1.0); +} \ No newline at end of file diff --git a/shader_samples/sample7-skybox/skybox.frag.spv b/shader_samples/sample7-skybox/skybox.frag.spv new file mode 100644 index 0000000..1dd6d54 Binary files /dev/null and b/shader_samples/sample7-skybox/skybox.frag.spv differ diff --git a/shader_samples/sample7-skybox/skybox.vert b/shader_samples/sample7-skybox/skybox.vert new file mode 100644 index 0000000..48f1d72 --- /dev/null +++ b/shader_samples/sample7-skybox/skybox.vert @@ -0,0 +1,36 @@ +// #version 460 + +// layout(location = 0) in vec3 inPosition; +// layout(location = 1) in vec3 inColor; +// layout(location = 2) in vec3 inNormals; +// layout(location = 3) in vec2 inTexCoords; + +// layout (location=0) out vec3 TexCoords; + +// layout (set = 0, binding = 0) readonly uniform UniformBuffer { +// mat4 view_proj; +// } ubo; + +// void main() { +// TexCoords = inPosition; +// vec4 pos = ubo.view_proj * vec4(inPosition, 1.0); +// gl_Position = pos.xyww; +// } +#version 460 + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inColor; +layout(location = 2) in vec3 inNormals; +layout(location = 3) in vec2 inTexCoords; + +layout (location=0) out vec3 TexCoords; + +layout (set = 0, binding = 0) readonly uniform UniformBuffer { + mat4 view_proj; +} ubo; + +void main() { + TexCoords = inPosition; + vec4 pos = ubo.view_proj * vec4(inPosition, 1.0); + gl_Position = pos.xyww; +} \ No newline at end of file diff --git a/shader_samples/sample7-skybox/skybox.vert.spv b/shader_samples/sample7-skybox/skybox.vert.spv new file mode 100644 index 0000000..d76d5b2 Binary files /dev/null and b/shader_samples/sample7-skybox/skybox.vert.spv differ diff --git a/shader_samples/sample8-descriptor-indexing/test.frag b/shader_samples/sample8-descriptor-indexing/test.frag new file mode 100644 index 0000000..44fad90 --- /dev/null +++ b/shader_samples/sample8-descriptor-indexing/test.frag @@ -0,0 +1,22 @@ +#version 450 + +#extension GL_EXT_nonuniform_qualifier : require + +layout(location = 0) in vec3 fragColor; +layout(location = 1) in vec2 fragTexCoords; +layout(location = 2) in vec3 fragNormals; +layout(location = 3) in flat int fragTexIndex; + +layout(location = 0) out vec4 outColor; + +// layout(set = 1, binding = 0) uniform sampler2D texture_image; +layout(set = 1, binding = 0) uniform sampler2D textures[]; + +void main() { + // outColor = vec4(fragColor, 1.0); + + // Adding texture + // outColor = texture(texture_image, fragTexCoords); + outColor = texture(textures[nonuniformEXT(fragTexIndex)], fragTexCoords); + +} \ No newline at end of file diff --git a/shader_samples/sample8-descriptor-indexing/test.frag.spv b/shader_samples/sample8-descriptor-indexing/test.frag.spv new file mode 100644 index 0000000..76f3cbe Binary files /dev/null and b/shader_samples/sample8-descriptor-indexing/test.frag.spv differ diff --git a/shader_samples/sample8-descriptor-indexing/test.vert b/shader_samples/sample8-descriptor-indexing/test.vert new file mode 100644 index 0000000..a05f65d --- /dev/null +++ b/shader_samples/sample8-descriptor-indexing/test.vert @@ -0,0 +1,30 @@ +#version 450 + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inColor; +layout(location = 2) in vec2 inTexCoords; +layout(location = 3) in vec3 inNormals; + +layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec2 fragTexCoords; +layout(location = 2) out vec3 fragNormals; +layout(location = 3) out flat int fragTexIndex; + + +layout(push_constant) uniform Constants { + int texture_index; +} push_const; + +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); + fragColor = inColor; + fragTexCoords = inTexCoords; + fragNormals = inNormals; + fragTexIndex = push_const.texture_index; +} \ No newline at end of file diff --git a/shader_samples/sample8-descriptor-indexing/test.vert.spv b/shader_samples/sample8-descriptor-indexing/test.vert.spv new file mode 100644 index 0000000..1f72704 Binary files /dev/null and b/shader_samples/sample8-descriptor-indexing/test.vert.spv differ diff --git a/shader_samples/sample9-buffer-device-address/test.frag b/shader_samples/sample9-buffer-device-address/test.frag new file mode 100644 index 0000000..0a98c20 --- /dev/null +++ b/shader_samples/sample9-buffer-device-address/test.frag @@ -0,0 +1,17 @@ +#version 450 + +#extension GL_EXT_nonuniform_qualifier : require + +layout(location = 0) in vec3 fragColor; +layout(location = 1) in vec2 fragTexCoords; +layout(location = 2) in vec3 fragNormals; +layout(location = 3) in flat int fragTexIndex; + +layout(location = 0) out vec4 outColor; + +layout(set = 0, binding = 1) uniform sampler2D textures[]; + +void main() { + + outColor = texture(textures[nonuniformEXT(fragTexIndex)], fragTexCoords); +} \ No newline at end of file diff --git a/shader_samples/sample9-buffer-device-address/test.frag.spv b/shader_samples/sample9-buffer-device-address/test.frag.spv new file mode 100644 index 0000000..7cc2604 Binary files /dev/null and b/shader_samples/sample9-buffer-device-address/test.frag.spv differ diff --git a/shader_samples/sample9-buffer-device-address/test.vert b/shader_samples/sample9-buffer-device-address/test.vert new file mode 100644 index 0000000..c270eec --- /dev/null +++ b/shader_samples/sample9-buffer-device-address/test.vert @@ -0,0 +1,36 @@ +#version 450 + +#extension GL_EXT_buffer_reference : require + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inColor; +layout(location = 2) in vec2 inTexCoords; +layout(location = 3) in vec3 inNormals; + +layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec2 fragTexCoords; +layout(location = 2) out vec3 fragNormals; +layout(location = 3) out flat int fragTexIndex; + +layout(buffer_reference, std140) buffer readonly UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +}; + +layout(push_constant) uniform Constants { + int texture_index; + UniformBufferObject global_ubo_address; +} push_const; + + + +void main() { + UniformBufferObject ubo = push_const.global_ubo_address; + + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); + fragColor = inColor; + fragTexCoords = inTexCoords; + fragNormals = inNormals; + fragTexIndex = push_const.texture_index; +} \ No newline at end of file diff --git a/shader_samples/sample9-buffer-device-address/test.vert.spv b/shader_samples/sample9-buffer-device-address/test.vert.spv new file mode 100644 index 0000000..fcb1ab4 Binary files /dev/null and b/shader_samples/sample9-buffer-device-address/test.vert.spv differ diff --git a/vulkan-cpp/buffer.cppm b/vulkan-cpp/buffer.cppm new file mode 100644 index 0000000..8ac0ce2 --- /dev/null +++ b/vulkan-cpp/buffer.cppm @@ -0,0 +1,298 @@ +module; + +#include +#include +#include +#include + +export module vk:buffer; + +export import :types; +export import :utilities; + +export namespace vk { + inline namespace v6 { + /** + * @brief Represents a VkBuffer handler for creating VkBuffer handle + * + * Purpose for using VkBuffer handle to streaming bytes of data into the + * GPU memory + * + */ + class buffer { + public: + buffer() = default; + + /** + * @brief constructs a buffer to write streams of data to GPU + * memory + * + * @param p_device is the logical device to construct the buffer + * handles + * @param p_device_size is size in bytes of the buffer to be created + * @param p_params are additional parameters for the buffer + * handles + */ + buffer(const VkDevice& p_device, + uint64_t p_device_size, + const buffer_parameters& p_params) + : m_device(p_device) { + construct(p_device_size, p_params); + } + + ~buffer() = default; + + void construct(uint64_t p_device_size, + const buffer_parameters& p_params) { + VkBufferCreateInfo buffer_ci = { + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = p_device_size, // size in bytes + .usage = static_cast(p_params.usage), + .sharingMode = p_params.share_mode, + }; + + vk_check( + vkCreateBuffer(m_device, &buffer_ci, nullptr, &m_handle), + "vkCreateBuffer"); + + // retrieving buffer memory requirements + VkMemoryRequirements memory_requirements = {}; + vkGetBufferMemoryRequirements( + m_device, m_handle, &memory_requirements); + uint32_t mapped_memory_requirements = + memory_requirements.memoryTypeBits & p_params.memory_mask; + uint32_t memory_index = + std::countr_zero(mapped_memory_requirements); + + VkMemoryAllocateInfo memory_alloc_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = memory_requirements.size, + .memoryTypeIndex = memory_index + }; + +#if _DEBUG + // 1. Define the structure + VkDebugUtilsObjectNameInfoEXT debug_info = { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, + .pNext = nullptr, + .objectType = VK_OBJECT_TYPE_BUFFER, + .objectHandle = (uint64_t) + m_handle, // specify vulkan to what object handle this is + .pObjectName = + p_params.debug_name // specify what type of buffer this is + }; + + if (p_params.vkSetDebugUtilsObjectNameEXT != nullptr) { + // vkSetDebugUtilsObjectNameEXT(m_device, &debug_info); + p_params.vkSetDebugUtilsObjectNameEXT(m_device, + &debug_info); + } +#endif + vk_check( + vkAllocateMemory( + m_device, &memory_alloc_info, nullptr, &m_device_memory), + "vkAllocateMemory"); + + // 5. bind memory resource of this buffer handle + vk_check( + vkBindBufferMemory(m_device, m_handle, m_device_memory, 0), + "vkBindBufferMemory"); + } + + /** + * @brief writes an arbitrary amount of uniforms of type T + * + * Performs runtime assertion checks if the bytes of type T are + * valid before mapping the uniforms for GPU to access + * + * Example Usage: + * + * ```C++ + * vk::uniform_buffer test_ubo(...); + * std::array ubo = { ... }; + * + * test_ubo.transfer(ubo); + * ``` + */ + template + void transfer(std::span p_in_data, uint32_t p_offset = 0) { + void* mapped = nullptr; + vk_check(vkMapMemory(m_device, + m_device_memory, + p_offset, + p_in_data.size_bytes(), + 0, + &mapped), + "vkMapMemory"); + memcpy(mapped, p_in_data.data(), p_in_data.size_bytes()); + vkUnmapMemory(m_device, m_device_memory); + } + + /** + * @brief writing uniforms that are represented into bytes + * @param p_data are the bytes to allow GPU to access + * Example Usage: + * + * ```C++ + * buffers staging_buffer(logical_device, ...); + * + * std::array white_color = { 0xFF, 0xFF, 0xFF, 0xFF }; + * staging_buffer.transfer(white_color); + * ``` + * + */ + void transfer(std::span p_data, + uint32_t p_offset = 0) { + void* mapped = nullptr; + vk_check(vkMapMemory(m_device, + m_device_memory, + p_offset, + p_data.size_bytes(), + 0, + &mapped), + "vkMapMemory"); + memcpy(mapped, p_data.data(), p_data.size_bytes()); + vkUnmapMemory(m_device, m_device_memory); + } + + /** + * @brief Transfers CPU-accessible data from the staging buffer to + * the GPU resource image. + * + * This API requires a command buffer to perform memory copying from + * a staging buffer to an image, performing any necessary format + * conversion or sizzling. + * + * Where swizzling is the transformation of pixel data to a more + * linear format in the given staging buffer to a more optimized + * non-linear layout using the vk::image_tiling_optimal + * + * @param p_command is the current command to record to perform this + * operation with + * @param p_image is the CPU-accessible data to be transferred to + * @param p_copies regions defining copy operations. Each entry + * specify: + * - .offset: starting byte for the staging buffer. + * - .row_length: Length of pixels in the buffer (0 tightly packed). + * - .aspect_mask: Part of the image target (color/depth/stencil). + * - .mip_level: Mipmap levels to update for LOD (level-of-detail). + * - .base_array_layer: Starting layer (face index for cubemaps). + * - .layer_count: Count of layers to copy into. + * - .image_offset: {x, y, z} starting coordinate within the image. + * - .image_extent: {w, h, d} size of the region to be updated. + * + * @brief Requirements when performing this operation + * + * - p_image: Must be in vk::image_layout::transfer_dst_optimal + * before command is in record state. Use pipeline barriers to + * transition before performing this operation. + * - source buffer: must be specified with + * vk::buffer_usage::transfer_src_bit, + * - dst image: must have vk::image_usage::transfer_dst_bit + * specified. + * - alignment: .offset must be a multiple of texel size (e.g., 4 + * for RGBA). Other hardware require to be a multiple of 4. + * - (.image_offset + .image_extent): Must not exceed actual + * dimensions of the image handle. + * - Command buffer must be from a queue family which supports + * transfer and graphics operation. + * + * @brief Pixels of data stored in the staging buffer before + * transferring to GPU-accessible memory. + * + * [ bytes 0 .......................................... bytes N ] + * |--- Region[0] Data --- | (Unused) | --- Region[1] --- | (etc.) + * \______________________/ \__________________________/ | + * | Copy Instruction 0 | Copy Instruction 1 + * | (p_copies[0]) | (p_copies[1]) + * V V + * @brief Destination for the GPU-resource Image + * + * Each vk::buffer_image_copy defines the specific parts of an image + * that is being transferred for being accessible to the GPU + * + * Each designated copy region is designated to different parts of a + * given image. Which wou + * + * +----------------------------------------------------+ + * | Mip Level 0: | + * | +--------------+ | + * | | [ Region 0 ] | <-- Maps from p_copies[0].offset | + * | +--------------+ | + * | | + * | Mip Level 1: | + * | +------------- | + * | | [ Region 1] | <-- Maps from p_copies[1].offset | + * | +--------------+ | + * +----------------------------------------------------+ + * + * + * Example Usage: + * + * ```C++ + * + * vk::buffer staging_buffer(logical_device, {...}); + * + * std::array copy_regions = { + * vk::buffer_image_copy{ + * .image_extent = { width, height, .depth=1 }, + * }, + * }; + * staging_buffer.copy_to_image(command, image, copy_regions); + * + * ``` + */ + void copy_to_image(const VkCommandBuffer& p_command, + const VkImage& p_image, + std::span p_copies) { + std::vector image_copies(p_copies.size()); + + for (uint32_t i = 0; i < image_copies.size(); i++) { + const buffer_image_copy image_copy = p_copies[i]; + image_copies[i] = { + .bufferOffset = image_copy.offset, + .bufferRowLength = image_copy.row_length, + .bufferImageHeight = image_copy.image_height, + .imageSubresource = { + .aspectMask = static_cast(image_copy.aspect_mask), + .mipLevel = image_copy.mip_level, + .baseArrayLayer = image_copy.base_array_layer, + .layerCount = image_copy.layer_count, + }, + .imageOffset = { static_cast(image_copy.image_offset.width), static_cast(image_copy.image_offset.height), static_cast(image_copy.image_offset.depth), }, + .imageExtent = { image_copy.image_extent.width, image_copy.image_extent.height, image_copy.image_extent.depth, }, + }; + } + + vkCmdCopyBufferToImage( + p_command, + m_handle, + p_image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + static_cast(image_copies.size()), + image_copies.data()); + } + + void destruct() { + if (m_handle != nullptr) { + vkDestroyBuffer(m_device, m_handle, nullptr); + } + + if (m_device_memory != nullptr) { + vkFreeMemory(m_device, m_device_memory, nullptr); + } + } + + operator VkBuffer() const { return m_handle; } + + operator VkBuffer() { return m_handle; } + + private: + VkDevice m_device = nullptr; + VkBuffer m_handle; + VkDeviceMemory m_device_memory; + }; + }; +}; \ No newline at end of file diff --git a/vulkan-cpp/buffer_streams16.cppm b/vulkan-cpp/buffer16.cppm similarity index 51% rename from vulkan-cpp/buffer_streams16.cppm rename to vulkan-cpp/buffer16.cppm index 050da88..8e48e01 100644 --- a/vulkan-cpp/buffer_streams16.cppm +++ b/vulkan-cpp/buffer16.cppm @@ -5,40 +5,40 @@ module; #include #include -export module vk:buffer_streams16; - +export module vk:buffer16; export import :types; export import :utilities; - export namespace vk { - inline namespace v1 { + inline namespace v6 { /** * @brief buffer stream for streaming arbitrary buffers of 32-bytes - */ - class buffer_stream16 { + */ + class buffer16 { public: - buffer_stream16() = default; - buffer_stream16(const VkDevice& p_device, const buffer_parameters& p_params); + buffer16() = default; + buffer16(const VkDevice&, uint64_t, const buffer_parameters&) {} /** * @brief write arbitrary buffer of 32-bytes to GPU-memory - */ - void write(std::span p_data); + */ + void write(std::span) {} - void copy_to_image(const VkCommandBuffer& p_command, const VkImage& p_image, image_extent p_extent); + void copy_to_image(const VkCommandBuffer& p_command, + const VkImage& p_image, + image_extent p_extent) {} - void destroy(); + void destruct() {} operator VkBuffer() { return m_handle; } operator VkBuffer() const { return m_handle; } private: - VkDevice m_device=nullptr; - VkDeviceMemory m_device_memory=nullptr; - VkBuffer m_handle=nullptr; + VkDevice m_device = nullptr; + VkDeviceMemory m_device_memory = nullptr; + VkBuffer m_handle = nullptr; }; }; }; \ No newline at end of file diff --git a/vulkan-cpp/buffer32.cppm b/vulkan-cpp/buffer32.cppm new file mode 100644 index 0000000..84cb879 --- /dev/null +++ b/vulkan-cpp/buffer32.cppm @@ -0,0 +1,152 @@ +module; + +#include +#include +#include +#include +#include + +export module vk:buffer32; + +export import :types; +export import :utilities; +export import :command_buffer; + +export namespace vk { + inline namespace v6 { + /** + * @brief buffer stream for streaming arbitrary buffers of 32-bytes + */ + class buffer32 { + public: + buffer32() = default; + buffer32(const VkDevice& p_device, + uint64_t p_device_size, + const buffer_parameters& p_params) + : m_device(p_device) { + construct(p_device_size, p_params); + } + + ~buffer32() = default; + + void construct(uint64_t p_device_size, + const buffer_parameters& p_params) { + VkBufferCreateInfo buffer_ci = { + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = p_device_size, // size in bytes + .usage = static_cast(p_params.usage), + .sharingMode = p_params.share_mode, + }; + + vk_check( + vkCreateBuffer(m_device, &buffer_ci, nullptr, &m_handle), + "vkCreateBuffer"); + + // retrieving buffer memory requirements + VkMemoryRequirements memory_requirements = {}; + vkGetBufferMemoryRequirements( + m_device, m_handle, &memory_requirements); + uint32_t mapped_memory_requirements = + memory_requirements.memoryTypeBits & p_params.memory_mask; + uint32_t memory_index = + std::countr_zero(mapped_memory_requirements); + + VkMemoryAllocateInfo memory_alloc_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = memory_requirements.size, + .memoryTypeIndex = memory_index + }; + +#if _DEBUG + // 1. Define the structure + VkDebugUtilsObjectNameInfoEXT debug_info = { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, + .pNext = nullptr, + .objectType = VK_OBJECT_TYPE_BUFFER, + .objectHandle = reinterpret_cast(m_handle), + .pObjectName = + p_params.debug_name // specify what type of buffer this is + }; + + if (p_params.vkSetDebugUtilsObjectNameEXT != nullptr) { + // vkSetDebugUtilsObjectNameEXT(m_device, &debug_info); + p_params.vkSetDebugUtilsObjectNameEXT(m_device, + &debug_info); + } +#endif + vk_check( + vkAllocateMemory( + m_device, &memory_alloc_info, nullptr, &m_device_memory), + "vkAllocateMemory"); + + // bind memory resource of this buffer handle + vk_check( + vkBindBufferMemory(m_device, m_handle, m_device_memory, 0), + "vkBindBufferMemory"); + } + + /** + * @brief write arbitrary buffer of 32-bytes to GPU-memory + */ + void transfer(std::span p_data) { + void* mapped = nullptr; + vk_check(vkMapMemory(m_device, + m_device_memory, + 0, + p_data.size_bytes(), + 0, + &mapped), + "vkMapMemory"); + memcpy(mapped, p_data.data(), p_data.size_bytes()); + vkUnmapMemory(m_device, m_device_memory); + } + + void copy_to_image(const VkCommandBuffer& p_command, + const VkImage& p_image, + image_extent p_extent) { + VkBufferImageCopy buffer_image_copy = { + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = { .aspectMask = + VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1 }, + .imageOffset = { .x = 0, .y = 0, .z = 0 }, + .imageExtent = { .width = p_extent.width, + .height = p_extent.height, + .depth = 1 } + }; + + vkCmdCopyBufferToImage(p_command, + m_handle, + p_image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + &buffer_image_copy); + } + + void destruct() { + if (m_handle != nullptr) { + vkDestroyBuffer(m_device, m_handle, nullptr); + } + + if (m_device_memory != nullptr) { + vkFreeMemory(m_device, m_device_memory, nullptr); + } + } + + operator VkBuffer() { return m_handle; } + + operator VkBuffer() const { return m_handle; } + + private: + VkDevice m_device = nullptr; + VkDeviceMemory m_device_memory = nullptr; + VkBuffer m_handle = nullptr; + }; + }; +}; \ No newline at end of file diff --git a/vulkan-cpp/buffer_streams.cppm b/vulkan-cpp/buffer_streams.cppm deleted file mode 100644 index bf1c172..0000000 --- a/vulkan-cpp/buffer_streams.cppm +++ /dev/null @@ -1,191 +0,0 @@ -module; - -#include -#include -#include - -export module vk:buffer_streams; - -export import :types; -export import :utilities; - -export namespace vk { - inline namespace v1 { - /** - * @brief Represents a VkBuffer handler for creating VkBuffer handle - * - * Purpose for using VkBuffer handle to streaming bytes of data into the GPU memory - * - */ - class buffer_stream { - public: - buffer_stream() = default; - buffer_stream(const VkDevice& p_device, - const buffer_parameters& p_settings) : m_device(p_device) { - m_allocation_size = p_settings.device_size; - - VkBufferCreateInfo buffer_ci = { - .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .size = m_allocation_size, // size in bytes - .usage = p_settings.usage, - .sharingMode = p_settings.share_mode, - }; - - vk_check(vkCreateBuffer(p_device, &buffer_ci, nullptr, &m_handle), - "vkCreateBuffer"); - - // 2. retrieving buffer memory requirements - VkMemoryRequirements memory_requirements = {}; - vkGetBufferMemoryRequirements(p_device, m_handle, &memory_requirements); - - // 3. selects the required memory requirements for this specific buffer - // allocations - uint32_t memory_index = - select_memory_requirements(p_settings.physical_memory_properties, - memory_requirements, - p_settings.property_flags); - - // 4. allocatring the necessary memory based on memory requirements for - // the buffer handles - VkMemoryAllocateInfo memory_alloc_info = { - .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, - .allocationSize = memory_requirements.size, - .memoryTypeIndex = memory_index - }; - - #if _DEBUG - // 1. Define the structure - VkDebugUtilsObjectNameInfoEXT debug_info = { - .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, - .pNext = nullptr, - .objectType = VK_OBJECT_TYPE_BUFFER, - .objectHandle = (uint64_t)m_handle, // specify vulkan to what object handle this is - .pObjectName = p_settings.debug_name // specify what type of buffer this is - }; - - if(p_settings.vkSetDebugUtilsObjectNameEXT != nullptr) { - // vkSetDebugUtilsObjectNameEXT(m_device, &debug_info); - p_settings.vkSetDebugUtilsObjectNameEXT(m_device, &debug_info); - } - #endif - vk_check(vkAllocateMemory( - p_device, &memory_alloc_info, nullptr, &m_device_memory), - "vkAllocateMemory"); - - // 5. bind memory resource of this buffer handle - vk_check(vkBindBufferMemory(p_device, m_handle, m_device_memory, 0), - "vkBindBufferMemory"); - } - - /** - * @param span writes some buffer data in the GPU's memory using - * vkMapMemory/vkUnmapMemory - */ - template - void write(std::span p_in_data) { - VkDeviceSize buffer_size = p_in_data.size_bytes(); - void* mapped = nullptr; - vk_check(vkMapMemory( - m_device, m_device_memory, 0, buffer_size, 0, &mapped), - "vkMapMemory"); - memcpy(mapped, p_in_data.data(), buffer_size); - vkUnmapMemory(m_device, m_device_memory); - } - - void write(const void* p_in_data, uint32_t p_size_bytes) { - void* mapped = nullptr; - vk_check(vkMapMemory( - m_device, m_device_memory, 0, p_size_bytes, 0, &mapped), - "vkMapMemory"); - memcpy(mapped, p_in_data, p_size_bytes); - vkUnmapMemory(m_device, m_device_memory); - } - - /** - * - * @brief This function automatically assumes the destination image - * layout is going to be set to VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL - * - * @param p_command is the current command buffer to perform and store - * this operation into - * @param p_image is the destination to copy data from the buffer to - * @param p_extent is the size of the image that is being copied - * - * ```C++ - * - * buffer_streams texture_image(logical_device, ...); - * - * texture_image.copy(temp_command_buffer, texture_image, texture_format, old_layout, new_layout); - * ``` - * - */ - void copy_to_image(const VkCommandBuffer& p_command, - const VkImage& p_image, - image_extent p_extent) { - VkBufferImageCopy buffer_image_copy = { - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .mipLevel = 0, - .baseArrayLayer = 0, - .layerCount = 1 }, - .imageOffset = { .x = 0, .y = 0, .z = 0 }, - .imageExtent = { .width = p_extent.width, .height = p_extent.height, .depth = 1 } - }; - - vkCmdCopyBufferToImage(p_command, - m_handle, - p_image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, - &buffer_image_copy); - } - - /** - * @param p_data is the bytes to write into the GPU's memory through the - * Vulkan vkMapMemory/vkUnmapMemory API's. - * - * ```C++ - * - * buffer_streams staging_buffer(logical_device, ...); - * - * std::array white_color = { 0xFF, 0xFF, 0xFF, 0xFF }; - * staging_buffer.write(white_color); - * ``` - * - */ - void write(std::span p_data) { - void* mapped = nullptr; - vk_check( - vkMapMemory( - m_device, m_device_memory, 0, p_data.size_bytes(), 0, &mapped), - "vkMapMemory"); - memcpy(mapped, p_data.data(), p_data.size_bytes()); - vkUnmapMemory(m_device, m_device_memory); - } - - void destroy() { - if (m_handle != nullptr) { - vkDestroyBuffer(m_device, m_handle, nullptr); - } - - if (m_device_memory != nullptr) { - vkFreeMemory(m_device, m_device_memory, nullptr); - } - } - - operator VkBuffer() const { return m_handle; } - - operator VkBuffer() { return m_handle; } - - private: - VkDevice m_device = nullptr; - VkBuffer m_handle; - VkDeviceMemory m_device_memory; - uint32_t m_allocation_size = 0; - }; - }; -}; \ No newline at end of file diff --git a/vulkan-cpp/buffer_streams32.cppm b/vulkan-cpp/buffer_streams32.cppm deleted file mode 100644 index 2c958b2..0000000 --- a/vulkan-cpp/buffer_streams32.cppm +++ /dev/null @@ -1,134 +0,0 @@ -module; - -#include -#include -#include -#include - -export module vk:buffer_streams32; - - -export import :types; -export import :utilities; -export import :command_buffer; - - -export namespace vk { - inline namespace v1 { - /** - * @brief buffer stream for streaming arbitrary buffers of 32-bytes - */ - class buffer_stream32 { - public: - buffer_stream32() = default; - buffer_stream32(const VkDevice& p_device, const buffer_parameters& p_params) : m_device(p_device) { - VkBufferCreateInfo buffer_ci = { - .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .size = p_params.device_size, // size in bytes - .usage = p_params.usage, - .sharingMode = p_params.share_mode, - }; - - vk_check(vkCreateBuffer(p_device, &buffer_ci, nullptr, &m_handle), - "vkCreateBuffer"); - - // 2. retrieving buffer memory requirements - VkMemoryRequirements memory_requirements = {}; - vkGetBufferMemoryRequirements(p_device, m_handle, &memory_requirements); - - // 3. selects the required memory requirements for this specific buffer - // allocations - uint32_t memory_index = - select_memory_requirements(p_params.physical_memory_properties, - memory_requirements, - p_params.property_flags); - - // 4. allocatring the necessary memory based on memory requirements for - // the buffer handles - VkMemoryAllocateInfo memory_alloc_info = { - .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, - .allocationSize = memory_requirements.size, - .memoryTypeIndex = memory_index - }; - - #if _DEBUG - // 1. Define the structure - VkDebugUtilsObjectNameInfoEXT debug_info = { - .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, - .pNext = nullptr, - .objectType = VK_OBJECT_TYPE_BUFFER, - .objectHandle = (uint64_t)m_handle, // specify vulkan to what object handle this is - .pObjectName = p_params.debug_name // specify what type of buffer this is - }; - - if(p_params.vkSetDebugUtilsObjectNameEXT != nullptr) { - // vkSetDebugUtilsObjectNameEXT(m_device, &debug_info); - p_params.vkSetDebugUtilsObjectNameEXT(m_device, &debug_info); - } - #endif - vk_check(vkAllocateMemory( - p_device, &memory_alloc_info, nullptr, &m_device_memory), - "vkAllocateMemory"); - - // 5. bind memory resource of this buffer handle - vk_check(vkBindBufferMemory(p_device, m_handle, m_device_memory, 0), - "vkBindBufferMemory"); - } - - /** - * @brief write arbitrary buffer of 32-bytes to GPU-memory - */ - void write(std::span p_data) { - void* mapped = nullptr; - vk_check( - vkMapMemory( - m_device, m_device_memory, 0, p_data.size_bytes(), 0, &mapped), - "vkMapMemory"); - memcpy(mapped, p_data.data(), p_data.size_bytes()); - vkUnmapMemory(m_device, m_device_memory); - } - - void copy_to_image(const VkCommandBuffer& p_command, const VkImage& p_image, image_extent p_extent) { - VkBufferImageCopy buffer_image_copy = { - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .mipLevel = 0, - .baseArrayLayer = 0, - .layerCount = 1 }, - .imageOffset = { .x = 0, .y = 0, .z = 0 }, - .imageExtent = { .width = p_extent.width, .height = p_extent.height, .depth = 1 } - }; - - vkCmdCopyBufferToImage(p_command, - m_handle, - p_image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, - &buffer_image_copy); - } - - void destroy() { - if (m_handle != nullptr) { - vkDestroyBuffer(m_device, m_handle, nullptr); - } - - if (m_device_memory != nullptr) { - vkFreeMemory(m_device, m_device_memory, nullptr); - } - } - - operator VkBuffer() { return m_handle; } - - operator VkBuffer() const { return m_handle; } - - private: - VkDevice m_device=nullptr; - VkDeviceMemory m_device_memory=nullptr; - VkBuffer m_handle=nullptr; - }; - }; -}; \ No newline at end of file diff --git a/vulkan-cpp/command_buffer.cppm b/vulkan-cpp/command_buffer.cppm index f1c7ff9..e73600c 100644 --- a/vulkan-cpp/command_buffer.cppm +++ b/vulkan-cpp/command_buffer.cppm @@ -3,15 +3,15 @@ module; #include #include #include +#include export module vk:command_buffer; - export import :types; export import :utilities; export namespace vk { - inline namespace v1 { + inline namespace v6 { struct command_inherit_info { VkRenderPass renderpass = nullptr; uint32_t subpass_index = 0; @@ -19,61 +19,107 @@ export namespace vk { }; /** - * @brief vk::command_buffer represents the wrapper around VkCommandBuffer - * - * Defines the command buffer and provides API's to directly interact with how - * the command buffer may be utilized based on the needs of the application that uses it - * - */ + * @brief vk::command_buffer is an abstraction around the + * VkCommandBuffer. + * + * vk::command_buffer can be represented as a VkCommandBuffer + * if the user decides to use the raw Vulkan API. + * + */ class command_buffer { public: command_buffer() = default; - command_buffer(const VkDevice& p_device, const command_params& p_enumerate_command_info) : m_device(p_device) { + command_buffer(const VkDevice& p_device, + const command_params& p_enumerate_command_info) + : m_device(p_device) { VkCommandPoolCreateInfo pool_ci = { - .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, - .pNext = nullptr, - // .flags = - // (VkCommandPoolCreateFlags)p_enumerate_command_info.pool_flag, - .flags = static_cast(p_enumerate_command_info.flags), - .queueFamilyIndex = p_enumerate_command_info.queue_index - }; - - vk_check( - vkCreateCommandPool(m_device, &pool_ci, nullptr, &m_command_pool), - "vkCreateCommandPool"); - - VkCommandBufferAllocateInfo command_buffer_alloc_info = { - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, - .pNext = nullptr, - .commandPool = m_command_pool, - .level = - static_cast(p_enumerate_command_info.levels), - .commandBufferCount = 1 - }; - - vk_check(vkAllocateCommandBuffers( - m_device, &command_buffer_alloc_info, &m_command_buffer), - "vkAllocateCommandBuffers"); + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .pNext = nullptr, + // .flags = + // (VkCommandPoolCreateFlags)p_enumerate_command_info.pool_flag, + .flags = static_cast( + p_enumerate_command_info.flags), + .queueFamilyIndex = p_enumerate_command_info.queue_index + }; + + vk_check(vkCreateCommandPool( + m_device, &pool_ci, nullptr, &m_command_pool), + "vkCreateCommandPool"); + + VkCommandBufferAllocateInfo command_buffer_alloc_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .pNext = nullptr, + .commandPool = m_command_pool, + .level = static_cast( + p_enumerate_command_info.levels), + .commandBufferCount = 1 + }; + + vk_check(vkAllocateCommandBuffers(m_device, + &command_buffer_alloc_info, + &m_command_buffer), + "vkAllocateCommandBuffers"); } /** - * @brief begins the recording operation of the particular command buffer - * - * @param p_usage are flags to specify the behavior of the command buffer - * @param p_inherit_info is if this command buffer is created and used as a secondary command buffer, then - * this defines any state that will be inherited from the primary command buffer - * - * + * + * @brief Begin operation for GPU-specific work and where it is + * defined. A common GPU workflow is queueing up tasks, in this case + * commands. + * + * @param p_usage are flags to specify recording behavior + * - ::one_time_submit: commands recorded once, then reset. + * - ::simultaneous_use: Commands that can be re-submitted while + * still running. + * + * @param p_inherit_info are used for command buffers specified as + * secondary + * - .renderpass: Inheriting renderpass handle from primary command. + * - .framebuffer: Inherit the "image" target from primary command. + * + * The CPU cannot directly offload tasks to the GPU, therefore this + * must be represented using command buffers which are queue'd up + * during recording states of the command buffer. + * + * @brief Additional Considerations: + * - Command buffer must be in 'initial' or 'executable' state + * before calling .begin/end. You cannot call `begin()` on a buffer + * that is already recording. + * - If a command buffer was already recording, it must be reset + * (manually or via + * VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT). + * - Every begin() call must follow-up with an end() call + * afterwards. + * - If using p_inherit_info, the command buffer must be created + * with the 'secondary' level flag. + * + * + * [ CPU Command ] [Record Stream] + * +----------------------+ +------------------------+ + * | .begin() <- start | | [Header: Usage Flags] | + * | [Draw Triangle] | =====> | [Op: Bind Pipeline] | + * | [Copy Image] | | | + * | .end() | | [Op: Draw Call] | + * | | | [Footer: End Tag] | + * +----------------------+ +------------------------+ + * + * Example Usage: + * * ```C++ - * - * vk::command_buffer temp_command(logical_device, ....); - * - * temp_command.begin(command_usage::one_time_submit); - * temp_command.end(); // required whenever .begin is called + * + * vk::command_buffer primary_command(logical_device, ...); + * + * primary_command.begin(vk::command_usage::one_time_submit); + * // perform GPU-specific work + * primary_command.end(); + * * ``` - * - */ - void begin(command_usage p_usage, std::span p_inherit_info = {}) { + * + * + */ + void begin( + command_usage p_usage, + std::span p_inherit_info = {}) { // Resets to zero if get called every frame if (m_begin_end_count == 2) { m_begin_end_count = 0; @@ -81,11 +127,12 @@ export namespace vk { m_begin_end_count++; std::vector inheritance_infos( - p_inherit_info.size()); + p_inherit_info.size()); for (size_t i = 0; i < inheritance_infos.size(); i++) { inheritance_infos[i] = { - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO, + .sType = + VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO, .renderPass = p_inherit_info[i].renderpass, .subpass = p_inherit_info[i].subpass_index, .framebuffer = p_inherit_info[i].framebuffer @@ -97,76 +144,511 @@ export namespace vk { .pNext = nullptr, .flags = static_cast(p_usage) }; - vk_check(vkBeginCommandBuffer(m_command_buffer, &command_begin_info), - "vkBeginCommandBuffer"); + vk_check( + vkBeginCommandBuffer(m_command_buffer, &command_begin_info), + "vkBeginCommandBuffer"); } /** - * @brief ends command buffer recording operation - */ + * @brief Stops the recording process, command moves from + * 'recording' to 'executing' state. Which implies the command is + * ready to be submitted to the vk::device_queue handle. + * + * @brief Additional Considerations: + * - Cannot end a command buffer if a renderpass is still active. + * (must invoked renderpass::end beforehand). + * - If the command_buffer::end returns an error, command handle + * becomes invalid and cannot be submitted. + * + */ void end() { m_begin_end_count++; vkEndCommandBuffer(m_command_buffer); } /** - * - * @brief Copy from the source buffer to a destination specified buffer - * - * - * Command buffer records this and expects a staging buffer to map chunks of data in GPU memory - * - * Then calling this API to handle copying the sources in staging buffer to its final destination buffer, moving those chunks into the final buffer handle - * - * - * @param p_src is the buffer to copy its region from - * @param p_dst is the buffer to copy that region into - * @param p_size_bytes is the amount of bytes stored in the buffer that is being copied into the destination buffer - * - * + * @brief Begin recording dynamic rendering pass instance + * + * Example Usage: * ```C++ - * - * // staging buffer to make sure we copy data regions chunks to vertex buffer correctly - * vk::buffer_stream staging_buffer(logical_device, ...); - * - * // vertex_buffer handle is the destination to copy the regions to - * vk::buffer_stream vertex_buffer(logical_device, ...); - * - * vk::command_buffer temp_command(logical_device, ...); - * - * temp_command.begin(command_usage::one_time_submit); - * temp_command.copy(staging_buffer, vertex_buffer, size_bytes); - * temp_command.end(); - * - * + * + * vk::command_buffer current_command = ...; + * + * std::array color_attachments = { + * vk::rendering_attachment{ + * .image_view = ..., + * .image_layout = ..., + * .resolve_mode = ..., + * .resolve_image_layout = ..., + * .load = ..., + * .store = ..., + * .clear_values = {...}, + * } + * }; + * + * std::array depth_attachments = { + * vk::rendering_attachment{ + * .image_view = ..., + * .image_layout = ..., + * .resolve_mode = ..., + * .resolve_image_layout = ..., + * .load = ..., + * .store = ..., + * .clear_values = {...}, + * } + * }; + * + * vk;:rendering_begin_parameters begin_params = { + * .render_area = {x, y}, + * .color_attachments = color_attachments, + * .depth_attachments = depth_attachments, + * }; + * current_command.begin_rendering(begin_params); + * + * current_command.end(); + * * ``` - * - */ - void copy_buffer(const VkBuffer& p_src, const VkBuffer& p_dst, uint64_t p_size_bytes) { + * + */ + void begin_rendering( + const rendering_begin_parameters& p_parameters) { + std::vector color_attachments( + p_parameters.color_attachments.size()); + + // Loading and setting color attachments (if any are set) + for (size_t i = 0; i < color_attachments.size(); i++) { + rendering_attachment color_attach = + p_parameters.color_attachments[i]; + color_attachments[i] = { + .sType = + VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR, + .pNext = nullptr, + .imageView = color_attach.image_view, + .imageLayout = + static_cast(color_attach.layout), + .resolveMode = static_cast( + color_attach.resolve_mode), + .resolveImageView = color_attach.resolve_image_view, + .resolveImageLayout = static_cast( + color_attach.resolve_image_layout), + .loadOp = + static_cast(color_attach.load), + .storeOp = + static_cast(color_attach.store), + .clearValue = color_attach.clear_values, + }; + } + + // Loading and setting depth attachments (if any are set) + rendering_attachment depth_attach = + p_parameters.depth_attachment; + VkRenderingAttachmentInfo depth_attachment = { + .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, + .pNext = nullptr, + .imageView = depth_attach.image_view, + .imageLayout = + static_cast(depth_attach.layout), + .resolveMode = static_cast( + depth_attach.resolve_mode), + .resolveImageView = depth_attach.resolve_image_view, + .resolveImageLayout = static_cast( + depth_attach.resolve_image_layout), + .loadOp = + static_cast(depth_attach.load), + .storeOp = + static_cast(depth_attach.store), + .clearValue = depth_attach.depth_values, + }; + + // Loading and setting stencil attachments (if any are set) + rendering_attachment stencil_attach = + p_parameters.stencil_attachment; + VkRenderingAttachmentInfo stencil_attachment = { + .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, + .pNext = nullptr, + .imageView = stencil_attach.image_view, + .imageLayout = + static_cast(stencil_attach.layout), + .resolveMode = static_cast( + stencil_attach.resolve_mode), + .resolveImageView = stencil_attach.resolve_image_view, + .resolveImageLayout = static_cast( + stencil_attach.resolve_image_layout), + .loadOp = + static_cast(stencil_attach.load), + .storeOp = + static_cast(stencil_attach.store), + .clearValue = stencil_attach.depth_values, + }; + + VkRenderingInfo rendering_begin_info = { + .sType = VK_STRUCTURE_TYPE_RENDERING_INFO_KHR, + .pNext = nullptr, + .flags = static_cast( + p_parameters.rendering_flags), + .renderArea = p_parameters.render_area, + .layerCount = p_parameters.layer_count, + .viewMask = p_parameters.view_mask, + .colorAttachmentCount = + static_cast(color_attachments.size()), + .pColorAttachments = color_attachments.data(), + .pDepthAttachment = &depth_attachment, + .pStencilAttachment = &stencil_attachment, + }; + + vkCmdBeginRendering(m_command_buffer, &rendering_begin_info); + } + + /** + * @brief End recording for dynamic rendering pass + */ + void end_rendering() { vkCmdEndRendering(m_command_buffer); } + + /** + * @brief Transferring the raw geomtric data of vertices to the + * vertex input stage of the graphics pipeline. + * + * This implies when starting to draw, to look at specific buffers + * to find the vertex attributes (position, normals, and color for + * each vertex) + * + * Unlike descriptor sets, vertex buffers are high-frequent data + * reads automatically by the hardware input's assembler for every + * vertex being processed. + * + * @param p_buffers an arbitrary span of buffers containg vertex + * attribute data. + * @param p_first_binding is the index of the first vertex binding + * to update (usually 0). + * @param p_offsets is a span of byte offsets specifying where the + * actual data begins inside each buffer. + * + * @brief Additional Consideration: + * - Number of buffers and their order MUST match what is specified + * with using `VkVertexInputBindingDescription` or + * `vk::vertex_attribute` specifications. + * - Size of `p_buffers` and `p_offsets` must be identical . Where + * each buffer needs a starting offset. + * - Every buffer MUST need to be created using + * `VK_BUFFER_USAGE_VERTEX_BUFFER_BIT`. + * - Data in these transfers + * + * + * Example Usage: + * + * ```C++ + * + * vk::command_buffer current = ...; + * + * std::array vertex = { vbo }; + * uint64_t offset = 0; + * current.bind_vertex_buffers(vertex, std::span(&offset, + * 1)); + * ``` + * + */ + void bind_vertex_buffers(std::span p_buffers, + std::span p_offsets = {}, + const uint32_t p_first_binding = 0) { + vkCmdBindVertexBuffers(m_command_buffer, + p_first_binding, + static_cast(p_buffers.size()), + p_buffers.data(), + p_offsets.data()); + } + + /** + * @brief Command tells the GPU what amount of bytes are stored in + * the indices for rendering. + * + * @param p_index_buffer is the handle containing the array of + * indices + * @param p_offset is the byte starting point (usually 0 unless you + * store multiple meshes in one buffer). + * + * @brief Additional Consideration: + * - Chosen API (8-bit, 16-bit, 32-bit) must match the bit-width of + * your data. If you bind a 32-bit buffer as 16-bit, the GPU will + * read twice as many indices, but each will be complete bad data. + * - .p_offset: must be aligned to size of the index type (e.g. + * p_offset % 4 == 0 for uint32). + * + * [ Index Buffer (memory) ] [ GPU Input Assembly ] + * +-----------------------+ +--------------------+ + * | [0] [1] [2] | --Bind--> | Indices Sequencer, | + * | [2] [3] [0] | | State | + * +-----------------------+ +--------------------+ + * (Indices 0, 1, 2, ... 5) (Select Vertices Order) + * + */ + + /** + * @brief Used 1 byte per index. + * + * Minimal memory, limited to 256 vertices. + * + * Example Usage: + * ```C++ + * + * vk::command_buffer current = ...; + * + * vk::index_buffer8 ibo = ...; + * current.bind_index_buffer8(ibo); + * ``` + * + */ + void bind_index_buffers8(const VkBuffer& p_index_buffer, + const uint64_t p_offset = 0) { + vkCmdBindIndexBuffer(m_command_buffer, + p_index_buffer, + p_offset, + VK_INDEX_TYPE_UINT8); + } + + /** + * @brief Use 2 bytes per index. + * + * Can be used for most 3D models up to 65k verties. + * + * ```C++ + * + * vk::command_buffer current = ...; + * + * vk::index_buffer16 ibo = ...; + * current.bind_index_buffer16(ibo); + * ``` + * + */ + void bind_index_buffers16(const VkBuffer& p_index_buffer, + const uint64_t p_offset = 0) { + vkCmdBindIndexBuffer(m_command_buffer, + p_index_buffer, + p_offset, + VK_INDEX_TYPE_UINT16); + } + + /** + * @brief Use 4 bytes per index. + * + * Used for more higher-density terrain for complex meshes such as + * terrain. + * + * ```C++ + * + * vk::command_buffer current = ...; + * + * vk::index_buffer32 ibo = ...; + * current.bind_index_buffer32(ibo); + * ``` + * + */ + void bind_index_buffers32(const VkBuffer& p_index_buffer, + const uint64_t p_offset = 0) { + vkCmdBindIndexBuffer(m_command_buffer, + p_index_buffer, + p_offset, + VK_INDEX_TYPE_UINT32); + } + + /** + * @brief Bind the current data that stored in memory to the + * active descriptor set for execution. + * + * This function records instructions into the command buffer to + * "map" that data into the GPU's register file. + * + * Specifically any addresses within the shader that have variables + * assigned to set = N. + * + * @param p_current is the active command recording to perform draw + * calls. + * @param p_pipeline_layout is the layout describing descriptor set + * resources are mapped to. + * + * @brief Additional Considerations: + * - `p_pipeline_layout` MUST be the same layout used to create the + * currently bound pipeline. + * - `VkDescriptorSetLayout` used to create the descriptors MUST be + * included in the graphics pipeline layout configuration. + * - `m_slot` must match the `set = N` declaration in your shader + * code. + * - The descriptor set must have been created with a layout that is + * "compatible" with the pipeline layout. + * - This must be invoke within a command buffer recording via + * `.begin()`. + * + * [ Descriptor Set (Data) ] [ Pipleine Layout ] + * +-------------------+ +-----------------------+ + * | [Uniform Buffer ] | | Slot 0: [ Attached ] | + * | [Image Sampler ] | -> Bind --> | Slot 1: [Empty] | + * +-------------------+ +-----------------------+ + * + */ + void bind_descriptors( + const VkPipelineLayout& p_pipeline_layout, + uint64_t p_pipeline_bind_point, + std::span p_descriptors, + std::span p_dynamic_offsets = {}, + const uint32_t p_starting_slot = 0) { + vkCmdBindDescriptorSets( + m_command_buffer, + static_cast(p_pipeline_bind_point), + p_pipeline_layout, + p_starting_slot, + static_cast(p_descriptors.size()), + p_descriptors.data(), + static_cast(p_dynamic_offsets.size()), + p_dynamic_offsets.data()); + } + + /** + * @brief Performs high-speed raw memory transfers between two + * buffer handles. + * + * Commonly used for uploading data by copying + * between CPU-visible staging buffers to GPU-local buffers. (faster + * for GPU to read) + * + * @param p_src is the handle to source buffer (incoming data) + * @param p_dst is the handle to destination buffer (data + * transfering to) + * @param p_size_bytes are the total amount of memory to copy in + * bytes. + * + * @brief Additional Considerations: + * - p_src must have vk::buffer_usage::transfer_src_bit set + * - p_dst must have vk::buffer_usage::transfer_dst_bit set. + * - p_size_bytes must be less or equals to size of both source and + * destination buffers. + * - Must be called between '.begin()' and '.end()' of a command + * buffer. + * - If when you plan to using p_dst after performing this + * operation, you need to make sure to perform a buffer memory + * barrier to ensure the copy is finished beforehand. + * + * [CPU-Visible Staging (buffer)] [Dst: GPU-local Buffer] + * +-------------------------+ +------------------------+ + * | [Data Segment] | | [Identical Copy] | + * | [Size: size_bytes] | =====> | [Size: size_bytes] | + * | | | | + * +-------------------------+ +------------------------+ + * + * Example Usage: + * + * ```C++ + * vk::buffer staging_buffer(logical_device, ...); + * vk::buffer vertex_buffer(logical_device, ...); + * + * vk::command_buffer primary_command(logical_device, ...); + * + * primary_command.begin(vk::command_usage::one_time_submit); + * + * // Transferring data from staging to GPU-accessible buffer handle + * const auto size_bytes = vertices.size_bytes(); + * primary_command.copy(staging_buffer, vertex_buffer, size_bytes) + * + * primary_command.end(); + * ``` + * + */ + void copy_buffer(const VkBuffer& p_src, + const VkBuffer& p_dst, + uint64_t p_size_bytes) { VkBufferCopy copy_region{}; copy_region.size = p_size_bytes; - vkCmdCopyBuffer(m_command_buffer, p_src, p_dst, 1, ©_region); + vkCmdCopyBuffer( + m_command_buffer, p_src, p_dst, 1, ©_region); } [[nodiscard]] bool alive() const { return m_command_buffer; } /** - * @brief Used to execute secondary command buffers + * @brief Primary command buffer calls batch of secondary command + * buffers. + * + * Useful for multithreaded rendering: Recording to different + * secondary commands with responsibilities of your scenes (UI, + * Shadow, Geometry, etc). + * + * Then being able to link the secondary command buffers to the + * primary command buffer altogether. + * + * @param p_commands are arbitrary secondary command buffers to be + * executed part of the primary command buffer, if m_command_buffer + * is a primary command. + * + * @brief Additional Considerations: + * - m_command_buffer must be a primary command buffer. + * - 'p_commands' MUST be secondary command buffers that have + * already been closed with the `.end()` API. + * - Inheritance info used in the secondary `.begin()` MUST match + * the current state of the primary command buffer. + * + * + * [ PRIMARY COMMAND ] [ Secondary Commands ] + * +--------------------+ +--------------------+ + * | Begin Command | | [Secondary Cmd A] | + * | | | (Draw UI) | + * | Begin RenderPass | -- [execute] --> +--------------------+ + * | | | [Secondary Cmd B] | + * | .execute(A, B) | | (Draw Particles) | + * +--------------------+ +--------------------+ * - * The command buffer that is executing these commands must be a - * specified primary command buffer * - * @param p_commands is the secondary command buffer that gets executed - * if the command buffer itself is a primary command buffer */ void execute(std::span p_commands) { vkCmdExecuteCommands(m_command_buffer, - static_cast(p_commands.size()), - p_commands.data()); + static_cast(p_commands.size()), + p_commands.data()); } - void destroy() { - vkFreeCommandBuffers(m_device, m_command_pool, 1, &m_command_buffer); + void set_viewport(uint32_t p_first_viewport, + uint32_t p_viewport_count, + std::span p_params) { + std::vector viewports(p_params.size()); + + for (uint32_t i = 0; i < viewports.size(); i++) { + const auto viewport = p_params[i]; + viewports[i] = { + .x = viewport.x, + .y = viewport.y, + .width = viewport.width, + .height = viewport.height, + .minDepth = viewport.min_depth, + .maxDepth = viewport.max_depth, + }; + } + vkCmdSetViewport(m_command_buffer, + p_first_viewport, + p_viewport_count, + viewports.data()); + } + + void set_scissor(uint32_t p_first_scissor, + uint32_t p_scissor_count, + std::span p_params) { + std::vector scissors(p_params.size()); + + for (uint32_t i = 0; i < scissors.size(); i++) { + const auto scissor = p_params[i]; + scissors[i] = { + .offset = scissor.offset, + .extent = scissor.extent, + }; + } + + vkCmdSetScissor(m_command_buffer, + p_first_scissor, + p_scissor_count, + scissors.data()); + } + + /** + * @brief Explicitly API to properly do command buffer cleanup + */ + void destruct() { + vkFreeCommandBuffers( + m_device, m_command_pool, 1, &m_command_buffer); vkDestroyCommandPool(m_device, m_command_pool, nullptr); } diff --git a/vulkan-cpp/descriptor_resource.cppm b/vulkan-cpp/descriptor_resource.cppm index 78dd88f..535d1fb 100644 --- a/vulkan-cpp/descriptor_resource.cppm +++ b/vulkan-cpp/descriptor_resource.cppm @@ -7,38 +7,128 @@ module; export module vk:descriptor_resource; - export import :types; export import :utilities; export import :uniform_buffer; export import :sample_image; export namespace vk { - inline namespace v1 { + inline namespace v6 { /** - * @param slot is the slot specific to the number slot for the descriptor. - * Ex. layout (set = 0) + * @param slot is the slot specific to the number slot for the + * descriptor. Ex. layout (set = 0) */ struct descriptor_layout { uint32_t slot = 0; uint32_t max_sets = 0; std::span entries; + std::span descriptor_counts = {}; }; + /** + * @brief Descriptor resources are an abstraction class around the + * descriptor set handles. + * + * Shaders are not able to directly have access to CPU-visible uniforms. + * Therefore, descriptor sets are used for performing the required + * configuration for descriptor set handles. + * + * @brief Additional Considerations: + * - Block of memory dedicated to holding these handles. + * - Defines what memory data layout the shader expects to receive. + * - VkDescriptorSet is the instance handle to used during draw call + * operations + * + */ class descriptor_resource { public: descriptor_resource() = default; - descriptor_resource(const VkDevice& p_device, const descriptor_layout& p_info) : m_device(p_device), m_slot(p_info.slot) { - std::vector pool_sizes(p_info.entries.size()); - std::vector descriptor_layout_bindings(p_info.entries.size()); + + /** + * @brief Constructs a descriptor resource for configuring the + * handle + * + * @param p_device is the logical device used to initiate these + * resources. + * @param p_info is the configuration for creating the descriptor + * set. + * - .slot: specifying the index to this particular + * resource (layout(set = N, binding = X)). + * - .max_sets: Maximum number of descriptors allowed in the + * pool. + * - .entries: Arbitrary of descriptor entries defining in + * each resource slot. + * - .type: Buffer/Image type (e.g. uniform) + * - .descriptor_count: Count of elements (1 for single, >1 or + * arrays). + * - .binding_point: Actual binding index specified in the + * shader. + * - .stage: Shader stage allowed to access this + * resource. + * + * + * [ Descriptor Pool ] [ Descriptor Layout ] + * +---------------------+ +---------------------------+ + * | [ UBO Memory Slot ] | | Binding 0: Uniform Buffer | + * | [ Sampler Slot] | | Binding 1: Tex Sampler | + * +---------------------+ +---------------------------+ + * | | + * \__________________________/ + * | + * V + * [ Descriptor Set Handle] + * + * + * Example Usage: + * + * ```C++ + * + * std::array entries = { + * vk::descriptor_entry{ + * .type = vk::descriptor_type::uniform, .binding_point = { + * .binding = 0, + * .stage = vk::shader_stage::vertex, + * }, + * .descriptor_count = 1, + * }, + * vk::descriptor_entry{ + * .type = vk::descriptor_type::combined_image_sampler, + * .binding_point = { + * .binding = 1, + * .stage = vk::shader_stage::fragment, + * }, + * .descriptor_count = 1, + * }, + * }; + * vk::descriptor_layout layout = { + * .slot = 0, + * .max_sets = 2, + * .entries = entries, + * }; + * vk::descriptor_resource set0(logical_device, layout); + * ``` + * + */ + descriptor_resource( + const VkDevice& p_device, + const descriptor_layout& p_info, + descriptor_layout_flags p_flags = descriptor_layout_flags::none) + : m_device(p_device) + , m_slot(p_info.slot) { + std::vector pool_sizes( + p_info.entries.size()); + std::vector + descriptor_layout_bindings(p_info.entries.size()); for (size_t i = 0; i < pool_sizes.size(); i++) { - VkDescriptorType descriptor_type = static_cast(p_info.entries[i].type); + VkDescriptorType descriptor_type = + static_cast(p_info.entries[i].type); pool_sizes[i] = { .type = descriptor_type, .descriptorCount = - static_cast(p_info.entries[i].descriptor_count) * - p_info.max_sets, + static_cast( + p_info.entries[i].descriptor_count) * + p_info.max_sets, }; } @@ -46,87 +136,205 @@ export namespace vk { descriptor_entry entry = p_info.entries[i]; descriptor_binding_point bind = entry.binding_point; - VkDescriptorType type = static_cast(entry.type); + VkDescriptorType type = + static_cast(entry.type); descriptor_layout_bindings[i] = { .binding = bind.binding, .descriptorType = type, .descriptorCount = entry.descriptor_count, - .stageFlags = static_cast(bind.stage), + .stageFlags = + static_cast(bind.stage), }; } VkDescriptorPoolCreateInfo pool_ci = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .pNext = nullptr, - .flags = 0, + .flags = static_cast( + (p_flags == + descriptor_layout_flags::update_after_bind_pool) + ? VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT + : 0), .maxSets = p_info.max_sets, .poolSizeCount = static_cast(pool_sizes.size()), .pPoolSizes = pool_sizes.data() }; vk_check(vkCreateDescriptorPool( - m_device, &pool_ci, nullptr, &m_descriptor_pool), - "vkCreateDescriptorPool"); + m_device, &pool_ci, nullptr, &m_descriptor_pool), + "vkCreateDescriptorPool"); + + // For Descriptor Indexing + // Enable binding flags + + std::vector binding_flags( + p_info.entries.size()); + + for (uint32_t i = 0; i < binding_flags.size(); i++) { + binding_flags[i] = static_cast( + p_info.entries[i].flags); + } + + VkDescriptorSetLayoutBindingFlagsCreateInfo + descriptor_layout_binding_flags = { + .sType = + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO, + .bindingCount = + static_cast(binding_flags.size()), + .pBindingFlags = binding_flags.data(), + }; VkDescriptorSetLayoutCreateInfo descriptor_layout_ci = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, - .pNext = nullptr, - .flags = 0, + .sType = + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .pNext = &descriptor_layout_binding_flags, + .flags = + static_cast(p_flags), .bindingCount = - static_cast(descriptor_layout_bindings.size()), + static_cast(descriptor_layout_bindings.size()), .pBindings = descriptor_layout_bindings.data() }; - vk_check( - vkCreateDescriptorSetLayout( - m_device, &descriptor_layout_ci, nullptr, &m_descriptor_layout), - "vkCreateDescriptorSetLayout"); + vk_check(vkCreateDescriptorSetLayout(m_device, + &descriptor_layout_ci, + nullptr, + &m_descriptor_layout), + "vkCreateDescriptorSetLayout"); + + VkDescriptorSetVariableDescriptorCountAllocateInfo + descriptor_variable_cound_info = { + .sType = + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO, + .descriptorSetCount = + static_cast(p_info.descriptor_counts.size()), + .pDescriptorCounts = p_info.descriptor_counts.data(), + }; + VkDescriptorSetAllocateInfo descriptor_set_alloc_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, - .pNext = nullptr, + .pNext = (p_info.descriptor_counts.size() == 0) + ? nullptr + : &descriptor_variable_cound_info, .descriptorPool = m_descriptor_pool, .descriptorSetCount = 1, .pSetLayouts = &m_descriptor_layout }; vk_check(vkAllocateDescriptorSets(m_device, - &descriptor_set_alloc_info, - &m_descriptor_set), - "vkAllocateDescriptorSets"); + &descriptor_set_alloc_info, + &m_descriptor_set), + "vkAllocateDescriptorSets"); } - void bind(const VkCommandBuffer& p_current, const VkPipelineLayout& p_pipeline_layout) { - vkCmdBindDescriptorSets(p_current, - VK_PIPELINE_BIND_POINT_GRAPHICS, - p_pipeline_layout, - m_slot, - 1, - &m_descriptor_set, - 0, - nullptr); - } - - void update(std::span p_uniforms, std::span p_images={}) { + /** + * @brief Performs the operation to actual update the descriptor set + * handle with the uniforms data segments. + * + * This maps the uniform VkBuffer and VkImage handles to the + * specific logical bindings that are associated with the shader + * declarations. + * + * Without setting this, the descriptor set would be implied to + * contain empty slots and pointing (lookup) to nothing. + * + * @brief Additional Considerations: + * - Cannot update a descriptor set that is currently in used by a + * command buffer that is "in-flight" (executing on the GPU). + * - Resource type (e.g. combind_image_sampler) must match what has + * been defined in the `descriptor layout` for that `dst_binding`. + * - Buffer/Image handles must remain valid until the GPU has + * finished executing the cmomand buffer that is using this + * particular descriptor set. + * + * + * [ CPU Uniforms Handles ] [ GPU Descriptor ] + * +--------------------+ +----------------------------+ + * | write_buffer | --maps--> | Binding 0: Buffer Ptr | + * | (Handle + Offset) | | (Address: 0x00FF...) | + * +--------------------+ +----------------------------+ + * | write_image | --maps--> | Binding 1: Image Ptr | + * | (View + Sampler) | | (Layout: shader_read_only) | + * +--------------------+ +----------------------------+ + * (Mapping CPU Data) (Active GPU Resources) + * + * [GLSL Shader] + * GLSL equivalent of the active GPU resources of the "GPU + * Descriptor" + * +-----------------------------------------------+ + * | layout(set=N, binding = 0) uniform buffer {}; | + * | layout(set=N, binding = 1) sampler2D texture; | + * +-----------------------------------------------+ + * (Executing GPU-visible Resources) + * + * Example Usage: + * + * ```C++ + * + * vk::descriptor_resource set0(logical_device, layout); + * + * // Uniform Buffers Handle + * std::array uniforms0 = { + * vk::write_buffer{ + * .buffer = test_ubo, + * .offset = 0, + * .range = static_cast(test_ubo.size_bytes()), + * }, + * }; + * std::array uniforms = { + * vk::write_buffer_descriptor{ + * .dst_binding = 0, + * .uniforms = uniforms0, + * }, + * }; + * + * // View + Samplers Handle + * std::array samplers = { + * vk::write_image{ + * .sampler = sampler, + * .view = image_view, + * .layout = vk::image_layout::shader_read_only_optimal, + * }, + * }; + * + * // Specify image descriptor images/samplers to the descriptor + * std::array sample_images = { + * vk::write_image_descriptor{ + * .dst_binding = 1, + * .sample_images = samplers, + * } + * }; + * set0_resource.update(uniforms, sample_images); + * ``` + * + */ + void update(std::span p_uniforms, + std::span p_images = {}) { std::vector write_descriptors; - // uint32_t represent the destination bindings to those resources (uniforms and sample images) - std::unordered_map> buffer_infos; - std::unordered_map> image_infos; + // uint32_t represent the destination bindings to those + // resources (uniforms and sample images) + std::unordered_map> + buffer_infos; + std::unordered_map> + image_infos; // handle uniforms - for(const auto& ubo : p_uniforms) { - for(const auto& uniform : ubo.uniforms) { - buffer_infos[ubo.dst_binding].emplace_back(uniform.buffer, uniform.offset, uniform.range); + for (const auto& ubo : p_uniforms) { + for (const auto& uniform : ubo.uniforms) { + buffer_infos[ubo.dst_binding].emplace_back( + uniform.buffer, uniform.offset, uniform.range); } VkWriteDescriptorSet write_descriptor = { - . sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .pNext = nullptr, .dstSet = m_descriptor_set, .dstBinding = ubo.dst_binding, .dstArrayElement = 0, - .descriptorCount = static_cast(buffer_infos[ubo.dst_binding].size()), + .descriptorCount = static_cast( + buffer_infos[ubo.dst_binding].size()), .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .pBufferInfo = buffer_infos[ubo.dst_binding].data(), }; @@ -134,10 +342,13 @@ export namespace vk { write_descriptors.emplace_back(write_descriptor); } - for(const auto& ubo : p_images) { + for (const auto& ubo : p_images) { - for(const auto& sample_image : ubo.sample_images) { - image_infos[ubo.dst_binding].emplace_back(sample_image.sampler, sample_image.view, static_cast(sample_image.layout)); + for (const auto& sample_image : ubo.sample_images) { + image_infos[ubo.dst_binding].emplace_back( + sample_image.sampler, + sample_image.view, + static_cast(sample_image.layout)); } VkWriteDescriptorSet write_descriptor = { @@ -146,8 +357,10 @@ export namespace vk { .dstSet = m_descriptor_set, .dstBinding = ubo.dst_binding, .dstArrayElement = 0, - .descriptorCount = static_cast(image_infos[ubo.dst_binding].size()), - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = static_cast( + image_infos[ubo.dst_binding].size()), + .descriptorType = + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImageInfo = image_infos[ubo.dst_binding].data(), }; @@ -155,13 +368,15 @@ export namespace vk { } vkUpdateDescriptorSets( - m_device, - static_cast(write_descriptors.size()), - write_descriptors.data(), - 0, - nullptr); - - // Ensures to clear up so we dont have any existing handles because they only need to exist until we've updated the descriptors + m_device, + static_cast(write_descriptors.size()), + write_descriptors.data(), + 0, + nullptr); + + // Ensures to clear up so we dont have any existing handles + // because they only need to exist until we've updated the + // descriptors buffer_infos.clear(); image_infos.clear(); } @@ -170,17 +385,22 @@ export namespace vk { return m_descriptor_layout; } - void destroy() { + void destruct() { if (m_descriptor_pool != nullptr) { - vkDestroyDescriptorPool(m_device, m_descriptor_pool, nullptr); + vkDestroyDescriptorPool( + m_device, m_descriptor_pool, nullptr); } if (m_descriptor_layout != nullptr) { vkDestroyDescriptorSetLayout( - m_device, m_descriptor_layout, nullptr); + m_device, m_descriptor_layout, nullptr); } } + operator VkDescriptorSet() const { return m_descriptor_set; } + + operator VkDescriptorSet() { return m_descriptor_set; } + private: VkDevice m_device = nullptr; uint32_t m_slot; diff --git a/vulkan-cpp/device.cppm b/vulkan-cpp/device.cppm index 311a184..ade58f0 100644 --- a/vulkan-cpp/device.cppm +++ b/vulkan-cpp/device.cppm @@ -7,9 +7,8 @@ export module vk:device; export import :types; export import :utilities; - export namespace vk { - inline namespace v1 { + inline namespace v6 { /** * @name device * @brief represents a vulkan logical device @@ -22,27 +21,29 @@ export namespace vk { }; public: - device(const VkPhysicalDevice& p_physical, const device_params& p_config) { + device(const VkPhysicalDevice& p_physical, + const device_params& p_config) { VkDeviceQueueCreateInfo device_queue_ci = { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .pNext = nullptr, .flags = 0, .queueFamilyIndex = p_config.queue_family_index, .queueCount = - static_cast(p_config.queue_priorities.size()), + static_cast(p_config.queue_priorities.size()), .pQueuePriorities = p_config.queue_priorities.data(), }; VkDeviceCreateInfo create_info = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, - .pNext = nullptr, + // .pNext = nullptr, + .pNext = p_config.features, .flags = 0, .queueCreateInfoCount = 1, .pQueueCreateInfos = &device_queue_ci, .enabledLayerCount = 0, .ppEnabledLayerNames = nullptr, .enabledExtensionCount = - static_cast(p_config.extensions.size()), + static_cast(p_config.extensions.size()), .ppEnabledExtensionNames = p_config.extensions.data(), }; @@ -51,21 +52,20 @@ export namespace vk { features.robustBufferAccess = false; create_info.pEnabledFeatures = &features; - vk_check(vkCreateDevice(p_physical, &create_info, nullptr, &m_device), - "vkCreateDevice"); + vk_check( + vkCreateDevice(p_physical, &create_info, nullptr, &m_device), + "vkCreateDevice"); } [[nodiscard]] queue_family family() const { return m_queue_family; } - void destroy() { + void destruct() { if (m_device != nullptr) { vkDestroyDevice(m_device, nullptr); } } - void wait() { - vkDeviceWaitIdle(m_device); - } + void wait() { vkDeviceWaitIdle(m_device); } operator VkDevice() const { return m_device; } diff --git a/vulkan-cpp/device_present_queue.cppm b/vulkan-cpp/device_present_queue.cppm index 0d795a8..af16202 100644 --- a/vulkan-cpp/device_present_queue.cppm +++ b/vulkan-cpp/device_present_queue.cppm @@ -10,7 +10,7 @@ export import :types; export import :utilities; export namespace vk { - inline namespace v1 { + inline namespace v6 { /** * @name device_present_queue * @brief Represents a presentation queue that must have an associated @@ -24,35 +24,34 @@ export namespace vk { public: device_present_queue() = default; device_present_queue(const VkDevice& p_device, - const VkSwapchainKHR& p_swapchain_context, - const queue_params& p_config) - : m_device(p_device), m_swapchain(p_swapchain_context) { - - vkGetDeviceQueue( - m_device, p_config.family, p_config.index, &m_queue_handler); + const VkSwapchainKHR& p_swapchain_context, + const queue_params& p_config) + : m_device(p_device) + , m_swapchain(p_swapchain_context) { + + vkGetDeviceQueue( + m_device, p_config.family, p_config.index, &m_queue_handler); m_work_completed = create_semaphore(m_device); m_presentation_completed = create_semaphore(m_device); m_out_of_date = false; } - void wait_idle() { - vkQueueWaitIdle(m_queue_handler); - } + void wait_idle() { vkQueueWaitIdle(m_queue_handler); } //! @return true if this queue is out of date - // Can occur when acquired_next_image or present_frame are out of date - // indication swapchain resizeability. - // TODO: Change this to using C++'s exceptions for handling out-of-date invalidation cases + // Can occur when acquired_next_image or present_frame are out of + // date indication swapchain resizeability. bool out_of_date(bool p_is_reset = true) { // The return value we return bool return_value = false; // If the bool is set to true meaning its out of date - // Then we set the internal variable tracking the acquired next image - // state to false, and return false This is for ensuring that we do not - // need to set the boolean ourselves. Should we handle this state to - // reset to false if checked or user should set the state??? + // Then we set the internal variable tracking the acquired next + // image state to false, and return false This is for ensuring + // that we do not need to set the boolean ourselves. Should we + // handle this state to reset to false if checked or user should + // set the state??? if (m_out_of_date) { return_value = m_out_of_date; if (p_is_reset) { @@ -69,7 +68,7 @@ export namespace vk { uint32_t image_acquired; VkResult acquired_next_image_res = - vkAcquireNextImageKHR(m_device, + vkAcquireNextImageKHR(m_device, m_swapchain, std::numeric_limits::max(), m_presentation_completed, @@ -94,35 +93,40 @@ export namespace vk { .waitSemaphoreCount = 0, .pWaitSemaphores = nullptr, .pWaitDstStageMask = nullptr, - .commandBufferCount = static_cast(p_commands.size()), + .commandBufferCount = + static_cast(p_commands.size()), .pCommandBuffers = p_commands.data(), .signalSemaphoreCount = 0, .pSignalSemaphores = nullptr, }; - VkResult res = vkQueueSubmit(m_queue_handler, 1, &submit_info, nullptr); + VkResult res = + vkQueueSubmit(m_queue_handler, 1, &submit_info, nullptr); vk_check(res, "vkQueueSubmit"); } //! @brief Submit commands to this specific present queue //! (asynchronously) void submit_async(std::span p_commands, - pipeline_stage_flags p_flags = + pipeline_stage_flags p_flags = pipeline_stage_flags::color_attachment_output) { - VkPipelineStageFlags flags = static_cast(p_flags); + VkPipelineStageFlags flags = + static_cast(p_flags); VkSubmitInfo submit_info = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .pNext = nullptr, .waitSemaphoreCount = 1, .pWaitSemaphores = &m_presentation_completed, .pWaitDstStageMask = &flags, - .commandBufferCount = static_cast(p_commands.size()), + .commandBufferCount = + static_cast(p_commands.size()), .pCommandBuffers = p_commands.data(), .signalSemaphoreCount = 1, .pSignalSemaphores = &m_work_completed, }; - VkResult res = vkQueueSubmit(m_queue_handler, 1, &submit_info, nullptr); + VkResult res = + vkQueueSubmit(m_queue_handler, 1, &submit_info, nullptr); vk_check(res, "vkQueueSubmit"); } @@ -139,14 +143,16 @@ export namespace vk { .pImageIndices = &p_frame_idx, }; - VkResult res = vkQueuePresentKHR(m_queue_handler, &present_info); + VkResult res = + vkQueuePresentKHR(m_queue_handler, &present_info); vk_check(res, "vkQueuePresentKHR"); - if (res == VK_ERROR_OUT_OF_DATE_KHR || res == VK_SUBOPTIMAL_KHR) { + if (res == VK_ERROR_OUT_OF_DATE_KHR || + res == VK_SUBOPTIMAL_KHR) { m_out_of_date = true; } } - void destroy() { + void destruct() { vkDeviceWaitIdle(m_device); vkDestroySemaphore(m_device, m_presentation_completed, nullptr); vkDestroySemaphore(m_device, m_work_completed, nullptr); diff --git a/vulkan-cpp/device_queue.cppm b/vulkan-cpp/device_queue.cppm index 4daecfc..77a4cf7 100644 --- a/vulkan-cpp/device_queue.cppm +++ b/vulkan-cpp/device_queue.cppm @@ -7,9 +7,8 @@ export module vk:device_queue; export import :types; export import :utilities; - export namespace vk { - inline namespace v1 { + inline namespace v6 { /** * @name * @brief Represents a queue part of a specific logical device created @@ -18,8 +17,10 @@ export namespace vk { public: device_queue() = default; - device_queue(const VkDevice& p_device, const queue_params& p_config) { - vkGetDeviceQueue(p_device, p_config.family, p_config.index, &m_queue_handler); + device_queue(const VkDevice& p_device, + const queue_params& p_config) { + vkGetDeviceQueue( + p_device, p_config.family, p_config.index, &m_queue_handler); } [[nodiscard]] bool alive() const { return m_queue_handler; } diff --git a/vulkan-cpp/dyn/buffer.cppm b/vulkan-cpp/dyn/buffer.cppm new file mode 100644 index 0000000..b728182 --- /dev/null +++ b/vulkan-cpp/dyn/buffer.cppm @@ -0,0 +1,174 @@ +module; + +#include +#include +#include +#include + +export module vk:buffer_device_address; + +import :types; +import :utilities; + +export namespace vk::dyn { + + class buffer { + public: + buffer() = delete; + + buffer(const VkDevice& p_device, + uint64_t p_device_size, + const buffer_parameters& p_params) + : m_device(p_device) { + construct(p_device_size, p_params); + } + + // Can be invoked to perform invalidation on this buffer + void construct(uint64_t p_device_size, + const buffer_parameters& p_params) { + + m_size_bytes = p_device_size; + + // Constructs this dyn::buffer + VkBufferCreateInfo buffer_ci = { + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = p_device_size, // size in bytes + .usage = static_cast(p_params.usage), + .sharingMode = p_params.share_mode, + }; + + vk_check(vkCreateBuffer(m_device, &buffer_ci, nullptr, &m_handle), + "vkCreateBuffer"); + + // Required to ensure the memory allocated is correspondent to the + // shader buffer device address bit + VkMemoryAllocateFlagsInfo allocate_flags_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO, + .pNext = nullptr, + .flags = + static_cast(p_params.allocate_flags), + }; + // retrieving buffer memory requirements + VkMemoryRequirements memory_requirements = {}; + vkGetBufferMemoryRequirements( + m_device, m_handle, &memory_requirements); + uint32_t mapped_memory_requirements = + memory_requirements.memoryTypeBits & p_params.memory_mask; + uint32_t memory_index = + std::countr_zero(mapped_memory_requirements); + + // Required to be set for buffer device addresses + VkMemoryAllocateInfo memory_alloc_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = &allocate_flags_info, + .allocationSize = memory_requirements.size, + .memoryTypeIndex = memory_index, + }; + + // Allocating for this buffer handle + vk_check(vkAllocateMemory( + m_device, &memory_alloc_info, nullptr, &m_device_memory), + "vkAllocateMemory"); + vk_check(vkBindBufferMemory(m_device, m_handle, m_device_memory, 0), + "vkBindBufferMemory"); + } + + void copy_to_image(const VkCommandBuffer& p_command, + const VkImage& p_image, + std::span p_copies) { + std::vector image_copies(p_copies.size()); + + for (uint32_t i = 0; i < image_copies.size(); i++) { + const buffer_image_copy image_copy = p_copies[i]; + image_copies[i] = { + .bufferOffset = image_copy.offset, + .bufferRowLength = image_copy.row_length, + .bufferImageHeight = image_copy.image_height, + .imageSubresource = { + .aspectMask = static_cast(image_copy.aspect_mask), + .mipLevel = image_copy.mip_level, + .baseArrayLayer = image_copy.base_array_layer, + .layerCount = image_copy.layer_count, + }, + .imageOffset = { + static_cast(image_copy.image_offset.width), + static_cast(image_copy.image_offset.height), + static_cast(image_copy.image_offset.depth), + }, + .imageExtent = { + image_copy.image_extent.width, + image_copy.image_extent.height, + image_copy.image_extent.depth, + }, + }; + } + + vkCmdCopyBufferToImage(p_command, + m_handle, + p_image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + static_cast(image_copies.size()), + image_copies.data()); + } + + //! @brief Destroys this object + void reset() { + if (m_handle != nullptr) { + vkDestroyBuffer(m_device, m_handle, nullptr); + } + + if (m_device_memory != nullptr) { + vkFreeMemory(m_device, m_device_memory, nullptr); + } + } + + template + void transfer(std::span p_data, uint32_t p_offset = 0) { + void* mapped = nullptr; + vk_check(vkMapMemory(m_device, + m_device_memory, + p_offset, + p_data.size_bytes(), + 0, + &mapped), + "vkMapMemory"); + memcpy(mapped, p_data.data(), p_data.size_bytes()); + vkUnmapMemory(m_device, m_device_memory); + } + + //! @brief Allows to retrieve the address to this particular buffer + //! handle + [[nodiscard("Cannot discard get_device_address()")]] const uint64_t + get_device_address() const { + VkBufferDeviceAddressInfo buffer_address_info = { + .sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, + .buffer = m_handle, + }; + + return static_cast( + vkGetBufferDeviceAddress(m_device, &buffer_address_info)); + } + + [[nodiscard("Cannot discard device_memory()")]] VkDeviceMemory + device_memory() const { + return m_device_memory; + } + + [[nodiscard("cannot discard size_bytes()")]] uint32_t size_bytes() + const { + return m_size_bytes; + } + + operator VkBuffer() { return m_handle; } + + operator VkBuffer() const { return m_handle; } + + private: + uint32_t m_size_bytes = 0; + VkDevice m_device = nullptr; + VkBuffer m_handle = nullptr; + VkDeviceMemory m_device_memory; + }; +}; \ No newline at end of file diff --git a/vulkan-cpp/dyn/shader_objects.cppm b/vulkan-cpp/dyn/shader_objects.cppm new file mode 100644 index 0000000..7203736 --- /dev/null +++ b/vulkan-cpp/dyn/shader_objects.cppm @@ -0,0 +1,234 @@ +module; + +#include +#include +#include +#include +#include + +export module vk:shader_objects; + +import :types; +import :utilities; + +export namespace vk::dyn::experimental { + inline namespace v6 { + struct shader_ext_params { + shader_stage stage; + shader_stage next_stage; + shader_code_type code_type; + std::span code_binary{}; + std::string_view name = ""; + std::span descriptor_layouts; + std::span push_constants{}; + }; + + // When querying the binary from the shaders + // If the pData is null, then we can retrieve both either the error to + // check if succeeded or the bytes needed when queried + struct shader_ext_error { + VkResult error; + uint32_t bytes = 0; + }; + + class shader_ext { + public: + shader_ext(const VkDevice& p_device, + const shader_ext_params& p_params) + : m_device(p_device) { + + vkCreateShadersEXT = reinterpret_cast( + vkGetDeviceProcAddr(m_device, "vkCreateShadersEXT")); + + std::println("ShadersEXT = {}", + (vkCreateShadersEXT == nullptr)); + + vkDestroyShaderEXT = reinterpret_cast( + vkGetDeviceProcAddr(m_device, "vkDestroyShaderEXT")); + vkCmdBindShadersEXT = reinterpret_cast( + vkGetDeviceProcAddr(m_device, "vkCmdBindShadersEXT")); + vkGetShaderBinaryDataEXT = + reinterpret_cast( + vkGetDeviceProcAddr(m_device, "vkGetShaderBinaryDataEXT")); + + vkCmdSetAlphaToCoverageEnableEXT = + reinterpret_cast( + vkGetDeviceProcAddr(m_device, + "vkCmdSetAlphaToCoverageEnableEXT")); + vkCmdSetColorBlendEnableEXT = + reinterpret_cast( + vkGetDeviceProcAddr(m_device, + "vkCmdSetColorBlendEnableEXT")); + vkCmdSetColorWriteMaskEXT = + reinterpret_cast( + vkGetDeviceProcAddr(m_device, "vkCmdSetColorWriteMaskEXT")); + vkCmdSetCullModeEXT = reinterpret_cast( + vkGetDeviceProcAddr(m_device, "vkCmdSetCullModeEXT")); + vkCmdSetDepthBiasEnableEXT = + reinterpret_cast( + vkGetDeviceProcAddr(m_device, + "vkCmdSetDepthBiasEnableEXT")); + vkCmdSetDepthCompareOpEXT = + reinterpret_cast( + vkGetDeviceProcAddr(m_device, "vkCmdSetDepthCompareOpEXT")); + vkCmdSetDepthTestEnableEXT = + reinterpret_cast( + vkGetDeviceProcAddr(m_device, + "vkCmdSetDepthTestEnableEXT")); + vkCmdSetDepthWriteEnableEXT = + reinterpret_cast( + vkGetDeviceProcAddr(m_device, + "vkCmdSetDepthWriteEnableEXT")); + vkCmdSetFrontFaceEXT = + reinterpret_cast( + vkGetDeviceProcAddr(m_device, "vkCmdSetFrontFaceEXT")); + vkCmdSetPolygonModeEXT = + reinterpret_cast( + vkGetDeviceProcAddr(m_device, "vkCmdSetPolygonModeEXT")); + vkCmdSetPrimitiveRestartEnableEXT = + reinterpret_cast( + vkGetDeviceProcAddr(m_device, + "vkCmdSetPrimitiveRestartEnableEXT")); + vkCmdSetPrimitiveTopologyEXT = + reinterpret_cast( + vkGetDeviceProcAddr(m_device, + "vkCmdSetPrimitiveTopologyEXT")); + vkCmdSetRasterizationSamplesEXT = + reinterpret_cast( + vkGetDeviceProcAddr(m_device, + "vkCmdSetRasterizationSamplesEXT")); + vkCmdSetRasterizerDiscardEnableEXT = + reinterpret_cast( + vkGetDeviceProcAddr(m_device, + "vkCmdSetRasterizerDiscardEnableEXT")); + vkCmdSetSampleMaskEXT = + reinterpret_cast( + vkGetDeviceProcAddr(m_device, "vkCmdSetSampleMaskEXT")); + vkCmdSetScissorWithCountEXT = + reinterpret_cast( + vkGetDeviceProcAddr(m_device, + "vkCmdSetScissorWithCountEXT")); + vkCmdSetStencilTestEnableEXT = + reinterpret_cast( + vkGetDeviceProcAddr(m_device, + "vkCmdSetStencilTestEnableEXT")); + vkCmdSetVertexInputEXT = + reinterpret_cast( + vkGetDeviceProcAddr(m_device, "vkCmdSetVertexInputEXT")); + vkCmdSetViewportWithCountEXT = + reinterpret_cast( + vkGetDeviceProcAddr(m_device, + "vkCmdSetViewportWithCountEXT")); + ; + + std::vector push_constants( + p_params.push_constants.size()); + + for (uint32_t i = 0; i < push_constants.size(); i++) { + const push_constant_range data = p_params.push_constants[i]; + push_constants[i] = { + .stageFlags = + static_cast(data.stage), + .offset = data.offset, + .size = data.range, + }; + } + + VkShaderCreateInfoEXT shader_info_ci = { + .sType = VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT, + .pNext = nullptr, + .flags = 0, + .stage = static_cast(p_params.stage), + .nextStage = + static_cast(p_params.next_stage), + .codeType = + static_cast(p_params.code_type), + .codeSize = + static_cast(p_params.code_binary.size()), + .pCode = p_params.code_binary.data(), + .pName = p_params.name.data(), + .setLayoutCount = + static_cast(p_params.descriptor_layouts.size()), + .pSetLayouts = p_params.descriptor_layouts.data(), + .pushConstantRangeCount = + static_cast(push_constants.size()), + .pPushConstantRanges = push_constants.data(), + }; + + vk_check( + vkCreateShadersEXT( + m_device, 1, &shader_info_ci, nullptr, &m_shader_handle), + "vkCreateShadersEXT"); + } + + void bind(const VkCommandBuffer& p_command, + uint32_t p_stage_count, + std::span p_stages) { + vkCmdBindShadersEXT( + p_command, + p_stage_count, + reinterpret_cast( + p_stages.data()), + &m_shader_handle); + } + + void destruct() { + vkDestroyShaderEXT(m_device, m_shader_handle, nullptr); + } + + private: + VkDevice m_device = nullptr; + VkShaderEXT m_shader_handle = nullptr; + + PFN_vkCreateShadersEXT vkCreateShadersEXT{ nullptr }; + PFN_vkDestroyShaderEXT vkDestroyShaderEXT{ nullptr }; + PFN_vkCmdBindShadersEXT vkCmdBindShadersEXT{ nullptr }; + PFN_vkGetShaderBinaryDataEXT vkGetShaderBinaryDataEXT{ nullptr }; + + // With VK_EXT_shader_object pipeline state must be set at command + // buffer creation using these functions + PFN_vkCmdSetAlphaToCoverageEnableEXT + vkCmdSetAlphaToCoverageEnableEXT{ nullptr }; + PFN_vkCmdSetColorBlendEnableEXT vkCmdSetColorBlendEnableEXT{ + nullptr + }; + PFN_vkCmdSetColorWriteMaskEXT vkCmdSetColorWriteMaskEXT{ nullptr }; + PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT{ nullptr }; + PFN_vkCmdSetDepthBiasEnableEXT vkCmdSetDepthBiasEnableEXT{ + nullptr + }; + PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT{ nullptr }; + PFN_vkCmdSetDepthTestEnableEXT vkCmdSetDepthTestEnableEXT{ + nullptr + }; + PFN_vkCmdSetDepthWriteEnableEXT vkCmdSetDepthWriteEnableEXT{ + nullptr + }; + PFN_vkCmdSetFrontFaceEXT vkCmdSetFrontFaceEXT{ nullptr }; + PFN_vkCmdSetPolygonModeEXT vkCmdSetPolygonModeEXT{ nullptr }; + PFN_vkCmdSetPrimitiveRestartEnableEXT + vkCmdSetPrimitiveRestartEnableEXT{ nullptr }; + PFN_vkCmdSetPrimitiveTopologyEXT vkCmdSetPrimitiveTopologyEXT{ + nullptr + }; + PFN_vkCmdSetRasterizationSamplesEXT vkCmdSetRasterizationSamplesEXT{ + nullptr + }; + PFN_vkCmdSetRasterizerDiscardEnableEXT + vkCmdSetRasterizerDiscardEnableEXT{ nullptr }; + PFN_vkCmdSetSampleMaskEXT vkCmdSetSampleMaskEXT{ nullptr }; + PFN_vkCmdSetScissorWithCountEXT vkCmdSetScissorWithCountEXT{ + nullptr + }; + PFN_vkCmdSetStencilTestEnableEXT vkCmdSetStencilTestEnableEXT{ + nullptr + }; + PFN_vkCmdSetViewportWithCountEXT vkCmdSetViewportWithCountEXT{ + nullptr + }; + + // VK_EXT_vertex_input_dynamic_state + PFN_vkCmdSetVertexInputEXT vkCmdSetVertexInputEXT{ nullptr }; + }; + }; +}; \ No newline at end of file diff --git a/vulkan-cpp/feature_extensions.cppm b/vulkan-cpp/feature_extensions.cppm new file mode 100644 index 0000000..961b69e --- /dev/null +++ b/vulkan-cpp/feature_extensions.cppm @@ -0,0 +1,233 @@ +module; + +#include +#include +#include +#include + +export module vk:feature_extensions; + +export namespace vk { + inline namespace v6 { + + /** + * @brief ExtensionConcept being a concept + * + * Used for mainly performing compile-time checks if the features + * specified are actual valid features + * + * Because Vulkan is a C API and .pNext being a void*. This can be + * bug-prone for accidentally pointing to a type that is not a feature + * struct like a std::string, for example. + * + * VulkanChainable concept is used to verify the conditions of the type + * that is being passed to chain_features. + * + * Which is important to ensure that we only accept types that do + * contain .sType and .pNext + * + * TODO: Probably might have this be a template specialization due to + * being able to verify at compile-time if the vulkan features, we need + * to check for are valid. This way even if the struct has `.sType` and + * `.pNext`, we can at least verify the struct is a feature that is + * valid + * + * + * Example: + * + * ```C++ + * + * // This trait should be false by default + * // Meaning if you reach this, then it should error out + * template + * struct device_feature : public std::false_type { + * // Having this be a compiler-error by default if this struct is + * reached. + * // Users should not be allowed to supply a non vulkan feature + * struct ONLY the vulkan feature struct static_assert(*this, "Invalid + * vulkan feature specified. Only accepting valid vulkan features."); + * }; + */ + template + concept ExtensionConcept = requires(T t) { + { t.sType } -> std::convertible_to; + { t.pNext } -> std::convertible_to; + }; + + /** + * @brief a trait that can be aliased to an + */ + template + struct feature_trait : public T { + + feature_trait(T p_initialize_feature) + : T(p_initialize_feature) { + this->sType = SType; + } + }; + + using physical_device_features2 = + feature_trait; + using buffer_device_address = feature_trait< + VkPhysicalDeviceBufferDeviceAddressFeatures, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES>; + + // Descriptor indexing to access unbounded array of a given uniform + using descriptor_indexing_feature = feature_trait< + VkPhysicalDeviceDescriptorIndexingFeatures, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES>; + + //! @brief Modernized Vulkan feature that reduces state managing for + //! renderpasses + framebuffers + using dynamic_rendering_feature = feature_trait< + VkPhysicalDeviceDynamicRenderingFeatures, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES>; + + //! @brief Represent buffers as direct pointer in shaders + // using buffer_device_address_feature = feature_trait< + // VkPhysicalDeviceBufferAddressFeatures, + // VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES>; + + //! @brief Modernize approach for bindless techniques + using descriptor_buffer_feature = feature_trait< + VkPhysicalDeviceDescriptorBufferFeaturesEXT, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_FEATURES_EXT>; + + //! @brief Updating descriptors similar fashion as push constants + using push_descriptors_feature = feature_trait< + VkPhysicalDevicePushDescriptorPropertiesKHR, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR>; + + //! @brief Feature that can skip monolith pipeline objects + using shader_object_feature = feature_trait< + VkPhysicalDeviceShaderObjectFeaturesEXT, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT>; + + //! @brief Handled dynamic pipeline states + using pipeline_library = feature_trait< + VkPhysicalDeviceGraphicsPipelineLibraryFeaturesEXT, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_FEATURES_EXT>; + + //! @brief Clean barriers and semaphores + using sync2_feature = feature_trait< + VkPhysicalDeviceSynchronization2Features, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES>; + + template + class device_features { + public: + device_features(Features&&... p_args) + : m_data(std::forward(p_args)...) { + + // Check unpacked structures of vulkan features + // Then we chain the pNext pointer altogether for each feature + // we unpack if there are any + if constexpr (sizeof...(Features) > 0) { + std::apply( + [this](auto&... spec) { chain_features(spec...); }, + m_data); + } + } + + /** + * @brief We provide the chained pNext ptr + * + * This will let us directly have the chained features the user can + * specify. + * + * @return nullptr if no features are specified, returns the pNext + * feature chain otherwise. + * + * @returns the internal data of the vulkan device extensions that + * are chained altogether. + * + * Will return nullptr if no extensions are enabled. + */ + [[nodiscard]] void* data() noexcept { + + // At compile-time we retrieve the head pointer of the entire + // feature list chain altogether. + if constexpr (sizeof...(Features) > 0) { + return &std::get<0>(m_data); + } + + return nullptr; + } + + private: + /** + * @brief Vulkan uses a linked-list specifying lists of features to + * enable. + * + * This linked-list feature is used to enable modern GPU features. + * This internal API allows to take in a collection of structs that + * are inter-connected them into a single chain the internal API's + * can read from. + * + * The pNext used for chaining + * + * [ Feature A ] [ Feature B ] [ Feature N ] + * +------------+ +------------+ +-----------+ + * | .sType | | .sType | | .sType | + * | .pNext | --> | .pNext | -> | .pNext | + * +------------+ +------------+ +-----------+ + * ( head ) ( internal ) ( tail ) + * + * Example Usage: + * + * ```C++ + * + * vk::device_features device_features{ + * vk::descriptor_indexing_feature{{ + * .descriptorBindingPartiallyBound = true, + * .descriptorBindingVariableDescriptorCount = true, + * .descriptorBindingSampledImageUpdateAfterBind = true, + * } }, + * vk::dynamic_rendering{ { + * .dynamicRendering = true, + * } }, + * }; + * + * vk::device_params config_logical_device = { + * .features = device_features, + * }; + * + * ``` + * + * + */ + template + void chain_features(T& head, Feature&... tail) { + /** + * @brief Passing an arbitrary sequences of + * VkPhysicalDevice*Features + * + * + * @brief Handled using Variadic Templates + * Compiler does not generate a loop, rather it'll generate a + * flat sequence of instructions. + * + * Essentially doing something like: + * chain_features(descriptor_indexing, dynamic_rendering). + * + * The compiler will automatically assign the chaining .pNext + * pointer per sequence: (where it will automatically assign the + * .pNext pointer per feature specified in it's sequence) + * + * 1.) current.pNext = &descriptor_indexing + * 2.) descriptor_indexing.pNext = &dynamic_rendering + * + */ + auto* current = reinterpret_cast(&head); + ((current->pNext = reinterpret_cast(&tail), + current = reinterpret_cast(&tail)), + ...); + current->pNext = nullptr; + } + + private: + std::tuple m_data; + }; + }; +}; \ No newline at end of file diff --git a/vulkan-cpp/framebuffer.cppm b/vulkan-cpp/framebuffer.cppm index 76731ea..e20513b 100644 --- a/vulkan-cpp/framebuffer.cppm +++ b/vulkan-cpp/framebuffer.cppm @@ -8,41 +8,44 @@ export import :types; export import :utilities; export namespace vk { - inline namespace v1 { + inline namespace v6 { /** * @name vk::framebuffer * * @param p_device is logical device that creates and manages the * destruction of vulkan framebuffer handles. - * @param p_setting are specifications to configure the creation of vulkan - * VkFramebuffer handles. + * @param p_setting are specifications to configure the creation of + * vulkan VkFramebuffer handles. */ class framebuffer { public: framebuffer() = default; framebuffer(const VkDevice& p_device, - const framebuffer_params& p_setting) : m_device(p_device) { - + const framebuffer_params& p_setting) + : m_device(p_device) { + VkFramebufferCreateInfo framebuffer_ci = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .pNext = nullptr, .flags = 0, .renderPass = p_setting.renderpass, - .attachmentCount = static_cast(p_setting.views.size()), + .attachmentCount = + static_cast(p_setting.views.size()), .pAttachments = p_setting.views.data(), .width = p_setting.extent.width, .height = p_setting.extent.height, .layers = 1 }; - vk::vk_check(vkCreateFramebuffer( - m_device, &framebuffer_ci, nullptr, &m_framebuffer), - "vkCreateFramebuffer"); + vk::vk_check( + vkCreateFramebuffer( + m_device, &framebuffer_ci, nullptr, &m_framebuffer), + "vkCreateFramebuffer"); } [[nodiscard]] bool alive() const { return m_framebuffer; } - void destroy() { + void destruct() { if (m_framebuffer != nullptr) { vkDestroyFramebuffer(m_device, m_framebuffer, nullptr); } diff --git a/vulkan-cpp/image.cppm b/vulkan-cpp/image.cppm new file mode 100644 index 0000000..8587244 --- /dev/null +++ b/vulkan-cpp/image.cppm @@ -0,0 +1,52 @@ +module; + +#include +#include + +export module vk:image; + +import :types; + +export namespace vk { + inline namespace v6 { + + /** + * @brief interface for the purpose of acting as an interface to + * different implementations for support variety of approaches in + * loading images. + */ + class image { + public: + virtual ~image() = default; + + /** + * @brief Perform loading operations specific to the implementation + * of this interface + */ + bool load(std::string_view p_path, texture_params p_params) { + return image_load(p_path, p_params); + } + + /** + * @brief Read you the bytes read from the specific image loading + * implementations + */ + [[nodiscard]] std::span read() const { + return image_read(); + } + + /** + * @return the extent that contains properties about the image + * (dimensions, depth, etc) + */ + [[nodiscard]] image_extent extent() const { return image_extent(); } + + protected: + virtual bool image_load(std::string_view, texture_params) = 0; + + virtual std::span image_read() const = 0; + + virtual image_extent image_extent() const = 0; + }; + }; +}; \ No newline at end of file diff --git a/vulkan-cpp/index_buffer.cppm b/vulkan-cpp/index_buffer.cppm index 558a372..0e78d14 100644 --- a/vulkan-cpp/index_buffer.cppm +++ b/vulkan-cpp/index_buffer.cppm @@ -5,56 +5,55 @@ module; export module vk:index_buffer; - export import :types; export import :utilities; export import :command_buffer; -export import :buffer_streams32; +export import :buffer32; export namespace vk { - inline namespace v1 { + inline namespace v6 { + + /** + * @brief Test implementation for index buffers + * + * This implementatino is meant to be used for an example. + * + * Though this can be used into your own code if you would like. + */ class index_buffer { public: index_buffer() = default; index_buffer(const VkDevice& p_device, - const index_params& p_info) : m_device(p_device) { - m_indices_count = p_info.indices.size(); - - buffer_parameters index_params = { - .device_size = p_info.indices.size_bytes(), - .physical_memory_properties = p_info.phsyical_memory_properties, - .property_flags = static_cast(memory_property::host_visible_bit | memory_property::host_cached_bit), - .usage = static_cast(buffer_usage::index_buffer_bit), - .debug_name = p_info.debug_name.c_str(), - .vkSetDebugUtilsObjectNameEXT = p_info.vkSetDebugUtilsObjectNameEXT - }; + std::span p_indices, + const buffer_parameters& p_params) + : m_device(p_device) { - m_index_buffer = buffer_stream32(m_device, index_params); + m_index_buffer = + buffer32(m_device, p_indices.size_bytes(), p_params); - m_index_buffer.write(p_info.indices); + m_index_buffer.transfer(p_indices); } - [[nodiscard]] bool alive() const { return m_index_buffer; } - - [[nodiscard]] uint32_t size() const { return m_indices_count; } + void construct(std::span p_indices, + const buffer_parameters& p_params) { + m_index_buffer.construct(p_indices.size_bytes(), p_params); + } - void bind(const VkCommandBuffer& p_current, uint64_t p_offset = 0) { - vkCmdBindIndexBuffer( - p_current, m_index_buffer, p_offset, VK_INDEX_TYPE_UINT32); + void transfer(std::span p_data) { + m_index_buffer.transfer(p_data); } + [[nodiscard]] bool alive() const { return m_index_buffer; } + operator VkBuffer() const { return m_index_buffer; } operator VkBuffer() { return m_index_buffer; } - void destroy() { - m_index_buffer.destroy(); - } + void destruct() { m_index_buffer.destruct(); } private: VkDevice m_device = nullptr; - uint32_t m_indices_count = 0; - buffer_stream32 m_index_buffer{}; + buffer32 m_index_buffer{}; }; }; }; \ No newline at end of file diff --git a/vulkan-cpp/instance.cppm b/vulkan-cpp/instance.cppm index 4375c90..0fd3320 100644 --- a/vulkan-cpp/instance.cppm +++ b/vulkan-cpp/instance.cppm @@ -3,22 +3,27 @@ module; #include #include #include +#include export module vk:instance; -export import :types; -export import :utilities; +import :types; +import :utilities; +import :physical_device; export namespace vk { - inline namespace v1 { + inline namespace v6 { /** - * @brief vk::instance represents VkInstance to initialize the vulkan API. + * @brief vk::instance represents VkInstance to initialize the vulkan + * API. * - * Provides configuration settings that can be applied to the application - * and the vulkan debug utility for the validation layers + * Provides configuration settings that can be applied to the + * application and the vulkan debug utility for the validation layers */ class instance { public: + instance() = delete("Disallow constructing empty vk::instance"); + /** * @param p_config sets the application information that vulkan has * optionally. @@ -26,7 +31,7 @@ export namespace vk { * tooling for debugging and enabling validation layers */ instance(const application_params& p_config, - const debug_message_utility& p_debug_message_utils) { + const debug_message_utility& p_debug_message_utils) { VkApplicationInfo app_info = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pNext = nullptr, @@ -43,87 +48,158 @@ export namespace vk { .pApplicationInfo = &app_info }; - // Setting up validation layers properties - uint32_t layer_count = 0; - std::vector layer_properties; - vkEnumerateInstanceLayerProperties(&layer_count, nullptr); - // std::vector layer_properties(layer_count); - layer_properties.resize(layer_count); - vkEnumerateInstanceLayerProperties(&layer_count, - layer_properties.data()); - - for (const VkLayerProperties property : layer_properties) { - m_layer_properties.emplace_back(property.layerName, - property.specVersion, - property.implementationVersion, - property.description); - } - // Setting up instance extensions instance_ci.enabledExtensionCount = - static_cast(p_config.extensions.size()); - instance_ci.ppEnabledExtensionNames = p_config.extensions.data(); + static_cast(p_config.extensions.size()); + instance_ci.ppEnabledExtensionNames = + p_config.extensions.data(); - #if defined(__APPLE__) - instance_ci.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; - #endif +#if defined(__APPLE__) + instance_ci.flags |= + VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; +#endif - // Only execute this if we are in the debug build - #if !defined(NDEBUG) || defined(_DEBUG) || defined(DEBUG) + // Only execute this if we are in the debug build +#if !defined(NDEBUG) || defined(_DEBUG) || defined(DEBUG) // Setting up validation layers instance_ci.enabledLayerCount = - static_cast(p_config.validations.size()); + static_cast(p_config.validations.size()); instance_ci.ppEnabledLayerNames = p_config.validations.data(); VkDebugUtilsMessengerCreateInfoEXT debug_create_info = { - .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, - .messageSeverity = static_cast(p_debug_message_utils.severity), - .messageType = static_cast(p_debug_message_utils.message_type), + .sType = + VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .messageSeverity = + static_cast( + p_debug_message_utils.severity), + .messageType = static_cast( + p_debug_message_utils.message_type), .pfnUserCallback = p_debug_message_utils.callback, }; - // This is to invoke the vulkan debug utils if it is a valid callback - // To ensure that we are not using an invalid debug callback + // This is to invoke the vulkan debug utils if it is a valid + // callback To ensure that we are not using an invalid debug + // callback if (p_debug_message_utils.callback != nullptr) { - // instance_ci.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debug_create_info; - instance_ci.pNext = reinterpret_cast(&debug_create_info); + // instance_ci.pNext = + // (VkDebugUtilsMessengerCreateInfoEXT*)&debug_create_info; + instance_ci.pNext = + reinterpret_cast( + &debug_create_info); } else { instance_ci.pNext = nullptr; } - #else +#else instance_ci.enabledLayerCount = 0; instance_ci.ppEnabledLayerNames = nullptr; instance_ci.pNext = nullptr; - #endif +#endif vk_check(vkCreateInstance(&instance_ci, nullptr, &m_instance), - "vkCreateInstance"); - - // Set the debug utility function pointer if we are in the debug build. - #if !defined(NDEBUG) || defined(_DEBUG) || defined(DEBUG) - // This needs to be created after the VkInstance is or else it wont be applied the debug information during validation layer error message execution - m_vk_set_debug_utils_object_name_ext = reinterpret_cast(vkGetInstanceProcAddr(m_instance, "vkSetDebugUtilsObjectNameEXT")); - #endif + "vkCreateInstance"); + +// Set the debug utility function pointer if we are in the debug build. +#if !defined(NDEBUG) || defined(_DEBUG) || defined(DEBUG) + // This needs to be created after the VkInstance is or else it + // wont be applied the debug information during validation layer + // error message execution + m_vk_set_debug_utils_object_name_ext = + reinterpret_cast( + vkGetInstanceProcAddr(m_instance, + "vkSetDebugUtilsObjectNameEXT")); +#endif } + ~instance() = default; + //! @return true if a valid VkInstance [[nodiscard]] bool alive() const { return !m_instance; } - //! @return available validation layers + //! @return Requesting available validation layers std::span validation() { + uint32_t layer_count = 0; + std::vector layer_properties; + vkEnumerateInstanceLayerProperties(&layer_count, nullptr); + + layer_properties.resize(layer_count); + vkEnumerateInstanceLayerProperties(&layer_count, + layer_properties.data()); + + for (const VkLayerProperties property : layer_properties) { + m_layer_properties.emplace_back( + property.layerName, + property.specVersion, + property.implementationVersion, + property.description); + } + return m_layer_properties; } /** - * @brief returns function pointer to allow for setting debug object name - * - * - * This allows for utilizing vkSetDebugUtilsObjectNameEXT during debug builds - * - * This allows for setting up object names that is useful to the programmer when a validation layer error message occurs unexpectedly - * - */ - [[nodiscard]] PFN_vkSetDebugUtilsObjectNameEXT get_debug_object_name() const { + * @brief Enumerate physical devices and select specific physical + * device to report properties from. + * + * @return vk::physical_device if successful + * @return VkResult if an unexpected error occurs + * + * + * ```C++ + * + * vk::instance api_instance = ...; + * std::expected expected = + * api_instance.enumerate_physical_device(vk::physical_device_type::integrated); + * + * vk::physical_device physical_device = expected.value(); + * + * ``` + */ + std::expected enumerate_physical_device( + physical_gpu p_device_type) { + uint32_t device_count = 0; + VkResult res = vkEnumeratePhysicalDevices( + m_instance, &device_count, nullptr); + + if (res != VK_SUCCESS) { + return std::unexpected(res); + } + + std::vector physical_devices(device_count); + res = vkEnumeratePhysicalDevices( + m_instance, &device_count, physical_devices.data()); + + if (res != VK_SUCCESS) { + return std::unexpected(res); + } + + for (const auto& device : physical_devices) { + VkPhysicalDeviceProperties device_properties; + vkGetPhysicalDeviceProperties(device, &device_properties); + + if (device_properties.deviceType == + static_cast(p_device_type)) { + return physical_device(device); + } + } + + return std::unexpected(res); + } + + /** + * @brief returns function pointer to allow for setting debug object + * name + * + * + * This allows for utilizing vkSetDebugUtilsObjectNameEXT during + * debug builds + * + * This allows for setting up object names that is useful to the + * programmer when a validation layer error message occurs + * unexpectedly + * + */ + [[nodiscard]] PFN_vkSetDebugUtilsObjectNameEXT + get_debug_object_name() const { return m_vk_set_debug_utils_object_name_ext; } @@ -132,13 +208,18 @@ export namespace vk { operator VkInstance() const { return m_instance; } //! @brief Invokes the destruction of the VkInstance. - void destroy() {} + void destruct() { + if (m_instance != nullptr) { + vkDestroyInstance(m_instance, nullptr); + } + } private: VkInstance m_instance = nullptr; std::vector m_layer_properties{}; // This needs to be set or else it becomes nullptr - PFN_vkSetDebugUtilsObjectNameEXT m_vk_set_debug_utils_object_name_ext; + PFN_vkSetDebugUtilsObjectNameEXT + m_vk_set_debug_utils_object_name_ext; }; }; }; \ No newline at end of file diff --git a/vulkan-cpp/physical_device.cppm b/vulkan-cpp/physical_device.cppm index c9aeeb5..03d59af 100644 --- a/vulkan-cpp/physical_device.cppm +++ b/vulkan-cpp/physical_device.cppm @@ -3,128 +3,254 @@ module; #include #include #include +#include export module vk:physical_device; -export import :utilities; -export import :types; - +import :utilities; +import :types; export namespace vk { - inline namespace v1 { + inline namespace v6 { class physical_device { public: - physical_device() = default; + physical_device() = + delete("Not allowed constructing empty vk::physical_device"); - physical_device(const VkInstance& p_instance, const physical_enumeration& p_physical_enumeration) { - m_physical_device = enumerate_physical_devices(p_instance, p_physical_enumeration.device_type); + physical_device(const VkPhysicalDevice& p_physical) + : m_physical_device(p_physical) {} - if (m_physical_device == nullptr) { - return; - } + ~physical_device() = default; - m_queue_family_properties = - enumerate_queue_family_properties(m_physical_device); + /** + * @brief reports the properties of this specific physical device + * + * @return VkPhysicalDeviceProperties of the selected physical + * device + */ + [[nodiscard]] VkPhysicalDeviceProperties properties() const { + VkPhysicalDeviceProperties device_properties; + vkGetPhysicalDeviceProperties(m_physical_device, + &device_properties); - // This makes sure that we get the graphics, compute, and transfer queue - // indices from the physical queue family assigned - uint32_t queue_index = 0; - for (const auto& queue_family : m_queue_family_properties) { - if (queue_family.queueFlags & VK_QUEUE_GRAPHICS_BIT) { - m_queue_family_indices.graphics = queue_index; - break; - } + return device_properties; + } - queue_index++; - } - queue_index = 0; + /** + * @brief Query for properties for queues for you specific physical + * device. + */ + [[nodiscard]] std::span + queue_family_properties() { + uint32_t queue_family_count = 0; + vkGetPhysicalDeviceQueueFamilyProperties( + m_physical_device, &queue_family_count, nullptr); + + m_queue_family_properties.resize(queue_family_count); + + vkGetPhysicalDeviceQueueFamilyProperties( + m_physical_device, + &queue_family_count, + m_queue_family_properties.data()); + + return m_queue_family_properties; + } + + //! @return true if physical device is valid + [[nodiscard]] bool alive() const { return m_physical_device; } + /** + * @brief Checks if the surface has presentation supported + * + * @param p_surface to perform presentation supported check on. + * @param p_queue_index to specify the queue family index to check + * presentation is supported. + * + * @return true if presentation supported, otherwise false + * + * + * Example: + * + * ```C++ + * + * vk::physical_device physical_device = ...; + * vk::surface surface = ...; + * + * bool is_present_supported = + * physical_device.is_present_supported(surface); + * + * if(is_present_supported) { ... } + * + * ``` + */ + [[nodiscard]] uint32_t is_present_supported( + const VkSurfaceKHR& p_surface, + uint32_t p_queue_index = 0) const { + uint32_t is_compatible = false; for (const auto& queue_family : m_queue_family_properties) { - if (queue_family.queueFlags & VK_QUEUE_COMPUTE_BIT) { - m_queue_family_indices.compute = queue_index; - } - queue_index++; + vk_check( + vkGetPhysicalDeviceSurfaceSupportKHR(m_physical_device, + p_queue_index, + p_surface, + &is_compatible), + "vkGetPhysicalDeviceSurfaceSupportKHR"); } - queue_index = 0; - for (const auto& queue_family : m_queue_family_properties) { - if (queue_family.queueFlags & VK_QUEUE_TRANSFER_BIT) { - m_queue_family_indices.transfer = queue_index; + return is_compatible; + } + + [[nodiscard]] uint32_t memory_properties( + memory_property p_property_required) const { + allocation_params return_params = {}; + + VkPhysicalDeviceMemoryProperties memory_properties; + vkGetPhysicalDeviceMemoryProperties(m_physical_device, + &memory_properties); + + uint32_t mask = 0; + for (uint32_t i = 0; i < memory_properties.memoryTypeCount; + i++) { + auto type_flags = + memory_properties.memoryTypes[i].propertyFlags; + + if ((type_flags & p_property_required) == + p_property_required) { + mask |= ((1 << i)); } - queue_index++; } - queue_index = 0; + return mask; } - //! @return true if physical device is valid - [[nodiscard]] bool alive() const { return m_physical_device; } + /** + * @brief Requests the depth format from the physical device + * + * @param p_format_supported are the arbitrary depth format + * selections to select if either are available. + * @return the format that is the one of the requested depth + * formats. + */ + [[nodiscard]] VkFormat request_depth_format( + std::span p_format_supported) { - //! @return queue family indices for graphics, compute, and transfer - //! operations - [[nodiscard]] queue_indices family_indices() const { - return m_queue_family_indices; + return request_formats( + p_format_supported, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT); } - //! @return the presentation index for the presentation queue - uint32_t queue_present_index(const VkSurfaceKHR& p_surface) { - uint32_t presentation_index = 0; - uint32_t compatible = false; - uint32_t i = 0; - for (const auto& queue_family : m_queue_family_properties) { - vk_check(vkGetPhysicalDeviceSurfaceSupportKHR( - m_physical_device, i, p_surface, &compatible), - "vkGetPhysicalDeviceSurfaceSupportKHR"); + /** + * @brief Requests for compatible formats to select + * + * @param p_tiling is selecting arrangements of the data format + * @param p_feature_flag is the bitmask selection for the image + * format + */ + [[nodiscard]] VkFormat request_formats( + std::span p_format_supported, + uint32_t p_tiling, + uint32_t p_feature_flag) { + VkFormat format = VK_FORMAT_UNDEFINED; + VkImageTiling tiling = static_cast(p_tiling); + VkFormatFeatureFlags feature_flag = + static_cast(p_feature_flag); + + for (uint32_t i = 0; i < p_format_supported.size(); i++) { + VkFormat current = + static_cast(p_format_supported[i]); + VkFormatProperties format_properties; + vkGetPhysicalDeviceFormatProperties( + m_physical_device, current, &format_properties); - if (compatible) { - presentation_index = i; + switch (tiling) { + case VK_IMAGE_TILING_LINEAR: + if (format_properties.linearTilingFeatures & + feature_flag) { + format = current; + } + break; + case VK_IMAGE_TILING_OPTIMAL: + if (format_properties.optimalTilingFeatures & + feature_flag) { + format = current; + } + break; + default: + break; } } - return presentation_index; + return format; } - //! @return physical device memory requirements - [[nodiscard]] VkPhysicalDeviceMemoryProperties memory_properties() const { - VkPhysicalDeviceMemoryProperties physical_memory_properties; - vkGetPhysicalDeviceMemoryProperties(m_physical_device, - &physical_memory_properties); - return physical_memory_properties; - } + [[nodiscard]] surface_params request_surface( + const VkSurfaceKHR& p_surface, + uint32_t p_format = VK_FORMAT_B8G8R8A8_SRGB, + uint32_t p_colorspace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + surface_params surface_properties{}; + vk_check(vkGetPhysicalDeviceSurfaceCapabilitiesKHR( + m_physical_device, + p_surface, + &surface_properties.capabilities), + "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"); - operator VkPhysicalDevice() { return m_physical_device; } + uint32_t format_count = 0; + std::vector formats; + vk_check( + vkGetPhysicalDeviceSurfaceFormatsKHR( + m_physical_device, p_surface, &format_count, nullptr), + "vkGetPhysicalDeviceSurfaceFormatsKHR"); - operator VkPhysicalDevice() const { return m_physical_device; } + formats.resize(format_count); + vk_check(vkGetPhysicalDeviceSurfaceFormatsKHR(m_physical_device, + p_surface, + &format_count, + formats.data()), + "vkGetPhysicalDeviceSurfaceFormatsKHR"); - private: - VkPhysicalDevice enumerate_physical_devices( - const VkInstance& p_instance, - const physical_gpu& p_physical_device_type) { - uint32_t device_count = 0; - vkEnumeratePhysicalDevices(p_instance, &device_count, nullptr); - - if (device_count == 0) { - return nullptr; + // These are format and colorspaces selected by the user. + VkFormat selected_format = static_cast(p_format); + VkColorSpaceKHR color_space = + static_cast(p_colorspace); + + for (const auto& format : formats) { + if (format.format == selected_format && + format.colorSpace == color_space) { + surface_properties.format = format; + } + } + + if (surface_properties.format.format == VK_FORMAT_UNDEFINED) { + surface_properties.format = formats[0]; } + // Requesting the image size based on the surface capabilitie + surface_properties.image_size = + request_surface_image_size(surface_properties.capabilities); + return surface_properties; + } - // TODO: Turn this into map - std::vector physical_devices(device_count); - vkEnumeratePhysicalDevices( - p_instance, &device_count, physical_devices.data()); - VkPhysicalDevice physical_device = nullptr; + operator VkPhysicalDevice() { return m_physical_device; } - for (const auto& device : physical_devices) { - VkPhysicalDeviceProperties device_properties; - vkGetPhysicalDeviceProperties(device, &device_properties); + operator VkPhysicalDevice() const { return m_physical_device; } - if (device_properties.deviceType == - static_cast(p_physical_device_type)) { - physical_device = device; - } + private: + uint32_t request_surface_image_size( + const VkSurfaceCapabilitiesKHR& p_capabilities) { + uint32_t requested_images = p_capabilities.minImageCount + 1; + + uint32_t final_image_count = 0; + + if ((p_capabilities.maxImageCount > 0) and + (requested_images > p_capabilities.maxImageCount)) { + final_image_count = p_capabilities.maxImageCount; + } + else { + final_image_count = requested_images; } - return physical_device; - } + + return final_image_count; + } private: VkPhysicalDevice m_physical_device = nullptr; diff --git a/vulkan-cpp/pipeline.cppm b/vulkan-cpp/pipeline.cppm index dfecade..81652b4 100644 --- a/vulkan-cpp/pipeline.cppm +++ b/vulkan-cpp/pipeline.cppm @@ -12,7 +12,7 @@ export import :types; export import :utilities; export namespace vk { - inline namespace v1 { + inline namespace v6 { struct input_assembly_state { primitive_topology topology = primitive_topology::triangle_list; @@ -20,7 +20,7 @@ export namespace vk { }; struct viewport_state { - uint8_t viewport_count= 1; + uint8_t viewport_count = 1; uint8_t scissor_count = 1; }; @@ -30,7 +30,7 @@ export namespace vk { polygon_mode polygon_mode = polygon_mode::fill; cull_mode cull_mode = cull_mode::none; front_face front_face = front_face::counter_clockwise; - bool depth_bias_enabled=false; + bool depth_bias_enabled = false; float depth_bias_constant = 0.f; float depth_bias_clamp = 0.f; float depth_bias_slope = 0.f; @@ -38,27 +38,30 @@ export namespace vk { }; struct multisample_state { - sample_bit rasterization_samples=sample_bit::count_1; - bool shading_enabled=false; - float min_shading = 1.f; // optional - std::span p_sample_masks={}; // optional - bool alpha_to_coverage_enable=false; // optional - bool alpha_to_one_enable=false; // optional + sample_bit rasterization_samples = sample_bit::count_1; + bool shading_enabled = false; + float min_shading = 1.f; // optional + std::span p_sample_masks = {}; // optional + bool alpha_to_coverage_enable = false; // optional + bool alpha_to_one_enable = false; // optional }; struct color_blend_attachment_state { bool blend_enabled = true; - blend_factor src_color_blend_factor=blend_factor::src_alpha; - blend_factor dst_color_blend_factor = blend_factor::one_minus_src_alpha; + blend_factor src_color_blend_factor = blend_factor::src_alpha; + blend_factor dst_color_blend_factor = + blend_factor::one_minus_src_alpha; blend_op color_blend_op = blend_op::add; blend_factor src_alpha_blend_factor = blend_factor::one; blend_factor dst_alpha_blend_factor = blend_factor::zero; blend_op alpha_blend_op = blend_op::add; - uint32_t color_write_mask = color_component::red | color_component::green | color_component::blue | color_component::alpha; + uint32_t color_write_mask = + color_component::red | color_component::green | + color_component::blue | color_component::alpha; }; struct color_blend_state { - bool logic_op_enable=false; + bool logic_op_enable = false; logical_op logical_op = logical_op::copy; std::span attachments; std::span blend_constants; @@ -72,27 +75,46 @@ export namespace vk { bool stencil_test_enable = false; }; + struct push_constant_range { + shader_stage stage; + uint32_t offset = 0; + uint32_t range = 0; + }; /** * @param renderpass is required for a VkPipeline to know up front - * @param shader_modules is a std::span of the loaded shader - * sources for the pipeline to correspond to - * @param descriptor_layouts are the VkDescriptorSetLayout that you pass up - * front to the graphics pipeline if there are any provided - * @param input_assembly is for configuring the state of the input assembly for the graphics pipeline - * @param viewport_state is for configuring state of the viewport for this graphics pipeline - * @param rasterization_state is to configure how the topology and rasterization with this graphics pipeline is configured. - * @param multisample is to configure the graphics pipeline's multisample state - * @param color_blend is configuring the graphics pipeline state for specifying color blending - * @param depth_stencil_enable is used to toggle to use this graphics pipeline with an additional of depth stencil or just the color blend. - * @param depth_stencil is for specifying to this graphics pipeline configuring the depth stencil configurations. - * @param dynamic_states is specifying the dynamic state of the viewport and scissor to configure for this graphics pipeline + * @param shader_modules is a std::span of the loaded + * shader sources for the pipeline to correspond to + * @param descriptor_layouts are the VkDescriptorSetLayout that you pass + * up front to the graphics pipeline if there are any provided + * @param input_assembly is for configuring the state of the input + * assembly for the graphics pipeline + * @param viewport_state is for configuring state of the viewport for + * this graphics pipeline + * @param rasterization_state is to configure how the topology and + * rasterization with this graphics pipeline is configured. + * @param multisample is to configure the graphics pipeline's + * multisample state + * @param color_blend is configuring the graphics pipeline state for + * specifying color blending + * @param depth_stencil_enable is used to toggle to use this graphics + * pipeline with an additional of depth stencil or just the color blend. + * @param depth_stencil is for specifying to this graphics pipeline + * configuring the depth stencil configurations. + * @param dynamic_states is specifying the dynamic state of the viewport + * and scissor to configure for this graphics pipeline */ struct pipeline_params { + bool use_render_pipeline = false; + std::span color_attachment_formats = {}; + const uint32_t depth_format; + const uint32_t stencil_format; VkRenderPass renderpass = nullptr; std::span shader_modules{}; - std::span vertex_attributes; - std::span vertex_bind_attributes; + std::span + vertex_attributes; + std::span + vertex_bind_attributes; std::span descriptor_layouts; input_assembly_state input_assembly; @@ -103,6 +125,8 @@ export namespace vk { bool depth_stencil_enabled = false; depth_stencil_state depth_stencil; std::span dynamic_states = {}; + + std::span push_constants{}; }; /** @@ -114,182 +138,284 @@ export namespace vk { /** * @brief constructs the graphics pipeline handle - * - * @param p_device is logical device to create the graphics pipeline handles - * @param p_info are the parameters for creating the pipelines with - */ - pipeline(const VkDevice& p_device, const pipeline_params& p_info) : m_device(p_device) { - invalidate(p_info); + * + * @param p_device is logical device to create the graphics pipeline + * handles + * @param p_params are the parameters for creating the pipelines + * with + */ + pipeline(const VkDevice& p_device, const pipeline_params& p_params) + : m_device(p_device) { + configure(p_params); } + ~pipeline() = default; + /** - * @brief explicit API for creating a VkPipeline and VkPipelineLayout - * handle - * + * @brief explicit API for creating a VkPipeline and + * VkPipelineLayout handle + * * Code Usage Example - * + * * ```C++ - * + * * vk::pipeline_params pipeline_params = { * .renderpass = main_renderpass // pass in VkRenderPass handle - * .shader_modules = shader_resource.handles() // sets the std::span - * .vertex_attributes = shader_resource.vertex_attributes(), - * .vertex_bind_attributes = shader_resource.vertex_bind_attributes(), - * .descriptor_layouts = layouts + * .shader_modules = shader_resource.handles() // sets the + * std::span .vertex_attributes = + * shader_resource.vertex_attributes(), .vertex_bind_attributes = + * shader_resource.vertex_bind_attributes(), .descriptor_layouts = + * layouts * }; - * + * * vk::pipeline graphics_pipeline(logical_device, pipeline_params); - * + * * // or when need to invalidate, explicitly call create * graphics_pipeline.create(pipeline_params); - * + * * ``` * - * @param p_info is the parameters required to set the graphics pipeline handles - * - * More info on vulkan's official + * @param p_params is the parameters required to set the graphics + * pipeline handles + * + * More info on vulkan's official * [docs](https://docs.vulkan.org/refpages/latest/refpages/source/vkCreateGraphicsPipelines.html) */ - void invalidate(const pipeline_params& p_info) { - std::vector pipeline_shader_stages(p_info.shader_modules.size()); + void configure(const pipeline_params& p_params) { + std::vector + pipeline_shader_stages(p_params.shader_modules.size()); uint32_t shader_src_index = 0; - // 1. Load in and setup the VKShaderModule handlers for VkPipeline - for (const shader_handle& src : p_info.shader_modules) { - VkShaderStageFlags stage = static_cast(src.stage); + // 1. Load in and setup the VKShaderModule handlers for + // VkPipeline + for (const shader_handle& src : p_params.shader_modules) { + VkShaderStageFlags stage = + static_cast(src.stage); pipeline_shader_stages[shader_src_index] = - VkPipelineShaderStageCreateInfo{ - .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, - .stage = (VkShaderStageFlagBits)stage, - .module = src.module, - .pName = "main" - }; + VkPipelineShaderStageCreateInfo{ + .sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = static_cast(stage), + .module = src.module, + .pName = "main" + }; shader_src_index++; } // 2. Setting up the vertex attribute details for VkPipeline - std::span bind_attributes = - p_info.vertex_bind_attributes; + std::span + bind_attributes = p_params.vertex_bind_attributes; std::span attributes = - p_info.vertex_attributes; + p_params.vertex_attributes; VkPipelineVertexInputStateCreateInfo vertex_input_info = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .sType = + VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, .vertexBindingDescriptionCount = - static_cast(bind_attributes.size()), + static_cast(bind_attributes.size()), .pVertexBindingDescriptions = bind_attributes.data(), .vertexAttributeDescriptionCount = - static_cast(attributes.size()), + static_cast(attributes.size()), .pVertexAttributeDescriptions = attributes.data() }; VkPipelineInputAssemblyStateCreateInfo input_assembly = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, - .topology = static_cast(p_info.input_assembly.topology), - .primitiveRestartEnable = p_info.input_assembly.primitive_restart_enable, + .sType = + VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .topology = static_cast( + p_params.input_assembly.topology), + .primitiveRestartEnable = + p_params.input_assembly.primitive_restart_enable, }; VkPipelineViewportStateCreateInfo viewport_state = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, - .viewportCount = p_info.viewport.viewport_count, - .scissorCount = p_info.viewport.scissor_count, + .sType = + VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .viewportCount = p_params.viewport.viewport_count, + .scissorCount = p_params.viewport.scissor_count, }; // if lineWidth is zero, validation layers will occur // because cant be zero. Must be set to 1.0f VkPipelineRasterizationStateCreateInfo rasterizer_ci = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, - .depthClampEnable = p_info.rasterization.depth_clamp_enabled, - .rasterizerDiscardEnable = p_info.rasterization.rasterizer_discard_enabled, - .polygonMode = static_cast(p_info.rasterization.polygon_mode), - .cullMode = static_cast(p_info.rasterization.cull_mode), - .frontFace = static_cast(p_info.rasterization.front_face), - .depthBiasEnable = p_info.rasterization.depth_bias_enabled, - .depthBiasConstantFactor = p_info.rasterization.depth_bias_constant, - .depthBiasClamp = p_info.rasterization.depth_bias_clamp, - .depthBiasSlopeFactor = p_info.rasterization.depth_bias_slope, - .lineWidth = p_info.rasterization.line_width + .sType = + VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .depthClampEnable = + p_params.rasterization.depth_clamp_enabled, + .rasterizerDiscardEnable = + p_params.rasterization.rasterizer_discard_enabled, + .polygonMode = static_cast( + p_params.rasterization.polygon_mode), + .cullMode = static_cast( + p_params.rasterization.cull_mode), + .frontFace = static_cast( + p_params.rasterization.front_face), + .depthBiasEnable = + p_params.rasterization.depth_bias_enabled, + .depthBiasConstantFactor = + p_params.rasterization.depth_bias_constant, + .depthBiasClamp = p_params.rasterization.depth_bias_clamp, + .depthBiasSlopeFactor = + p_params.rasterization.depth_bias_slope, + .lineWidth = p_params.rasterization.line_width }; VkPipelineMultisampleStateCreateInfo multisampling_ci = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, - .rasterizationSamples = static_cast(p_info.multisample.rasterization_samples), - .sampleShadingEnable = p_info.multisample.shading_enabled, - .minSampleShading = p_info.multisample.min_shading, - .pSampleMask = p_info.multisample.p_sample_masks.data(), - .alphaToCoverageEnable = p_info.multisample.alpha_to_coverage_enable, - .alphaToOneEnable = p_info.multisample.alpha_to_one_enable, + .sType = + VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .rasterizationSamples = static_cast( + p_params.multisample.rasterization_samples), + .sampleShadingEnable = p_params.multisample.shading_enabled, + .minSampleShading = p_params.multisample.min_shading, + .pSampleMask = p_params.multisample.p_sample_masks.data(), + .alphaToCoverageEnable = + p_params.multisample.alpha_to_coverage_enable, + .alphaToOneEnable = + p_params.multisample.alpha_to_one_enable, }; - std::vector color_blend_attachments(p_info.color_blend.attachments.size()); + std::vector + color_blend_attachments( + p_params.color_blend.attachments.size()); - for(size_t i = 0; i < color_blend_attachments.size(); i++) { + for (size_t i = 0; i < color_blend_attachments.size(); i++) { color_blend_attachments[i] = { - .blendEnable = p_info.color_blend.attachments[i].blend_enabled, - .srcColorBlendFactor = static_cast(p_info.color_blend.attachments[i].src_color_blend_factor), // Enabled: alpha blending - .dstColorBlendFactor = static_cast(p_info.color_blend.attachments[i].dst_color_blend_factor), // Enabled: alpha blending - .colorBlendOp = static_cast(p_info.color_blend.attachments[i].color_blend_op), // Enabled: alpha blending - .srcAlphaBlendFactor = static_cast(p_info.color_blend.attachments[i].src_alpha_blend_factor), // Enabled: alpha blending - .dstAlphaBlendFactor = static_cast(p_info.color_blend.attachments[i].dst_alpha_blend_factor), // Enabled: alpha blending - .alphaBlendOp = static_cast(p_info.color_blend.attachments[i].alpha_blend_op), // Enabled: alpha blending - .colorWriteMask = static_cast(p_info.color_blend.attachments[i].color_write_mask), + .blendEnable = + p_params.color_blend.attachments[i].blend_enabled, + .srcColorBlendFactor = static_cast( + p_params.color_blend.attachments[i] + .src_color_blend_factor), // Enabled: alpha blending + .dstColorBlendFactor = static_cast( + p_params.color_blend.attachments[i] + .dst_color_blend_factor), // Enabled: alpha blending + .colorBlendOp = static_cast( + p_params.color_blend.attachments[i] + .color_blend_op), // Enabled: alpha blending + .srcAlphaBlendFactor = static_cast( + p_params.color_blend.attachments[i] + .src_alpha_blend_factor), // Enabled: alpha blending + .dstAlphaBlendFactor = static_cast( + p_params.color_blend.attachments[i] + .dst_alpha_blend_factor), // Enabled: alpha blending + .alphaBlendOp = static_cast( + p_params.color_blend.attachments[i] + .alpha_blend_op), // Enabled: alpha blending + .colorWriteMask = static_cast( + p_params.color_blend.attachments[i].color_write_mask), }; } - VkPipelineColorBlendStateCreateInfo color_blending_ci = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, - .logicOpEnable = p_info.color_blend.logic_op_enable, - .logicOp = static_cast(p_info.color_blend.logical_op), // Optional - .attachmentCount = static_cast(color_blend_attachments.size()), + .sType = + VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .logicOpEnable = p_params.color_blend.logic_op_enable, + .logicOp = static_cast( + p_params.color_blend.logical_op), // Optional + .attachmentCount = + static_cast(color_blend_attachments.size()), .pAttachments = color_blend_attachments.data(), // these are optional - .blendConstants = {0.f, 0.f, 0.f, 0.f} // optional -- set to default in being 0.0f's + .blendConstants = { 0.f, 0.f, 0.f, 0.f } + // optional -- set to default in being 0.0f's }; - // Using ranges to load in the floats from an arbitrary array into this. Though it should only be valid to accept only 4 floats rather then N arbitrary floats in this buffer. - if(!p_info.color_blend.blend_constants.empty()) { + // Using ranges to load in the floats from an arbitrary array + // into this. Though it should only be valid to accept only 4 + // floats rather then N arbitrary floats in this buffer. + if (!p_params.color_blend.blend_constants.empty()) { // Get the first 4 elements in the span as those are // the data we are to set the .blendConstants to. - // As .blendConstants only take up to 4 elements in the array. - std::span color_blend_constants = p_info.color_blend.blend_constants.first<4>(); - std::ranges::copy(color_blend_constants.begin(), color_blend_constants.end(), color_blending_ci.blendConstants); + // As .blendConstants only take up to 4 elements in the + // array. + std::span color_blend_constants = + p_params.color_blend.blend_constants.first<4>(); + std::ranges::copy(color_blend_constants.begin(), + color_blend_constants.end(), + color_blending_ci.blendConstants); } - VkPipelineDepthStencilStateCreateInfo pipeline_deth_stencil_state_ci = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, - .depthTestEnable = p_info.depth_stencil.depth_test_enable, - .depthWriteEnable = p_info.depth_stencil.depth_write_enable, - .depthCompareOp = static_cast(p_info.depth_stencil.depth_compare_op), - .depthBoundsTestEnable = p_info.depth_stencil.depth_bounds_test_enable, - .stencilTestEnable = p_info.depth_stencil.stencil_test_enable, - }; - - //! @note -- pipeline states needs to be baked into the pipeline state + VkPipelineDepthStencilStateCreateInfo + pipeline_deth_stencil_state_ci = { + .sType = + VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .depthTestEnable = + p_params.depth_stencil.depth_test_enable, + .depthWriteEnable = + p_params.depth_stencil.depth_write_enable, + .depthCompareOp = static_cast( + p_params.depth_stencil.depth_compare_op), + .depthBoundsTestEnable = + p_params.depth_stencil.depth_bounds_test_enable, + .stencilTestEnable = + p_params.depth_stencil.stencil_test_enable, + }; + + //! @note -- pipeline states needs to be baked into the pipeline + //! state VkPipelineDynamicStateCreateInfo dynamic_state_ci = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, - .dynamicStateCount = static_cast(p_info.dynamic_states.size()), - .pDynamicStates = reinterpret_cast(p_info.dynamic_states.data()) + .sType = + VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .dynamicStateCount = + static_cast(p_params.dynamic_states.size()), + .pDynamicStates = reinterpret_cast( + p_params.dynamic_states.data()) }; - // Specifies layout of the uniforms (data resources) to be used by this specified graphics pipeline + std::vector push_constants( + p_params.push_constants.size()); + + for (uint32_t i = 0; i < push_constants.size(); i++) { + const push_constant_range data = p_params.push_constants[i]; + push_constants[i] = { + .stageFlags = + static_cast(data.stage), + .offset = data.offset, + .size = data.range, + }; + } + + // Specifies layout of the uniforms (data resources) to be used + // by this specified graphics pipeline VkPipelineLayoutCreateInfo pipeline_layout_ci = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, - .setLayoutCount = static_cast(p_info.descriptor_layouts.size()), - .pSetLayouts = p_info.descriptor_layouts.data(), + .setLayoutCount = + static_cast(p_params.descriptor_layouts.size()), + .pSetLayouts = p_params.descriptor_layouts.data(), + .pushConstantRangeCount = + static_cast(push_constants.size()), + .pPushConstantRanges = push_constants.data(), }; - vk_check(vkCreatePipelineLayout( - m_device, &pipeline_layout_ci, nullptr, &m_pipeline_layout), - "vkCreatePipelineLayout"); + vk_check( + vkCreatePipelineLayout( + m_device, &pipeline_layout_ci, nullptr, &m_pipeline_layout), + "vkCreatePipelineLayout"); + + VkPipelineRenderingCreateInfo rendering_ci = { + // .sType = VK_STRUCTURE_TYPE_PIPELINE_CREATE_INFO_KHR, + .sType = + VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR, + .pNext = nullptr, + .colorAttachmentCount = static_cast( + p_params.color_attachment_formats.size()), + .pColorAttachmentFormats = + reinterpret_cast( + p_params.color_attachment_formats.data()), + .depthAttachmentFormat = + static_cast(p_params.depth_format), + .stencilAttachmentFormat = + static_cast(p_params.stencil_format), + }; VkGraphicsPipelineCreateInfo graphics_pipeline_ci = { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, - .pNext = nullptr, + .pNext = + p_params.use_render_pipeline ? &rendering_ci : nullptr, .flags = 0, - .stageCount = static_cast(pipeline_shader_stages.size()), + .stageCount = + static_cast(pipeline_shader_stages.size()), .pStages = pipeline_shader_stages.data(), .pVertexInputState = &vertex_input_info, .pInputAssemblyState = &input_assembly, @@ -298,18 +424,23 @@ export namespace vk { .pMultisampleState = &multisampling_ci, .pDepthStencilState = &pipeline_deth_stencil_state_ci, .pColorBlendState = &color_blending_ci, - .pDynamicState = (p_info.depth_stencil_enabled) ? &dynamic_state_ci : nullptr, + .pDynamicState = (p_params.depth_stencil_enabled) + ? &dynamic_state_ci + : nullptr, .layout = m_pipeline_layout, - .renderPass = p_info.renderpass, + .renderPass = p_params.renderpass, .subpass = 0, .basePipelineHandle = nullptr, .basePipelineIndex = -1 }; - vk::vk_check( - vkCreateGraphicsPipelines( - m_device, nullptr, 1, &graphics_pipeline_ci, nullptr, &m_pipeline), - "vkCreateGraphicsPipelines"); + vk::vk_check(vkCreateGraphicsPipelines(m_device, + nullptr, + 1, + &graphics_pipeline_ci, + nullptr, + &m_pipeline), + "vkCreateGraphicsPipelines"); } /** @@ -322,40 +453,48 @@ export namespace vk { * * ```C++ * - * vk::pipeline graphics_pipeline(logical_device, *assume pipeline_params is specified*); + * vk::pipeline graphics_pipeline(logical_device, *assume + * pipeline_params is specified*); * * // bound to current command buffer - * // in this example we set binding point to VK_PIPELINE_BIND_POINT_GRAPHICS + * // in this example we set binding point to + * VK_PIPELINE_BIND_POINT_GRAPHICS * graphics_pipeline.bind(current_command, * pipeline_bind_point::graphics); * ``` * - * @param p_command is the current command buffer the graphics pipeline - * is bound to - * @param p_bind_point is the specified bind point graphics pipeline is - * bound to. - * - * More info on vulkan's official + * @param p_command is the current command buffer the graphics + * pipeline is bound to + * @param p_bind_point is the specified bind point graphics pipeline + * is bound to. + * + * More info on vulkan's official * [docs](https://docs.vulkan.org/refpages/latest/refpages/source/vkCmdBindPipeline.html) */ - void bind(const VkCommandBuffer& p_command, pipeline_bind_point p_bind_point = pipeline_bind_point::graphics) { - vkCmdBindPipeline(p_command, static_cast(p_bind_point), m_pipeline); + void bind(const VkCommandBuffer& p_command, + pipeline_bind_point p_bind_point = + pipeline_bind_point::graphics) { + vkCmdBindPipeline( + p_command, + static_cast(p_bind_point), + m_pipeline); } /** * @brief Update values of push constants - * + * * Push constants let us send small amount of data with a small * limited size to the shader * - * Push constants only accept up to a max of 128 bytes of push constant - * data + * Push constants only accept up to a max of 128 bytes of push + * constant data * * They allow for sending data to any specified shader stage (both - * vertex and fragment) that are stored within the command buffer itself + * vertex and fragment) that are stored within the command buffer + * itself * - * Using push constants are commonly used when you have chunks of data - * that may be changed every frame. + * Using push constants are commonly used when you have chunks of + * data that may be changed every frame. * * Data that may need to be updated every frame such as camera * projection/view and model matrices. @@ -363,56 +502,74 @@ export namespace vk { * Example Code Usage: * * ```C++ - * - * vk::pipeline graphics_pipeline(logical_device, *assume pipeline_params is specified*); - * + * + * vk::pipeline graphics_pipeline(logical_device, *assume + * pipeline_params is specified*); + * * m_pipeline.push_constants(current, shader_stage::vertex, 0, 1, * &global_data); - * + * * ``` * - * @param p_current current command to push constants directly to the - * shader - * @param p_stage is specifying what stage of the push constants are at - * @param p_offset is specified of the beginning of the offset to start - * from - * @param p_range is the range of bytes of the specified push constant - * data + * @tparam T is the type of the push constant + * @tparam max_size parameter for controlling max of bytes to send + * + * @param p_current current command to push constants directly to + * the shader + * @param p_stage is specifying what stage of the push constants are + * at + * @param p_offset is specified of the beginning of the offset to + * start from + * @param p_range is the range of bytes of the specified push + * constant data * @param p_data is the data that is represented into bytes to push * constants * - * More info on vulkan's official + * More info on vulkan's official * [docs](https://docs.vulkan.org/refpages/latest/refpages/source/vkCmdPushConstants.html) */ + template void push_constant(const VkCommandBuffer& p_current, - shader_stage p_stage, - uint32_t p_offset, - uint32_t p_range, - const void* p_data) { - vkCmdPushConstants(p_current, m_pipeline_layout, static_cast(p_stage), p_offset, p_range, p_data); + const T& p_data, + shader_stage p_stage, + uint32_t p_offset) { + + // Perform compile-time checks if push constant data exceeds + // maximum size of data to be transferred via push constant + static_assert(sizeof(T) <= max_size, + "Type T exceeds max allowed size of bytes for " + "push constants."); + + vkCmdPushConstants(p_current, + m_pipeline_layout, + static_cast(p_stage), + p_offset, + sizeof(T), + &p_data); } //! @return true if m_pipeline is valid, false if invalid [[nodiscard]] bool alive() const { return m_pipeline; } - //! @return VkPipelineLayout that has been created with the vk::pipeline - //! handle + //! @return the VkPipelineLayout handle [[nodiscard]] VkPipelineLayout layout() const { return m_pipeline_layout; } //! @brief explicit cleanup performed on vk::pipeline - void destroy() { + void destruct() { if (m_pipeline_layout != nullptr) { - vkDestroyPipelineLayout(m_device, m_pipeline_layout, nullptr); + vkDestroyPipelineLayout( + m_device, m_pipeline_layout, nullptr); } if (m_pipeline != nullptr) { vkDestroyPipeline(m_device, m_pipeline, nullptr); } } - //! @brief allows for treating vk::pipeline as a VkPipeline handle for - //! simple use + /** + * @brief Directly vk::pipeline as a VkPipeline handle + */ operator VkPipeline() const { return m_pipeline; } operator VkPipeline() { return m_pipeline; } diff --git a/vulkan-cpp/renderpass.cppm b/vulkan-cpp/renderpass.cppm index f9ec230..a7e337d 100644 --- a/vulkan-cpp/renderpass.cppm +++ b/vulkan-cpp/renderpass.cppm @@ -11,7 +11,7 @@ export import :types; export import :utilities; export namespace vk { - inline namespace v1 { + inline namespace v6 { /** * @name renderpass * @@ -19,31 +19,71 @@ export namespace vk { * renderpasses * @param p_renderpass_attachment is a vk::attachment to specify the * individual attachment operation that handle in creating - * VkAttachmentDescription, VkAttachmentReference, and VkSubpassDescription + * VkAttachmentDescription, VkAttachmentReference, and + * VkSubpassDescription * - * @param p_enable_subpass because subpasses are optional, this is a boolean - * to enable if we want to apply subpasses + * @param p_enable_subpass because subpasses are optional, this is a + * boolean to enable if we want to apply subpasses */ class renderpass { public: renderpass() = default; renderpass(const VkDevice& p_device, - std::span p_renderpass_attachments, - bool p_enable_subpasses = true) : m_device(p_device) { - create(p_renderpass_attachments, p_enable_subpasses); + std::span p_renderpass_attachments, + bool p_enable_subpasses = true) + : m_device(p_device) { + configure(p_renderpass_attachments, p_enable_subpasses); } - void create(std::span p_renderpass_attachments, bool p_enable_subpasses = true) { + /** + * @brief Condifures the renderpass pipeline. By defining what kinds + * of image handles will be used and how their memory should be + * treated. + * + * Which includes how to treat these images for the particular tasks + * that can be done on those images. + * + * @param p_renderpass_attachments + * - .format: Image bit-depth + * - .load/store: Configuring for loading/storing pixels + * - .initial_layout: stage the image starts in. + * - .final_layout: stage the image is left for the next task. + * + * @brief Additional Considerations: + * - Framebuffers must be synced with the configuration of this + * renderpass handle when constructed. + * - .initial_layout and .final_layout must be valid for the image + * type specified. + * - Attachment references in subpass must point to a valid indices + * within the p_renderpass_attachment parameters + * + * +---------------------+ +-------------------------+ + * | Attch 0: RGBA8 Color|--- [Ref] ---> | .pColorAttachments | + * | Attch 1: D32 Depth |--- [Ref] ---> | .pDepthStencilAttachment| + * +---------------------+ +-------------------------+ + * | Load: Clear | | | + * | Store: Store | | | + * +---------------------+ +-------------------------+ + * | | + * \____________________________________/ + * | + * V + * [ Configure RenderPass Handle] + */ + void configure(std::span p_renderpass_attachments, + bool p_enable_subpasses = true) { // 1. Specifically for setting up the attachment description std::vector attachment_description( - p_renderpass_attachments.size()); + p_renderpass_attachments.size()); // color attachment reference slots to set to help the - // VkAttachmentReference know which color attachment they correspond to + // VkAttachmentReference know which color attachment they + // correspond to std::vector color_attachment_indices; // depth attachment reference slots to set to help the - // VkAttachmentReference know which depth attachment they correspond to + // VkAttachmentReference know which depth attachment they + // correspond to std::vector depth_attachment_indices; for (size_t i = 0; i < attachment_description.size(); i++) { @@ -51,23 +91,25 @@ export namespace vk { attachment_description[i] = { .flags = 0, .format = attachment_spec.format, - // .samples = VK_SAMPLE_COUNT_1_BIT, - .samples = static_cast(attachment_spec.samples), - // .loadOp = to_attachment_load(attachment_spec.load), - .loadOp = static_cast(attachment_spec.load), - // .storeOp = to_attachment_store(attachment_spec.store), - .storeOp = static_cast(attachment_spec.store), - .stencilLoadOp = - static_cast(attachment_spec.stencil_load), - .stencilStoreOp = - static_cast(attachment_spec.stencil_store), - .initialLayout = static_cast(attachment_spec.initial_layout), - .finalLayout = static_cast(attachment_spec.final_layout) + .samples = static_cast( + attachment_spec.samples), + .loadOp = + static_cast(attachment_spec.load), + .storeOp = static_cast( + attachment_spec.store), + .stencilLoadOp = static_cast( + attachment_spec.stencil_load), + .stencilStoreOp = static_cast( + attachment_spec.stencil_store), + .initialLayout = static_cast( + attachment_spec.initial_layout), + .finalLayout = static_cast( + attachment_spec.final_layout) }; // I do a check here to save the slots for specifying the - // VkAttachmentReference Since .attachment is the slot index for - // corresponding which attachment layout is to which + // VkAttachmentReference Since .attachment is the slot index + // for corresponding which attachment layout is to which if (has_depth_specified(attachment_spec.layout)) { depth_attachment_indices.emplace_back(i); } @@ -76,49 +118,54 @@ export namespace vk { } } - // 2. Setting up the color attachment reference to specifying specific - // attachments they correspond to (using the indices) + // 2. Setting up the color attachment reference to specifying + // specific attachments they correspond to (using the indices) std::vector color_attachment_references( - color_attachment_indices.size()); + color_attachment_indices.size()); for (size_t i = 0; i < color_attachment_indices.size(); i++) { uint32_t slot = color_attachment_indices[i]; color_attachment_references[i] = { .attachment = slot, - // .layout = to_image_layout(p_renderpass_attachments[slot].layout) - .layout = static_cast(p_renderpass_attachments[slot].layout) + // .layout = + // to_image_layout(p_renderpass_attachments[slot].layout) + .layout = static_cast( + p_renderpass_attachments[slot].layout) }; } - // 3. Setting up the depth attachment reference to specifying specific - // attachments they correspond to (using the indices) + // 3. Setting up the depth attachment reference to specifying + // specific attachments they correspond to (using the indices) std::vector depth_attachment_references( - depth_attachment_indices.size()); + depth_attachment_indices.size()); for (size_t i = 0; i < depth_attachment_indices.size(); i++) { uint32_t slot = depth_attachment_indices[i]; depth_attachment_references[i] = { .attachment = slot, - // .layout = to_image_layout(p_renderpass_attachments[slot].layout) - .layout = static_cast(p_renderpass_attachments[slot].layout) + .layout = static_cast( + p_renderpass_attachments[slot].layout) }; } - // 4. Setting up subpass descriptions that may/may not be applied to - // this renderpass based on the p_enable_subpass is set to true - // TODO: VkSubpassDescription is deprecated in vulkan 1.2+, we should - // change to VkSubpassDescription2 - // TODO: Change from VkRenderPassCreateInfo to VkRenderPassCreateInfo2 - // since they have different parameter modifications + // 4. Setting up subpass descriptions that may/may not be + // applied to this renderpass based on the p_enable_subpass is + // set to true + // TODO: VkSubpassDescription is deprecated in vulkan 1.2+, we + // should change to VkSubpassDescription2 + // TODO: Change from VkRenderPassCreateInfo to + // VkRenderPassCreateInfo2 since they have different parameter + // modifications VkSubpassDescription subpass_description = { .flags = 0, .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .inputAttachmentCount = 0, .pInputAttachments = nullptr, .colorAttachmentCount = - static_cast(color_attachment_references.size()), + static_cast(color_attachment_references.size()), .pColorAttachments = color_attachment_references.data(), .pResolveAttachments = nullptr, - .pDepthStencilAttachment = depth_attachment_references.data(), + .pDepthStencilAttachment = + depth_attachment_references.data(), .preserveAttachmentCount = 0, .pPreserveAttachments = nullptr }; @@ -134,7 +181,7 @@ export namespace vk { .pNext = nullptr, .flags = 0, .attachmentCount = - static_cast(attachment_description.size()), + static_cast(attachment_description.size()), .pAttachments = attachment_description.data(), .subpassCount = static_cast(subpasses.size()), .pSubpasses = subpasses.data(), @@ -142,16 +189,49 @@ export namespace vk { .pDependencies = nullptr }; - vk_check( - vkCreateRenderPass(m_device, &renderpass_ci, nullptr, &m_renderpass), - "vkCreateRenderPass"); + vk_check(vkCreateRenderPass( + m_device, &renderpass_ci, nullptr, &m_renderpass), + "vkCreateRenderPass"); } + /** + * @brief checks if the renderpass handle is valid. + */ [[nodiscard]] bool alive() const { return m_renderpass; } - void begin(const renderpass_begin_params& p_begin_info) { - // TODO: Move VkViewport and VkScissor to vk::swapchain since these are - // information more closely set by the swapchain + /** + * @brief Initiates starting state for the renderpass to begin the + * rendering operation. + * + * Implicitly clears the screen to specific color/depth and sets up + * the render area (viewport, scissor) for geometry are mapped to + * the pixels correctly. + * + * @param p_command is the command buffer to start recording + rendering operations with. + * @param p_begin_info is the specifications for configuring the + rendering operation + * + * @brief Additional Considerations: + * - Requires valid framebuffer compatible to the renderpass. + * - Requires to be in recording state of a command buffer to + * perform this operation. + * - If framebuffer image is used by the swapchain, it must be + * acquired before invoking this operation. + * + * [ CPU-SIDE Recording ] [ GPU-SIDE Executing ] + * +---------------------+ +---------------------+ + * | renderpass::begin | | Clear/Load Images | + * | - Load Operation | | | + * | - Render Area | (Submit) | Subpass Execute Cmd | + * | [draw commands] | ============> | | + * | [viewport/scissor] | | Resolve/Store | + * | renderpass::end | | (Store to VRAM) | + * +---------------------+ +---------------------+ + * + */ + void begin(const VkCommandBuffer& p_command, + const renderpass_begin_params& p_begin_info) { VkViewport viewport = { .x = 0.0f, .y = 0.0f, @@ -161,20 +241,21 @@ export namespace vk { .maxDepth = 1.0f, }; - vkCmdSetViewport(p_begin_info.current_command, 0, 1, &viewport); + vkCmdSetViewport(p_command, 0, 1, &viewport); VkRect2D scissor = { .offset = { 0, 0 }, - .extent = { p_begin_info.extent.width, p_begin_info.extent.height }, + .extent = p_begin_info.extent, }; - vkCmdSetScissor(p_begin_info.current_command, 0, 1, &scissor); + vkCmdSetScissor(p_command, 0, 1, &scissor); // setting color for this specific renderpass - VkClearColorValue renderpass_color = { { p_begin_info.color.at(0), - p_begin_info.color.at(1), - p_begin_info.color.at(2), - p_begin_info.color.at(3) } }; + VkClearColorValue renderpass_color = {}; + std::copy(p_begin_info.color.begin(), + p_begin_info.color.end(), + renderpass_color.float32); + std::array clear_values = {}; clear_values[0].color = renderpass_color; @@ -199,16 +280,34 @@ export namespace vk { .pClearValues = clear_values.data(), }; - vkCmdBeginRenderPass(p_begin_info.current_command, - &renderpass_begin_params, - static_cast(p_begin_info.subpass)); + vkCmdBeginRenderPass( + p_command, + &renderpass_begin_params, + static_cast(p_begin_info.subpass)); } + /** + * @brief Specified when to stop performing rendering operation. + * + * This is for flushing to move to its final layout. Readying to be + * rendered to the screen or the shader to be read by. + * + * @brief Additional Consideration when calling this API + * - Must follow-up with renderpass::begin with the same command + * buffer. + * - Cannot record any more draw operations when this operation is + * invoked. + * - Image will auitomatically be moved to its .final_layout + * transition. + * + * @param p_current is the command buffer to stop recording drawing + * commands to. + */ void end(const VkCommandBuffer& p_current) { vkCmdEndRenderPass(p_current); } - void destroy() { + void destruct() { vkDestroyRenderPass(m_device, m_renderpass, nullptr); } diff --git a/vulkan-cpp/sample_image.cppm b/vulkan-cpp/sample_image.cppm index 70f7787..7671d9d 100644 --- a/vulkan-cpp/sample_image.cppm +++ b/vulkan-cpp/sample_image.cppm @@ -3,6 +3,7 @@ module; #include #include #include +#include export module vk:sample_image; @@ -10,55 +11,75 @@ export import :types; export import :utilities; export namespace vk { - inline namespace v1 { + inline namespace v6 { /** - * We shift 32-bits to the high 32-bits for the old layout and OR the new layout lsb 32-bits additional to the shifted 32-bits - * - * This is to ensure the image layouts do not overlap and can be used to directly jump to set specific image loyouts rather then doing an if-statement originally to check for that - */ - constexpr uint64_t image_layout(VkImageLayout p_old, VkImageLayout p_new) { - // Shift the old_layout into the high 32 bits, and combine with new_layout in the low 32 bits. - return (static_cast(p_old) << 32) | static_cast(p_new); + * We shift 32-bits to the high 32-bits for the old layout and OR the + * new layout lsb 32-bits additional to the shifted 32-bits + * + * This is to ensure the image layouts do not overlap and can be used to + * directly jump to set specific image loyouts rather then doing an + * if-statement originally to check for that + */ + constexpr uint64_t image_layout(VkImageLayout p_old, + VkImageLayout p_new) { + // Shift the old_layout into the high 32 bits, and combine with + // new_layout in the low 32 bits. + return (static_cast(p_old) << 32) | + static_cast(p_new); } class sample_image { public: sample_image() = default; sample_image(const VkDevice& p_device, - const image_params& p_image_properties) : m_device(p_device) { - // 1. creating VkImage handle + const image_params& p_image_params) + : m_device(p_device) { + construct(p_image_params); + } + + sample_image(const VkDevice& p_device, + const VkImage& p_image, + const image_params& p_image_params) + : m_device(p_device) { + construct(p_image, p_image_params); + } + + void construct(const image_params& p_image_params) { VkImageCreateInfo image_ci = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .pNext = nullptr, - .flags = p_image_properties.image_flags, + .flags = p_image_params.image_flags, .imageType = VK_IMAGE_TYPE_2D, - .format = p_image_properties.format, - .extent = { .width = p_image_properties.extent.width, - .height = p_image_properties.extent.height, - .depth = 1 }, - .mipLevels = p_image_properties.mip_levels, - .arrayLayers = p_image_properties.array_layers, + .format = p_image_params.format, + .extent = { .width = p_image_params.extent.width, + .height = p_image_params.extent.height, + .depth = 1, }, + .mipLevels = p_image_params.mip_levels, + .arrayLayers = p_image_params.array_layers, .samples = VK_SAMPLE_COUNT_1_BIT, .tiling = VK_IMAGE_TILING_OPTIMAL, - .usage = static_cast(p_image_properties.usage), + .usage = + static_cast(p_image_params.usage), .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 0, .pQueueFamilyIndices = nullptr, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED }; - vk_check(vkCreateImage(p_device, &image_ci, nullptr, &m_image), - "vkCreateImage"); + vk_check(vkCreateImage(m_device, &image_ci, nullptr, &m_image), + "vkCreateImage"); - // 2. get image memory requirements from physical device + // get image memory requirements from physical device VkMemoryRequirements memory_requirements; - vkGetImageMemoryRequirements(p_device, m_image, &memory_requirements); - // uint32_t memory_type_index = - // vk::image_memory_requirements(p_image_properties.physical_device, - // p_device, m_image); - uint32_t memory_index = select_memory_requirements( - p_image_properties.phsyical_memory_properties, - memory_requirements, - p_image_properties.property); + vkGetImageMemoryRequirements( + m_device, m_image, &memory_requirements); + + uint32_t mapped_memory_requirements = + memory_requirements.memoryTypeBits & + p_image_params.memory_mask; + + // Retrieving the next available bits that have been mapped + uint32_t memory_index = + std::countr_zero(mapped_memory_requirements); // 4. Allocate info VkMemoryAllocateInfo memory_alloc_info = { @@ -68,53 +89,58 @@ export namespace vk { .memoryTypeIndex = memory_index }; - vk_check(vkAllocateMemory( - p_device, &memory_alloc_info, nullptr, &m_device_memory), - "vkAllocateMemory"); + vk_check( + vkAllocateMemory( + m_device, &memory_alloc_info, nullptr, &m_device_memory), + "vkAllocateMemory"); - // 5. bind image memory - vk_check(vkBindImageMemory(p_device, m_image, m_device_memory, 0), - "vkBindImageMemory"); + // Binding the image memory + vk_check( + vkBindImageMemory(m_device, m_image, m_device_memory, 0), + "vkBindImageMemory"); - // Needs to create VkImageView after VkImage - // because VkImageView expects a VkImage to be binded to a singl - // VkDeviceMemory beforehand + // Need to bind image view to the VkDeviceMemory for the VkImage + // before using the VkImageView VkImageViewCreateInfo image_view_ci = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .pNext = nullptr, .flags = 0, .image = m_image, - // .viewType = VK_IMAGE_VIEW_TYPE_2D, - .viewType = p_image_properties.view_type, - .format = p_image_properties.format, - .components = { .r = VK_COMPONENT_SWIZZLE_IDENTITY, - .g = VK_COMPONENT_SWIZZLE_IDENTITY, - .b = VK_COMPONENT_SWIZZLE_IDENTITY, - .a = VK_COMPONENT_SWIZZLE_IDENTITY }, - .subresourceRange = { .aspectMask = static_cast( - p_image_properties.aspect), - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = - p_image_properties.layer_count }, + .viewType = p_image_params.view_type, + .format = p_image_params.format, + .components = { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = { + .aspectMask = static_cast(p_image_params.aspect), + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = p_image_params.layer_count, + }, }; - vk_check( - vkCreateImageView(p_device, &image_view_ci, nullptr, &m_image_view), - "vkCreateImage"); + vk_check(vkCreateImageView( + m_device, &image_view_ci, nullptr, &m_image_view), + "vkCreateImage"); // Create VkSampler handler VkSamplerCreateInfo sampler_info = { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .pNext = nullptr, .flags = 0, - .magFilter = p_image_properties.range.min, - .minFilter = p_image_properties.range.max, + .magFilter = p_image_params.range.min, + .minFilter = p_image_params.range.max, .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, - .addressModeU = static_cast(p_image_properties.address_mode_u), - .addressModeV = static_cast(p_image_properties.addrses_mode_v), - .addressModeW = static_cast(p_image_properties.addrses_mode_w), + .addressModeU = static_cast( + p_image_params.address_mode_u), + .addressModeV = static_cast( + p_image_params.addrses_mode_v), + .addressModeW = static_cast( + p_image_params.addrses_mode_w), .mipLodBias = 0.0f, .anisotropyEnable = false, .maxAnisotropy = 1, @@ -126,53 +152,55 @@ export namespace vk { .unnormalizedCoordinates = false }; - vk_check(vkCreateSampler(p_device, &sampler_info, nullptr, &m_sampler), - "vkCreateSampler"); + vk_check( + vkCreateSampler(m_device, &sampler_info, nullptr, &m_sampler), + "vkCreateSampler"); } - - sample_image(const VkDevice& p_device, - const VkImage& p_image, - const image_params& p_image_properties) - : m_device(p_device), - m_image(p_image) { - // Needs to create VkImageView after VkImage - // because VkImageView expects a VkImage to be binded to a singl - // VkDeviceMemory beforehand + + void construct(const VkImage& p_image, + const image_params& p_image_params) { + m_image = p_image; + VkImageViewCreateInfo image_view_ci = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .pNext = nullptr, .flags = 0, .image = m_image, .viewType = VK_IMAGE_VIEW_TYPE_2D, - .format = p_image_properties.format, - .components = { .r = VK_COMPONENT_SWIZZLE_IDENTITY, - .g = VK_COMPONENT_SWIZZLE_IDENTITY, - .b = VK_COMPONENT_SWIZZLE_IDENTITY, - .a = VK_COMPONENT_SWIZZLE_IDENTITY }, - .subresourceRange = { .aspectMask = static_cast( - p_image_properties.aspect), - .baseMipLevel = 0, - .levelCount = p_image_properties.mip_levels, - .baseArrayLayer = 0, - .layerCount = - p_image_properties.layer_count }, + .format = p_image_params.format, + .components = { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = { + .aspectMask = static_cast(p_image_params.aspect), + .baseMipLevel = 0, + .levelCount = p_image_params.mip_levels, + .baseArrayLayer = 0, + .layerCount = p_image_params.layer_count, + }, }; - vk_check( - vkCreateImageView(p_device, &image_view_ci, nullptr, &m_image_view), - "vkCreateImage"); + vk_check(vkCreateImageView( + m_device, &image_view_ci, nullptr, &m_image_view), + "vkCreateImage"); // Create VkSampler handler VkSamplerCreateInfo sampler_info = { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .pNext = nullptr, .flags = 0, - .magFilter = p_image_properties.range.min, - .minFilter = p_image_properties.range.max, + .magFilter = p_image_params.range.min, + .minFilter = p_image_params.range.max, .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, - .addressModeU = static_cast(p_image_properties.address_mode_u), - .addressModeV = static_cast(p_image_properties.addrses_mode_v), - .addressModeW = static_cast(p_image_properties.addrses_mode_w), + .addressModeU = static_cast( + p_image_params.address_mode_u), + .addressModeV = static_cast( + p_image_params.addrses_mode_v), + .addressModeW = static_cast( + p_image_params.addrses_mode_w), .mipLodBias = 0.0f, .anisotropyEnable = false, .maxAnisotropy = 1, @@ -184,44 +212,60 @@ export namespace vk { .unnormalizedCoordinates = false }; - vk_check(vkCreateSampler(p_device, &sampler_info, nullptr, &m_sampler), - "vkCreateSampler"); + vk_check( + vkCreateSampler(m_device, &sampler_info, nullptr, &m_sampler), + "vkCreateSampler"); m_only_destroy_image_view = true; } [[nodiscard]] VkSampler sampler() const { return m_sampler; } - [[nodiscard]] VkImageView image_view() const { return m_image_view; } - + [[nodiscard]] VkImageView image_view() const { + return m_image_view; + } /** - * @brief performs vkCmdPipelineBarrier to handle transitioning image layouts - * - * @param p_command is the current command buffer to record the image layout transition - * @param p_format is the image format to make sure if there is a depth format available then request the aspect mask to include the stencil bit + * @brief performs vkCmdPipelineBarrier to handle transitioning + * image layouts + * + * @param p_command is the current command buffer to record the + * image layout transition + * @param p_format is the image format to make sure if there is a + * depth format available then request the aspect mask to include + * the stencil bit * @param p_old is the source image layout transition from * @param p_new is the destination image layout transition to. - * - * + * + * * ```C++ - * + * * sample_image texture_image(logical_device, ...); - * - * - * texture_image.memory_barrier(temp_command, some_vk_format, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - * + * + * + * texture_image.memory_barrier(temp_command, some_vk_format, + * VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + * * // do some operation during this transition image layout * // such as copying from an buffer data to an image * // staging_buffer.copy_to_image(...); - * - * texture_image.memory_barrier(temp_command, some_vk_format, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - * + * + * texture_image.memory_barrier(temp_command, some_vk_format, + * VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + * VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + * * ``` - * - */ - void memory_barrier(const VkCommandBuffer& p_command, VkFormat p_format, VkImageLayout p_old, VkImageLayout p_new) { - /* + * + */ + void memory_barrier( + const VkCommandBuffer& p_command, + VkFormat p_format, + VkImageLayout p_old, + VkImageLayout p_new, + uint32_t p_aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT) { + + // 1. Image Memory Barrier Initialization (using C++ Designated + // Initializers - C++20) VkImageMemoryBarrier image_memory_barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = nullptr, @@ -232,306 +276,247 @@ export namespace vk { .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = m_image, - .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 } + .subresourceRange = { .aspectMask = + static_cast( + p_aspect_mask), + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1 } }; - VkPipelineStageFlags source_stage; - VkPipelineStageFlags dst_stages; + VkPipelineStageFlags source_stage = VK_PIPELINE_STAGE_NONE; + VkPipelineStageFlags dst_stages = VK_PIPELINE_STAGE_NONE; + // 2. Aspect Mask Logic (Keep as if/else, but use helper + // function) if (p_new == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL || - (p_format == VK_FORMAT_D16_UNORM) || - (p_format == VK_FORMAT_X8_D24_UNORM_PACK32) || - (p_format == VK_FORMAT_D32_SFLOAT) || - (p_format == VK_FORMAT_S8_UINT) || - (p_format == VK_FORMAT_D16_UNORM_S8_UINT) || - (p_format == VK_FORMAT_D24_UNORM_S8_UINT)) { - image_memory_barrier.subresourceRange.aspectMask = - VK_IMAGE_ASPECT_DEPTH_BIT; + has_stencil_attachment(p_format)) { + image_memory_barrier.subresourceRange.aspectMask = + VK_IMAGE_ASPECT_DEPTH_BIT; + + // Assuming has_stencil_attachment(p_format) is defined + // elsewhere works as the same as the if-statement, leaving + // it here for testing purposes + // image_memory_barrier.subresourceRange.aspectMask |= + // has_stencil_attachment(p_format) ? + // VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; if (has_stencil_attachment(p_format)) { image_memory_barrier.subresourceRange.aspectMask |= - VK_IMAGE_ASPECT_STENCIL_BIT; + VK_IMAGE_ASPECT_STENCIL_BIT; } } else { image_memory_barrier.subresourceRange.aspectMask = - VK_IMAGE_ASPECT_COLOR_BIT; - } - - if (p_old == VK_IMAGE_LAYOUT_UNDEFINED && - p_new == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { - image_memory_barrier.srcAccessMask = 0; - image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - - source_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - dst_stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - } - else if (p_old == VK_IMAGE_LAYOUT_UNDEFINED && - p_new == VK_IMAGE_LAYOUT_GENERAL) { - image_memory_barrier.srcAccessMask = 0; - image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - - source_stage = VK_PIPELINE_STAGE_TRANSFER_BIT; - dst_stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - } - - if (p_old == VK_IMAGE_LAYOUT_UNDEFINED && - p_new == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { - image_memory_barrier.srcAccessMask = 0; - image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - - source_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - dst_stages = VK_PIPELINE_STAGE_TRANSFER_BIT; - } // Convert back from read-only to updateable - else if (p_old == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && - p_new == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { - image_memory_barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; - image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - - source_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - dst_stages = VK_PIPELINE_STAGE_TRANSFER_BIT; - } // Convert from updateable texture to shader read-only - else if (p_old == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && - p_new == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { - image_memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - - source_stage = VK_PIPELINE_STAGE_TRANSFER_BIT; - dst_stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - } // Convert depth texture from undefined state to depth-stencil buffer - else if (p_old == VK_IMAGE_LAYOUT_UNDEFINED && - p_new == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { - image_memory_barrier.srcAccessMask = 0; - image_memory_barrier.dstAccessMask = - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - - source_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - dst_stages = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; - } // Wait for render pass to complete - else if (p_old == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && - p_new == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { - image_memory_barrier.srcAccessMask = - 0; // VK_ACCESS_SHADER_READ_BIT; - image_memory_barrier.dstAccessMask = 0; - source_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - dst_stages = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; - dst_stages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - source_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dst_stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - } // Convert back from read-only to color attachment - else if (p_old == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && - p_new == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { - image_memory_barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; - image_memory_barrier.dstAccessMask = - VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - - source_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - dst_stages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - } // Convert from updateable texture to shader read-only - else if (p_old == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL && - p_new == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { - image_memory_barrier.srcAccessMask = - VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - - source_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dst_stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - } // Convert back from read-only to depth attachment - else if (p_old == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && - p_new == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { - image_memory_barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; - image_memory_barrier.dstAccessMask = - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - - source_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - dst_stages = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - } // Convert from updateable depth texture to shader read-only - else if (p_old == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL && - p_new == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { - image_memory_barrier.srcAccessMask = - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - - source_stage = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - dst_stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - } - - vkCmdPipelineBarrier(p_command, - source_stage, - dst_stages, - 0, - 0, - nullptr, - 0, - nullptr, - 1, - &image_memory_barrier); - */ - // 1. Image Memory Barrier Initialization (using C++ Designated Initializers - C++20) - VkImageMemoryBarrier image_memory_barrier = { - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .pNext = nullptr, - .srcAccessMask = 0, - .dstAccessMask = 0, - .oldLayout = p_old, - .newLayout = p_new, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = m_image, - .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 } - }; - - VkPipelineStageFlags source_stage = VK_PIPELINE_STAGE_NONE; - VkPipelineStageFlags dst_stages = VK_PIPELINE_STAGE_NONE; - - // 2. Aspect Mask Logic (Keep as if/else, but use helper function) - if (p_new == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL || has_stencil_attachment(p_format)) { - - image_memory_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - - // Assuming has_stencil_attachment(p_format) is defined elsewhere - // works as the same as the if-statement, leaving it here for testing purposes - // image_memory_barrier.subresourceRange.aspectMask |= has_stencil_attachment(p_format) ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; - if (has_stencil_attachment(p_format)) { - image_memory_barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; - } - } - else { - image_memory_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + VK_IMAGE_ASPECT_COLOR_BIT; } // 3. Main Transition Logic using Combined Switch const uint64_t current_layout = image_layout(p_old, p_new); switch (current_layout) { - + // UNDEFINED -> SHADER_READ_ONLY_OPTIMAL - case image_layout(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL): { + case image_layout( + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL): { image_memory_barrier.srcAccessMask = 0; - image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + image_memory_barrier.dstAccessMask = + VK_ACCESS_SHADER_READ_BIT; source_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; dst_stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; break; } // UNDEFINED -> GENERAL - case image_layout(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL): { + case image_layout(VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL): { image_memory_barrier.srcAccessMask = 0; - image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + image_memory_barrier.dstAccessMask = + VK_ACCESS_SHADER_READ_BIT; source_stage = VK_PIPELINE_STAGE_TRANSFER_BIT; dst_stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; break; } // UNDEFINED -> TRANSFER_DST_OPTIMAL - case image_layout(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL): { + case image_layout(VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL): { image_memory_barrier.srcAccessMask = 0; - image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + image_memory_barrier.dstAccessMask = + VK_ACCESS_TRANSFER_WRITE_BIT; source_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; dst_stages = VK_PIPELINE_STAGE_TRANSFER_BIT; break; } - - // SHADER_READ_ONLY_OPTIMAL -> TRANSFER_DST_OPTIMAL (Convert back from read-only to transferr) - case image_layout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL): { - image_memory_barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; - image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + // SHADER_READ_ONLY_OPTIMAL -> TRANSFER_DST_OPTIMAL (Convert + // back from read-only to transferr) + case image_layout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL): { + image_memory_barrier.srcAccessMask = + VK_ACCESS_SHADER_READ_BIT; + image_memory_barrier.dstAccessMask = + VK_ACCESS_TRANSFER_WRITE_BIT; source_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; dst_stages = VK_PIPELINE_STAGE_TRANSFER_BIT; break; } - // TRANSFER_DST_OPTIMAL -> SHADER_READ_ONLY_OPTIMAL (Convert from updateable texture to shader read-only) - case image_layout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL): { - image_memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + // TRANSFER_DST_OPTIMAL -> SHADER_READ_ONLY_OPTIMAL (Convert + // from updateable texture to shader read-only) + case image_layout( + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL): { + image_memory_barrier.srcAccessMask = + VK_ACCESS_TRANSFER_WRITE_BIT; + image_memory_barrier.dstAccessMask = + VK_ACCESS_SHADER_READ_BIT; source_stage = VK_PIPELINE_STAGE_TRANSFER_BIT; dst_stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; break; } - // UNDEFINED -> DEPTH_STENCIL_ATTACHMENT_OPTIMAL (Convert depth texture from undefined state) - case image_layout(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL): { + // UNDEFINED -> DEPTH_STENCIL_ATTACHMENT_OPTIMAL (Convert + // depth texture from undefined state) + case image_layout( + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL): { image_memory_barrier.srcAccessMask = 0; - image_memory_barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + image_memory_barrier.dstAccessMask = + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; source_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; dst_stages = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; break; } - // SHADER_READ_ONLY_OPTIMAL -> SHADER_READ_ONLY_OPTIMAL (Wait for render pass to complete - Note: This case is unusual but kept as per your original logic) - case image_layout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL): { - // Note: Your original code had conflicting re-assignments for source_stage/dst_stages here. - // The last pair of assignments is used. - image_memory_barrier.srcAccessMask = 0; + // SHADER_READ_ONLY_OPTIMAL -> SHADER_READ_ONLY_OPTIMAL + // (Wait for render pass to complete - Note: This case is + // unusual but kept as per your original logic) + case image_layout( + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL): { + // Note: Your original code had conflicting + // re-assignments for source_stage/dst_stages here. The + // last pair of assignments is used. + image_memory_barrier.srcAccessMask = 0; image_memory_barrier.dstAccessMask = 0; - source_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + source_stage = + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dst_stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; break; } - - // SHADER_READ_ONLY_OPTIMAL -> COLOR_ATTACHMENT_OPTIMAL (Convert back from read-only to color attachment) - case image_layout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL): { - image_memory_barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; - image_memory_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + // SHADER_READ_ONLY_OPTIMAL -> COLOR_ATTACHMENT_OPTIMAL + // (Convert back from read-only to color attachment) + case image_layout( + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL): { + image_memory_barrier.srcAccessMask = + VK_ACCESS_SHADER_READ_BIT; + image_memory_barrier.dstAccessMask = + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; source_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - dst_stages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dst_stages = + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; break; } - - // COLOR_ATTACHMENT_OPTIMAL -> SHADER_READ_ONLY_OPTIMAL (Convert from updateable color to shader read-only) - case image_layout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL): { - image_memory_barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - source_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + + // COLOR_ATTACHMENT_OPTIMAL -> SHADER_READ_ONLY_OPTIMAL + // (Convert from updateable color to shader read-only) + case image_layout( + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL): { + image_memory_barrier.srcAccessMask = + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + image_memory_barrier.dstAccessMask = + VK_ACCESS_SHADER_READ_BIT; + source_stage = + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dst_stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; break; } - // SHADER_READ_ONLY_OPTIMAL -> DEPTH_STENCIL_ATTACHMENT_OPTIMAL (Convert back from read-only to depth attachment) - case image_layout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL): { - image_memory_barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; - image_memory_barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + // SHADER_READ_ONLY_OPTIMAL -> + // DEPTH_STENCIL_ATTACHMENT_OPTIMAL (Convert back from + // read-only to depth attachment) + case image_layout( + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL): { + image_memory_barrier.srcAccessMask = + VK_ACCESS_SHADER_READ_BIT; + image_memory_barrier.dstAccessMask = + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; source_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; dst_stages = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; break; } - // DEPTH_STENCIL_ATTACHMENT_OPTIMAL -> SHADER_READ_ONLY_OPTIMAL (Convert from updateable depth texture to shader read-only) - case image_layout(VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL): { - image_memory_barrier.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - source_stage = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + // DEPTH_STENCIL_ATTACHMENT_OPTIMAL -> + // SHADER_READ_ONLY_OPTIMAL (Convert from updateable depth + // texture to shader read-only) + case image_layout( + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL): { + image_memory_barrier.srcAccessMask = + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + image_memory_barrier.dstAccessMask = + VK_ACCESS_SHADER_READ_BIT; + source_stage = + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; dst_stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; break; } + // LAYOUT_UNDEFINED -> COLOR_ATTACHMENT_OPTIMAL + // Transition to a color attachment + case image_layout( + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL): { + image_memory_barrier.srcAccessMask = 0; + image_memory_barrier.dstAccessMask = + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + source_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + dst_stages = + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + } + + // COLOR_ATTACHMENT_OPTIMAL -> PRESENT_SRC_KHR + // Transition from being a color attachment to being a + // presentable operation + case image_layout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR): { + image_memory_barrier.srcAccessMask = + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + image_memory_barrier.dstAccessMask = 0; + + source_stage = + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dst_stages = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + } + default: { // Unhandled Transition break; } } - - vkCmdPipelineBarrier( - p_command, - source_stage, - dst_stages, - 0, // dependencyFlags - 0, nullptr, - 0, nullptr, - 1, &image_memory_barrier); + + vkCmdPipelineBarrier(p_command, + source_stage, + dst_stages, + 0, // dependencyFlags + 0, + nullptr, + 0, + nullptr, + 1, + &image_memory_barrier); } - void destroy() { + void destruct() { if (m_image_view != nullptr) { vkDestroyImageView(m_device, m_image_view, nullptr); } diff --git a/vulkan-cpp/shader_resource.cppm b/vulkan-cpp/shader_resource.cppm index fe3a2d3..56b4a70 100644 --- a/vulkan-cpp/shader_resource.cppm +++ b/vulkan-cpp/shader_resource.cppm @@ -12,13 +12,13 @@ export import :types; export import :utilities; export namespace vk { - inline namespace v1 { + inline namespace v6 { /** * @param sources holds data the shader source and stage the source it * corresponds to. - * @param vertex_attributes are the vertex attributes that are used to setup - * vulkan vertex attributes and the binding attributes. + * @param vertex_attributes are the vertex attributes that are used to + * setup vulkan vertex attributes and the binding attributes. */ struct shader_resource_info { std::span sources{}; @@ -32,7 +32,8 @@ export namespace vk { * Every graphics pipeline as an associated group of shader sources with * their own specific shader stages they are apart of. * - * @param p_device is the logical device for creating the vulkan handlers + * @param p_device is the logical device for creating the vulkan + * handlers * @param p_info is the shader_resource properties for providing shader * sources to load and what vertex attributes to return from this shader * resource @@ -41,12 +42,15 @@ export namespace vk { class shader_resource { public: shader_resource() = default; - shader_resource(const VkDevice& p_device, const shader_resource_info& p_info) : m_device(p_device) { + shader_resource(const VkDevice& p_device, + const shader_resource_info& p_info) + : m_device(p_device) { m_shader_module_handlers.resize(p_info.sources.size()); for (size_t i = 0; i < p_info.sources.size(); i++) { const shader_source shader_src = p_info.sources[i]; - std::vector blob = compile_binary_shader_source(shader_src); + std::vector blob = + compile_binary_shader_source(shader_src); if (blob.empty()) { m_is_resource_valid = false; @@ -63,11 +67,12 @@ export namespace vk { // Setting m_shader_module_handlers[i]'s stage and the // VkShaderModule handle altogether - vk_check(vkCreateShaderModule(m_device, - &shader_module_ci, - nullptr, - &m_shader_module_handlers[i].module), - "vkCreateShaderModule"); + vk_check( + vkCreateShaderModule(m_device, + &shader_module_ci, + nullptr, + &m_shader_module_handlers[i].module), + "vkCreateShaderModule"); m_shader_module_handlers[i].stage = shader_src.stage; } @@ -76,22 +81,27 @@ export namespace vk { [[nodiscard]] bool is_valid() const { return m_is_resource_valid; } - void vertex_attributes(std::span p_attributes) { + void vertex_attributes( + std::span p_attributes) { m_vertex_binding_attributes.resize(p_attributes.size()); - for (size_t i = 0; i < m_vertex_binding_attributes.size(); i++) { + for (size_t i = 0; i < m_vertex_binding_attributes.size(); + i++) { // setting up vertex binding const vertex_attribute attribute = p_attributes[i]; m_vertex_attributes.resize(attribute.entries.size()); m_vertex_binding_attributes[i] = { .binding = attribute.binding, .stride = attribute.stride, - .inputRate = to_input_rate(attribute.input_rate), + .inputRate = + static_cast(attribute.input_rate), }; - // then setting up the vertex attributes for the vertex data layouts + // then setting up the vertex attributes for the vertex data + // layouts for (size_t j = 0; j < attribute.entries.size(); j++) { - const vertex_attribute_entry entry = attribute.entries[j]; + const vertex_attribute_entry entry = + attribute.entries[j]; m_vertex_attributes[j] = { .location = entry.location, .binding = attribute.binding, @@ -102,8 +112,8 @@ export namespace vk { } } - //! @return the handlers of vulkan shader modules for each individual - //! shader source loaded altogether + //! @return the handlers of vulkan shader modules for each + //! individual shader source loaded altogether [[nodiscard]] std::span handles() const { return m_shader_module_handlers; } @@ -121,7 +131,7 @@ export namespace vk { } //! @brief used for explicit cleanup for this resource - void destroy() { + void destruct() { for (auto& handle : m_shader_module_handlers) { if (handle.module != nullptr) { vkDestroyShaderModule(m_device, handle.module, nullptr); @@ -146,12 +156,14 @@ export namespace vk { return out_buffer; } - //! @brief Ensure file reads are valid before reading raw .spv binaries + //! @brief Ensure file reads are valid before reading raw .spv + //! binaries std::vector compile_binary_shader_source( - const shader_source& p_shader_source) { + const shader_source& p_shader_source) { std::vector binary_blob{}; - if (std::filesystem::is_regular_file(p_shader_source.filename)) { + if (std::filesystem::is_regular_file( + p_shader_source.filename)) { binary_blob = read(p_shader_source.filename); } @@ -163,7 +175,7 @@ export namespace vk { bool m_is_resource_valid = false; std::vector m_vertex_attributes; std::vector - m_vertex_binding_attributes; + m_vertex_binding_attributes; std::vector m_shader_module_handlers; }; }; diff --git a/vulkan-cpp/surface.cppm b/vulkan-cpp/surface.cppm index ad55854..49e4200 100644 --- a/vulkan-cpp/surface.cppm +++ b/vulkan-cpp/surface.cppm @@ -14,23 +14,26 @@ module; export module vk:surface; - export import :types; export import :utilities; export import :instance; export namespace vk { - inline namespace v1 { + inline namespace v6 { class surface { public: - surface(const VkInstance& p_instance, GLFWwindow* p_window_handle) : m_instance(p_instance) { - vk_check(glfwCreateWindowSurface( m_instance, p_window_handle, nullptr, &m_surface_handler), "glfwCreateWindowSurface"); + surface(const VkInstance& p_instance, GLFWwindow* p_window_handle) + : m_instance(p_instance) { + vk_check( + glfwCreateWindowSurface( + m_instance, p_window_handle, nullptr, &m_surface_handler), + "glfwCreateWindowSurface"); } [[nodiscard]] bool alive() const { return m_surface_handler; } - void destroy() { + void destruct() { if (m_surface_handler != nullptr) { vkDestroySurfaceKHR(m_instance, m_surface_handler, nullptr); } diff --git a/vulkan-cpp/swapchain.cppm b/vulkan-cpp/swapchain.cppm index 0401ad0..c55c61e 100644 --- a/vulkan-cpp/swapchain.cppm +++ b/vulkan-cpp/swapchain.cppm @@ -1,9 +1,9 @@ module; #include -#include #include #include +#include export module vk:swapchain; @@ -12,75 +12,91 @@ export import :utilities; export import :device_queue; export namespace vk { - inline namespace v1 { + inline namespace v6 { + class swapchain { public: - swapchain(const VkDevice& p_device, - const VkSurfaceKHR& p_surface, - const swapchain_params& p_settings, - const surface_params& p_surface_properties) : m_device(p_device), m_surface_handler(p_surface), m_surface_params(p_surface_properties) { - m_image_size = surface_image_size(m_surface_params.capabilities); + swapchain() = delete("Cannot construct empty swapchain"); - std::println("Surface Image Size = {}", m_image_size); + swapchain(const VkDevice& p_device, + const VkSurfaceKHR& p_surface, + const swapchain_params& p_settings, + const surface_params& p_surface_properties) + : m_device(p_device) + , m_surface_handler(p_surface) { - create(p_settings); + construct(p_settings, p_surface_properties); } - void create(const swapchain_params& p_settings) { + ~swapchain() = default; + + void construct(const swapchain_params& p_settings, + const surface_params& p_surface_properties) { + VkSwapchainCreateInfoKHR swapchain_ci = { .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .pNext = nullptr, .surface = m_surface_handler, - .minImageCount = m_image_size, - .imageFormat = m_surface_params.format.format, - .imageColorSpace = m_surface_params.format.colorSpace, - // use physical device surface formats to getting the right formats - // in vulkan - .imageExtent = m_surface_params.capabilities.currentExtent, + .minImageCount = p_surface_properties.image_size, + .imageFormat = p_surface_properties.format.format, + .imageColorSpace = p_surface_properties.format.colorSpace, + // use physical device surface formats to getting the right + // formats in vulkan + .imageExtent = + p_surface_properties.capabilities.currentExtent, .imageArrayLayers = 1, - .imageUsage = (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | - VK_IMAGE_USAGE_TRANSFER_DST_BIT), - .queueFamilyIndexCount = 1, - .pQueueFamilyIndices = &p_settings.present_index, - .preTransform = m_surface_params.capabilities.currentTransform, + + // Remove COLOR_ATTACHMENT flag because its not needed + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + .preTransform = + p_surface_properties.capabilities.currentTransform, .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, - .presentMode = static_cast(p_settings.present_mode), + .presentMode = + static_cast(p_settings.present_mode), .clipped = p_settings.clipped, }; + VkResult res = vkCreateSwapchainKHR( + m_device, &swapchain_ci, nullptr, &m_swapchain_handler); - vk_check(vkCreateSwapchainKHR( - m_device, &swapchain_ci, nullptr, &m_swapchain_handler), - "vkCreateSwapchainKHR"); + vk_check(res, "vkCreateSwapchainKHR"); } - /** * @brief gets the presentable images from this associated swapchain - * - * @return std::span which are the presentable available images that can be presented to the swapchain - * + * + * @return std::span which are the presentable + * available images that can be presented to the swapchain + * * ```C++ - * + * * vk::swapchain main_swapchain(logical_device, ...); - * + * * std::span images = main_swapchain.get_images(); - * + * * ``` - * - */ + * TODO: Have this return std::span + * + */ std::span get_images() { - uint32_t image_count=0; - vkGetSwapchainImagesKHR(m_device, m_swapchain_handler, &image_count, nullptr); + uint32_t image_count = 0; + vkGetSwapchainImagesKHR( + m_device, m_swapchain_handler, &image_count, nullptr); m_images.resize(image_count); - vkGetSwapchainImagesKHR(m_device, m_swapchain_handler, &image_count, m_images.data()); + vkGetSwapchainImagesKHR( + m_device, m_swapchain_handler, &image_count, m_images.data()); return m_images; } - void destroy() { + void destruct() { vkDestroySwapchainKHR(m_device, m_swapchain_handler, nullptr); } + [[nodiscard]] bool alive() const { return m_swapchain_handler; } + operator VkSwapchainKHR() const { return m_swapchain_handler; } operator VkSwapchainKHR() { return m_swapchain_handler; } @@ -89,11 +105,6 @@ export namespace vk { VkDevice m_device = nullptr; VkSwapchainKHR m_swapchain_handler = nullptr; VkSurfaceKHR m_surface_handler = nullptr; - surface_params m_surface_params{}; - uint32_t m_image_size = 0; - - device_queue m_present_queue; - std::vector m_images; }; }; diff --git a/vulkan-cpp/texture.cppm b/vulkan-cpp/texture.cppm index a5f2fa5..787548b 100644 --- a/vulkan-cpp/texture.cppm +++ b/vulkan-cpp/texture.cppm @@ -5,247 +5,246 @@ module; #include #include -#ifndef STB_IMAGE_IMPLEMENTATION -#define STB_IMAGE_IMPLEMENTATION -#include -#endif - export module vk:texture; - -export import :types; -export import :utilities; -export import :buffer_streams; -export import :sample_image; -export import :command_buffer; +import :types; +import :utilities; +import :buffer; +import :sample_image; +import :command_buffer; +import :image; export namespace vk { - inline namespace v1 { - sample_image create_texture_with_data(const VkDevice& p_device, const image_params& p_config, const void* p_data) { - // 1. Creating temporary command buffer for texture - command_params copy_command_params = { - .levels = command_levels::primary, - .queue_index = 0, - .flags = command_pool_flags::reset, - }; - command_buffer temp_command_buffer = - command_buffer(p_device, copy_command_params); - - // 2. loading texture - - sample_image texture_image = sample_image(p_device, p_config); - int bytes_per_pixel = bytes_per_texture_format(p_config.format); - - // 3. getting layer size - uint32_t layer_size_with_bytes = - p_config.extent.width * p_config.extent.height * bytes_per_pixel; - uint32_t layer_count = 1; - uint32_t image_size = layer_size_with_bytes * layer_count; - - // 4. transfer data from staging buffer - uint32_t property_flag = - memory_property::host_visible_bit | memory_property::host_cached_bit; - // buffer_configuration staging_buffer_config = { - // .device_size = (uint32_t)image_size, - // .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - // .property_flags = (memory_property)property_flag, - // .physical = p_config.physical_device - // }; - - buffer_parameters staging_buffer_config = { - .device_size = (uint32_t)image_size, - .physical_memory_properties = p_config.phsyical_memory_properties, - .property_flags = (memory_property)property_flag, - .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - // .physical = p_config.physical_device - }; - - // buffer_handle staging_buffer = create_buffer(p_device, - // staging_buffer_config); - buffer_stream staging(p_device, staging_buffer_config); - - // 5. write data to the staging buffer with specific size specified - // write(p_device, staging, p_data, image_size); - staging.write(p_data, image_size); - - // 6. start recording to this command buffer - VkImageLayout old_layout = VK_IMAGE_LAYOUT_UNDEFINED; - VkImageLayout new_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - VkFormat texture_format = p_config.format; - - temp_command_buffer.begin(command_usage::one_time_submit); - - // 6.1 -- transition image layout - // image_memory_barrier(temp_command_buffer, - // texture_image, - // texture_format, - // old_layout, - // new_layout); - texture_image.memory_barrier(temp_command_buffer, texture_format, old_layout, new_layout); - - // 6.2 -- copy buffer to image handles - // copy(temp_command_buffer, - // texture_image, - // staging, - // p_config.extent.width, - // p_config.extent.height); - staging.copy_to_image(temp_command_buffer, texture_image, p_config.extent); - - // 6.3 -- transition image layout back to the layout specification - old_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - new_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - // image_memory_barrier(temp_command_buffer, - // texture_image, - // texture_format, - // old_layout, - // new_layout); - texture_image.memory_barrier(temp_command_buffer, texture_format, old_layout, new_layout); - - temp_command_buffer.end(); - - // 7. Create temporary graphics queue to offload the texture image into - // GPU memory - //! TODO: Do this better then just retrieving graphics queue 0 - uint32_t queue_family_index = 0; - uint32_t queue_index = 0; - VkQueue temp_graphics_queue; - vkGetDeviceQueue( - p_device, queue_family_index, queue_index, &temp_graphics_queue); - - // 8. now submit that texture data to be stored in GPU memory - VkCommandBuffer handle = temp_command_buffer; - VkSubmitInfo submit_info = { - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .commandBufferCount = 1, - .pCommandBuffers = &handle, - }; - - vkQueueSubmit(temp_graphics_queue, 1, &submit_info, nullptr); - vkQueueWaitIdle(temp_graphics_queue); - - temp_command_buffer.destroy(); - // free_buffer(p_device, staging_buffer); - staging.destroy(); - - return texture_image; - } - - struct texture_info { - // for getting image memory requirements for the texture - VkPhysicalDeviceMemoryProperties phsyical_memory_properties; - std::filesystem::path filepath; - uint32_t mip_levels = 1; - uint32_t layer_count = 1; - }; - - struct texture_extent { - uint32_t width=0; - uint32_t height=0; - }; + inline namespace v6 { class texture { public: - texture() = default; - - texture(const VkDevice& p_device, const image_extent& p_extent, VkPhysicalDeviceMemoryProperties p_property) : m_device(p_device) { - command_params settings = { + texture() = delete; + + texture(const VkDevice& p_device, + const image_extent& p_extent, + std::span p_color, + uint32_t p_memory_mask, + uint32_t p_mip_levels = 1, + uint32_t p_layer_count = 1) + : m_device(p_device) + , m_extent(p_extent) { + + construct(p_extent, + p_color, + p_memory_mask, + p_mip_levels, + p_layer_count); + m_texture_loaded = true; + } + + texture(const VkDevice& p_device, + image* p_image, + const texture_params& p_texture_params) + : m_device(p_device) { + construct(p_image, p_texture_params); + } + + void construct(image_extent p_extent, + std::span p_data, + uint32_t p_memory_mask, + uint32_t p_mip_levels = 1, + uint32_t p_layer_count = 1) { + m_extent = p_extent; + + const VkFormat texture_format = + static_cast(format::r8g8b8a8_unorm); + + image_params img_options = { + .extent = p_extent, + .format = texture_format, + .memory_mask = p_memory_mask, + .usage = + image_usage::transfer_dst_bit | image_usage::sampled_bit, + .mip_levels = p_mip_levels, + .layer_count = p_layer_count, + }; + + m_image = sample_image(m_device, img_options); + + // Performing staging transfers + buffer_parameters staging_options = { + .memory_mask = img_options.memory_mask, + .property_flags = memory_property::host_visible_bit | + memory_property::host_cached_bit, + .usage = buffer_usage::transfer_src_bit, + }; + buffer staging(m_device, p_data.size(), staging_options); + + staging.transfer(p_data); + + // Performing transfers as a command to GPU memory for + // preparations + command_params copy_command_params = { .levels = command_levels::primary, .queue_index = 0, .flags = command_pool_flags::reset, }; + command_buffer temp_command_buffer = + command_buffer(m_device, copy_command_params); + + temp_command_buffer.begin(command_usage::one_time_submit); + + // Performing image layouts + VkImageLayout old_layout = VK_IMAGE_LAYOUT_UNDEFINED; + VkImageLayout new_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + m_image.memory_barrier(temp_command_buffer, + img_options.format, + old_layout, + new_layout); + + std::array region_copies = { + vk::buffer_image_copy{ + .image_offset = { .width = 0, .height = 0, .depth = 0, }, + .image_extent = { .width = img_options.extent.width, .height = img_options.extent.height, .depth = 1, }, + } + }; - // 1.) Load in extent dimensions - // Loading in raw white pixels for our texture. - // TODO: Take in a std::span for pixels that will then be - // written to the texture - std::array white_color = { 0xFF, 0xFF, 0xFF, 0xFF }; - - m_width = p_extent.width; - m_height = p_extent.height; - - // texture_properties properties = { - // .width = m_width, - // .height = m_height, - // .usage = (VkImageUsageFlagBits)(VK_IMAGE_USAGE_TRANSFER_DST_BIT | - // VK_IMAGE_USAGE_SAMPLED_BIT), - // .property = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - // // .format = VK_FORMAT_R8G8B8A8_UNORM, - // .format = VK_FORMAT_R8G8B8A8_SRGB - // // .format = VK_FORMAT_R64G64B64A64_SFLOAT - // }; - image_params config_image = { - .extent = { .width = p_extent.width, .height = p_extent.height }, - .format = VK_FORMAT_R8G8B8A8_UNORM, - .property = memory_property::device_local_bit, - .aspect = image_aspect_flags::color_bit, - // .usage = (VkImageUsageFlags)(VK_IMAGE_USAGE_TRANSFER_DST_BIT | - // VK_IMAGE_USAGE_SAMPLED_BIT), - .usage = image_usage::transfer_dst_bit | image_usage::sampled_bit, - // .physical_device = p_texture_info.physical - .phsyical_memory_properties = p_property + staging.copy_to_image( + temp_command_buffer, m_image, region_copies); + + old_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + new_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + m_image.memory_barrier(temp_command_buffer, + img_options.format, + old_layout, + new_layout); + + temp_command_buffer.end(); + + uint32_t queue_family = 0; + uint32_t queue_index = 0; + VkQueue temp_graphics_queue = nullptr; + vkGetDeviceQueue( + m_device, queue_family, queue_index, &temp_graphics_queue); + + const VkCommandBuffer handle = temp_command_buffer; + VkSubmitInfo submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .commandBufferCount = 1, + .pCommandBuffers = &handle, }; - m_image = - create_texture_with_data(m_device, config_image, white_color.data()); - m_texture_loaded = true; + + vkQueueSubmit(temp_graphics_queue, 1, &submit_info, nullptr); + vkQueueWaitIdle(temp_graphics_queue); + + temp_command_buffer.destruct(); + staging.destruct(); } - texture(const VkDevice& p_device, const texture_info& p_texture_info) : m_device(p_device) { - // 1. load from file - int w, h; - int channels; - stbi_uc* image_pixel_data = - stbi_load(p_texture_info.filepath.string().c_str(), - &w, - &h, - &channels, - STBI_rgb_alpha); - - m_width = w; - m_height = h; - - if (!image_pixel_data) { - m_texture_loaded = false; - return; - } - - // 2. create vulkan image handlers + loading in the image data - uint32_t property_flag = memory_property::device_local_bit; - - image_params config_image = { - .extent = { .width = (uint32_t)w, .height = (uint32_t)h }, - .format = VK_FORMAT_R8G8B8A8_UNORM, - .property = (memory_property)property_flag, - .aspect = image_aspect_flags::color_bit, - .usage = image_usage::transfer_dst_bit | image_usage::sampled_bit, - .mip_levels = p_texture_info.mip_levels, - .layer_count = p_texture_info.layer_count, - .phsyical_memory_properties = p_texture_info.phsyical_memory_properties, + void construct(image* p_image, + const texture_params& p_texture_params) { + m_extent = p_image->extent(); + + const VkFormat texture_format = + static_cast(format::r8g8b8a8_unorm); + + image_params img_options = { + .extent = p_image->extent(), + .format = texture_format, + .memory_mask = p_texture_params.memory_mask, + .usage = + image_usage::transfer_dst_bit | image_usage::sampled_bit, + .mip_levels = p_texture_params.mip_levels, + .layer_count = p_texture_params.layer_count, }; - m_image = create_texture_with_data(p_device, config_image, image_pixel_data); + m_image = sample_image(m_device, img_options); - m_texture_loaded = true; + // Setup staging buffer + uint32_t property_flag = memory_property::host_visible_bit | + memory_property::host_cached_bit; + + buffer_parameters staging_options = { + .memory_mask = img_options.memory_mask, + .property_flags = + static_cast(property_flag), + .usage = buffer_usage::transfer_src_bit, + }; + buffer staging( + m_device, p_image->read().size(), staging_options); + + staging.transfer(p_image->read()); + + // 5. Creating temporary command buffer for texture + command_params copy_command_params = { + .levels = command_levels::primary, + .queue_index = 0, + .flags = command_pool_flags::reset, + }; + command_buffer temp_command_buffer = + command_buffer(m_device, copy_command_params); + + temp_command_buffer.begin(command_usage::one_time_submit); + + // Performing image layouts + VkImageLayout old_layout = VK_IMAGE_LAYOUT_UNDEFINED; + VkImageLayout new_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + m_image.memory_barrier(temp_command_buffer, + img_options.format, + old_layout, + new_layout); + + std::array region_copies = { + vk::buffer_image_copy{ + .image_offset = { .width = 0, .height = 0, .depth = 0, }, + .image_extent = { .width = img_options.extent.width, .height = img_options.extent.height, .depth = 1, }, + } + }; + + staging.copy_to_image( + temp_command_buffer, m_image, region_copies); + + old_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + new_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + m_image.memory_barrier(temp_command_buffer, + img_options.format, + old_layout, + new_layout); + + temp_command_buffer.end(); + + uint32_t queue_family = 0; + uint32_t queue_index = 0; + VkQueue temp_graphics_queue = nullptr; + vkGetDeviceQueue( + m_device, queue_family, queue_index, &temp_graphics_queue); + + const VkCommandBuffer handle = temp_command_buffer; + VkSubmitInfo submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .commandBufferCount = 1, + .pCommandBuffers = &handle, + }; + + vkQueueSubmit(temp_graphics_queue, 1, &submit_info, nullptr); + vkQueueWaitIdle(temp_graphics_queue); + + temp_command_buffer.destruct(); + staging.destruct(); } + //! @return true if image loaded, false if texture did not load + //! correctly [[nodiscard]] bool loaded() const { return m_texture_loaded; } [[nodiscard]] sample_image image() const { return m_image; } - [[nodiscard]] uint32_t width() const { return m_width; } + [[nodiscard]] image_extent extent() const { return m_extent; } - [[nodiscard]] uint32_t height() const { return m_height; } - - void destroy() { - m_image.destroy(); - } + void destruct() { m_image.destruct(); } private: VkDevice m_device = nullptr; bool m_texture_loaded = false; - // sampled_image m_image_handle{}; sample_image m_image{}; - uint32_t m_width = 0; - uint32_t m_height = 0; + image_extent m_extent; + class image* m_image_loader = nullptr; }; }; }; \ No newline at end of file diff --git a/vulkan-cpp/types.cppm b/vulkan-cpp/types.cppm index 3a1569a..676047a 100644 --- a/vulkan-cpp/types.cppm +++ b/vulkan-cpp/types.cppm @@ -10,8 +10,10 @@ module; export module vk:types; +import :feature_extensions; + export namespace vk { - inline namespace v1 { + inline namespace v6 { enum format : uint32_t { // Core Formats undefined = VK_FORMAT_UNDEFINED, @@ -484,6 +486,12 @@ export namespace vk { std::string description; }; + struct allocation_params { + // uint32_t size=0; + uint32_t memory_supported_mask = 0; + // uint32_t memory_index=0; + }; + //! @brief Defines the enum types for a selection of gpu device // types to select according to your hardware specs @@ -492,10 +500,14 @@ export namespace vk { * types to select according to your hardware specs * * @param other - device does not match any other available types - * @param integrated - the device is typically embedded in or tightly coupled with the host. - * @param discrete - the device is typically a separate processor connected to the host via an interlink. - * @param virtual - the device is typically a virtual node in a virtualization environment. - * @param type_cpu - the device is typically running on the same processor as the host + * @param integrated - the device is typically embedded in or tightly + * coupled with the host. + * @param discrete - the device is typically a separate processor + * connected to the host via an interlink. + * @param virtual - the device is typically a virtual node in a + * virtualization environment. + * @param type_cpu - the device is typically running on the same + * processor as the host */ enum class physical_gpu : uint8_t { other = VK_PHYSICAL_DEVICE_TYPE_OTHER, @@ -518,6 +530,7 @@ export namespace vk { struct surface_params { VkSurfaceCapabilitiesKHR capabilities; VkSurfaceFormatKHR format; + uint32_t image_size = 0; // requested surface image size }; struct queue_params { @@ -532,6 +545,7 @@ export namespace vk { }; struct device_params { + void* features{}; std::span queue_priorities{}; std::span extensions{}; // Can add VK_KHR_SWAPCHAIN_EXTENSION_NAME to this @@ -539,41 +553,6 @@ export namespace vk { uint32_t queue_family_index = 0; }; - // raw image handlers - // struct image { - // VkImage image = nullptr; - // VkImageView view = nullptr; - // }; - - // sampler + raw image handlers - // struct sampled_image { - // VkImage image = nullptr; - // VkImageView view = nullptr; - // VkSampler sampler = nullptr; - // VkDeviceMemory device_memory = nullptr; - // }; - - //! @brief enumeration if an image is provided - // struct swapchain_image_enumeration { - // VkImage image = nullptr; - // VkFormat format; - // // VkImageAspectFlags aspect; - // image_aspect_flags aspect; - // uint32_t layer_count = 0; - // uint32_t mip_levels = 1; - // }; - - // Image enumeration for creating a brand new VkImage/VkImageView - // handlers struct image_enumeration { - // uint32_t width = -1; - // uint32_t height = -1; - // VkFormat format; - // // VkImageAspectFlags aspect; - // image_aspect_flags aspect; - // uint32_t layer_count = 1; - // uint32_t mip_levels = 1; - // }; - /** * @param renderpass vulkan requires framebuffers to know renderpasses * up front @@ -658,8 +637,10 @@ export namespace vk { mailbox_khr = VK_PRESENT_MODE_MAILBOX_KHR, fifo_khr = VK_PRESENT_MODE_FIFO_KHR, fifo_relaxed_khr = VK_PRESENT_MODE_FIFO_RELAXED_KHR, - shared_demand_refresh_khr = VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR, - shared_continuous_refresh_khr = VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR + shared_demand_refresh_khr = + VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR, + shared_continuous_refresh_khr = + VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR }; struct swapchain_params { @@ -741,6 +722,25 @@ export namespace vk { shader_read_only_optimal = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL }; + enum class resolved_mode_flags : uint32_t { + none = VK_RESOLVE_MODE_NONE, + sample_zero_bit = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT, + average_bit = VK_RESOLVE_MODE_AVERAGE_BIT, + min_bit = VK_RESOLVE_MODE_MIN_BIT, + max_bit = VK_RESOLVE_MODE_MAX_BIT, + // external_format_downsample_bit = + // VK_RESOLVE_MODE_EXTERNAL_FORMAT_DOWNSAMPLE_BIT_ANDROID, + // custom_bit = VK_RESOLVE_MODE_CUSTOM_BIT_EXT, + none_khr = VK_RESOLVE_MODE_NONE_KHR, + sample_zero_bit_khr = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT_KHR, + average_bit_khr = VK_RESOLVE_MODE_AVERAGE_BIT_KHR, + min_bit_khr = VK_RESOLVE_MODE_MIN_BIT_KHR, + max_bit_khr = VK_RESOLVE_MODE_MAX_BIT_KHR, + + external_format_downsample_android = + VK_RESOLVE_MODE_EXTERNAL_FORMAT_DOWNSAMPLE_ANDROID + }; + enum primitive_topology : uint8_t { point_light = VK_PRIMITIVE_TOPOLOGY_POINT_LIST, line_light = VK_PRIMITIVE_TOPOLOGY_LINE_LIST, @@ -748,10 +748,14 @@ export namespace vk { triangle_list = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, triangle_strip = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, triangle_fan = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, - line_list_with_adjacent = VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY, - line_strip_with_adjacent = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY, - triangle_list_with_adjacent = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY, - triangle_strip_with_adjacent = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY, + line_list_with_adjacent = + VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY, + line_strip_with_adjacent = + VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY, + triangle_list_with_adjacent = + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY, + triangle_strip_with_adjacent = + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY, patch_list = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST }; @@ -775,10 +779,10 @@ export namespace vk { }; enum class blend_factor : uint8_t { - zero=VK_BLEND_FACTOR_ZERO, - one=VK_BLEND_FACTOR_ONE, - src_color=VK_BLEND_FACTOR_SRC_COLOR, - one_minus_src_color=VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, + zero = VK_BLEND_FACTOR_ZERO, + one = VK_BLEND_FACTOR_ONE, + src_color = VK_BLEND_FACTOR_SRC_COLOR, + one_minus_src_color = VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, dst_color = VK_BLEND_FACTOR_DST_COLOR, one_minus_dst_color = VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, src_alpha = VK_BLEND_FACTOR_SRC_ALPHA, @@ -805,84 +809,98 @@ export namespace vk { index_buffer_bit = VK_BUFFER_USAGE_INDEX_BUFFER_BIT, vertex_buffer_bit = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, indirect_buffer_bit = VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, - shader_device_address_bit = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, + shader_device_address_bit = + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, video_decode_src_bit_khr = VK_BUFFER_USAGE_VIDEO_DECODE_SRC_BIT_KHR, video_decode_dst_bit_khr = VK_BUFFER_USAGE_VIDEO_DECODE_DST_BIT_KHR, - transform_feedback_buffer_bit_ext = VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT, - transform_feedback_counter_buffer_bit_ext = VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT, - conditoinal_rendering_bit_ext = VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT, - #ifdef VK_ENABLE_BETA_EXTENSIONS - execution_graph_scratch_bit_amdx = VK_BUFFER_USAGE_EXECUTION_GRAPH_SCRATCH_BIT_AMDX, - #endif - acceleration_structure_build_input_read_only_bit_khr = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR, - acceleration_structure_storage_bit_khr = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR, - shader_binding_table_bit_khr = VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, + transform_feedback_buffer_bit_ext = + VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT, + transform_feedback_counter_buffer_bit_ext = + VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT, + conditoinal_rendering_bit_ext = + VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT, +#ifdef VK_ENABLE_BETA_EXTENSIONS + execution_graph_scratch_bit_amdx = + VK_BUFFER_USAGE_EXECUTION_GRAPH_SCRATCH_BIT_AMDX, +#endif + acceleration_structure_build_input_read_only_bit_khr = + VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR, + acceleration_structure_storage_bit_khr = + VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR, + shader_binding_table_bit_khr = + VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, video_encode_dst_bit_khr = VK_BUFFER_USAGE_VIDEO_ENCODE_DST_BIT_KHR, encode_src_bit_khr = VK_BUFFER_USAGE_VIDEO_ENCODE_SRC_BIT_KHR, - sampler_descriptor_buffer_bit_ext = VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT, - descriptor_buffer_bit_ext = VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT, - descriptors_descriptor_buffer_bit_ext = VK_BUFFER_USAGE_PUSH_DESCRIPTORS_DESCRIPTOR_BUFFER_BIT_EXT, - micromap_build_input_read_only_bit_ext = VK_BUFFER_USAGE_MICROMAP_BUILD_INPUT_READ_ONLY_BIT_EXT, + sampler_descriptor_buffer_bit_ext = + VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT, + descriptor_buffer_bit_ext = + VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT, + descriptors_descriptor_buffer_bit_ext = + VK_BUFFER_USAGE_PUSH_DESCRIPTORS_DESCRIPTOR_BUFFER_BIT_EXT, + micromap_build_input_read_only_bit_ext = + VK_BUFFER_USAGE_MICROMAP_BUILD_INPUT_READ_ONLY_BIT_EXT, micromap_storage_bit_ext = VK_BUFFER_USAGE_MICROMAP_STORAGE_BIT_EXT, // tile_memory_bit_qcom = VK_BUFFER_USAGE_TILE_MEMORY_BIT_QCOM, ray_tracing_bit_nv = VK_BUFFER_USAGE_RAY_TRACING_BIT_NV, - shader_device_address_bit_ext = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_EXT, - shader_device_address_bit_khr = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_KHR, + shader_device_address_bit_ext = + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_EXT, + shader_device_address_bit_khr = + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_KHR, flags_bit_max_enum = VK_BUFFER_USAGE_FLAG_BITS_MAX_ENUM }; enum class blend_op : uint32_t { - add = VK_BLEND_OP_ADD, - subtract = VK_BLEND_OP_SUBTRACT, - reverse_subtract = VK_BLEND_OP_REVERSE_SUBTRACT, - min = VK_BLEND_OP_MIN, - max = VK_BLEND_OP_MAX, - zero_ext = VK_BLEND_OP_ZERO_EXT, - src_ext = VK_BLEND_OP_SRC_EXT, - dst_ext = VK_BLEND_OP_DST_EXT, - src_over_ext = VK_BLEND_OP_SRC_OVER_EXT, - dst_over_ext = VK_BLEND_OP_DST_OVER_EXT, - src_in_ext = VK_BLEND_OP_SRC_IN_EXT, - dst_in_ext = VK_BLEND_OP_DST_IN_EXT, - src_out_ext = VK_BLEND_OP_SRC_OUT_EXT, - dst_out_ext = VK_BLEND_OP_DST_OUT_EXT, - src_atop_ext = VK_BLEND_OP_SRC_ATOP_EXT, - dst_atop_ext = VK_BLEND_OP_DST_ATOP_EXT, - xor_ext = VK_BLEND_OP_XOR_EXT, - multiply_ext = VK_BLEND_OP_MULTIPLY_EXT, - screen_ext = VK_BLEND_OP_SCREEN_EXT, - overlay_ext = VK_BLEND_OP_OVERLAY_EXT, - darken_ext = VK_BLEND_OP_DARKEN_EXT, - lighten_ext = VK_BLEND_OP_LIGHTEN_EXT, - colordodge_ext = VK_BLEND_OP_COLORDODGE_EXT, - colorburn_ext = VK_BLEND_OP_COLORBURN_EXT, - hardlight_ext = VK_BLEND_OP_HARDLIGHT_EXT, - softlight_ext = VK_BLEND_OP_SOFTLIGHT_EXT, - difference_ext = VK_BLEND_OP_DIFFERENCE_EXT, - exclusion_ext = VK_BLEND_OP_EXCLUSION_EXT, - invert_ext = VK_BLEND_OP_INVERT_EXT, - invert_rgb_ext = VK_BLEND_OP_INVERT_RGB_EXT, - lineardodge_ext = VK_BLEND_OP_LINEARDODGE_EXT, - linearburn_ext = VK_BLEND_OP_LINEARBURN_EXT, - vividlight_ext = VK_BLEND_OP_VIVIDLIGHT_EXT, - linearlight_ext = VK_BLEND_OP_LINEARLIGHT_EXT, - pinlight_ext = VK_BLEND_OP_PINLIGHT_EXT, - hardmix_ext = VK_BLEND_OP_HARDMIX_EXT, - hsl_hue_ext = VK_BLEND_OP_HSL_HUE_EXT, - hsl_saturation_ext = VK_BLEND_OP_HSL_SATURATION_EXT, - hsl_color_ext = VK_BLEND_OP_HSL_COLOR_EXT, - hsl_luminosity_ext = VK_BLEND_OP_HSL_LUMINOSITY_EXT, - plus_ext = VK_BLEND_OP_PLUS_EXT, - plus_clamped_ext = VK_BLEND_OP_PLUS_CLAMPED_EXT, + add = VK_BLEND_OP_ADD, + subtract = VK_BLEND_OP_SUBTRACT, + reverse_subtract = VK_BLEND_OP_REVERSE_SUBTRACT, + min = VK_BLEND_OP_MIN, + max = VK_BLEND_OP_MAX, + zero_ext = VK_BLEND_OP_ZERO_EXT, + src_ext = VK_BLEND_OP_SRC_EXT, + dst_ext = VK_BLEND_OP_DST_EXT, + src_over_ext = VK_BLEND_OP_SRC_OVER_EXT, + dst_over_ext = VK_BLEND_OP_DST_OVER_EXT, + src_in_ext = VK_BLEND_OP_SRC_IN_EXT, + dst_in_ext = VK_BLEND_OP_DST_IN_EXT, + src_out_ext = VK_BLEND_OP_SRC_OUT_EXT, + dst_out_ext = VK_BLEND_OP_DST_OUT_EXT, + src_atop_ext = VK_BLEND_OP_SRC_ATOP_EXT, + dst_atop_ext = VK_BLEND_OP_DST_ATOP_EXT, + xor_ext = VK_BLEND_OP_XOR_EXT, + multiply_ext = VK_BLEND_OP_MULTIPLY_EXT, + screen_ext = VK_BLEND_OP_SCREEN_EXT, + overlay_ext = VK_BLEND_OP_OVERLAY_EXT, + darken_ext = VK_BLEND_OP_DARKEN_EXT, + lighten_ext = VK_BLEND_OP_LIGHTEN_EXT, + colordodge_ext = VK_BLEND_OP_COLORDODGE_EXT, + colorburn_ext = VK_BLEND_OP_COLORBURN_EXT, + hardlight_ext = VK_BLEND_OP_HARDLIGHT_EXT, + softlight_ext = VK_BLEND_OP_SOFTLIGHT_EXT, + difference_ext = VK_BLEND_OP_DIFFERENCE_EXT, + exclusion_ext = VK_BLEND_OP_EXCLUSION_EXT, + invert_ext = VK_BLEND_OP_INVERT_EXT, + invert_rgb_ext = VK_BLEND_OP_INVERT_RGB_EXT, + lineardodge_ext = VK_BLEND_OP_LINEARDODGE_EXT, + linearburn_ext = VK_BLEND_OP_LINEARBURN_EXT, + vividlight_ext = VK_BLEND_OP_VIVIDLIGHT_EXT, + linearlight_ext = VK_BLEND_OP_LINEARLIGHT_EXT, + pinlight_ext = VK_BLEND_OP_PINLIGHT_EXT, + hardmix_ext = VK_BLEND_OP_HARDMIX_EXT, + hsl_hue_ext = VK_BLEND_OP_HSL_HUE_EXT, + hsl_saturation_ext = VK_BLEND_OP_HSL_SATURATION_EXT, + hsl_color_ext = VK_BLEND_OP_HSL_COLOR_EXT, + hsl_luminosity_ext = VK_BLEND_OP_HSL_LUMINOSITY_EXT, + plus_ext = VK_BLEND_OP_PLUS_EXT, + plus_clamped_ext = VK_BLEND_OP_PLUS_CLAMPED_EXT, plus_clamped_alpha_ext = VK_BLEND_OP_PLUS_CLAMPED_ALPHA_EXT, - plus_darker_ext = VK_BLEND_OP_PLUS_DARKER_EXT, - minus_ext = VK_BLEND_OP_MINUS_EXT, - minus_clamped_ext = VK_BLEND_OP_MINUS_CLAMPED_EXT, - contrast_ext = VK_BLEND_OP_CONTRAST_EXT, - invert_ovg_ext = VK_BLEND_OP_INVERT_OVG_EXT, - red_ext = VK_BLEND_OP_RED_EXT, - green_ext = VK_BLEND_OP_GREEN_EXT, - blue_ext = VK_BLEND_OP_BLUE_EXT + plus_darker_ext = VK_BLEND_OP_PLUS_DARKER_EXT, + minus_ext = VK_BLEND_OP_MINUS_EXT, + minus_clamped_ext = VK_BLEND_OP_MINUS_CLAMPED_EXT, + contrast_ext = VK_BLEND_OP_CONTRAST_EXT, + invert_ovg_ext = VK_BLEND_OP_INVERT_OVG_EXT, + red_ext = VK_BLEND_OP_RED_EXT, + green_ext = VK_BLEND_OP_GREEN_EXT, + blue_ext = VK_BLEND_OP_BLUE_EXT }; // VkColorComponentFlags @@ -894,112 +912,145 @@ export namespace vk { }; enum class logical_op : uint8_t { - clear = VK_LOGIC_OP_CLEAR, - bit_and = VK_LOGIC_OP_AND, - and_reverse = VK_LOGIC_OP_AND_REVERSE, - copy = VK_LOGIC_OP_COPY, - and_inverted = VK_LOGIC_OP_AND_INVERTED, - no_op = VK_LOGIC_OP_NO_OP, - bit_xor = VK_LOGIC_OP_XOR, - bit_or = VK_LOGIC_OP_OR, - nor = VK_LOGIC_OP_NOR, - equivalent = VK_LOGIC_OP_EQUIVALENT, - invert = VK_LOGIC_OP_INVERT, - or_reverse = VK_LOGIC_OP_OR_REVERSE, - copy_inverted = VK_LOGIC_OP_COPY_INVERTED, - or_inverted = VK_LOGIC_OP_OR_INVERTED, - nand = VK_LOGIC_OP_NAND, - set = VK_LOGIC_OP_SET + clear = VK_LOGIC_OP_CLEAR, + bit_and = VK_LOGIC_OP_AND, + and_reverse = VK_LOGIC_OP_AND_REVERSE, + copy = VK_LOGIC_OP_COPY, + and_inverted = VK_LOGIC_OP_AND_INVERTED, + no_op = VK_LOGIC_OP_NO_OP, + bit_xor = VK_LOGIC_OP_XOR, + bit_or = VK_LOGIC_OP_OR, + nor = VK_LOGIC_OP_NOR, + equivalent = VK_LOGIC_OP_EQUIVALENT, + invert = VK_LOGIC_OP_INVERT, + or_reverse = VK_LOGIC_OP_OR_REVERSE, + copy_inverted = VK_LOGIC_OP_COPY_INVERTED, + or_inverted = VK_LOGIC_OP_OR_INVERTED, + nand = VK_LOGIC_OP_NAND, + set = VK_LOGIC_OP_SET }; enum class compare_op : uint8_t { - never = VK_COMPARE_OP_NEVER, - less = VK_COMPARE_OP_LESS, - equal = VK_COMPARE_OP_EQUAL, - less_or_equal = VK_COMPARE_OP_LESS_OR_EQUAL, - greater = VK_COMPARE_OP_GREATER, - not_equal = VK_COMPARE_OP_NOT_EQUAL, + never = VK_COMPARE_OP_NEVER, + less = VK_COMPARE_OP_LESS, + equal = VK_COMPARE_OP_EQUAL, + less_or_equal = VK_COMPARE_OP_LESS_OR_EQUAL, + greater = VK_COMPARE_OP_GREATER, + not_equal = VK_COMPARE_OP_NOT_EQUAL, greater_or_equal = VK_COMPARE_OP_GREATER_OR_EQUAL, - always = VK_COMPARE_OP_ALWAYS + always = VK_COMPARE_OP_ALWAYS }; - // VkDynamicState + // VkDynamicState enum class dynamic_state : uint32_t { - viewport = VK_DYNAMIC_STATE_VIEWPORT, - scissor = VK_DYNAMIC_STATE_SCISSOR, - line_width = VK_DYNAMIC_STATE_LINE_WIDTH, - depth_bias = VK_DYNAMIC_STATE_DEPTH_BIAS, - blend_constants = VK_DYNAMIC_STATE_BLEND_CONSTANTS, - depth_bounds = VK_DYNAMIC_STATE_DEPTH_BOUNDS, - stencil_compare_mask = VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK, - stencil_write_mask = VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, - stencil_reference = VK_DYNAMIC_STATE_STENCIL_REFERENCE, - cull_mode = VK_DYNAMIC_STATE_CULL_MODE, - front_face = VK_DYNAMIC_STATE_FRONT_FACE, - primitive_topology = VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY, - viewport_with_count = VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT, - scissor_with_count = VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT, - vertex_input_binding_stride = VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE, - depth_test_enable = VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE, - depth_write_enable = VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE, - depth_compare_op = VK_DYNAMIC_STATE_DEPTH_COMPARE_OP, - depth_bounds_test_enable = VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE, - stencil_test_enable = VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE, - stencil_op = VK_DYNAMIC_STATE_STENCIL_OP, - rasterizer_discard_enable = VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE, - depth_bias_enable = VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE, - primitive_restart_enable = VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE, - line_stipple = VK_DYNAMIC_STATE_LINE_STIPPLE, - patch_control_points_ext = VK_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT, - logic_op_ext = VK_DYNAMIC_STATE_LOGIC_OP_EXT, - color_write_enable_ext = VK_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT, - depth_clamp_enable_ext = VK_DYNAMIC_STATE_DEPTH_CLAMP_ENABLE_EXT, - polygon_mode_ext = VK_DYNAMIC_STATE_POLYGON_MODE_EXT, - rasterization_samples_ext = VK_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT, - sample_mask_ext = VK_DYNAMIC_STATE_SAMPLE_MASK_EXT, - alpha_to_coverage_enable_ext = VK_DYNAMIC_STATE_ALPHA_TO_COVERAGE_ENABLE_EXT, - alpha_to_one_enable_ext = VK_DYNAMIC_STATE_ALPHA_TO_ONE_ENABLE_EXT, - logic_op_enable_ext = VK_DYNAMIC_STATE_LOGIC_OP_ENABLE_EXT, - color_blend_enable_ext = VK_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT, - color_blend_equation_ext = VK_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT, - color_write_mask_ext = VK_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT, - tessellation_domain_origin_ext = VK_DYNAMIC_STATE_TESSELLATION_DOMAIN_ORIGIN_EXT, - rasterization_stream_ext = VK_DYNAMIC_STATE_RASTERIZATION_STREAM_EXT, - conservative_raster_mode_ext = VK_DYNAMIC_STATE_CONSERVATIVE_RASTERIZATION_MODE_EXT, - extra_primitive_overestim_ext = VK_DYNAMIC_STATE_EXTRA_PRIMITIVE_OVERESTIMATION_SIZE_EXT, - depth_clip_enable_ext = VK_DYNAMIC_STATE_DEPTH_CLIP_ENABLE_EXT, - sample_locations_enable_ext = VK_DYNAMIC_STATE_SAMPLE_LOCATIONS_ENABLE_EXT, - color_blend_advanced_ext = VK_DYNAMIC_STATE_COLOR_BLEND_ADVANCED_EXT, - provoking_vertex_mode_ext = VK_DYNAMIC_STATE_PROVOKING_VERTEX_MODE_EXT, - line_rasterization_mode_ext = VK_DYNAMIC_STATE_LINE_RASTERIZATION_MODE_EXT, - line_stipple_enable_ext = VK_DYNAMIC_STATE_LINE_STIPPLE_ENABLE_EXT, - depth_clip_negative_one_ext = VK_DYNAMIC_STATE_DEPTH_CLIP_NEGATIVE_ONE_TO_ONE_EXT, - viewport_w_scaling_nv = VK_DYNAMIC_STATE_VIEWPORT_W_SCALING_NV, - discard_rectangle_ext = VK_DYNAMIC_STATE_DISCARD_RECTANGLE_EXT, - discard_rectangle_enable_ext = VK_DYNAMIC_STATE_DISCARD_RECTANGLE_ENABLE_EXT, - discard_rectangle_mode_ext = VK_DYNAMIC_STATE_DISCARD_RECTANGLE_MODE_EXT, - sample_locations_ext = VK_DYNAMIC_STATE_SAMPLE_LOCATIONS_EXT, - ray_tracing_stack_size_khr = VK_DYNAMIC_STATE_RAY_TRACING_PIPELINE_STACK_SIZE_KHR, - shading_rate_palette_nv = VK_DYNAMIC_STATE_VIEWPORT_SHADING_RATE_PALETTE_NV, - coarse_sample_order_nv = VK_DYNAMIC_STATE_VIEWPORT_COARSE_SAMPLE_ORDER_NV, - exclusive_scissor_enable_nv = VK_DYNAMIC_STATE_EXCLUSIVE_SCISSOR_ENABLE_NV, - exclusive_scissor_nv = VK_DYNAMIC_STATE_EXCLUSIVE_SCISSOR_NV, - fragment_shading_rate_khr = VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR, - vertex_input_ext = VK_DYNAMIC_STATE_VERTEX_INPUT_EXT, - viewport_swizzle_nv = VK_DYNAMIC_STATE_VIEWPORT_SWIZZLE_NV, - coverage_to_color_enable_nv = VK_DYNAMIC_STATE_COVERAGE_TO_COLOR_ENABLE_NV, - coverage_to_color_location_nv = VK_DYNAMIC_STATE_COVERAGE_TO_COLOR_LOCATION_NV, - coverage_modulation_mode_nv = VK_DYNAMIC_STATE_COVERAGE_MODULATION_MODE_NV, - coverage_modulation_table_en_nv = VK_DYNAMIC_STATE_COVERAGE_MODULATION_TABLE_ENABLE_NV, - coverage_modulation_table_nv = VK_DYNAMIC_STATE_COVERAGE_MODULATION_TABLE_NV, - shading_rate_image_enable_nv = VK_DYNAMIC_STATE_SHADING_RATE_IMAGE_ENABLE_NV, - representative_frag_test_nv = VK_DYNAMIC_STATE_REPRESENTATIVE_FRAGMENT_TEST_ENABLE_NV, - coverage_reduction_mode_nv = VK_DYNAMIC_STATE_COVERAGE_REDUCTION_MODE_NV, - attachment_feedback_loop_ext = VK_DYNAMIC_STATE_ATTACHMENT_FEEDBACK_LOOP_ENABLE_EXT, - depth_clamp_range_ext = VK_DYNAMIC_STATE_DEPTH_CLAMP_RANGE_EXT - }; - - enum buffer : uint8_t { + viewport = VK_DYNAMIC_STATE_VIEWPORT, + scissor = VK_DYNAMIC_STATE_SCISSOR, + line_width = VK_DYNAMIC_STATE_LINE_WIDTH, + depth_bias = VK_DYNAMIC_STATE_DEPTH_BIAS, + blend_constants = VK_DYNAMIC_STATE_BLEND_CONSTANTS, + depth_bounds = VK_DYNAMIC_STATE_DEPTH_BOUNDS, + stencil_compare_mask = VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK, + stencil_write_mask = VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, + stencil_reference = VK_DYNAMIC_STATE_STENCIL_REFERENCE, + cull_mode = VK_DYNAMIC_STATE_CULL_MODE, + front_face = VK_DYNAMIC_STATE_FRONT_FACE, + primitive_topology = VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY, + viewport_with_count = VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT, + scissor_with_count = VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT, + vertex_input_binding_stride = + VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE, + depth_test_enable = VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE, + depth_write_enable = VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE, + depth_compare_op = VK_DYNAMIC_STATE_DEPTH_COMPARE_OP, + depth_bounds_test_enable = + VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE, + stencil_test_enable = VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE, + stencil_op = VK_DYNAMIC_STATE_STENCIL_OP, + rasterizer_discard_enable = + VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE, + depth_bias_enable = VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE, + primitive_restart_enable = + VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE, + line_stipple = VK_DYNAMIC_STATE_LINE_STIPPLE, + patch_control_points_ext = + VK_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT, + logic_op_ext = VK_DYNAMIC_STATE_LOGIC_OP_EXT, + color_write_enable_ext = VK_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT, + depth_clamp_enable_ext = VK_DYNAMIC_STATE_DEPTH_CLAMP_ENABLE_EXT, + polygon_mode_ext = VK_DYNAMIC_STATE_POLYGON_MODE_EXT, + rasterization_samples_ext = + VK_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT, + sample_mask_ext = VK_DYNAMIC_STATE_SAMPLE_MASK_EXT, + alpha_to_coverage_enable_ext = + VK_DYNAMIC_STATE_ALPHA_TO_COVERAGE_ENABLE_EXT, + alpha_to_one_enable_ext = VK_DYNAMIC_STATE_ALPHA_TO_ONE_ENABLE_EXT, + logic_op_enable_ext = VK_DYNAMIC_STATE_LOGIC_OP_ENABLE_EXT, + color_blend_enable_ext = VK_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT, + color_blend_equation_ext = + VK_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT, + color_write_mask_ext = VK_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT, + tessellation_domain_origin_ext = + VK_DYNAMIC_STATE_TESSELLATION_DOMAIN_ORIGIN_EXT, + rasterization_stream_ext = + VK_DYNAMIC_STATE_RASTERIZATION_STREAM_EXT, + conservative_raster_mode_ext = + VK_DYNAMIC_STATE_CONSERVATIVE_RASTERIZATION_MODE_EXT, + extra_primitive_overestim_ext = + VK_DYNAMIC_STATE_EXTRA_PRIMITIVE_OVERESTIMATION_SIZE_EXT, + depth_clip_enable_ext = VK_DYNAMIC_STATE_DEPTH_CLIP_ENABLE_EXT, + sample_locations_enable_ext = + VK_DYNAMIC_STATE_SAMPLE_LOCATIONS_ENABLE_EXT, + color_blend_advanced_ext = + VK_DYNAMIC_STATE_COLOR_BLEND_ADVANCED_EXT, + provoking_vertex_mode_ext = + VK_DYNAMIC_STATE_PROVOKING_VERTEX_MODE_EXT, + line_rasterization_mode_ext = + VK_DYNAMIC_STATE_LINE_RASTERIZATION_MODE_EXT, + line_stipple_enable_ext = VK_DYNAMIC_STATE_LINE_STIPPLE_ENABLE_EXT, + depth_clip_negative_one_ext = + VK_DYNAMIC_STATE_DEPTH_CLIP_NEGATIVE_ONE_TO_ONE_EXT, + viewport_w_scaling_nv = VK_DYNAMIC_STATE_VIEWPORT_W_SCALING_NV, + discard_rectangle_ext = VK_DYNAMIC_STATE_DISCARD_RECTANGLE_EXT, + discard_rectangle_enable_ext = + VK_DYNAMIC_STATE_DISCARD_RECTANGLE_ENABLE_EXT, + discard_rectangle_mode_ext = + VK_DYNAMIC_STATE_DISCARD_RECTANGLE_MODE_EXT, + sample_locations_ext = VK_DYNAMIC_STATE_SAMPLE_LOCATIONS_EXT, + ray_tracing_stack_size_khr = + VK_DYNAMIC_STATE_RAY_TRACING_PIPELINE_STACK_SIZE_KHR, + shading_rate_palette_nv = + VK_DYNAMIC_STATE_VIEWPORT_SHADING_RATE_PALETTE_NV, + coarse_sample_order_nv = + VK_DYNAMIC_STATE_VIEWPORT_COARSE_SAMPLE_ORDER_NV, + exclusive_scissor_enable_nv = + VK_DYNAMIC_STATE_EXCLUSIVE_SCISSOR_ENABLE_NV, + exclusive_scissor_nv = VK_DYNAMIC_STATE_EXCLUSIVE_SCISSOR_NV, + fragment_shading_rate_khr = + VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR, + vertex_input_ext = VK_DYNAMIC_STATE_VERTEX_INPUT_EXT, + viewport_swizzle_nv = VK_DYNAMIC_STATE_VIEWPORT_SWIZZLE_NV, + coverage_to_color_enable_nv = + VK_DYNAMIC_STATE_COVERAGE_TO_COLOR_ENABLE_NV, + coverage_to_color_location_nv = + VK_DYNAMIC_STATE_COVERAGE_TO_COLOR_LOCATION_NV, + coverage_modulation_mode_nv = + VK_DYNAMIC_STATE_COVERAGE_MODULATION_MODE_NV, + coverage_modulation_table_en_nv = + VK_DYNAMIC_STATE_COVERAGE_MODULATION_TABLE_ENABLE_NV, + coverage_modulation_table_nv = + VK_DYNAMIC_STATE_COVERAGE_MODULATION_TABLE_NV, + shading_rate_image_enable_nv = + VK_DYNAMIC_STATE_SHADING_RATE_IMAGE_ENABLE_NV, + representative_frag_test_nv = + VK_DYNAMIC_STATE_REPRESENTATIVE_FRAGMENT_TEST_ENABLE_NV, + coverage_reduction_mode_nv = + VK_DYNAMIC_STATE_COVERAGE_REDUCTION_MODE_NV, + attachment_feedback_loop_ext = + VK_DYNAMIC_STATE_ATTACHMENT_FEEDBACK_LOOP_ENABLE_EXT, + depth_clamp_range_ext = VK_DYNAMIC_STATE_DEPTH_CLAMP_RANGE_EXT + }; + + enum descriptor_type : uint8_t { uniform = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, // represents // VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER @@ -1014,7 +1065,7 @@ export namespace vk { // VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }; - enum image_usage : uint32_t { + enum class image_usage : uint32_t { transfer_src_bit = VK_IMAGE_USAGE_TRANSFER_SRC_BIT, transfer_dst_bit = VK_IMAGE_USAGE_TRANSFER_DST_BIT, sampled_bit = VK_IMAGE_USAGE_SAMPLED_BIT, @@ -1077,10 +1128,10 @@ export namespace vk { * - instance-based specification next data entry * */ - enum class input_rate : uint8_t { - vertex, - instance, - max_enum, + enum class input_rate : uint32_t { + vertex = VK_VERTEX_INPUT_RATE_VERTEX, + instance = VK_VERTEX_INPUT_RATE_INSTANCE, + max_enum = VK_VERTEX_INPUT_RATE_MAX_ENUM, }; //! @brief Equivalent to doing VkSampleCountFlagBits but simplified @@ -1179,6 +1230,20 @@ export namespace vk { VK_PIPELINE_BIND_POINT_MAX_ENUM // VK_PIPELINE_BIND_POINT_MAX_ENUM }; + struct viewport_params { + float x = 0.f; + float y = 0.f; + float width = 0.f; + float height = 0.f; + float min_depth = 0.f; + float max_depth = 1.f; + }; + + struct scissor_params { + VkOffset2D offset; + VkExtent2D extent; + }; + /** * @brief Specifies a specific attachment that a renderpass may operate * using @@ -1199,8 +1264,41 @@ export namespace vk { image_layout final_layout; }; + /** + * + * @brief Rendering attachment specifically used for performing + * attachments specifically for dynamic rendering. + * + */ + struct rendering_attachment { + VkImageView image_view = nullptr; + image_layout layout; + resolved_mode_flags resolve_mode; + VkImageView resolve_image_view = nullptr; + image_layout resolve_image_layout; + attachment_load load; + attachment_store store; + VkClearValue clear_values; + VkClearValue depth_values; + }; + + /** + * + * @brief Performing begin/end semantics for rendering specifically when + * dynamic rendering has been enabled. + * + */ + struct rendering_begin_parameters { + uint32_t rendering_flags = 0; + VkRect2D render_area{}; + uint32_t layer_count = 1; + uint32_t view_mask = 0; + std::span color_attachments{}; + const rendering_attachment depth_attachment{}; + const rendering_attachment stencil_attachment{}; + }; + struct renderpass_begin_params { - VkCommandBuffer current_command = nullptr; VkExtent2D extent; VkFramebuffer current_framebuffer = nullptr; std::array color; @@ -1224,71 +1322,80 @@ export namespace vk { }; /** - * @brief memory_property is a representation of vulkan's - * VkMemoryPropertyFlags. - * - * @param device_local_bit + * @brief Wrapper enum class for VkMemoryPropertyFlags * - * Meaning: indicates memory allocated with this type is most efficient - * for the GPU to access. \n + * Defines the physical locations and CPU-to-GPU access behavior for + * allocated memory heaps. * - * Implications: The memory with this bit typically - * resides on the GPU's VRAM. Accessing memory directly from GPU's since - * its faster. \n - * - * Usage: For resources that are primarily accessed by the GPU in the - * case of textures, vertex buffers, and framebuffers. If a memory type - * has this bit associated with it, the heap memory will also have be - * set along with VK_MEMORY_HEAP_DEVICE_LOCAL_BIT. \n + * @param device_local_bit + * - Used for high-speed GPU access. + * - Memory that is physically located on the GPU VRAM. + * - Usage: Performant-critical resources such as vertex buffers, + * framebuffers, textures, etc. CPU access is usually impossible unless + * Bar-Resize is used. * * @param host_visible_bit - * - * Meaning: Indicates memory alloated can be mapped to host's (CPU) - * address space using the vkMapMemory API. \n - * - * Implications: ALlows CPU to directly - * read from and write to memory. Crucial for transferring data between - * CPU to GPU. \n - * - * Usage: Use-case is for staging buffers, where data initially - * uploaded from CPU before being copied to device-local memory or for - * resourcfes that need frequent CPU updates. \n + * - Used for CPU-to-GPU transfers. + * - Memory can be mapped to CPU-address space via vkMapMemory + * - Usage: staging buffers. Allowing CPU to view and write data, + * the GPU will start to use. * * @param host_coherent_bit - * - * Meaning: Indicates host cache managemnet commands - * (vkFlushMappedMemoryRanges and vkInvalidateMappedMemoryRanges) are - * not needed. Writes made by host will automatically become visible to - * the device, and writes made by device will automatically be visible - * to the host. \n - * - * Implications: Simplifies memory synchronization between CPU and GPU. - * Though can lead to slower CPU access if it means bypassing the CPU - * caches or involving more complex cache coherence protocols. \n - * - * Usage: Used with 'VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT' for easy data - * transfers, especially for frequent updated data where manual flushing - * would be cumbersome. \n - * + * - Used for automatic synchronization + * - Removes need for manual cache flushing/invalidation. + * - Implies writing from CPU are automatically visible to the GPU (and + * vice-versa). + * - Usage: Combined with `host_visible_bit` for frequent updated data + * (like uniform buffers) to avoid complex manual sync calls. * * @param host_cached_bit + * - Used for fast CPU reads. + * - Memory stored in CPU's L1/L2/L3 cache heirarchy. + * - Implies massive performance boost for CPU reads. Without this, CPU + * reads from host-visible memory which can be slow. + * - Usage: Use when needing for Readback information such as retrieving + * computed data on the GPU or analyzing results done in the compute + * shaders, even. * - * Meaning: Indicates memory allocated with this type is cached on the - * host (CPU). \n + * Additional Considerations: In cases `host_coherent_bit` is NOT set, + * MUST manually call `vkInvalidateMappedMemoryRanges` before reading to + * ensure the CPU cache isn't hoilding onto outdated data. * - * Implications: Host memory accesses (read/writes) to this memory - * type will go through CPU cache heirarchy. Significantly improves - * performance where random access patterns. If not set on - * `HOST_VISIBLE` memory, CPU accesses are often uncached and - * write-combined, meanming writes should be sequential and reads should - * be avoided for good performance. \n + * @param lazily_allocated_bit + * - Used for memory virtualization (mobile/tile-based optimziation). + * - GPU doesn't actually allocated physical VRAM for this until the + * moment it is accessed. + * - Implies data stays "on-chip" (like depth-buffer or renderpass), it + * may never be written to VRAM at all. + * - Can be used to save massive amounts of battery/bandwidth on mobile + * GPU's. + * - Usage: Strictly for "Transient Attachments" (Depth/Stencil or MSAA + * buffers) which are created and destroyed in a single renderpass. * - * Usage: Does well for CPU-side reading of data written to GPU - * (screenshots or feedback data) and for CPU-side writing of data to be - * accessed randomly. Flag usually implies explicit cache management - * (flushing/invalidating) is required if `HOST_COHERENT_BIT` is not - * also set. \n + * @param device_protected_bit + * - Content Security (DRM) + * - Places memory in a secure heap that cannot be read by the CPU or + * non-protected GPU queue's. + * - Prevents unauthorized memory scraping. If you try to copy this + * memory to a non-protected buffer, this operation will fail. + * - Usage: Critical data protection (video streaming/DRM) or sensitive + * computed data. * + * @param device_coherent_bit_amd + * - Performs fine-grained GPU-to-GPU synchronization (on AMD hardware). + * - Ensures memory writes from one GPU shader are immediately visible + * to other parts of the GPU without explicit cache flushes. + * - Usage: GPGPU or compute-heavy tasks on specifically AMD hardware + * where manual barriers overhead is too high. + * + * @param rdma_capable_bit_nv + * - Performs direct peer-to-peer transfer (to NVIDIA hardware). + * - Allows for external devices (like 100GB NICs or other GPUs) to + * read/write to this memory directly via Remote Direct Memory Access. + * - Bypasses the GPU entirely for network-to-GPU transfers, reducing + * latency close to near zero. + * - Usage: Can be used if you need to set the memory property for + * ultra-low latecy videop streaming * */ enum memory_property : uint32_t { @@ -1306,6 +1413,18 @@ export namespace vk { flag_bits_max_enum = VK_MEMORY_PROPERTY_FLAG_BITS_MAX_ENUM }; + enum memory_allocate_flags : uint64_t { + device_mask_bit = VK_MEMORY_ALLOCATE_DEVICE_MASK_BIT, + device_address_bit = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT, + device_address_capture_replay_bit = + VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT, + // zero_initialize_bit = VK_MEMORY_ALLOCATE_ZERO_INITIALIZE_BIT_EXT, + device_mask_bit_khr = VK_MEMORY_ALLOCATE_DEVICE_MASK_BIT_KHR, + device_address_bit_khr = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT, + device_address_capture_replay_bit_khr = + VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT + }; + enum class shader_stage { vertex = VK_SHADER_STAGE_VERTEX_BIT, fragment = VK_SHADER_STAGE_FRAGMENT_BIT, @@ -1358,6 +1477,27 @@ export namespace vk { // VK_DESCRIPTOR_SET_LAYOUT_CREATE_FLAG_BITS_MAX_ENUM }; + enum class descriptor_layout_flags : uint32_t { + none = 0x00000000, + update_after_bind_pool = + VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT, + }; + + enum class descriptor_bind_flags : uint32_t { + update_after_bind = + VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT, // represents + // VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT + update_unused_while_pending = + VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT, // represents + // VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT + partially_bound_bit = + VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT, // represents + // VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT + variable_descriptor_count_bit = + VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT, // represents + // VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT + }; + //! @brief high-level specification for a shader source struct shader_source { std::string filename; @@ -1402,40 +1542,16 @@ export namespace vk { VkBuffer dst; }; - struct vertex_params { - VkPhysicalDeviceMemoryProperties phsyical_memory_properties; - std::span vertices; - std::string debug_name; - PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT = - nullptr; - }; - - struct index_params { - VkPhysicalDeviceMemoryProperties phsyical_memory_properties; - std::span indices; - std::string debug_name; - PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT = - nullptr; - }; - - struct uniform_params { - // VkPhysicalDevice physical_handle=nullptr; - VkPhysicalDeviceMemoryProperties phsyical_memory_properties; - uint32_t size_bytes = 0; - std::string debug_name; - PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT = - nullptr; - }; - struct descriptor_binding_point { uint32_t binding; shader_stage stage; }; struct descriptor_entry { - buffer type; + descriptor_type type; descriptor_binding_point binding_point; uint32_t descriptor_count; + descriptor_bind_flags flags; }; struct write_image { @@ -1464,40 +1580,54 @@ export namespace vk { struct image_extent { uint32_t width = 1; uint32_t height = 1; + uint32_t depth = 1; }; struct image_params { - image_extent extent; - VkFormat format; + image_extent extent{}; + VkFormat format = VK_FORMAT_UNDEFINED; memory_property property = memory_property::device_local_bit; + uint32_t memory_mask = 0; image_aspect_flags aspect = image_aspect_flags::color_bit; - // VkImageUsageFlags usage; - uint32_t usage; + image_usage usage; VkImageCreateFlags image_flags = 0; VkImageViewType view_type = VK_IMAGE_VIEW_TYPE_2D; uint32_t mip_levels = 1; uint32_t layer_count = 1; uint32_t array_layers = 1; - VkPhysicalDeviceMemoryProperties phsyical_memory_properties; filter_range range{ .min = VK_FILTER_LINEAR, .max = VK_FILTER_LINEAR, }; - // VkSamplerAddressMode address_mode_u = - // VK_SAMPLER_ADDRESS_MODE_REPEAT; VkSamplerAddressMode - // addrses_mode_v = VK_SAMPLER_ADDRESS_MODE_REPEAT; - // VkSamplerAddressMode addrses_mode_w = - // VK_SAMPLER_ADDRESS_MODE_REPEAT; uint32_t address_mode_u = sampler_address_mode::repeat; uint32_t addrses_mode_v = sampler_address_mode::repeat; uint32_t addrses_mode_w = sampler_address_mode::repeat; }; + // TODO: Remove redundant struct and replace with vk::image_params + struct texture_params { + uint32_t memory_mask = 0; + uint32_t mip_levels = 1; + uint32_t layer_count = 1; + }; + + struct buffer_image_copy { + uint32_t offset = 0; + uint32_t row_length = 0; + uint32_t image_height = 0; + image_aspect_flags aspect_mask = image_aspect_flags::color_bit; + uint32_t mip_level = 0; + uint32_t base_array_layer = 0; + uint32_t layer_count = 1; + image_extent image_offset{}; + image_extent image_extent{}; + }; + struct buffer_parameters { - VkDeviceSize device_size = 0; - VkPhysicalDeviceMemoryProperties physical_memory_properties; + uint32_t memory_mask = 0; memory_property property_flags; - VkBufferUsageFlags usage; + buffer_usage usage; + memory_allocate_flags allocate_flags; VkSharingMode share_mode = VK_SHARING_MODE_EXCLUSIVE; const char* debug_name = nullptr; PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT = diff --git a/vulkan-cpp/uniform_buffer.cppm b/vulkan-cpp/uniform_buffer.cppm index c266b65..a7f024f 100644 --- a/vulkan-cpp/uniform_buffer.cppm +++ b/vulkan-cpp/uniform_buffer.cppm @@ -3,17 +3,17 @@ module; #include #include #include +#include export module vk:uniform_buffer; - export import :types; export import :utilities; export import :command_buffer; -export import :buffer_streams; +export import :buffer; export namespace vk { - inline namespace v1 { + inline namespace v6 { /** * @brief represents a vulkan uniform buffer * @@ -23,42 +23,37 @@ export namespace vk { public: uniform_buffer() = default; uniform_buffer(const VkDevice& p_device, - const uniform_params& p_uniform_info) : m_device(p_device), m_size_bytes(p_uniform_info.size_bytes) { - - uint32_t property_flags = memory_property::host_visible_bit | - memory_property::host_coherent_bit; - buffer_parameters uniform_info = { - .device_size = m_size_bytes, - .physical_memory_properties = - p_uniform_info.phsyical_memory_properties, - .property_flags = (memory_property)property_flags, - .usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - .debug_name = p_uniform_info.debug_name.c_str(), - .vkSetDebugUtilsObjectNameEXT = p_uniform_info.vkSetDebugUtilsObjectNameEXT - }; - m_uniform_handle = buffer_stream(m_device, uniform_info); + uint64_t p_size_bytes, + const buffer_parameters& p_uniform_params) + : m_device(p_device) + , m_size_bytes(p_size_bytes) { + m_uniform_handle = + buffer(m_device, p_size_bytes, p_uniform_params); } [[nodiscard]] bool alive() const { return m_uniform_handle; } - void update(const void* p_data) { - m_uniform_handle.write(p_data, m_size_bytes); + template + void transfer(std::span p_uniform_data) { + m_uniform_handle.transfer(p_uniform_data); + } + + void transfer(std::span p_uniforms) { + m_uniform_handle.transfer(p_uniforms); } + [[nodiscard]] uint64_t size_bytes() const { return m_size_bytes; } + operator VkBuffer() const { return m_uniform_handle; } operator VkBuffer() { return m_uniform_handle; } - [[nodiscard]] uint32_t size_bytes() const { return m_size_bytes; } - - void destroy() { - m_uniform_handle.destroy(); - } + void destruct() { m_uniform_handle.destruct(); } private: - uint32_t m_size_bytes = 0; + uint64_t m_size_bytes; VkDevice m_device = nullptr; - buffer_stream m_uniform_handle{}; + buffer m_uniform_handle{}; }; }; }; \ No newline at end of file diff --git a/vulkan-cpp/utilities.cppm b/vulkan-cpp/utilities.cppm index b9005a5..0272de9 100644 --- a/vulkan-cpp/utilities.cppm +++ b/vulkan-cpp/utilities.cppm @@ -9,317 +9,205 @@ module; export module vk:utilities; -export import :types; +import :types; export namespace vk { - inline namespace v1 { + inline namespace v6 { - void vk_check(const VkResult& p_result, - const std::string& p_name, - const std::source_location& p_source={}) { - if (p_result != VK_SUCCESS) { - std::println( - "File {} on line {} failed VkResult check", - std::filesystem::relative(p_source.file_name()).string(), - p_source.line()); - std::println("Current Function Location = {}", - p_source.function_name()); - std::println("{} VkResult returned: {}", p_name, (int)p_result); - } - } - - std::vector enumerate_queue_family_properties( - const VkPhysicalDevice& p_physical) { - uint32_t queue_family_count = 0; - vkGetPhysicalDeviceQueueFamilyProperties( - p_physical, &queue_family_count, nullptr); - std::vector queue_family_properties( - queue_family_count); - - vkGetPhysicalDeviceQueueFamilyProperties( - p_physical, &queue_family_count, queue_family_properties.data()); - - return queue_family_properties; - } - - VkFormat select_compatible_formats( - const VkPhysicalDevice& p_physical, - std::span p_format_selection, - VkImageTiling p_tiling, - VkFormatFeatureFlags p_feature_flag) { - VkFormat format = VK_FORMAT_UNDEFINED; - - for (size_t i = 0; i < p_format_selection.size(); i++) { - VkFormat current_format = static_cast(p_format_selection[i]); - VkFormatProperties format_properties; - vkGetPhysicalDeviceFormatProperties( - p_physical, current_format, &format_properties); - - if (p_tiling == VK_IMAGE_TILING_LINEAR) { - if (format_properties.linearTilingFeatures & p_feature_flag) { - format = current_format; - } - } - else if (p_tiling == VK_IMAGE_TILING_OPTIMAL and - format_properties.optimalTilingFeatures & p_feature_flag) { - format = current_format; + void vk_check(const VkResult& p_result, const std::string& p_name) { + if (p_result != VK_SUCCESS) { + std::println("{} VkResult returned: {}", + p_name, + static_cast(p_result)); } } - return format; - } - - VkFormat select_depth_format(const VkPhysicalDevice& p_physical, - std::span p_format_selection) { - - VkFormat format = select_compatible_formats( - p_physical, - p_format_selection, - VK_IMAGE_TILING_OPTIMAL, - VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT); - return format; - } + VkSemaphore create_semaphore(const VkDevice& p_device) { + // creating semaphores + VkSemaphoreCreateInfo semaphore_ci = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = nullptr, + .flags = 0 + }; + + VkSemaphore semaphore; + vk_check( + vkCreateSemaphore(p_device, &semaphore_ci, nullptr, &semaphore), + "vkCreateSemaphore"); + return semaphore; + } - uint32_t physical_memory_properties(const VkPhysicalDevice& p_physical, - uint32_t p_type_filter, - VkMemoryPropertyFlags p_property_flag) { - VkPhysicalDeviceMemoryProperties mem_props; - vkGetPhysicalDeviceMemoryProperties(p_physical, &mem_props); + bool has_depth_specified(image_layout p_layout) { + if (p_layout == image_layout::depth_stencil_optimal) { + return true; + } - for (uint32_t i = 0; i < mem_props.memoryTypeCount; i++) { - if ((p_type_filter & (1 << i)) and - (mem_props.memoryTypes[i].propertyFlags & p_property_flag) == - p_property_flag) { - return i; + if (p_layout == image_layout::depth_stencil_read_only_optimal) { + return true; } + return false; } - return -1; - } - - // VkMemoryPropertyFlags to_memory_property_flags(memory_property p_flag) { - // VkMemoryPropertyFlags flags = 0; - // if (p_flag & memory_property::device_local_bit) { - // flags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; - // } - // if (p_flag & memory_property::host_visible_bit) { - // flags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; - // } - // if (p_flag & memory_property::host_coherent_bit) { - // flags |= VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; - // } - // if (p_flag & memory_property::host_cached_bit) { - // flags |= VK_MEMORY_PROPERTY_HOST_CACHED_BIT; - // } - // if (p_flag & memory_property::lazily_allocated_bit) { - // flags |= VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT; - // } - // if (p_flag & memory_property::device_protected_bit) { - // flags |= VK_MEMORY_PROPERTY_PROTECTED_BIT; - // } - // if (p_flag & memory_property::device_coherent_bit_amd) { - // flags |= VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD; - // } - // if (p_flag & memory_property::device_uncached_bit_amd) { - // flags |= VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD; - // } - // if (p_flag & memory_property::rdma_capable_bit_nv) { - // flags |= VK_MEMORY_PROPERTY_RDMA_CAPABLE_BIT_NV; - // } - - // return flags; - // } - - surface_params enumerate_surface(const VkPhysicalDevice& p_physical, - const VkSurfaceKHR& p_surface) { - surface_params enumerate_surface_properties{}; - vk_check( - vkGetPhysicalDeviceSurfaceCapabilitiesKHR( - p_physical, p_surface, &enumerate_surface_properties.capabilities), - "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"); - - uint32_t format_count = 0; - std::vector formats; - vk_check(vkGetPhysicalDeviceSurfaceFormatsKHR( - p_physical, p_surface, &format_count, nullptr), - "vkGetPhysicalDeviceSurfaceFormatsKHR"); - - formats.resize(format_count); - - vk_check(vkGetPhysicalDeviceSurfaceFormatsKHR( - p_physical, p_surface, &format_count, formats.data()), - "vkGetPhysicalDeviceSurfaceFormatsKHR"); - - for (const auto& format : formats) { - if (format.format == VK_FORMAT_B8G8R8A8_SRGB && - format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { - enumerate_surface_properties.format = format; + int bytes_per_texture_format(VkFormat p_format) { + switch (p_format) { + case VK_FORMAT_R8_SINT: + case VK_FORMAT_R8_UNORM: + return 1; + case VK_FORMAT_R16_SFLOAT: + return 2; + case VK_FORMAT_R16G16_SFLOAT: + case VK_FORMAT_B8G8R8A8_UNORM: + case VK_FORMAT_R8G8B8A8_UNORM: + return 4; + case VK_FORMAT_R16G16B16A16_SFLOAT: + return 4 * sizeof(uint16_t); + case VK_FORMAT_R32G32B32A32_SFLOAT: + return 4 * sizeof(float); + case VK_FORMAT_R8G8B8A8_SRGB: + return 4 * sizeof(uint8_t); + default: + return 0; } - } - if(enumerate_surface_properties.format.format == VK_FORMAT_UNDEFINED) { - enumerate_surface_properties.format = formats[0]; + return 0; } - return enumerate_surface_properties; - } - - uint32_t surface_image_size( - const VkSurfaceCapabilitiesKHR& p_capabilities) { - uint32_t requested_images = p_capabilities.minImageCount + 1; - - uint32_t final_image_count = 0; - - if ((p_capabilities.maxImageCount > 0) and - (requested_images > p_capabilities.maxImageCount)) { - final_image_count = p_capabilities.maxImageCount; + bool has_stencil_attachment(VkFormat p_format) { + return ((p_format == VK_FORMAT_D32_SFLOAT_S8_UINT) || + (p_format == VK_FORMAT_D24_UNORM_S8_UINT)); } - else { - final_image_count = requested_images; - } - - return final_image_count; - } - - VkSampler create_sampler(const VkDevice& p_device, - const filter_range& p_range, - VkSamplerAddressMode p_address_mode) { - VkSamplerCreateInfo sampler_info = { - .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .magFilter = p_range.min, - .minFilter = p_range.max, - .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, - .addressModeU = p_address_mode, - .addressModeV = p_address_mode, - .addressModeW = p_address_mode, - .mipLodBias = 0.0f, - .anisotropyEnable = false, - .maxAnisotropy = 1, - .compareEnable = false, - .compareOp = VK_COMPARE_OP_ALWAYS, - .minLod = 0.0f, - .maxLod = 0.0f, - .borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK, - .unnormalizedCoordinates = false - }; - - VkSampler sampler = nullptr; - VkResult res = - vkCreateSampler(p_device, &sampler_info, nullptr, &sampler); - vk_check(res, "vkCreateSampler"); - return sampler; - } - - VkSemaphore create_semaphore(const VkDevice& p_device) { - // creating semaphores - VkSemaphoreCreateInfo semaphore_ci = { - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, - .pNext = nullptr, - .flags = 0 - }; - - VkSemaphore semaphore; - vk_check( - vkCreateSemaphore(p_device, &semaphore_ci, nullptr, &semaphore), - "vkCreateSemaphore"); - return semaphore; - } - - uint32_t image_memory_requirements(const VkPhysicalDevice& p_physical, - const VkDevice& p_device, - const VkImage& p_image, - memory_property p_property) { - VkMemoryRequirements memory_requirements; - vkGetImageMemoryRequirements(p_device, p_image, &memory_requirements); - uint32_t type_filter = memory_requirements.memoryTypeBits; - VkMemoryPropertyFlags property_flag = static_cast(p_property); - - VkPhysicalDeviceMemoryProperties mem_props; - vkGetPhysicalDeviceMemoryProperties(p_physical, &mem_props); - - for (uint32_t i = 0; i < mem_props.memoryTypeCount; i++) { - if ((type_filter & (1 << i)) and - (mem_props.memoryTypes[i].propertyFlags & property_flag) == - property_flag) { - return i; - } + /** + * @brief Used to convert a given set of types T into chunks of bytes. + * + * This is used for the vulkan-cpp API's that expect to take in a + * span to transfer data over to the Vulkan C API, that + * expects a void*. + */ + template + std::span to_bytes(T p_data) { + return std::span(reinterpret_cast(&p_data), + sizeof(p_data)); } - return -1; - } - - VkVertexInputRate to_input_rate(input_rate p_input_rate) { - switch (p_input_rate) { - case input_rate::vertex: - return VK_VERTEX_INPUT_RATE_VERTEX; - case input_rate::instance: - return VK_VERTEX_INPUT_RATE_INSTANCE; - default: - return VK_VERTEX_INPUT_RATE_MAX_ENUM; + /** + * @brief Bitwise OR Overloads Adapters + * + * These operator overloads allow for `enum class`'s which are strongly + * typed to be temporarily truncates the types to their original type. + * + * Performing bitwise OR operation to those particular enum class types + * that can perform those operations. + * + */ + inline memory_property operator|(memory_property p_lhs, + memory_property p_rhs) { + // Lets us truncate the underlying type of the enum (class) to allow + // it to be bitwise OR'd + using T = std::underlying_type_t; + return static_cast(static_cast(p_lhs) | + static_cast(p_rhs)); } - } - bool has_depth_specified(image_layout p_layout) { - if (p_layout == image_layout::depth_stencil_optimal) { - return true; + inline buffer_usage operator|(buffer_usage p_lhs, buffer_usage p_rhs) { + // Lets us truncate the underlying type of the enum (class) to allow + // it to be bitwise OR'd + using T = std::underlying_type_t; + return static_cast(static_cast(p_lhs) | + static_cast(p_rhs)); } - if (p_layout == image_layout::depth_stencil_read_only_optimal) { - return true; + inline image_usage operator|(image_usage p_lhs, image_usage p_rhs) { + // Lets us truncate the underlying type of the enum (class) to allow + // it to be bitwise OR'd + using T = std::underlying_type_t; + return static_cast(static_cast(p_lhs) | + static_cast(p_rhs)); } - return false; - } - int bytes_per_texture_format(VkFormat p_format) { - switch (p_format) { - case VK_FORMAT_R8_SINT: - case VK_FORMAT_R8_UNORM: - return 1; - case VK_FORMAT_R16_SFLOAT: - return 2; - case VK_FORMAT_R16G16_SFLOAT: - case VK_FORMAT_B8G8R8A8_UNORM: - case VK_FORMAT_R8G8B8A8_UNORM: - return 4; - case VK_FORMAT_R16G16B16A16_SFLOAT: - return 4 * sizeof(uint16_t); - case VK_FORMAT_R32G32B32A32_SFLOAT: - return 4 * sizeof(float); - case VK_FORMAT_R8G8B8A8_SRGB: - return 4 * sizeof(uint8_t); - default: - return 0; + inline image_aspect_flags operator|(image_aspect_flags p_lhs, + image_aspect_flags p_rhs) { + // Lets us truncate the underlying type of the enum (class) to allow + // it to be bitwise OR'd + using T = std::underlying_type_t; + return static_cast(static_cast(p_lhs) | + static_cast(p_rhs)); } - return 0; - } - - bool has_stencil_attachment(VkFormat p_format) { - return ((p_format == VK_FORMAT_D32_SFLOAT_S8_UINT) || - (p_format == VK_FORMAT_D24_UNORM_S8_UINT)); - } - - uint32_t select_memory_requirements( - VkPhysicalDeviceMemoryProperties p_physical_memory_props, - VkMemoryRequirements p_memory_requirements, - memory_property p_property) { - uint32_t memory_bits = p_memory_requirements.memoryTypeBits; - VkMemoryPropertyFlags property_flag = - static_cast(p_property); - - for (uint32_t i = 0; i < p_physical_memory_props.memoryTypeCount; i++) { - if ((memory_bits & (1 << i)) and - (p_physical_memory_props.memoryTypes[i].propertyFlags & - property_flag) == property_flag) { - return i; - } + inline descriptor_bind_flags operator|(descriptor_bind_flags p_lhs, + descriptor_bind_flags p_rhs) { + using T = std::underlying_type_t; + return static_cast(static_cast(p_lhs) | + static_cast(p_rhs)); } - return -1; - } + /** + * @brief GPU memory is optimized differently for different tasks. An + * image optimized for 'Transfer Destination' (filling with bytes) can + * be look different in memory then an image configured for 'Shader Read + * Only'. + * + * This API performs a transition operation which 'reformats' the image + * data at the hardware-level. + * + * Usually acts as a syncing point, ensuring the GPU finishes writing to + * the image before it makes an attempt to read from it. + * + * [ Image Memory ] [ Transition ] [ Image Memory ] + * +------------------+ +----------------+ +---------------------+ + * | Layout: Undefined| | Memory Flush | | Layout: Shader Read | + * | Access: 0 | => | Layout Reformat| => | Access: Shader Read | + * | (Initial/Invalid)| | Exec Stall | | (For Sampling | + * +------------------+ +----------------+ +---------------------+ + * + * @brief Additional Consideration: + * - uses vkQueueWaitIdle, which stalls the CPU until the GPU is idle. + * - Only used for initialization or infrequent updates. + * - Use vk::image_layout::undefined as the old layout is faster but + * discards pixel data. + * - Transition applies to the entire subresource range (all + * mips/layers) defined within the p_image barrier logic. + * + */ + /* + void transition_image_layout(VkDevice p_device, + vk::sample_image& p_image, + VkFormat p_format, + VkImageLayout p_old, + VkImageLayout p_new) { + vk::command_params copy_command_params = { + .levels = vk::command_levels::primary, + .queue_index = 0, + .flags = vk::command_pool_flags::reset, + }; + vk::command_buffer temp_command_buffer = + vk::command_buffer(p_device, copy_command_params); + + temp_command_buffer.begin(vk::command_usage::one_time_submit); + + p_image.memory_barrier(temp_command_buffer, p_format, p_old, p_new); + + temp_command_buffer.end(); + + VkCommandBuffer handle = temp_command_buffer; + VkSubmitInfo submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .commandBufferCount = 1, + .pCommandBuffers = &handle, + }; + + uint32_t queue_family_index = 0; + uint32_t queue_index = 0; + VkQueue temp_graphics_queue; + vkGetDeviceQueue( + p_device, queue_family_index, queue_index, &temp_graphics_queue); + + vkQueueSubmit(temp_graphics_queue, 1, &submit_info, nullptr); + vkQueueWaitIdle(temp_graphics_queue); + + temp_command_buffer.destruct(); + } + */ -}; // end of v1 namespace + }; // end of v1 namespace }; \ No newline at end of file diff --git a/vulkan-cpp/vertex_buffer.cppm b/vulkan-cpp/vertex_buffer.cppm index e3ce41f..7e713af 100644 --- a/vulkan-cpp/vertex_buffer.cppm +++ b/vulkan-cpp/vertex_buffer.cppm @@ -6,85 +6,75 @@ module; export module vk:vertex_buffer; - export import :types; export import :utilities; export import :command_buffer; -export import :buffer_streams; +export import :buffer; export namespace vk { - inline namespace v1 { + inline namespace v6 { /** - * @brief vulkan implementation for loading in vertices to a vulkan buffer handle - * - * This implementation automates handle in loading the vertices and its memories for it + * @brief vulkan implementation for loading in vertices to a vulkan + * buffer handle + * + * TODO: Example implementation. Should consider implementing your own + * vertex buffer handle specifically to your needs. + * + * If you'd like to use this for getting a head start to loading your + * vertices, this is there to provide an implementation example */ class vertex_buffer { public: vertex_buffer() = default; + vertex_buffer(const VkDevice& p_device, - const vertex_params& p_vertex_info) : m_device(p_device) { - m_size = p_vertex_info.vertices.size(); - m_size_bytes = p_vertex_info.vertices.size_bytes(); - - VkBufferUsageFlags usage = - VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; - - // 1. creating staging buffer - uint32_t property_flags = - memory_property::host_visible_bit | memory_property::host_cached_bit; - - buffer_parameters new_staging_buffer_settings = { - .device_size = m_size_bytes, - .physical_memory_properties = - p_vertex_info.phsyical_memory_properties, - .property_flags = (memory_property)property_flags, - .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | - VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, - .debug_name = p_vertex_info.debug_name.c_str(), - .vkSetDebugUtilsObjectNameEXT = p_vertex_info.vkSetDebugUtilsObjectNameEXT - }; - buffer_stream staging_buffer(m_device, new_staging_buffer_settings); - std::span vertices = p_vertex_info.vertices; - staging_buffer.write(vertices); - - // 3.) Now creating our actual vertex buffer handler - buffer_parameters vertex_params = { - .device_size = m_size_bytes, - .physical_memory_properties = - p_vertex_info.phsyical_memory_properties, - .property_flags = memory_property::device_local_bit, - .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + std::span p_vertices, + const buffer_parameters& p_params) + : m_device(p_device) { + + // Staging buffer operations + buffer_parameters staging_buffer_params = { + .memory_mask = p_params.memory_mask, + .property_flags = static_cast( + memory_property::host_visible_bit | + memory_property::host_cached_bit), + .usage = buffer_usage::transfer_src_bit | + buffer_usage::storage_buffer_bit, + .debug_name = p_params.debug_name, + .vkSetDebugUtilsObjectNameEXT = + p_params.vkSetDebugUtilsObjectNameEXT }; - m_vertex_handler = buffer_stream(m_device, vertex_params); + buffer staging_buffer( + m_device, p_vertices.size_bytes(), staging_buffer_params); + staging_buffer.transfer(p_vertices); - // 4. Copy data from staging buffer to the actual vertex buffer itself! - buffer_copy_info info = { .src = staging_buffer, - .dst = m_vertex_handler }; - // copy(m_device, info, m_size_bytes); + // Creating vertex buffer handle + m_vertex_handler = + buffer(m_device, p_vertices.size_bytes(), p_params); - // 1. Retrieve the first queue + // Retrieve the first queue // TODO: Use vk::device_queue for this VkQueue temp_graphics_queue = nullptr; uint32_t queue_family_index = 0; uint32_t queue_index = 0; - vkGetDeviceQueue( - p_device, queue_family_index, queue_index, &temp_graphics_queue); + vkGetDeviceQueue(p_device, + queue_family_index, + queue_index, + &temp_graphics_queue); // command_buffer_info command_params enumerate_command_info = { .levels = command_levels::primary, .queue_index = 0, }; - command_buffer copy_command_buffer(p_device, enumerate_command_info); + command_buffer copy_command_buffer(p_device, + enumerate_command_info); copy_command_buffer.begin(command_usage::one_time_submit); - // VkBufferCopy copy_region{}; - // copy_region.size = (VkDeviceSize)m_size_bytes; - // vkCmdCopyBuffer( - // copy_command_buffer, staging_buffer, m_vertex_handler, 1, ©_region); - copy_command_buffer.copy_buffer(staging_buffer, m_vertex_handler, m_size_bytes); + + copy_command_buffer.copy_buffer( + staging_buffer, m_vertex_handler, p_vertices.size_bytes()); + copy_command_buffer.end(); VkCommandBuffer temp = copy_command_buffer; VkSubmitInfo submit_info{}; @@ -95,39 +85,99 @@ export namespace vk { vkQueueSubmit(temp_graphics_queue, 1, &submit_info, nullptr); vkQueueWaitIdle(temp_graphics_queue); - // vkFreeCommandBuffers(, command_pool, 1, ©_cmd_buffer); - // vkDestroyCommandPool(driver, command_pool, nullptr); - copy_command_buffer.destroy(); + copy_command_buffer.destruct(); - // 5. cleanup staging buffer -- no longer used - staging_buffer.destroy(); + // Cleaning up staging buffer after using it + staging_buffer.destruct(); } - [[nodiscard]] uint32_t size_bytes() const { return m_size_bytes; } + ~vertex_buffer() = default; + + void construct(const VkDevice& p_device, + std::span p_vertices, + const buffer_parameters& p_params) { + + // Can be used to invalidate 3D mesh vertices + // Staging buffer + const uint32_t transfer = + static_cast(buffer_usage::transfer_src_bit); + const uint32_t storage = + static_cast(buffer_usage::storage_buffer_bit); + uint32_t usage = transfer | storage; + buffer_parameters staging_buffer_params = { + .memory_mask = p_params.memory_mask, + .property_flags = static_cast( + memory_property::host_visible_bit | + memory_property::host_cached_bit), + .usage = buffer_usage::transfer_src_bit | + buffer_usage::storage_buffer_bit, + .debug_name = p_params.debug_name, + .vkSetDebugUtilsObjectNameEXT = + p_params.vkSetDebugUtilsObjectNameEXT + }; + buffer staging_buffer( + m_device, p_vertices.size_bytes(), staging_buffer_params); + staging_buffer.transfer(p_vertices); - [[nodiscard]] uint32_t size() const { return m_size; } + // Creating vertex buffer handle + // m_vertex_handler = + // buffer(m_device, p_vertices.size_bytes(), p_params); + m_vertex_handler.construct(p_vertices.size_bytes(), p_params); + + // 1. Retrieve the first queue + // TODO: Use vk::device_queue for this + VkQueue temp_graphics_queue = nullptr; + uint32_t queue_family_index = 0; + uint32_t queue_index = 0; + vkGetDeviceQueue(p_device, + queue_family_index, + queue_index, + &temp_graphics_queue); + + // creating command to copy data to GPU for available accessing + command_params enumerate_command_info = { + .levels = command_levels::primary, + .queue_index = 0, + }; + command_buffer copy_command_buffer(p_device, + enumerate_command_info); + + copy_command_buffer.begin(command_usage::one_time_submit); + + copy_command_buffer.copy_buffer( + staging_buffer, m_vertex_handler, p_vertices.size_bytes()); + + copy_command_buffer.end(); + VkCommandBuffer temp = copy_command_buffer; + VkSubmitInfo submit_info{}; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &temp; + + vkQueueSubmit(temp_graphics_queue, 1, &submit_info, nullptr); + vkQueueWaitIdle(temp_graphics_queue); + + copy_command_buffer.destruct(); + + // cleanup staging buffer -- no longer used + staging_buffer.destruct(); + } + + void destruct() { m_vertex_handler.destruct(); } [[nodiscard]] bool alive() const { return m_vertex_handler; } - void bind(const VkCommandBuffer& p_current) { - std::array handlers = { m_vertex_handler }; - VkDeviceSize offsets[] = { 0 }; - vkCmdBindVertexBuffers(p_current, 0, 1, handlers.data(), offsets); + void transfer(std::span p_vertices) { + m_vertex_handler.transfer(p_vertices); } operator VkBuffer() const { return m_vertex_handler; } operator VkBuffer() { return m_vertex_handler; } - void destroy() { - m_vertex_handler.destroy(); - } - private: VkDevice m_device = nullptr; - uint32_t m_size_bytes = 0; - uint32_t m_size = 0; - buffer_stream m_vertex_handler; + buffer m_vertex_handler; }; }; }; \ No newline at end of file diff --git a/vulkan-cpp/vk.cppm b/vulkan-cpp/vk.cppm index 8170de9..19dfbb1 100644 --- a/vulkan-cpp/vk.cppm +++ b/vulkan-cpp/vk.cppm @@ -3,6 +3,7 @@ export module vk; export import :types; export import :instance; export import :physical_device; +export import :feature_extensions; export import :device; export import :device_queue; export import :surface; @@ -14,15 +15,16 @@ export import :framebuffer; export import :sample_image; export import :shader_resource; export import :pipeline; -export import :buffer_streams; -export import :buffer_streams32; +export import :buffer; +export import :buffer32; export import :vertex_buffer; export import :index_buffer; export import :uniform_buffer; export import :descriptor_resource; export import :texture; +export import :buffer_device_address; +export import :image; namespace vk { - inline namespace v1 { - }; + inline namespace v6 {}; }; \ No newline at end of file