diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..6afe136 --- /dev/null +++ b/.clang-format @@ -0,0 +1,30 @@ +--- +# xrlib code style + +AlignAfterOpenBracket: AlwaysBreak +AlignTrailingComments: 'true' +AllowAllParametersOfDeclarationOnNextLine: 'false' +AlwaysBreakBeforeMultilineStrings: 'false' +BinPackArguments: 'false' +BinPackParameters: 'false' +BreakBeforeBraces: Allman +BreakBeforeTernaryOperators: 'false' +BreakConstructorInitializers: BeforeComma +ColumnLimit: '250' +ExperimentalAutoDetectBinPacking: 'false' +IndentCaseLabels: 'true' +IndentPPDirectives: BeforeHash +IndentWidth: '4' +IndentWrappedFunctionNames: 'true' +NamespaceIndentation: All +PointerAlignment: Right +SpaceBeforeCpp11BracedList: 'true' +SpaceInEmptyParentheses: 'false' +SpacesInAngles: 'true' +SpacesInCStyleCastParentheses: 'false' +SpaceAfterCStyleCast: 'true' +SpacesInContainerLiterals: 'true' +SpacesInParentheses: 'true' +SpacesInSquareBrackets: 'true' +TabWidth: '4' +UseTab: Always diff --git a/.github/workflows/all_platforms.yml b/.github/workflows/all_platforms.yml new file mode 100644 index 0000000..bc0c0c5 --- /dev/null +++ b/.github/workflows/all_platforms.yml @@ -0,0 +1,77 @@ +# xrlib +# Vulkan is installed on the runner prior to compilation + +name: All platform CI builds [ Linux|Windows, Debug|Release, g++|clang++|cl ] + +on: + push: + branches: [ "**" ] + pull_request: + branches: [ "**" ] + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + build_type: [Debug, Release] + cpp_compiler: [g++, clang++, cl] + include: + - os: windows-latest + cpp_compiler: cl + - os: ubuntu-latest + cpp_compiler: g++ + - os: ubuntu-latest + cpp_compiler: clang++ + exclude: + - os: windows-latest + cpp_compiler: g++ + - os: windows-latest + cpp_compiler: clang++ + - os: ubuntu-latest + cpp_compiler: cl + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 1 + + - name: Set reusable strings + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + + - name: Prepare Vulkan SDK + uses: humbletim/setup-vulkan-sdk@v1.2.0 + with: + vulkan-query-version: 1.3.204.0 + vulkan-components: Vulkan-Headers, Vulkan-Loader + vulkan-use-cache: true + + - name: Configure CMake on Linux + if: runner.os == 'Linux' + run: | + cmake -B ${{ steps.strings.outputs.build-output-dir }} \ + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} \ + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + -S ${{ github.workspace }} + + - name: Configure CMake on Windows + if: runner.os == 'Windows' + run: | + cmake -B ${{ steps.strings.outputs.build-output-dir }} ` + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} ` + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} ` + -S ${{ github.workspace }} + + - name: Build + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} + + - name: Test + working-directory: ${{ steps.strings.outputs.build-output-dir }} + run: ctest --build-config ${{ matrix.build_type }} diff --git a/.github/workflows/no_xrvk.yml b/.github/workflows/no_xrvk.yml new file mode 100644 index 0000000..1c14dc3 --- /dev/null +++ b/.github/workflows/no_xrvk.yml @@ -0,0 +1,75 @@ +# xrlib +# Vulkan is installed on the runner prior to compilation + +name: Exclude optional module [ xrvk - pbr renderer ] + +on: + push: + branches: [ "**" ] + pull_request: + branches: [ "**" ] + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + build_type: [Release] + cpp_compiler: [g++, cl] + include: + - os: windows-latest + cpp_compiler: cl + - os: ubuntu-latest + cpp_compiler: g++ + exclude: + - os: windows-latest + cpp_compiler: g++ + - os: ubuntu-latest + cpp_compiler: cl + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 1 + + - name: Set reusable strings + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + + - name: Prepare Vulkan SDK + uses: humbletim/setup-vulkan-sdk@v1.2.0 + with: + vulkan-query-version: 1.3.204.0 + vulkan-components: Vulkan-Headers, Vulkan-Loader + vulkan-use-cache: true + + - name: Configure CMake on Linux + if: runner.os == 'Linux' + run: | + cmake -B ${{ steps.strings.outputs.build-output-dir }} \ + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} \ + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + -DENABLE_XRVK=OFF \ + -S ${{ github.workspace }} + + - name: Configure CMake on Windows + if: runner.os == 'Windows' + run: | + cmake -B ${{ steps.strings.outputs.build-output-dir }} ` + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} ` + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} ` + -DENABLE_XRVK=OFF ` + -S ${{ github.workspace }} + + - name: Build + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} + + - name: Test + working-directory: ${{ steps.strings.outputs.build-output-dir }} + run: ctest --build-config ${{ matrix.build_type }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8fafb2e --- /dev/null +++ b/.gitignore @@ -0,0 +1,378 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# build directories +**/build/ + +# binaries +**/bin/* +**/lib/* + +# Android auto-generated +**/.cxx/ +**/.gradle/ +**/settings.gradle +**/local.properties + +# IDEs auto-generated +**/.vscode/ +**/.idea/ +**/.vs/ + +# Winmerge backup files +*.bak + +# Blender backup files +*.blend1 + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +//[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Exceptions +!*.spv +!LICENSE diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..08bae0f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "third_party/openxr"] + path = third_party/openxr + url = https://github.com/KhronosGroup/OpenXR-SDK.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..097a2e3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,269 @@ +# xrlib +# Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) +# Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.22 FATAL_ERROR) +set(CMAKE_SUPPRESS_REGENERATION true) + + +###################### +# PROJECT DEFINITION # +###################### + +# Set project variables +set(XRLIB "xrlib") +project("${XRLIB}" VERSION 1.0.0) + +# Set project directories +set(XRLIB_ROOT "${CMAKE_CURRENT_SOURCE_DIR}") +set(XRLIB_SRC "${XRLIB_ROOT}/src") +set(XRLIB_INCLUDE "${XRLIB_ROOT}/include") +set(XRLIB_BIN_OUT "${XRLIB_ROOT}/bin") +set(XRLIB_LIB_OUT "${XRLIB_ROOT}/lib") +set(THIRD_PARTY "${XRLIB_ROOT}/third_party") + +set(XRVK_INCLUDE "${XRLIB_INCLUDE}/xrvk") +set(XRVK_SRC "${XRLIB_SRC}/xrvk") + +# Set project configuration files +set(XRLIB_CONFIG_IN "${XRLIB_ROOT}/project_config.h.in") +set(XRLIB_CONFIG_OUT "${XRLIB_INCLUDE}/project_config.h") + +# Set config files +file(GLOB XRLIB_CONFIG + "${XRLIB_ROOT}/.clang-format" + "${XRLIB_ROOT}/.gitignore" + "${XRLIB_ROOT}/README.md" + "${XRLIB_ROOT}/LICENSE" + "${XRLIB_ROOT}/CMakeLists.txt" + "${XRLIB_ROOT}/*.h.in" + ) + +# Set CI files +file(GLOB XRLIB_CI + "${XRLIB_ROOT}/.github/workflows/*.yml" + ) + +# Set headers +file(GLOB_RECURSE XRLIB_HEADERS + "${XRLIB_INCLUDE}/*.h*" + "${XRLIB_INCLUDE}/{$XRLIB}/*.h*" + "${XRLIB_SRC}/*.h*" + ) + +file(GLOB_RECURSE XRVK_HEADERS + "${XRVK_INCLUDE}/*.h*" + "${XRVK_SRC}/*.h*" + ) + +# Set source code +file(GLOB_RECURSE XRLIB_SOURCE + "${XRLIB_SRC}/*.c*" + ) + +file(GLOB_RECURSE XRVK_SOURCE + "${XRVK_SRC}/*.c*" + ) + +# Set openxr +set(OPENXR_ROOT "${THIRD_PARTY}/openxr") +set(OPENXR_INCLUDE "${OPENXR_ROOT}/include") + + +####################### +# SET COMPILE OPTIONS # +####################### + +# xrvk - pbr render module +option(ENABLE_XRVK "Compile xrvk - pbr render module" ON) + + +###################################### +# SET PROJECT TECHNICAL REQUIREMENTS # +###################################### + +# C++ standard for this project +set(CPP_STD 20) +set(CMAKE_CXX_STANDARD ${CPP_STD}) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +message(STATUS "[${XRLIB}] Project language set to C++ ${CPP_STD}") + +# Check platform architecture +if(NOT PLATFORM) + if(CMAKE_SIZEOF_VOID_P MATCHES 8) + set(PLATFORM 64) + else() + message(FATAL_ERROR "[${XRLIB}] ERROR: Only 64-bit platforms are supported.") + endif() +endif() + +# OS specific +if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + add_definitions(-D_WIN32) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS True) +endif() + +# Debug settings +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + + # Assertions enabled + message(STATUS "[${XRLIB}] Debug build: Assertions enabled.") + + if(NOT ANDROID) + # Enable vulkan validation and debugging + add_definitions(-DXRVK_VULKAN_VALIDATION_ENABLE -DXRVK_VULKAN_DEBUG_ENABLE ) + message(STATUS "[${XRLIB}] Debug build: Vulkan validation and debugging enabled.") + endif() + +else() + # Disable assertions by defining NDEBUG + add_definitions(-DNDEBUG) + message(STATUS "[${XRLIB}] Release build: Assertions disabled.") +endif() + + +################################## +# BUILD THIRD PARTY DEPENDENCIES # +################################## + +# Add cmake known packages +find_package(Vulkan) +if(NOT Vulkan_FOUND) + message(FATAL_ERROR "[${XRLIB}] ERROR: Unable to find Vulkan library - install the Vulkan SDK from https://vulkan.lunarg.com/") +else() + message(STATUS "[${XRLIB}] Vulkan library loaded: ${Vulkan_LIBRARY}") +endif() + +add_subdirectory(${OPENXR_ROOT}) + + +##################### +# BINARY DEFINITION # +##################### + +# Organize source folders +set_property(GLOBAL PROPERTY USE_FOLDERS ON) +source_group(CI FILES ${XRLIB_CI}) +source_group(config FILES ${XRLIB_CONFIG}) + +source_group(TREE ${XRLIB_INCLUDE} PREFIX include FILES ${XRLIB_HEADERS}) +source_group(TREE ${XRLIB_SRC} PREFIX src FILES ${XRLIB_SOURCE}) + +source_group(TREE ${XRVK_INCLUDE} PREFIX xrvk_include FILES ${XRVK_HEADERS}) +source_group(TREE ${XRVK_SRC} PREFIX xrvk_src FILES ${XRVK_SOURCE}) + +# Set project config header which contains this project's current version number +configure_file(${XRLIB_CONFIG_IN} ${XRLIB_CONFIG_OUT}) +message(STATUS "[${XRLIB}] Project version is ${CMAKE_PROJECT_VERSION}") + + +# Assemble all source code (and optional modules/code) that will be compiled for the library +# See "Set Compile Options" section +set (ALLSOURCE + ${XRLIB_CONFIG} + ${XRLIB_CI} + ${XRLIB_HEADERS} + ${XRLIB_SOURCE} + $ + ) + +if(ENABLE_XRVK) + list(APPEND ALLSOURCE + ${XRVK_HEADERS} + ${XRVK_SOURCE} + ) + + add_compile_definitions(XRVK_ENABLED) + message(STATUS "[${XRLIB}] Optional module included: xrvk (test with macro XRVK_ENABLED)") +endif() + +# Add project header and source files to project (including statically linking openxr_loader object) +add_library(${XRLIB} SHARED ${ALLSOURCE}) +message(STATUS "[${XRLIB}] Project library defined.") + +# Set project public include headers +target_include_directories(${XRLIB} PUBLIC "${XRLIB_SRC}" + "${XRLIB_INCLUDE}" + "${XRLIB_ROOT}" + "${THIRD_PARTY}" + "${Vulkan_INCLUDE_DIRS}" + ) +# For Android, add the native app glue NDK directory +if(ANDROID) + # Add native app glue + find_path(ANDROID_NATIVE_APP_GLUE NAMES android_native_app_glue.h PATHS ${ANDROID_NDK}/sources/android/native_app_glue) + + if(NOT ANDROID_NATIVE_APP_GLUE) + message(FATAL_ERROR "[${XRLIB}] ERROR: Unable to find native_app_glue in: ${ANDROID_APP_GLUE}") + endif() + + target_include_directories(${XRLIB} PUBLIC ${ANDROID_NATIVE_APP_GLUE}) + target_compile_definitions(${XRLIB} PRIVATE XR_USE_PLATFORM_ANDROID=1) +endif() + +message(STATUS "[${XRLIB}] Public include directories defined.") + +# Generate export headers +include(GenerateExportHeader) +generate_export_header(${XRLIB}) + +message(STATUS "[${XRLIB}] Export headers generated.") + + +########################################### +# LINK THIRD PARTY DEPENDENCIES TO BINARY # +########################################### + +# Add external libraries +if(ANDROID) + target_compile_definitions(${XRLIB} PRIVATE XR_USE_PLATFORM_ANDROID=1) + + find_library(ANDROID_LIBRARY NAMES android) + find_library(ANDROID_LOG_LIBRARY NAMES log) + target_link_libraries( + ${XRLIB} PUBLIC + openxr_loader + ${Vulkan_LIBRARY} + ${ANDROID_LIBRARY} + ${ANDROID_LOG_LIBRARY} + ) +else() + target_link_libraries( + ${XRLIB} PUBLIC + openxr_loader + ${Vulkan_LIBRARY} + ) +endif() + +message(STATUS "[${XRLIB}] Third party libraries linked.") + + +################ +# BUILD BINARY # +################ + +# Set output directories +set_target_properties(${XRLIB} PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${XRLIB_LIB_OUT}" + LIBRARY_OUTPUT_DIRECTORY_DEBUG "${XRLIB_LIB_OUT}" + RUNTIME_OUTPUT_DIRECTORY_DEBUG "${XRLIB_BIN_OUT}" + ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${XRLIB_LIB_OUT}" + LIBRARY_OUTPUT_DIRECTORY_RELEASE "${XRLIB_LIB_OUT}" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${XRLIB_BIN_OUT}" +) + +message(STATUS "[${XRLIB}] Current build/working directory is: ${CMAKE_CURRENT_BINARY_DIR}") +message(STATUS "[${XRLIB}] Project archives will be built in: ${XRLIB_LIB_OUT}") +message(STATUS "[${XRLIB}] Project libraries will be built in: ${XRLIB_LIB_OUT}") +message(STATUS "[${XRLIB}] Project binaries will be built in: ${XRLIB_BIN_OUT}") + +# Post-Build +add_custom_command( + TARGET ${XRLIB} POST_BUILD + + # Create output directories + COMMAND ${CMAKE_COMMAND} -E make_directory "${XRLIB_LIB_OUT}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${XRLIB_BIN_OUT}" + ) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..37c5e64 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# xrlib +[![All platform CI builds [ Linux|Windows, Debug|Release, g++|clang++|cl ] [Linux|Windows, Debug|Release, cl|g++|clang++]](https://github.com/1runeberg/xrlib/actions/workflows/all_platforms.yml/badge.svg)](https://github.com/1runeberg/xrlib/actions/workflows/all_platforms.yml) + + C++20 OpenXR wrapper library designed to abstract the complexities of the OpenXR API while retaining its flexibility, allowing developers to focus on creating immersive experiences without getting bogged down by low-level details. The library is Vulkan-native and includes an optional PBR renderer, designed from the ground up with mixed reality in mind. + + **Supports: Android (aarch64), Linux (x86_64, aarch64), Windows (x86_64)** + + ## Note + This repository currently hosts a simplified version of an internal, comprehensive rewrite of [OpenXRProvider_v2](https://github.com/1runeberg/OpenXRProvider_v2). + +The internal library is significantly more advanced but is still in active development as the rewrite is in conjunction with development of a full-featured mixed reality application, scheduled for release by [Beyond Reality Labs](https://beyondreality.io). To avoid backward-incompatible changes, only the most stable components of the rewritten library are made available here. diff --git a/include/project_config.h b/include/project_config.h new file mode 100644 index 0000000..bd48f62 --- /dev/null +++ b/include/project_config.h @@ -0,0 +1,16 @@ +#define XRLIB_NAME "xrlib" +#define XRLIB_VERSION_MAJOR 1 +#define XRLIB_VERSION_MINOR 0 +#define XRLIB_VERSION_PATCH 0 + +#ifdef XR_USE_PLATFORM_ANDROID + #define XRLIB_ASSETS_FOLDER "" +#else + #define XRLIB_ASSETS_FOLDER "assets/" + // #define XRVK_VULKAN_DEBUG2_ENABLE 1 + // #define XRVK_VULKAN_DEBUG3_ENABLE 1 +#endif + +#define XRLIB_SHADERS_FOLDER XRLIB_ASSETS_FOLDER "shaders/" +#define XRLIB_MODELS_FOLDER XRLIB_ASSETS_FOLDER "models/" +#define XRLIB_TEXTURES_FOLDER XRLIB_ASSETS_FOLDER "textures/" diff --git a/include/xrlib.hpp b/include/xrlib.hpp new file mode 100644 index 0000000..903b784 --- /dev/null +++ b/include/xrlib.hpp @@ -0,0 +1,12 @@ +/* + * xrlib + * Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) + * Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * SPDX-License-Identifier: Apache-2.0 +*/ + +#pragma once + +#include +#include + diff --git a/include/xrlib/common.cpp b/include/xrlib/common.cpp new file mode 100644 index 0000000..e69de29 diff --git a/include/xrlib/common.hpp b/include/xrlib/common.hpp new file mode 100644 index 0000000..9a4c250 --- /dev/null +++ b/include/xrlib/common.hpp @@ -0,0 +1,120 @@ +/* + * Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) + * Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * SPDX-License-Identifier: Apache-2.0 +*/ + +#pragma once + +#define XR_USE_GRAPHICS_API_VULKAN 1 +#define XR_LEFT 0 +#define XR_RIGHT 1 + +#include +#include +#include + +#ifndef M_PI + #define M_PI 3.14159265358979323846 +#endif + +// Add android headers - must include before the openxr headers +#ifdef XR_USE_PLATFORM_ANDROID + #include + #include + #include + #include + #include + #include +#endif + +// Openxr headers +#include +#include +#include +#include + +namespace xrlib +{ + static const float GetDistance( XrVector3f &a, XrVector3f &b ) + { + float dx = a.x - b.x; + float dy = a.y - b.y; + float dz = a.z - b.z; + return std::sqrt( dx * dx + dy * dy + dz * dz ); + }; + + #ifdef XR_USE_PLATFORM_ANDROID + struct AndroidAppState + { + ANativeWindow *NativeWindow = nullptr; + bool Resumed = false; + }; + + static void app_handle_cmd( struct android_app *app, int32_t cmd ) + { + AndroidAppState *appState = (AndroidAppState *) app->userData; + + switch ( cmd ) + { + case APP_CMD_RESUME: + { + appState->Resumed = true; + break; + } + case APP_CMD_PAUSE: + { + appState->Resumed = false; + break; + } + case APP_CMD_DESTROY: + { + appState->NativeWindow = NULL; + break; + } + case APP_CMD_INIT_WINDOW: + { + appState->NativeWindow = app->window; + break; + } + case APP_CMD_TERM_WINDOW: + { + appState->NativeWindow = NULL; + break; + } + } + } + #endif + + #define XR_MAKE_VERSION32( major, minor, patch ) ( ( ( major ) << 22 ) | ( ( minor ) << 12 ) | ( patch ) ) + #define XR_VERSION_MAJOR32( version ) ( (uint32_t) ( version ) >> 22 ) + #define XR_VERSION_MINOR32( version ) ( ( (uint32_t) ( version ) >> 12 ) & 0x3ff ) + #define XR_VERSION_PATCH32( version ) ( (uint32_t) (version) &0xfff ) + + #define XR_ENUM_STRINGIFY( sEnum, val ) \ + case sEnum: \ + return #sEnum; + #define XR_ENUM_TYPE_STRINGIFY( xrEnumType ) \ + constexpr const char *XrEnumToString( xrEnumType eNum ) \ + { \ + switch ( eNum ) \ + { \ + XR_LIST_ENUM_##xrEnumType( XR_ENUM_STRINGIFY ) default : return "Unknown Enum. Define in openxr_reflection.h"; \ + } \ + } + + XR_ENUM_TYPE_STRINGIFY( XrResult ); + XR_ENUM_TYPE_STRINGIFY( XrViewConfigurationType ); + XR_ENUM_TYPE_STRINGIFY( XrSessionState ); + XR_ENUM_TYPE_STRINGIFY( XrReferenceSpaceType ); + + #define INIT_PFN( instance, pfn ) xrGetInstanceProcAddr( instance, #pfn, (PFN_xrVoidFunction *) ( &pfn ) ); + + #define XR_RETURN_ON_ERROR( xrResult ) \ + do \ + { \ + XrResult result = xrResult; \ + if ( result != XR_SUCCESS ) \ + return result; \ + } while ( false ); +} \ No newline at end of file diff --git a/include/xrlib/data_types.hpp b/include/xrlib/data_types.hpp new file mode 100644 index 0000000..48a2f28 --- /dev/null +++ b/include/xrlib/data_types.hpp @@ -0,0 +1,21 @@ + +/* + * Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) + * Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * SPDX-License-Identifier: Apache-2.0 +*/ + +#pragma once + +#include +#include + +#include // Generated by cmake from project_config.h.in +#include + +namespace xrlib +{ + typedef uint32_t XrVersion32; + typedef uint64_t XrHandleType; + +} \ No newline at end of file diff --git a/include/xrlib/ext/EXT_hand_tracking.hpp b/include/xrlib/ext/EXT_hand_tracking.hpp new file mode 100644 index 0000000..d36b2d5 --- /dev/null +++ b/include/xrlib/ext/EXT_hand_tracking.hpp @@ -0,0 +1,67 @@ +/* + * Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) + * Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include + +namespace xrlib::EXT +{ + class CHandTracking : public ExtBase + { + public: + CHandTracking( XrInstance xrInstance ); + ~CHandTracking(); + + XrResult Init( XrSession xrSession, + XrHandJointSetEXT leftHandJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT, + void *pNextLeft = nullptr, + XrHandJointSetEXT rightHandJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT, + void *pNextRight = nullptr ); + + struct SJointVelocities + { + XrHandJointVelocitiesEXT left { XR_TYPE_HAND_JOINT_VELOCITIES_EXT }; + XrHandJointVelocitiesEXT right { XR_TYPE_HAND_JOINT_VELOCITIES_EXT }; + + std::array< XrHandJointVelocityEXT, XR_HAND_JOINT_COUNT_EXT > leftVelocities; + std::array< XrHandJointVelocityEXT, XR_HAND_JOINT_COUNT_EXT > rightVelocities; + + SJointVelocities( void *pNextLeft = nullptr, void *pNextRight = nullptr ); + ~SJointVelocities() {}; + }; + + struct SJointLocations + { + XrHandJointLocationsEXT left { XR_TYPE_HAND_JOINT_LOCATIONS_EXT }; + XrHandJointLocationsEXT right { XR_TYPE_HAND_JOINT_LOCATIONS_EXT }; + + std::array< XrHandJointLocationEXT, XR_HAND_JOINT_COUNT_EXT > leftJointLocations; + std::array< XrHandJointLocationEXT, XR_HAND_JOINT_COUNT_EXT > rightJointLocations; + + SJointLocations( SJointVelocities &jointVelocities ); + SJointLocations( void *pNextLeft = nullptr, void *pNextRight = nullptr ); + ~SJointLocations() {}; + }; + + XrResult LocateHandJoints( SJointLocations *outHandJointLocations, XrSpace baseSpace, XrTime time, void *pNextLeft = nullptr, void *pNextRight = nullptr ); + XrResult LocateHandJoints( XrHandJointLocationsEXT *outHandJointLocations, XrHandEXT hand, XrSpace baseSpace, XrTime time, void *pNext = nullptr ); + + XrSystemHandTrackingPropertiesEXT GenerateSystemProperties( void *pNext = nullptr ); + + XrHandTrackerEXT *GetHandTracker( XrHandEXT hand ); + std::vector< XrHandTrackerEXT > &GetHandTrackers() { return m_vecHandTrackers; } + + private: + XrSession m_xrSession = XR_NULL_HANDLE; + PFN_xrLocateHandJointsEXT xrLocateHandJointsEXT = nullptr; + std::vector< XrHandTrackerEXT > m_vecHandTrackers = { XR_NULL_HANDLE, XR_NULL_HANDLE }; + + }; + +} // namespace xrlib diff --git a/include/xrlib/ext/ext_base.hpp b/include/xrlib/ext/ext_base.hpp new file mode 100644 index 0000000..fca145d --- /dev/null +++ b/include/xrlib/ext/ext_base.hpp @@ -0,0 +1,37 @@ +/* + * Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) + * Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include + +#include + +namespace xrlib +{ + class ExtBase + { + public: + ExtBase( XrInstance xrInstance, std::string sName ) : + m_xrInstance( xrInstance ), + m_sName( sName ) + { + assert( xrInstance != XR_NULL_HANDLE ); + assert( !sName.empty() ); + } + + ~ExtBase() {}; + + std::string GetName() { return m_sName; } + + protected: + XrInstance m_xrInstance = XR_NULL_HANDLE; + std::string m_sName; + + }; + +} diff --git a/include/xrlib/input.hpp b/include/xrlib/input.hpp new file mode 100644 index 0000000..e69de29 diff --git a/include/xrlib/instance.hpp b/include/xrlib/instance.hpp new file mode 100644 index 0000000..246e21e --- /dev/null +++ b/include/xrlib/instance.hpp @@ -0,0 +1,132 @@ +/* + * Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) + * Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include +#include + +namespace xrlib +{ + static constexpr uint8_t k_Left = 0; + static constexpr uint8_t k_Right = 1; + + class CInstance + { + public: + #ifdef XR_USE_PLATFORM_ANDROID + CInstance( struct android_app *pAndroidApp, const std::string &sAppName, const XrVersion32 unAppVersion, const ELogLevel eMinLogLevel = ELogLevel::LogVerbose ); + #else + CInstance( const std::string &sAppName, const XrVersion32 unAppVersion, const ELogLevel eMinLogLevel = ELogLevel::LogVerbose ); + #endif + + ~CInstance(); + + XrResult Init( + std::vector< const char * > vecInstanceExtensions, + std::vector< const char * > vecAPILayers, + const XrInstanceCreateFlags createFlags = 0, + const void *pNext = nullptr ); + + bool IsApiLayerEnabled( std::string &sApiLayerName ); + + XrResult GetSupportedApiLayers( std::vector< XrApiLayerProperties > &vecApiLayers ); + + XrResult GetSupportedApiLayers( std::vector< std::string > &vecApiLayers ); + + bool IsExtensionEnabled( const char *extensionName ); + + bool IsExtensionEnabled( std::string &sExtensionName ); + + XrResult GetSupportedExtensions( std::vector< XrExtensionProperties > &outExtensions, const char *pcharApiLayerName = nullptr ); + + XrResult GetSupportedExtensions( std::vector< std::string > &outExtensions, const char *pcharApiLayerName = nullptr ); + + XrResult FilterForSupportedExtensions( std::vector< std::string > &vecRequestedExtensionNames ); + + XrResult FilterOutUnsupportedExtensions( std::vector< const char * > &vecExtensionNames ); + + XrResult FilterOutUnsupportedApiLayers( std::vector< const char * > &vecApiLayerNames ); + + void FilterOutUnsupportedGraphicsApis( std::vector< const char * > &vecExtensionNames ); + + std::vector< XrViewConfigurationType > GetSupportedViewConfigurations(); + + void GetApilayerNames( std::vector< std::string > &outApiLayerNames, const std::vector< XrApiLayerProperties > &vecApilayerProperties ); + + void GetExtensionNames( std::vector< std::string > &outExtensionNames, const std::vector< XrExtensionProperties > &vecExtensionProperties ); + + const char *GetAppName() { return m_sAppName.c_str(); } + + const XrVersion32 GetAppVersion() { return m_unAppVersion; } + + const XrInstance GetXrInstance() { return m_xrInstance; } + + const XrInstanceProperties *GetXrInstanceProperties() { return &m_xrInstanceProperties; } + + const XrSystemId GetXrSystemId() { return m_xrSystemId; } + + const XrSystemProperties *GetXrSystemProperties( bool bUpdate = false, void *pNext = nullptr ); + + const std::vector< std::string > &GetEnabledApiLayers() { return m_vecEnabledApiLayers; } + + const std::vector< std::string > &GetEnabledExtensions() { return m_vecEnabledExtensions; } + + const ELogLevel GetMinLogLevel() { return m_eMinLogLevel; } + + + #ifdef XR_USE_PLATFORM_ANDROID + AndroidAppState androidAppState {}; + + XrResult InitAndroidLoader( void *pNext = nullptr ) + { + m_pAndroidApp->activity->vm->AttachCurrentThread( &m_jniEnv, nullptr ); + m_pAndroidApp->userData = &androidAppState; + + PFN_xrInitializeLoaderKHR initializeLoader = nullptr; + XrResult xrResult = xrGetInstanceProcAddr( XR_NULL_HANDLE, "xrInitializeLoaderKHR", (PFN_xrVoidFunction *) ( &initializeLoader ) ); + if ( XR_SUCCEEDED( xrResult ) ) + { + XrLoaderInitInfoAndroidKHR loaderInitInfoAndroid; + memset( &loaderInitInfoAndroid, 0, sizeof( loaderInitInfoAndroid ) ); + loaderInitInfoAndroid.type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR; + loaderInitInfoAndroid.next = pNext; + loaderInitInfoAndroid.applicationVM = m_pAndroidApp->activity->vm; + loaderInitInfoAndroid.applicationContext = m_pAndroidApp->activity->clazz; + initializeLoader( (const XrLoaderInitInfoBaseHeaderKHR *) &loaderInitInfoAndroid ); + } + + return xrResult; + } + + struct android_app *GetAndroidApp() { return m_pAndroidApp; } + JNIEnv *GetJNIEnv() { return m_jniEnv; } + AAssetManager *GetAssetManager() { return m_pAndroidApp->activity->assetManager; } + + private: + JNIEnv *m_jniEnv = nullptr; + struct android_app *m_pAndroidApp = nullptr; + + #else + private: + #endif + + std::string m_sAppName; + XrVersion32 m_unAppVersion = 0; + ELogLevel m_eMinLogLevel = ELogLevel::LogVerbose; + + XrInstance m_xrInstance = XR_NULL_HANDLE; + XrInstanceProperties m_xrInstanceProperties { XR_TYPE_INSTANCE_PROPERTIES }; + XrSystemId m_xrSystemId = XR_NULL_SYSTEM_ID; + XrSystemProperties m_xrSystemProperties { XR_TYPE_SYSTEM_PROPERTIES }; + + std::vector< std::string > m_vecEnabledApiLayers; + std::vector< std::string > m_vecEnabledExtensions; + }; + +} diff --git a/include/xrlib/log.hpp b/include/xrlib/log.hpp new file mode 100644 index 0000000..70ff4ca --- /dev/null +++ b/include/xrlib/log.hpp @@ -0,0 +1,194 @@ +/* + * Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) + * Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once +#pragma warning( disable : 4996 ) // windows: warning C4996: 'strncpy' + +#include +#include +#include +#include + +#ifdef XR_USE_PLATFORM_ANDROID + #include +#endif + +#include // Generated by cmake from project_config.h.in + +namespace xrlib +{ + constexpr const char *LOG_CATEGORY_DEFAULT = "xrlib"; + + enum class ELogLevel + { + LogNone = 0, + LogError = 1, + LogWarning = 2, + LogInfo = 3, + LogDebug = 4, + LogVerbose = 5, + LogEMax + }; + + static const char *GetLogLevelName( ELogLevel eLogLevel ) + { + switch ( eLogLevel ) + { + case ELogLevel::LogVerbose: + return "Verbose"; + break; + case ELogLevel::LogDebug: + return "Debug"; + break; + case ELogLevel::LogInfo: + return "Info"; + break; + case ELogLevel::LogWarning: + return "Warning"; + break; + case ELogLevel::LogError: + return "Error"; + break; + case ELogLevel::LogNone: + case ELogLevel::LogEMax: + default: + break; + } + + return "None"; + } + + static bool CheckLogLevel( ELogLevel eLogLevel, ELogLevel eMinLogLevel ) + { + if ( eLogLevel < eMinLogLevel || eLogLevel == ELogLevel::LogNone ) + return false; + + return true; + } + + static bool CheckLogLevelDebug( ELogLevel eLogLevel ) { return CheckLogLevel( eLogLevel, ELogLevel::LogDebug ); } + + static bool CheckLogLevelVerbose( ELogLevel eLogLevel ) { return CheckLogLevel( eLogLevel, ELogLevel::LogVerbose ); } + + static void Log( ELogLevel eLoglevel, std::string sCategory, const char *pccMessageFormat, ... ) + { + va_list pArgs; + va_start( pArgs, pccMessageFormat ); + + const char *pccCategory = sCategory.empty() ? LOG_CATEGORY_DEFAULT : sCategory.c_str(); + +#ifdef XR_USE_PLATFORM_ANDROID + switch ( eLoglevel ) + { + case xrlib::ELogLevel::LogDebug: + __android_log_vprint( ANDROID_LOG_DEBUG, pccCategory, pccMessageFormat, pArgs ); + break; + case xrlib::ELogLevel::LogInfo: + __android_log_vprint( ANDROID_LOG_INFO, pccCategory, pccMessageFormat, pArgs ); + break; + case xrlib::ELogLevel::LogWarning: + __android_log_vprint( ANDROID_LOG_WARN, pccCategory, pccMessageFormat, pArgs ); + break; + case xrlib::ELogLevel::LogError: + __android_log_vprint( ANDROID_LOG_ERROR, pccCategory, pccMessageFormat, pArgs ); + break; + case xrlib::ELogLevel::LogNone: + case xrlib::ELogLevel::LogEMax: + default: + break; + } +#else + printf( "[%s][%s] ", pccCategory, GetLogLevelName( eLoglevel ) ); + vfprintf( stdout, pccMessageFormat, pArgs ); + printf( "\n" ); +#endif + va_end( pArgs ); + } + + static void LogInfo( std::string sCategory, const char *pccMessageFormat, ... ) + { + va_list pArgs; + va_start( pArgs, pccMessageFormat ); + + const char *pccCategory = sCategory.empty() ? LOG_CATEGORY_DEFAULT : sCategory.c_str(); + +#ifdef XR_USE_PLATFORM_ANDROID + __android_log_vprint( ANDROID_LOG_INFO, pccCategory, pccMessageFormat, pArgs ); +#else + printf( "[%s][%s] ", pccCategory, GetLogLevelName( ELogLevel::LogInfo ) ); + vfprintf( stdout, pccMessageFormat, pArgs ); + printf( "\n" ); +#endif + va_end( pArgs ); + } + + static void LogVerbose( std::string sCategory, const char *pccMessageFormat, ... ) + { + va_list pArgs; + va_start( pArgs, pccMessageFormat ); + + const char *pccCategory = sCategory.empty() ? LOG_CATEGORY_DEFAULT : sCategory.c_str(); + +#ifdef XR_USE_PLATFORM_ANDROID + __android_log_vprint( ANDROID_LOG_VERBOSE, pccCategory, pccMessageFormat, pArgs ); +#else + printf( "[%s][%s] ", pccCategory, GetLogLevelName( ELogLevel::LogVerbose ) ); + vfprintf( stdout, pccMessageFormat, pArgs ); + printf( "\n" ); +#endif + va_end( pArgs ); + } + + static void LogDebug( std::string sCategory, const char *pccMessageFormat, ... ) + { + va_list pArgs; + va_start( pArgs, pccMessageFormat ); + + const char *pccCategory = sCategory.empty() ? LOG_CATEGORY_DEFAULT : sCategory.c_str(); + +#ifdef XR_USE_PLATFORM_ANDROID + __android_log_vprint( ANDROID_LOG_DEBUG, pccCategory, pccMessageFormat, pArgs ); +#else + printf( "[%s][%s] ", pccCategory, GetLogLevelName( ELogLevel::LogDebug ) ); + vfprintf( stdout, pccMessageFormat, pArgs ); + printf( "\n" ); +#endif + va_end( pArgs ); + } + + static void LogWarning( std::string sCategory, const char *pccMessageFormat, ... ) + { + va_list pArgs; + va_start( pArgs, pccMessageFormat ); + + const char *pccCategory = sCategory.empty() ? LOG_CATEGORY_DEFAULT : sCategory.c_str(); + +#ifdef XR_USE_PLATFORM_ANDROID + __android_log_vprint( ANDROID_LOG_WARN, pccCategory, pccMessageFormat, pArgs ); +#else + printf( "[%s][%s] ", pccCategory, GetLogLevelName( ELogLevel::LogWarning ) ); + vfprintf( stdout, pccMessageFormat, pArgs ); + printf( "\n" ); +#endif + va_end( pArgs ); + } + + static void LogError( std::string sCategory, const char *pccMessageFormat, ... ) + { + va_list pArgs; + va_start( pArgs, pccMessageFormat ); + + const char *pccCategory = sCategory.empty() ? LOG_CATEGORY_DEFAULT : sCategory.c_str(); + +#ifdef XR_USE_PLATFORM_ANDROID + __android_log_vprint( ANDROID_LOG_ERROR, pccCategory, pccMessageFormat, pArgs ); +#else + printf( "[%s][%s] ", pccCategory, GetLogLevelName( ELogLevel::LogError ) ); + vfprintf( stdout, pccMessageFormat, pArgs ); + printf( "\n" ); +#endif + } +} diff --git a/include/xrlib/session.hpp b/include/xrlib/session.hpp new file mode 100644 index 0000000..2aaf4cd --- /dev/null +++ b/include/xrlib/session.hpp @@ -0,0 +1,114 @@ +/* + * Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) + * Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include + +namespace xrlib +{ + class CInstance; + class CVulkan; + + class CSession + { + public: + CSession( CInstance *pInstance ); + ~CSession(); + + friend class CVulkan; + + XrPosef xrAppReferencePose { { 0.f, 0.f, 0.f, 1.f }, { 0.f, 0.f, 0.f } }; + XrReferenceSpaceType xrAppReferenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE; + XrViewConfigurationType xrViewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; + + XrResult Init( + VkSurfaceKHR *pSurface = nullptr, + XrSessionCreateFlags flgAdditionalCreateInfo = 0, + void *pVkInstanceNext = nullptr, + void *pXrVkInstanceNext = nullptr, + void *pVkLogicalDeviceNext = nullptr, + void *pXrLogicalDeviceNext = nullptr ); + + XrResult InitVulkan( VkSurfaceKHR *pSurface = nullptr, void *pVkInstanceNext = nullptr, void *pXrVkInstanceNext = nullptr, void *pVkLogicalDeviceNext = nullptr, void *pXrLogicalDeviceNext = nullptr ); + XrResult CreateXrSession( XrSessionCreateFlags flgAdditionalCreateInfo = 0, void *pNext = nullptr ); + XrResult CreateAppSpace( XrPosef referencePose, XrReferenceSpaceType referenceSpaceType, void *pNext = nullptr ); + + XrResult Start( void *pOtherBeginInfo = nullptr); + XrResult Poll( XrEventDataBaseHeader *outEventData ); + XrResult End( bool bRequestToExit = false ); + + XrResult StartFrame( XrFrameState* pFrameState, void *pWaitFrameNext = nullptr, void *pBeginFrameNext = nullptr ); + + XrResult UpdateEyeStates( + std::vector< XrView > &outEyeViews, + std::vector< XrMatrix4x4f > &outEyeProjections, + XrViewState *outEyeViewsState, + XrFrameState *pFrameState, + XrSpace space, + const float fNearZ = 0.1f, + const float fFarZ = 100.f, + XrViewConfigurationType viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, + void *pNext = nullptr ); + + XrResult AcquireFrameImage( + uint32_t *outImageIndex, + XrSwapchain swapchain, + void *pNext = nullptr ); + + XrResult AcquireFrameImage( + uint32_t *outColorImageIndex, + uint32_t *outDepthImageIndex, + XrSwapchain colorSwapchain, + XrSwapchain depthSwapchain, + void *pColorAcquireNext = nullptr, + void *pDepthAcquireNext = nullptr ); + + XrResult WaitForFrameImage( + XrSwapchain swapchain, + XrDuration duration = XR_INFINITE_DURATION, + void *pNext = nullptr ); + + XrResult WaitForFrameImage( + XrSwapchain colorSwapchain, + XrSwapchain depthSwapchain, + XrDuration duration = XR_INFINITE_DURATION, + void *pNext = nullptr ); + + XrResult ReleaseFrameImage( XrSwapchain swapchain, void *pNext = nullptr ); + XrResult ReleaseFrameImage( XrSwapchain colorSwapchain, XrSwapchain depthSwapchain, void *pNext = nullptr ); + + XrResult EndFrame( + XrFrameState *pFrameState, + std::vector< XrCompositionLayerBaseHeader * > &vecFrameLayers, + XrEnvironmentBlendMode blendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE, + void *pNext = nullptr ); + + std::vector< XrReferenceSpaceType > GetSupportedReferenceSpaceTypes(); + XrResult GetSupportedTextureFormats( std::vector< int64_t > &outSupportedFormats ); + int64_t SelectColorTextureFormat( const std::vector< int64_t > &vecRequestedFormats ); + int64_t SelectDepthTextureFormat( const std::vector< int64_t > &vecRequestedFormats ); + + CInstance *GetAppInstance() { return m_pInstance; } + CVulkan *GetVulkan() { return m_pVulkan; } + const XrSession GetXrSession() { return m_xrSession; } + const XrSessionState GetState() { return m_xrSessionState; } + const XrSpace GetAppSpace() { return m_xrAppSpace; } + const bool IsSessionRunning() { return m_xrSessionState < XR_SESSION_STATE_VISIBLE; } + + const ELogLevel GetMinLogLevel() { return m_pInstance->GetMinLogLevel(); } + + private: + CInstance *m_pInstance = nullptr; + CVulkan *m_pVulkan = nullptr; + + XrSession m_xrSession = XR_NULL_HANDLE; + XrSessionState m_xrSessionState = XR_SESSION_STATE_UNKNOWN; + XrSpace m_xrAppSpace = XR_NULL_HANDLE; + + }; +} \ No newline at end of file diff --git a/include/xrlib/utility_functions.hpp b/include/xrlib/utility_functions.hpp new file mode 100644 index 0000000..18a5e5b --- /dev/null +++ b/include/xrlib/utility_functions.hpp @@ -0,0 +1,83 @@ + +/* + * Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) + * Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * SPDX-License-Identifier: Apache-2.0 +*/ + +#pragma once +#include +#include +#include + +namespace xrlib +{ + static void StringReset( char *outString, uint32_t stringLength ) + { + std::memset( outString, '\0', stringLength * sizeof( char ) ); + } + + static bool StringCopy( char *outString, const uint32_t unOutStringLength, const std::string &sSourceString ) + { + if ( unOutStringLength < static_cast< uint32_t >( sSourceString.size() ) ) + { + StringReset( outString, unOutStringLength ); + return false; + } + + strcpy( outString, sSourceString.c_str() ); + return true; + } + + static bool StringCopy( char *outString, const uint32_t unOutStringLength, const char *pccSourceString, const uint32_t unSourceStringLength ) + { + if ( unOutStringLength < unSourceStringLength ) + { + StringReset( outString, unOutStringLength ); + return false; + } + + std::memcpy( outString, pccSourceString, unSourceStringLength * sizeof( char ) ); + return true; + } + + static bool FindStringInVector( std::vector< std::string > &vecStrings, std::string &sString ) + { + for ( auto &str : vecStrings ) + { + if ( str == sString ) + { + return true; + } + } + + return false; + } + + static std::vector< const char * > ConvertDelimitedCharArray( char *arrChars, char cDelimiter ) + { + std::vector< const char * > vecOutput; + + // Loop 'til end of array + while ( *arrChars != 0 ) + { + vecOutput.push_back( arrChars ); + while ( *( ++arrChars ) != 0 ) + { + if ( *arrChars == cDelimiter ) + { + *arrChars++ = '\0'; + break; + } + } + } + return vecOutput; + } + + static inline XrPosef IdentityPosef() + { + XrPosef xrPose {}; + xrPose.orientation.w = 1.f; + return xrPose; + } +} \ No newline at end of file diff --git a/include/xrlib/vulkan.hpp b/include/xrlib/vulkan.hpp new file mode 100644 index 0000000..f5a592b --- /dev/null +++ b/include/xrlib/vulkan.hpp @@ -0,0 +1,103 @@ +/* + * Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) + * Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +namespace xrlib +{ + class CVulkan + { + public: + CVulkan( CSession* pSession ); + ~CVulkan(); + + VkPhysicalDeviceFeatures vkPhysicalDeviceFeatures {}; + + std::vector< const char * > vecExtensions; + std::vector< const char * > vecLayers; + std::vector< const char * > vecLogicalDeviceExtensions; + + #ifdef XRVK_VULKAN_VALIDATION_ENABLE + const std::vector< const char * > m_vecValidationLayers = { "VK_LAYER_KHRONOS_validation" }; + const std::vector< const char * > m_vecValidationExtensions = { VK_EXT_DEBUG_UTILS_EXTENSION_NAME }; + #else + const std::vector< const char * > m_vecValidationLayers; + const std::vector< const char * > m_vecValidationExtensions; + #endif + + XrResult Init( + VkSurfaceKHR *pSurface = nullptr, + void *pVkInstanceNext = nullptr, + void *pXrVkInstanceNext = nullptr, + void *pVkLogicalDeviceNext = nullptr, + void *pXrLogicalDeviceNext = nullptr ); + + static VKAPI_ATTR VkBool32 VKAPI_CALL Callback_Debug( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData, + void *pUserData ); + + const bool IsDepthFormat( VkFormat vkFormat ); + + CSession *GetAppSession() { return m_pSession; } + CInstance *GetAppInstance() { return m_pSession->m_pInstance; } + + XrGraphicsRequirementsVulkan2KHR *GetGraphicsRequirements() { return &m_xrGraphicsRequirements; } + XrGraphicsBindingVulkan2KHR *GetGraphicsBinding() { return &m_xrGraphicsBinding; } + + const VkBool32 GetSupportsSurfacePresent() { return m_vkSupportsSurfacePresent; } + + const VkInstance GetVkInstance() { return m_vkInstance; } + const VkPhysicalDevice GetVkPhysicalDevice() { return m_vkPhysicalDevice; } + const VkDevice GetVkLogicalDevice() { return m_vkDevice; } + + const VkQueue GetVkQueue_Graphics(){ return m_vkQueue_Graphics; } + const uint32_t GetVkQueueIndex_GraphicsFamily() { return m_vkQueueIndex_GraphicsFamily; } + const uint32_t GetVkQueueIndex_Graphics() { return m_vkQueueIndex_Graphics; } + + const VkQueue GetVkQueue_Transfer() { return m_vkQueue_Transfer; } + const uint32_t GetVkQueueIndex_TransferFamily() { return m_vkQueueIndex_TransferFamily; } + const uint32_t GetVkQueueIndex_Transfer() { return m_vkQueueIndex_Transfer; } + + const VkQueue GetVkQueue_Present() { return m_vkQueue_Present; } + const uint32_t GetVkQueueIndex_PresentFamily() { return m_vkQueueIndex_PresentFamily; } + const uint32_t GetVkQueueIndex_Present() { return m_vkQueueIndex_Present; } + + private: + CSession *m_pSession = nullptr; + + XrGraphicsRequirementsVulkan2KHR m_xrGraphicsRequirements { XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN2_KHR }; + XrGraphicsBindingVulkan2KHR m_xrGraphicsBinding { XR_TYPE_GRAPHICS_BINDING_VULKAN2_KHR }; + + VkBool32 m_vkSupportsSurfacePresent = VK_FALSE; + + VkInstance m_vkInstance = VK_NULL_HANDLE; + VkPhysicalDevice m_vkPhysicalDevice = VK_NULL_HANDLE; + VkDevice m_vkDevice = VK_NULL_HANDLE; + + VkQueue m_vkQueue_Graphics = VK_NULL_HANDLE; + uint32_t m_vkQueueIndex_GraphicsFamily = 0; + uint32_t m_vkQueueIndex_Graphics = 0; + + VkQueue m_vkQueue_Transfer = VK_NULL_HANDLE; + uint32_t m_vkQueueIndex_TransferFamily = 0; + uint32_t m_vkQueueIndex_Transfer = 0; + + VkQueue m_vkQueue_Present = VK_NULL_HANDLE; + uint32_t m_vkQueueIndex_PresentFamily = 0; + uint32_t m_vkQueueIndex_Present = 0; + + XrResult GetVulkanGraphicsRequirements(); + XrResult CreateVkInstance( VkResult &outVkResult, const XrInstance xrInstance, const XrVulkanInstanceCreateInfoKHR *xrVulkanInstanceCreateInfo ); + XrResult GetVulkanGraphicsPhysicalDevice(); + XrResult CreateVulkanLogicalDevice( VkSurfaceKHR *pSurface, void *pVkLogicalDeviceNext, void *pXrLogicalDeviceNext ); + XrResult CreateVkDevice( VkResult &outVkResult, const XrVulkanDeviceCreateInfoKHR *xrVulkanDeviceCreateInfo ); + }; + +} \ No newline at end of file diff --git a/include/xrvk/buffer.hpp b/include/xrvk/buffer.hpp new file mode 100644 index 0000000..4950bbd --- /dev/null +++ b/include/xrvk/buffer.hpp @@ -0,0 +1,45 @@ +/* + * Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) + * Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * SPDX-License-Identifier: Apache-2.0 +*/ + +#pragma once + +#include +#include + +namespace xrlib +{ + class CDeviceBuffer + { + public: + + + CDeviceBuffer( CSession *pSession ); + ~CDeviceBuffer(); + + VkResult Init( VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memPropFlags, VkDeviceSize unSize, void *pData = nullptr, VkAllocationCallbacks *pCallbacks = nullptr ); + uint32_t FindMemoryType( VkMemoryPropertyFlags memPropFlags, uint32_t unBits ); + VkResult FlushMemory( VkDeviceSize unSize = VK_WHOLE_SIZE, VkDeviceSize unOffset = 0, uint32_t unRangeCount = 1 ); + void UnmapMemory(); + + VkPhysicalDevice GetPhysicalDevice(); + VkDevice GetLogicalDevice(); + + VkBuffer GetVkBuffer() { return m_vkBufferInfo.buffer; } + VkBuffer *GetVkBufferPtr() { return &m_vkBufferInfo.buffer; }; + VkDescriptorBufferInfo *GetBufferInfo() { return &m_vkBufferInfo; } + + private: + CSession *m_pSession = nullptr; + + VkDescriptorBufferInfo m_vkBufferInfo { VK_NULL_HANDLE, 0, 0 }; + VkDeviceMemory m_vkDeviceMemory = VK_NULL_HANDLE; + VkDeviceSize m_vkMemoryAlignment = 0; + VkDeviceSize m_vkMemorySize = VK_WHOLE_SIZE; + + void *m_pData = nullptr; + }; + +} diff --git a/include/xrvk/mesh.hpp b/include/xrvk/mesh.hpp new file mode 100644 index 0000000..e69de29 diff --git a/include/xrvk/pipeline.hpp b/include/xrvk/pipeline.hpp new file mode 100644 index 0000000..e69de29 diff --git a/include/xrvk/primitive.hpp b/include/xrvk/primitive.hpp new file mode 100644 index 0000000..8ee94b7 --- /dev/null +++ b/include/xrvk/primitive.hpp @@ -0,0 +1,201 @@ +/* + * Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) + * Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +namespace xrlib +{ + constexpr XrVector3f k_ColorRed { 1.f, 0.f, 0.f }; + constexpr XrVector3f k_ColorGreen { 0.f, 1.f, 0.f }; + constexpr XrVector3f k_ColorBlue { 0.f, 0.f, 1.f }; + constexpr XrVector3f k_ColorGold { 0.75f, 0.75f, 0.f }; + constexpr XrVector3f k_ColorPurple { 0.25f, 0.f, 0.25f }; + constexpr XrVector3f k_ColorTeal { 0.0f, 0.25f, 0.25f }; + constexpr XrVector3f k_ColorMagenta { 1.f, 0.f, 1.f }; + constexpr XrVector3f k_ColorOrange { 1.f, 0.25f, 0.f }; + + enum class EAssetMotionType + { + STATIC_INIT = 0, + STATIC_NO_INIT = 1, + DYNAMIC = 2, + MAX = 3 + + }; + + struct SColoredVertex + { + XrVector3f position = { 0.f, 0.f, 0.f }; + XrVector4f color = { 0.f, 0.f, 0.f, 1.f }; + }; + + class CDeviceBuffer; + class CPrimitive + { + public: + + struct SInstanceState + { + EAssetMotionType motionType = EAssetMotionType::STATIC_INIT; + XrSpace space = XR_NULL_HANDLE; + XrPosef pose = { { 0.f, 0.f, 0.f, 1.f }, { 0.f, 0.f, 0.f } }; + XrVector3f scale = { 1.f, 1.f, 1.f }; + }; + + CPrimitive( + CSession *pSession, + uint32_t drawPriority = 0, + EAssetMotionType motionType = EAssetMotionType::STATIC_INIT, + VkPipeline graphicsPipeline = VK_NULL_HANDLE, + XrSpace space = XR_NULL_HANDLE ); + ~CPrimitive(); + + bool isVisible = true; + + std::vector< SInstanceState > instances; + std::vector< XrMatrix4x4f > instanceMatrices; + + uint32_t unDrawPriority = 0; + VkPipeline pipeline = VK_NULL_HANDLE; + + const VkDeviceSize vertexOffsets[ 1 ] = { 0 }; + const VkDeviceSize instanceOffsets[ 4 ] = { 0, 4 * sizeof( float ), 8 * sizeof( float ), 12 * sizeof( float ) }; + + VkResult InitBuffers(); + + VkResult InitBuffer( + CDeviceBuffer *pBuffer, + VkBufferUsageFlags usageFlags, + VkDeviceSize unSize, + void *pData, + VkMemoryPropertyFlags memPropFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + VkAllocationCallbacks *pCallbacks = nullptr ); + + void ResetScale( float x, float y, float z, uint32_t unInstanceIndex = 0 ); + void ResetScale( float fScale, uint32_t unInstanceIndex = 0 ); + void Scale( float fPercent, uint32_t unInstanceIndex = 0 ); + + void AddTri( XrVector3f v1, XrVector3f v2, XrVector3f v3 ); + void AddQuadCW( XrVector3f v1, XrVector3f v2, XrVector3f v3, XrVector3f v4 ); + void AddQuadCCW( XrVector3f v1, XrVector3f v2, XrVector3f v3, XrVector3f v4 ); + + void AddIndex( unsigned short index ); + void AddVertex( XrVector3f vertex ); + + void Reset(); + void ResetIndices(); + void ResetVertices(); + + CDeviceBuffer *UpdateBuffer( VkCommandBuffer transferCmdBuffer ); + + uint32_t GetInstanceCount() { return (uint32_t) instances.size(); } + uint32_t AddInstance( uint32_t unCount ); + + CDeviceBuffer *GetIndexBuffer() { return m_pIndexBuffer; } + CDeviceBuffer *GetVertexBuffer() { return m_pVertexBuffer; } + CDeviceBuffer *GetInstanceBuffer() { return m_pInstanceBuffer; } + + XrVector3f *GetPosition( uint32_t unInstanceindex ) { return &instances[ unInstanceindex ].pose.position; } + XrQuaternionf *GetOrientation( uint32_t unInstanceindex ) { return &instances[ unInstanceindex ].pose.orientation; } + XrVector3f *GetScale( uint32_t unInstanceindex ) { return &instances[ unInstanceindex ].scale; } + + void UpdateModelMatrix( uint32_t unInstanceIndex = 0, XrSpace baseSpace = XR_NULL_HANDLE, XrTime time = 0, bool bForceUpdate = false ); + XrMatrix4x4f *GetModelMatrix( uint32_t unInstanceIndex = 0, bool bRefresh = false ); + XrMatrix4x4f *GetUpdatedModelMatrix( uint32_t unInstanceIndex = 0 ) { return GetModelMatrix( unInstanceIndex, true ); } + + std::vector< unsigned short > &GetIndices() { return m_vecIndices; } + std::vector< XrVector3f > &GetVertices() { return m_vecVertices; } + + + protected: + CSession *m_pSession = nullptr; + CDeviceBuffer *m_pIndexBuffer = nullptr; + CDeviceBuffer *m_pVertexBuffer = nullptr; + CDeviceBuffer *m_pInstanceBuffer = nullptr; + + std::vector< unsigned short > m_vecIndices; + std::vector< XrVector3f > m_vecVertices; + + + void DeleteBuffers(); + }; + + class CColoredPrimitive : public CPrimitive + { + public: + CColoredPrimitive( + CSession *pSession, + uint32_t drawPriority = 0, + EAssetMotionType motionType = EAssetMotionType::STATIC_INIT, + float fAlpha = 1.f, + VkPipeline graphicsPipeline = VK_NULL_HANDLE, + XrSpace space = XR_NULL_HANDLE ); + ~CColoredPrimitive(); + + const VkDeviceSize vertexOffsets[ 2 ] = { 0, sizeof( XrVector3f ) }; + + void AddVertex( XrVector3f vertex ); + void AddColoredVertex( XrVector3f vertex, XrVector3f color, float fAlpha = 1.f ); + + void AddTri( XrVector3f v1, XrVector3f v2, XrVector3f v3 ); + void AddColoredTri( XrVector3f v1, XrVector3f v2, XrVector3f v3, XrVector3f color, float fAlpha = 1.f ); + + void AddQuadCW( XrVector3f v1, XrVector3f v2, XrVector3f v3, XrVector3f v4 ); + void AddColoredQuadCW( XrVector3f v1, XrVector3f v2, XrVector3f v3, XrVector3f v4, XrVector3f color, float fAlpha = 1.f ); + + void AddQuadCCW( XrVector3f v1, XrVector3f v2, XrVector3f v3, XrVector3f v4 ); + void AddColoredQuadCCW( XrVector3f v1, XrVector3f v2, XrVector3f v3, XrVector3f v4, XrVector3f color, float fAlpha = 1.f ); + + void Recolor( XrVector3f color, float fAlpha = 1.f ); + + VkResult InitBuffers(); + + std::vector< SColoredVertex > &GetVertices() { return m_vecVertices; } + + private: + std::vector< SColoredVertex > m_vecVertices; + }; + + class CPyramid : public CPrimitive + { + public: + CPyramid( + CSession *pSession, + uint32_t drawPriority = 0, + EAssetMotionType motionType = EAssetMotionType::STATIC_INIT, + VkPipeline graphicsPipeline = VK_NULL_HANDLE, + XrSpace space = XR_NULL_HANDLE ); + ~CPyramid(); + }; + + class CColoredPyramid : public CColoredPrimitive + { + public: + CColoredPyramid( + CSession *pSession, + uint32_t drawPriority = 0, + EAssetMotionType motionType = EAssetMotionType::STATIC_INIT, + float fAlpha = 1.f, + VkPipeline graphicsPipeline = VK_NULL_HANDLE, + XrSpace space = XR_NULL_HANDLE ); + ~CColoredPyramid(); + }; + + class CColoredCube : public CColoredPrimitive + { + public: + CColoredCube( + CSession *pSession, + uint32_t drawPriority = 0, + EAssetMotionType motionType = EAssetMotionType::STATIC_INIT, + float fAlpha = 1.f, + VkPipeline graphicsPipeline = VK_NULL_HANDLE, + XrSpace space = XR_NULL_HANDLE ); + ~CColoredCube(); + }; +} \ No newline at end of file diff --git a/include/xrvk/render.hpp b/include/xrvk/render.hpp new file mode 100644 index 0000000..dfd8b0c --- /dev/null +++ b/include/xrvk/render.hpp @@ -0,0 +1,462 @@ +/* + * Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) + * Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * SPDX-License-Identifier: Apache-2.0 +*/ + +#pragma once + +#include +#include +#include +#include + +#include + +namespace xrlib +{ + #ifdef XR_USE_PLATFORM_ANDROID + static std::vector< char > ReadBinaryFile( AAssetManager *assetManager, const std::string &sFilename ) + { + AAsset *file = AAssetManager_open( assetManager, sFilename.c_str(), AASSET_MODE_BUFFER ); + if ( !file ) + LogError( "", "Unable to load binary file: %s", sFilename.c_str() ); + + size_t fileLength = AAsset_getLength( file ); + char *fileContent = new char[ fileLength ]; + AAsset_read( file, fileContent, fileLength ); + AAsset_close( file ); + std::vector< char > vec( fileContent, fileContent + fileLength ); + + return vec; + } + #else + static std::vector< char > ReadBinaryFile( const std::string &sFilename ) + { + std::ifstream file( sFilename, std::ios::ate | std::ios::binary ); + + if ( !file.is_open() ) + { + std::filesystem::path cwd = std::filesystem::current_path(); + LogError( "Unable to read file: %s (%s)", sFilename.c_str(), cwd.generic_string().c_str() ); + throw std::runtime_error( "failed to open file!" ); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector< char > buffer( fileSize ); + + file.seekg( 0 ); + file.read( buffer.data(), fileSize ); + + file.close(); + assert( fileSize > 0 ); + return buffer; + } + #endif + + struct SGraphicsPipelineLayout + { + uint32_t size = 0; + VkPipelineLayout layout = VK_NULL_HANDLE; + + SGraphicsPipelineLayout( uint32_t unSize ) + : size( unSize ) {}; + + ~SGraphicsPipelineLayout() {}; + }; + + struct SShader + { + public: + SShader( std::string sFilename, std::string sEntrypoint = "main" ): + m_sFilename( sFilename ), + m_sEntrypoint( sEntrypoint ) + { + assert( !sFilename.empty() ); + assert( !sEntrypoint.empty() ); + }; + + ~SShader(); + + #ifdef XR_USE_PLATFORM_ANDROID + VkPipelineShaderStageCreateInfo Init( + AAssetManager *assetManager, + VkDevice vkLogicalDevice, + VkShaderStageFlagBits shaderStage, + VkShaderModuleCreateFlags createFlags = 0, + void *pNext = nullptr ); + #else + VkPipelineShaderStageCreateInfo Init( + VkDevice vkLogicalDevice, + VkShaderStageFlagBits shaderStage, + VkShaderModuleCreateFlags createFlags = 0, + void *pNext = nullptr ); + #endif + + VkShaderModule GetShaderModule() { return m_vkShaderModule; } + + private: + std::string m_sFilename; + std::string m_sEntrypoint; + + VkDevice m_vkLogicalDevice = VK_NULL_HANDLE; + VkShaderModule m_vkShaderModule = VK_NULL_HANDLE; + }; + + struct SShaderSet + { + public: + SShaderSet( + std::string sVertexShaderFilename, + std::string sFragmentShaderFilename, + std::string sVertexShaderEntrypoint = "main", + std::string sFragmentShaderEntrypoint = "main" ); + + ~SShaderSet(); + + SShader *vertexShader = nullptr; + SShader *fragmentShader = nullptr; + + std::vector< VkPipelineShaderStageCreateInfo > stages; + + #ifdef XR_USE_PLATFORM_ANDROID + void Init( + AAssetManager *assetManager, + VkDevice vkLogicalDevice, + VkShaderStageFlagBits vertexShaderStage = VK_SHADER_STAGE_VERTEX_BIT, + VkShaderStageFlagBits fragmentShaderStage = VK_SHADER_STAGE_FRAGMENT_BIT, + VkShaderModuleCreateFlags vertexShaderCreateFlags = 0, + VkShaderModuleCreateFlags fragmentShaderCreateFlags = 0, + void *pVertexNext = nullptr, + void *pFragmentNext = nullptr ); + #else + void Init( + VkDevice vkLogicalDevice, + VkShaderStageFlagBits vertexShaderStage = VK_SHADER_STAGE_VERTEX_BIT, + VkShaderStageFlagBits fragmentShaderStage = VK_SHADER_STAGE_FRAGMENT_BIT, + VkShaderModuleCreateFlags vertexShaderCreateFlags = 0, + VkShaderModuleCreateFlags fragmentShaderCreateFlags = 0, + void *pVertexNext = nullptr, + void *pFragmentNext = nullptr ); + #endif + + std::vector< VkVertexInputBindingDescription > vertexBindings; + std::vector< VkVertexInputAttributeDescription > vertexAttributes; + + }; + + struct SRenderInfo + { + public: + float nearZ = 0.f; + float farZ = 100.f; + + float minDepth = 0.f; + float maxDepth = 1.f; + + XrFrameState frameState { XR_TYPE_FRAME_STATE }; + XrViewState sharedEyeState { XR_TYPE_VIEW_STATE }; + + XrVector3f eyeScale = { 1.0f, 1.0f, 1.0f }; + std::vector< XrMatrix4x4f > eyeProjectionMatrices = { XrMatrix4x4f(), XrMatrix4x4f() }; + std::vector< XrMatrix4x4f > eyeViewMatrices = { XrMatrix4x4f(), XrMatrix4x4f() }; + + std::vector< XrOffset2Di > imageRectOffsets = { { 0, 0 }, { 0, 0 } }; + std::vector< VkClearValue > clearValues; + + std::vector< XrCompositionLayerProjectionView > projectionLayers = { + XrCompositionLayerProjectionView { XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW }, + XrCompositionLayerProjectionView { XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW } }; + + std::vector< XrCompositionLayerBaseHeader * > frameLayers; + + SRenderInfo() + { + clearValues.resize( 2 ); + clearValues[ 0 ].color = { 0.0f, 0.0f, 0.0f, 1.0f }; + clearValues[ 1 ].depthStencil = { 1.0f, 0 }; + }; + + ~SRenderInfo() {}; + }; + + class CStereoRender + { + public: + CStereoRender( CSession *pSession, VkFormat vkColorFormat, VkFormat vkDepthFormat ); + ~CStereoRender(); + + const uint32_t k_EyeCount = 2; + const uint32_t k_unStereoViewMask = 0b00000011; + const uint32_t k_unStereoConcurrentMask = 0b00000011; + + VkClearColorValue vkClearColor = { { 0.05f, 0.05f, 0.05f, 1.0f } }; + + struct SMultiviewRenderTarget + { + VkImage vkColorTexture = VK_NULL_HANDLE; + VkDescriptorImageInfo vkColorImageDescriptor { VK_NULL_HANDLE, VK_NULL_HANDLE, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL }; + + VkImage vkDepthTexture = VK_NULL_HANDLE; + VkImageView vkDepthImageView = VK_NULL_HANDLE; + + VkFramebuffer vkFrameBuffer = VK_NULL_HANDLE; + + VkCommandBuffer vkRenderCommandBuffer = VK_NULL_HANDLE; + VkFence vkRenderCommandFence = VK_NULL_HANDLE; + + VkCommandBuffer vkTransferCommandBuffer = VK_NULL_HANDLE; + VkFence vkTransferCommandFence = VK_NULL_HANDLE; + + void SetImageViewArray( std::array < VkImageView, 2 > &arrImageViews ) + { + arrImageViews[ 0 ] = vkColorImageDescriptor.imageView; + arrImageViews[ 1 ] = vkDepthImageView; + } + }; + + std::vector< VkRenderPass > vecRenderPasses; + + XrResult Init( uint32_t unTextureFaceCount = 1, uint32_t unTextureMipCount = 1 ); + XrResult CreateSwapchains( uint32_t unFaceCount = 1, uint32_t unMipCount = 1 ); + XrResult CreateSwapchainImages( std::vector < XrSwapchainImageVulkan2KHR > &outSwapchainImages, XrSwapchain xrSwapchain ); + + XrResult InitDefaultMultiviewRendering( VkRenderPass &outRenderPass ); + XrResult PrepareDefaultMultiviewRendering(); + XrResult AddDefaultMultiviewRenderPass(); + XrResult CreateDefaultMultiviewFramebuffers( VkRenderPass vkRenderPass ); + + XrResult CreateMultiviewRenderTargets( + VkImageViewCreateFlags colorCreateFlags = 0, + VkImageViewCreateFlags depthCreateFlags = 0, + void *pColorNext = nullptr, + void *pDepthNext = nullptr, + VkAllocationCallbacks *pCallbacks = nullptr ); + XrResult CreateRenderTargetSamplers( VkSamplerCreateInfo &samplerCI, VkAllocationCallbacks *pCallbacks = nullptr ); + + + VkAttachmentDescription GenerateColorAttachmentDescription(); + VkAttachmentDescription GenerateDepthAttachmentDescription(); + VkSubpassDescription GenerateSubpassDescription(); + + VkRenderPassMultiviewCreateInfo GenerateMultiviewCI(); + + VkRenderPassCreateInfo GenerateRenderPassCI( + std::vector< VkAttachmentDescription > &vecAttachmentDescriptions, + std::vector< VkSubpassDescription > &vecSubpassDescriptions, + std::vector < VkSubpassDependency > &vecSubpassDependencies, + const VkRenderPassCreateFlags vkCreateFlags = 0, + const void *pNext = nullptr ); + + VkFramebufferCreateInfo GenerateMultiviewFrameBufferCI( + std::array< VkImageView, 2 > &arrImageViews, + VkRenderPass vkRenderPass, + VkFramebufferCreateFlags vkCreateFlags = 0, + const void *pNext = nullptr); + + VkSamplerCreateInfo GenerateImageSamplerCI( VkSamplerCreateFlags flags = 0, void *pNext = nullptr ); + + VkPipelineColorBlendAttachmentState GenerateColorBlendAttachment(); + + VkPipelineLayoutCreateInfo GeneratePipelineLayoutCI( + std::vector < VkPushConstantRange > &vecPushConstantRanges, + std::vector< VkDescriptorSetLayout > &vecDescriptorSetLayouts, + VkPipelineLayoutCreateFlags createFlags = 0, + void *pNext = nullptr ); + + VkPipelineVertexInputStateCreateInfo GeneratePipelineStateCI_VertexInput( + std::vector < VkVertexInputBindingDescription > &vecVertexBindingDescriptions, + std::vector< VkVertexInputAttributeDescription > &vecVertexAttributeDescriptions, + VkPipelineVertexInputStateCreateFlags createFlags = 0, + void *pNext = nullptr ); + + VkPipelineInputAssemblyStateCreateInfo GeneratePipelineStateCI_Assembly( + VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + VkBool32 primitiveRestartEnable = VK_FALSE, + VkPipelineInputAssemblyStateCreateFlags createFlags = 0, + void *pNext = nullptr + ); + + VkPipelineTessellationStateCreateInfo GeneratePipelineStateCI_TesselationCI + ( + uint32_t patchControlPoints = 0, + VkPipelineTessellationStateCreateFlags createFlags = 0, + void *pNext = nullptr + ); + + VkPipelineViewportStateCreateInfo GeneratePipelineStateCI_ViewportCI + ( + std::vector < VkViewport > &vecViewports, + std::vector < VkRect2D > &vecScissors, + VkPipelineViewportStateCreateFlags createFlags = 0, + void *pNext = nullptr + ); + + VkPipelineRasterizationStateCreateInfo GeneratePipelineStateCI_RasterizationCI + ( + VkPolygonMode polygonMode = VK_POLYGON_MODE_FILL, + VkCullModeFlags cullMode = VK_CULL_MODE_BACK_BIT, + VkFrontFace frontFace = VK_FRONT_FACE_CLOCKWISE, + float lineWidth = 1.0f, + VkBool32 depthClampEnable = VK_FALSE, + float depthBiasClamp = 0.f, + VkBool32 depthBiasEnable = VK_FALSE, + float depthBiasConstantFactor = 0.f, + float depthBiasSlopeFactor = 0.f, + VkBool32 rasterizerDiscardEnable = VK_FALSE, + VkPipelineRasterizationStateCreateFlags createFlags = 0, + void *pNext = nullptr + ); + + VkPipelineMultisampleStateCreateInfo GeneratePipelineStateCI_MultisampleCI + ( + VkSampleCountFlagBits rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, + VkBool32 sampleShadingEnable = VK_FALSE, + float minSampleShading = 0.f, + VkSampleMask *pSampleMask = nullptr, + VkBool32 alphaToCoverageEnable = VK_FALSE, + VkBool32 alphaToOneEnable = VK_FALSE, + VkPipelineMultisampleStateCreateFlags createFlags = 0, + void *pNext = nullptr + ); + + VkPipelineDepthStencilStateCreateInfo GeneratePipelineStateCI_DepthStencilCI + ( + VkBool32 depthTestEnable = VK_FALSE, //VK_TRUE, + VkBool32 depthWriteEnable = VK_FALSE, //VK_TRUE, + VkCompareOp depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL, + VkBool32 depthBoundsTestEnable = VK_FALSE, + VkBool32 stencilTestEnable = VK_FALSE, + VkStencilOpState front = {}, + VkStencilOpState back = {}, + float minDepthBounds = 0.f, + float maxDepthBounds = 0.f, + VkPipelineDepthStencilStateCreateFlags createFlags = 0, + void *pNext = nullptr + ); + + VkPipelineColorBlendStateCreateInfo GeneratePipelineStateCI_ColorBlendCI + ( + std::vector< VkPipelineColorBlendAttachmentState > &vecColorBlenAttachmentStates, + VkBool32 logicOpEnable = VK_FALSE, + VkLogicOp logicOp = VK_LOGIC_OP_CLEAR, + VkPipelineColorBlendStateCreateFlags createFlags = 0, + void *pNext = nullptr + ); + + VkPipelineDynamicStateCreateInfo GeneratePipelineStateCI_DynamicStateCI + ( + std::vector< VkDynamicState > &vecDynamicStates, + VkPipelineDynamicStateCreateFlags createFlags = 0, + void *pNext = nullptr + ); + + VkResult CreateGraphicsPipeline( + VkPipeline &outPipeline, + VkPipelineLayout pipelineLayout, + VkRenderPass renderPass, + std::vector < VkPipelineShaderStageCreateInfo > &vecShaderStages, + const VkPipelineVertexInputStateCreateInfo *pVertexInputCI, + const VkPipelineInputAssemblyStateCreateInfo *pInputAssemblyCI, + const VkPipelineTessellationStateCreateInfo *pTessellationCI, + const VkPipelineViewportStateCreateInfo *pViewportStateCI, + const VkPipelineRasterizationStateCreateInfo *pRasterizationCI, + const VkPipelineMultisampleStateCreateInfo *pMultisampleCI, + const VkPipelineDepthStencilStateCreateInfo *pDepthStencilCI, + const VkPipelineColorBlendStateCreateInfo *pColorBlendCI, + const VkPipelineDynamicStateCreateInfo *pDynamicStateCI, + const VkPipelineCache pipelineCache = VK_NULL_HANDLE, + const uint32_t unSubpassIndex = 0, + const VkPipelineCreateFlags createFlags = 0, + const void *pNext = nullptr, + const VkAllocationCallbacks *pCallbacks = nullptr ); + + VkResult AddRenderPass( VkRenderPassCreateInfo *renderPassCI, VkAllocationCallbacks *pAllocator = nullptr ); + + void BeginDraw( + const uint32_t unSwpachainImageIndex, + std::vector< VkClearValue > &vecClearValues, + const bool startCommandBufferRecording = true, + const VkRenderPass renderpass = VK_NULL_HANDLE, + const VkSubpassContents subpass = VK_SUBPASS_CONTENTS_INLINE ); + + void SubmitDraw( + const uint32_t unSwpachainImageIndex, + std::vector< CDeviceBuffer* >&vecStagingBuffers, + const uint32_t timeoutNs = 1000000000, + const VkCommandBufferResetFlags transferBufferResetFlags = 0, + const VkCommandBufferResetFlags renderBufferResetFlags = 0 ); + + void BeginBufferUpdates( const uint32_t unSwpachainImageIndex ); + void SubmitBufferUpdates( const uint32_t unSwpachainImageIndex ); + void CalculateViewMatrices( std::vector< XrMatrix4x4f > &outViewMatrices, const XrVector3f *eyeScale ); + + const uint32_t GetTextureWidth() { return m_unTextureWidth; } + const uint32_t GetTextureHeight() { return m_unTextureHeight; } + + const VkFormat GetColorFormat() { return m_vkColorFormat; } + const VkFormat GetDepthFormat() { return m_vkDepthFormat; } + const VkCommandPool GetCommandPool() { return m_vkRenderCommandPool; } + + VkAttachmentReference *GetColorAttachmentReference() { return &m_vkColorAttachmentReference; } + VkAttachmentReference *GetDepthAttachmentReference() { return &m_vkDepthAttachmentReference; } + + const XrSwapchain GetColorSwapchain() { return m_xrColorSwapchain; } + const XrSwapchain GetDepthSwapchain() { return m_xrDepthSwapchain; } + + std::vector< XrSwapchainImageVulkan2KHR > &GetSwapchainColorImages() { return m_vecSwapchainColorImages; } + std::vector< XrSwapchainImageVulkan2KHR > &GetSwapchainDepthImages() { return m_vecSwapchainDepthImages; } + std::vector< XrViewConfigurationView > &GetEyeConfigs() { return m_vecEyeConfigs; } + std::vector< XrView > &GetEyeViews() { return m_vecEyeViews; } + std::vector< SMultiviewRenderTarget > &GetMultiviewRenderTargets() { return m_vecMultiviewRenderTargets; } + + XrSwapchainImageVulkan2KHR *GetSwapchainColorImage( uint32_t unIndex ) { return &m_vecSwapchainColorImages[ unIndex ]; } + XrSwapchainImageVulkan2KHR *GetSwapchainDepthImage( uint32_t unIndex ) { return &m_vecSwapchainColorImages[ unIndex ]; } + + VkImage *GetColorTexture( uint32_t unIndex ) { return &m_vecSwapchainColorImages[ unIndex ].image; } + VkImage *GetDepthTexture( uint32_t unIndex ) { return &m_vecSwapchainDepthImages[ unIndex ].image; } + + XrViewConfigurationView *GetEyeConfig( uint32_t unEye ) { return &m_vecEyeConfigs[ unEye ]; } + XrView *GetEyeView( uint32_t unEye ) { return &m_vecEyeViews[ unEye ]; } + + CSession *GetAppSession() { return m_pSession; } + CInstance *GetAppInstance() { return m_pSession->GetAppInstance(); } + + VkPhysicalDevice GetPhysicalDevice(); + VkDevice GetLogicalDevice(); + + VkExtent2D GetTextureExtent() { return { m_unTextureWidth, m_unTextureHeight }; } + XrExtent2Di GetTexutreExtent2Di() { return { (int32_t) m_unTextureWidth, (int32_t) m_unTextureHeight }; } + + const ELogLevel GetMinLogLevel() { return GetAppInstance()->GetMinLogLevel(); } + + private: + CSession *m_pSession = nullptr; + + uint32_t m_unTextureWidth = 0; + uint32_t m_unTextureHeight = 0; + + VkFormat m_vkColorFormat = VK_FORMAT_UNDEFINED; + VkFormat m_vkDepthFormat = VK_FORMAT_UNDEFINED; + + VkCommandPool m_vkRenderCommandPool = XR_NULL_HANDLE; + VkCommandPool m_vkTransferCommandPool = XR_NULL_HANDLE; + + VkAttachmentReference m_vkColorAttachmentReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; + VkAttachmentReference m_vkDepthAttachmentReference = { 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; + + XrSwapchain m_xrColorSwapchain = XR_NULL_HANDLE; + XrSwapchain m_xrDepthSwapchain = XR_NULL_HANDLE; + + std::vector< XrSwapchainImageVulkan2KHR > m_vecSwapchainColorImages; + std::vector< XrSwapchainImageVulkan2KHR > m_vecSwapchainDepthImages; + std::vector< SMultiviewRenderTarget > m_vecMultiviewRenderTargets; + + // Contains texture information such as recommended and max extents (width, height) + std::vector< XrViewConfigurationView > m_vecEyeConfigs; + + // Contains fov and pose info for each view + std::vector< XrView > m_vecEyeViews; + + }; +} diff --git a/include/xrvk/renderdoc_api.h b/include/xrvk/renderdoc_api.h new file mode 100644 index 0000000..23faf31 --- /dev/null +++ b/include/xrvk/renderdoc_api.h @@ -0,0 +1,741 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2024 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#pragma once + +////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Documentation for the API is available at https://renderdoc.org/docs/in_application_api.html +// + +#if !defined(RENDERDOC_NO_STDINT) +#include +#endif + +#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#define RENDERDOC_CC __cdecl +#elif defined(__linux__) || defined(__FreeBSD__) +#define RENDERDOC_CC +#elif defined(__APPLE__) +#define RENDERDOC_CC +#else +#error "Unknown platform" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Constants not used directly in below API + +// This is a GUID/magic value used for when applications pass a path where shader debug +// information can be found to match up with a stripped shader. +// the define can be used like so: const GUID RENDERDOC_ShaderDebugMagicValue = +// RENDERDOC_ShaderDebugMagicValue_value +#define RENDERDOC_ShaderDebugMagicValue_struct \ + { \ + 0xeab25520, 0x6670, 0x4865, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \ + } + +// as an alternative when you want a byte array (assuming x86 endianness): +#define RENDERDOC_ShaderDebugMagicValue_bytearray \ + { \ + 0x20, 0x55, 0xb2, 0xea, 0x70, 0x66, 0x65, 0x48, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \ + } + +// truncated version when only a uint64_t is available (e.g. Vulkan tags): +#define RENDERDOC_ShaderDebugMagicValue_truncated 0x48656670eab25520ULL + +////////////////////////////////////////////////////////////////////////////////////////////////// +// RenderDoc capture options +// + +typedef enum RENDERDOC_CaptureOption +{ + // Allow the application to enable vsync + // + // Default - enabled + // + // 1 - The application can enable or disable vsync at will + // 0 - vsync is force disabled + eRENDERDOC_Option_AllowVSync = 0, + + // Allow the application to enable fullscreen + // + // Default - enabled + // + // 1 - The application can enable or disable fullscreen at will + // 0 - fullscreen is force disabled + eRENDERDOC_Option_AllowFullscreen = 1, + + // Record API debugging events and messages + // + // Default - disabled + // + // 1 - Enable built-in API debugging features and records the results into + // the capture, which is matched up with events on replay + // 0 - no API debugging is forcibly enabled + eRENDERDOC_Option_APIValidation = 2, + eRENDERDOC_Option_DebugDeviceMode = 2, // deprecated name of this enum + + // Capture CPU callstacks for API events + // + // Default - disabled + // + // 1 - Enables capturing of callstacks + // 0 - no callstacks are captured + eRENDERDOC_Option_CaptureCallstacks = 3, + + // When capturing CPU callstacks, only capture them from actions. + // This option does nothing without the above option being enabled + // + // Default - disabled + // + // 1 - Only captures callstacks for actions. + // Ignored if CaptureCallstacks is disabled + // 0 - Callstacks, if enabled, are captured for every event. + eRENDERDOC_Option_CaptureCallstacksOnlyDraws = 4, + eRENDERDOC_Option_CaptureCallstacksOnlyActions = 4, + + // Specify a delay in seconds to wait for a debugger to attach, after + // creating or injecting into a process, before continuing to allow it to run. + // + // 0 indicates no delay, and the process will run immediately after injection + // + // Default - 0 seconds + // + eRENDERDOC_Option_DelayForDebugger = 5, + + // Verify buffer access. This includes checking the memory returned by a Map() call to + // detect any out-of-bounds modification, as well as initialising buffers with undefined contents + // to a marker value to catch use of uninitialised memory. + // + // NOTE: This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do + // not do the same kind of interception & checking and undefined contents are really undefined. + // + // Default - disabled + // + // 1 - Verify buffer access + // 0 - No verification is performed, and overwriting bounds may cause crashes or corruption in + // RenderDoc. + eRENDERDOC_Option_VerifyBufferAccess = 6, + + // The old name for eRENDERDOC_Option_VerifyBufferAccess was eRENDERDOC_Option_VerifyMapWrites. + // This option now controls the filling of uninitialised buffers with 0xdddddddd which was + // previously always enabled + eRENDERDOC_Option_VerifyMapWrites = eRENDERDOC_Option_VerifyBufferAccess, + + // Hooks any system API calls that create child processes, and injects + // RenderDoc into them recursively with the same options. + // + // Default - disabled + // + // 1 - Hooks into spawned child processes + // 0 - Child processes are not hooked by RenderDoc + eRENDERDOC_Option_HookIntoChildren = 7, + + // By default RenderDoc only includes resources in the final capture necessary + // for that frame, this allows you to override that behaviour. + // + // Default - disabled + // + // 1 - all live resources at the time of capture are included in the capture + // and available for inspection + // 0 - only the resources referenced by the captured frame are included + eRENDERDOC_Option_RefAllResources = 8, + + // **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or + // getting it will be ignored, to allow compatibility with older versions. + // In v1.1 the option acts as if it's always enabled. + // + // By default RenderDoc skips saving initial states for resources where the + // previous contents don't appear to be used, assuming that writes before + // reads indicate previous contents aren't used. + // + // Default - disabled + // + // 1 - initial contents at the start of each captured frame are saved, even if + // they are later overwritten or cleared before being used. + // 0 - unless a read is detected, initial contents will not be saved and will + // appear as black or empty data. + eRENDERDOC_Option_SaveAllInitials = 9, + + // In APIs that allow for the recording of command lists to be replayed later, + // RenderDoc may choose to not capture command lists before a frame capture is + // triggered, to reduce overheads. This means any command lists recorded once + // and replayed many times will not be available and may cause a failure to + // capture. + // + // NOTE: This is only true for APIs where multithreading is difficult or + // discouraged. Newer APIs like Vulkan and D3D12 will ignore this option + // and always capture all command lists since the API is heavily oriented + // around it and the overheads have been reduced by API design. + // + // 1 - All command lists are captured from the start of the application + // 0 - Command lists are only captured if their recording begins during + // the period when a frame capture is in progress. + eRENDERDOC_Option_CaptureAllCmdLists = 10, + + // Mute API debugging output when the API validation mode option is enabled + // + // Default - enabled + // + // 1 - Mute any API debug messages from being displayed or passed through + // 0 - API debugging is displayed as normal + eRENDERDOC_Option_DebugOutputMute = 11, + + // Option to allow vendor extensions to be used even when they may be + // incompatible with RenderDoc and cause corrupted replays or crashes. + // + // Default - inactive + // + // No values are documented, this option should only be used when absolutely + // necessary as directed by a RenderDoc developer. + eRENDERDOC_Option_AllowUnsupportedVendorExtensions = 12, + + // Define a soft memory limit which some APIs may aim to keep overhead under where + // possible. Anything above this limit will where possible be saved directly to disk during + // capture. + // This will cause increased disk space use (which may cause a capture to fail if disk space is + // exhausted) as well as slower capture times. + // + // Not all memory allocations may be deferred like this so it is not a guarantee of a memory + // limit. + // + // Units are in MBs, suggested values would range from 200MB to 1000MB. + // + // Default - 0 Megabytes + eRENDERDOC_Option_SoftMemoryLimit = 13, +} RENDERDOC_CaptureOption; + +// Sets an option that controls how RenderDoc behaves on capture. +// +// Returns 1 if the option and value are valid +// Returns 0 if either is invalid and the option is unchanged +typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionU32)(RENDERDOC_CaptureOption opt, uint32_t val); +typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionF32)(RENDERDOC_CaptureOption opt, float val); + +// Gets the current value of an option as a uint32_t +// +// If the option is invalid, 0xffffffff is returned +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionU32)(RENDERDOC_CaptureOption opt); + +// Gets the current value of an option as a float +// +// If the option is invalid, -FLT_MAX is returned +typedef float(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionF32)(RENDERDOC_CaptureOption opt); + +typedef enum RENDERDOC_InputButton +{ + // '0' - '9' matches ASCII values + eRENDERDOC_Key_0 = 0x30, + eRENDERDOC_Key_1 = 0x31, + eRENDERDOC_Key_2 = 0x32, + eRENDERDOC_Key_3 = 0x33, + eRENDERDOC_Key_4 = 0x34, + eRENDERDOC_Key_5 = 0x35, + eRENDERDOC_Key_6 = 0x36, + eRENDERDOC_Key_7 = 0x37, + eRENDERDOC_Key_8 = 0x38, + eRENDERDOC_Key_9 = 0x39, + + // 'A' - 'Z' matches ASCII values + eRENDERDOC_Key_A = 0x41, + eRENDERDOC_Key_B = 0x42, + eRENDERDOC_Key_C = 0x43, + eRENDERDOC_Key_D = 0x44, + eRENDERDOC_Key_E = 0x45, + eRENDERDOC_Key_F = 0x46, + eRENDERDOC_Key_G = 0x47, + eRENDERDOC_Key_H = 0x48, + eRENDERDOC_Key_I = 0x49, + eRENDERDOC_Key_J = 0x4A, + eRENDERDOC_Key_K = 0x4B, + eRENDERDOC_Key_L = 0x4C, + eRENDERDOC_Key_M = 0x4D, + eRENDERDOC_Key_N = 0x4E, + eRENDERDOC_Key_O = 0x4F, + eRENDERDOC_Key_P = 0x50, + eRENDERDOC_Key_Q = 0x51, + eRENDERDOC_Key_R = 0x52, + eRENDERDOC_Key_S = 0x53, + eRENDERDOC_Key_T = 0x54, + eRENDERDOC_Key_U = 0x55, + eRENDERDOC_Key_V = 0x56, + eRENDERDOC_Key_W = 0x57, + eRENDERDOC_Key_X = 0x58, + eRENDERDOC_Key_Y = 0x59, + eRENDERDOC_Key_Z = 0x5A, + + // leave the rest of the ASCII range free + // in case we want to use it later + eRENDERDOC_Key_NonPrintable = 0x100, + + eRENDERDOC_Key_Divide, + eRENDERDOC_Key_Multiply, + eRENDERDOC_Key_Subtract, + eRENDERDOC_Key_Plus, + + eRENDERDOC_Key_F1, + eRENDERDOC_Key_F2, + eRENDERDOC_Key_F3, + eRENDERDOC_Key_F4, + eRENDERDOC_Key_F5, + eRENDERDOC_Key_F6, + eRENDERDOC_Key_F7, + eRENDERDOC_Key_F8, + eRENDERDOC_Key_F9, + eRENDERDOC_Key_F10, + eRENDERDOC_Key_F11, + eRENDERDOC_Key_F12, + + eRENDERDOC_Key_Home, + eRENDERDOC_Key_End, + eRENDERDOC_Key_Insert, + eRENDERDOC_Key_Delete, + eRENDERDOC_Key_PageUp, + eRENDERDOC_Key_PageDn, + + eRENDERDOC_Key_Backspace, + eRENDERDOC_Key_Tab, + eRENDERDOC_Key_PrtScrn, + eRENDERDOC_Key_Pause, + + eRENDERDOC_Key_Max, +} RENDERDOC_InputButton; + +// Sets which key or keys can be used to toggle focus between multiple windows +// +// If keys is NULL or num is 0, toggle keys will be disabled +typedef void(RENDERDOC_CC *pRENDERDOC_SetFocusToggleKeys)(RENDERDOC_InputButton *keys, int num); + +// Sets which key or keys can be used to capture the next frame +// +// If keys is NULL or num is 0, captures keys will be disabled +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureKeys)(RENDERDOC_InputButton *keys, int num); + +typedef enum RENDERDOC_OverlayBits +{ + // This single bit controls whether the overlay is enabled or disabled globally + eRENDERDOC_Overlay_Enabled = 0x1, + + // Show the average framerate over several seconds as well as min/max + eRENDERDOC_Overlay_FrameRate = 0x2, + + // Show the current frame number + eRENDERDOC_Overlay_FrameNumber = 0x4, + + // Show a list of recent captures, and how many captures have been made + eRENDERDOC_Overlay_CaptureList = 0x8, + + // Default values for the overlay mask + eRENDERDOC_Overlay_Default = (eRENDERDOC_Overlay_Enabled | eRENDERDOC_Overlay_FrameRate | + eRENDERDOC_Overlay_FrameNumber | eRENDERDOC_Overlay_CaptureList), + + // Enable all bits + eRENDERDOC_Overlay_All = ~0U, + + // Disable all bits + eRENDERDOC_Overlay_None = 0, +} RENDERDOC_OverlayBits; + +// returns the overlay bits that have been set +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetOverlayBits)(); +// sets the overlay bits with an and & or mask +typedef void(RENDERDOC_CC *pRENDERDOC_MaskOverlayBits)(uint32_t And, uint32_t Or); + +// this function will attempt to remove RenderDoc's hooks in the application. +// +// Note: that this can only work correctly if done immediately after +// the module is loaded, before any API work happens. RenderDoc will remove its +// injected hooks and shut down. Behaviour is undefined if this is called +// after any API functions have been called, and there is still no guarantee of +// success. +typedef void(RENDERDOC_CC *pRENDERDOC_RemoveHooks)(); + +// DEPRECATED: compatibility for code compiled against pre-1.4.1 headers. +typedef pRENDERDOC_RemoveHooks pRENDERDOC_Shutdown; + +// This function will unload RenderDoc's crash handler. +// +// If you use your own crash handler and don't want RenderDoc's handler to +// intercede, you can call this function to unload it and any unhandled +// exceptions will pass to the next handler. +typedef void(RENDERDOC_CC *pRENDERDOC_UnloadCrashHandler)(); + +// Sets the capture file path template +// +// pathtemplate is a UTF-8 string that gives a template for how captures will be named +// and where they will be saved. +// +// Any extension is stripped off the path, and captures are saved in the directory +// specified, and named with the filename and the frame number appended. If the +// directory does not exist it will be created, including any parent directories. +// +// If pathtemplate is NULL, the template will remain unchanged +// +// Example: +// +// SetCaptureFilePathTemplate("my_captures/example"); +// +// Capture #1 -> my_captures/example_frame123.rdc +// Capture #2 -> my_captures/example_frame456.rdc +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFilePathTemplate)(const char *pathtemplate); + +// returns the current capture path template, see SetCaptureFileTemplate above, as a UTF-8 string +typedef const char *(RENDERDOC_CC *pRENDERDOC_GetCaptureFilePathTemplate)(); + +// DEPRECATED: compatibility for code compiled against pre-1.1.2 headers. +typedef pRENDERDOC_SetCaptureFilePathTemplate pRENDERDOC_SetLogFilePathTemplate; +typedef pRENDERDOC_GetCaptureFilePathTemplate pRENDERDOC_GetLogFilePathTemplate; + +// returns the number of captures that have been made +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetNumCaptures)(); + +// This function returns the details of a capture, by index. New captures are added +// to the end of the list. +// +// filename will be filled with the absolute path to the capture file, as a UTF-8 string +// pathlength will be written with the length in bytes of the filename string +// timestamp will be written with the time of the capture, in seconds since the Unix epoch +// +// Any of the parameters can be NULL and they'll be skipped. +// +// The function will return 1 if the capture index is valid, or 0 if the index is invalid +// If the index is invalid, the values will be unchanged +// +// Note: when captures are deleted in the UI they will remain in this list, so the +// capture path may not exist anymore. +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCapture)(uint32_t idx, char *filename, + uint32_t *pathlength, uint64_t *timestamp); + +// Sets the comments associated with a capture file. These comments are displayed in the +// UI program when opening. +// +// filePath should be a path to the capture file to add comments to. If set to NULL or "" +// the most recent capture file created made will be used instead. +// comments should be a NULL-terminated UTF-8 string to add as comments. +// +// Any existing comments will be overwritten. +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFileComments)(const char *filePath, + const char *comments); + +// returns 1 if the RenderDoc UI is connected to this application, 0 otherwise +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsTargetControlConnected)(); + +// DEPRECATED: compatibility for code compiled against pre-1.1.1 headers. +// This was renamed to IsTargetControlConnected in API 1.1.1, the old typedef is kept here for +// backwards compatibility with old code, it is castable either way since it's ABI compatible +// as the same function pointer type. +typedef pRENDERDOC_IsTargetControlConnected pRENDERDOC_IsRemoteAccessConnected; + +// This function will launch the Replay UI associated with the RenderDoc library injected +// into the running application. +// +// if connectTargetControl is 1, the Replay UI will be launched with a command line parameter +// to connect to this application +// cmdline is the rest of the command line, as a UTF-8 string. E.g. a captures to open +// if cmdline is NULL, the command line will be empty. +// +// returns the PID of the replay UI if successful, 0 if not successful. +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_LaunchReplayUI)(uint32_t connectTargetControl, + const char *cmdline); + +// RenderDoc can return a higher version than requested if it's backwards compatible, +// this function returns the actual version returned. If a parameter is NULL, it will be +// ignored and the others will be filled out. +typedef void(RENDERDOC_CC *pRENDERDOC_GetAPIVersion)(int *major, int *minor, int *patch); + +// Requests that the replay UI show itself (if hidden or not the current top window). This can be +// used in conjunction with IsTargetControlConnected and LaunchReplayUI to intelligently handle +// showing the UI after making a capture. +// +// This will return 1 if the request was successfully passed on, though it's not guaranteed that +// the UI will be on top in all cases depending on OS rules. It will return 0 if there is no current +// target control connection to make such a request, or if there was another error +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_ShowReplayUI)(); + +////////////////////////////////////////////////////////////////////////// +// Capturing functions +// + +// A device pointer is a pointer to the API's root handle. +// +// This would be an ID3D11Device, HGLRC/GLXContext, ID3D12Device, etc +typedef void *RENDERDOC_DevicePointer; + +// A window handle is the OS's native window handle +// +// This would be an HWND, GLXDrawable, etc +typedef void *RENDERDOC_WindowHandle; + +// A helper macro for Vulkan, where the device handle cannot be used directly. +// +// Passing the VkInstance to this macro will return the RENDERDOC_DevicePointer to use. +// +// Specifically, the value needed is the dispatch table pointer, which sits as the first +// pointer-sized object in the memory pointed to by the VkInstance. Thus we cast to a void** and +// indirect once. +#define RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(inst) (*((void **)(inst))) + +// This sets the RenderDoc in-app overlay in the API/window pair as 'active' and it will +// respond to keypresses. Neither parameter can be NULL +typedef void(RENDERDOC_CC *pRENDERDOC_SetActiveWindow)(RENDERDOC_DevicePointer device, + RENDERDOC_WindowHandle wndHandle); + +// capture the next frame on whichever window and API is currently considered active +typedef void(RENDERDOC_CC *pRENDERDOC_TriggerCapture)(); + +// capture the next N frames on whichever window and API is currently considered active +typedef void(RENDERDOC_CC *pRENDERDOC_TriggerMultiFrameCapture)(uint32_t numFrames); + +// When choosing either a device pointer or a window handle to capture, you can pass NULL. +// Passing NULL specifies a 'wildcard' match against anything. This allows you to specify +// any API rendering to a specific window, or a specific API instance rendering to any window, +// or in the simplest case of one window and one API, you can just pass NULL for both. +// +// In either case, if there are two or more possible matching (device,window) pairs it +// is undefined which one will be captured. +// +// Note: for headless rendering you can pass NULL for the window handle and either specify +// a device pointer or leave it NULL as above. + +// Immediately starts capturing API calls on the specified device pointer and window handle. +// +// If there is no matching thing to capture (e.g. no supported API has been initialised), +// this will do nothing. +// +// The results are undefined (including crashes) if two captures are started overlapping, +// even on separate devices and/oror windows. +typedef void(RENDERDOC_CC *pRENDERDOC_StartFrameCapture)(RENDERDOC_DevicePointer device, + RENDERDOC_WindowHandle wndHandle); + +// Returns whether or not a frame capture is currently ongoing anywhere. +// +// This will return 1 if a capture is ongoing, and 0 if there is no capture running +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsFrameCapturing)(); + +// Ends capturing immediately. +// +// This will return 1 if the capture succeeded, and 0 if there was an error capturing. +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_EndFrameCapture)(RENDERDOC_DevicePointer device, + RENDERDOC_WindowHandle wndHandle); + +// Ends capturing immediately and discard any data stored without saving to disk. +// +// This will return 1 if the capture was discarded, and 0 if there was an error or no capture +// was in progress +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_DiscardFrameCapture)(RENDERDOC_DevicePointer device, + RENDERDOC_WindowHandle wndHandle); + +// Only valid to be called between a call to StartFrameCapture and EndFrameCapture. Gives a custom +// title to the capture produced which will be displayed in the UI. +// +// If multiple captures are ongoing, this title will be applied to the first capture to end after +// this call. The second capture to end will have no title, unless this function is called again. +// +// Calling this function has no effect if no capture is currently running, and if it is called +// multiple times only the last title will be used. +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureTitle)(const char *title); + +////////////////////////////////////////////////////////////////////////////////////////////////// +// RenderDoc API versions +// + +// RenderDoc uses semantic versioning (http://semver.org/). +// +// MAJOR version is incremented when incompatible API changes happen. +// MINOR version is incremented when functionality is added in a backwards-compatible manner. +// PATCH version is incremented when backwards-compatible bug fixes happen. +// +// Note that this means the API returned can be higher than the one you might have requested. +// e.g. if you are running against a newer RenderDoc that supports 1.0.1, it will be returned +// instead of 1.0.0. You can check this with the GetAPIVersion entry point +typedef enum RENDERDOC_Version +{ + eRENDERDOC_API_Version_1_0_0 = 10000, // RENDERDOC_API_1_0_0 = 1 00 00 + eRENDERDOC_API_Version_1_0_1 = 10001, // RENDERDOC_API_1_0_1 = 1 00 01 + eRENDERDOC_API_Version_1_0_2 = 10002, // RENDERDOC_API_1_0_2 = 1 00 02 + eRENDERDOC_API_Version_1_1_0 = 10100, // RENDERDOC_API_1_1_0 = 1 01 00 + eRENDERDOC_API_Version_1_1_1 = 10101, // RENDERDOC_API_1_1_1 = 1 01 01 + eRENDERDOC_API_Version_1_1_2 = 10102, // RENDERDOC_API_1_1_2 = 1 01 02 + eRENDERDOC_API_Version_1_2_0 = 10200, // RENDERDOC_API_1_2_0 = 1 02 00 + eRENDERDOC_API_Version_1_3_0 = 10300, // RENDERDOC_API_1_3_0 = 1 03 00 + eRENDERDOC_API_Version_1_4_0 = 10400, // RENDERDOC_API_1_4_0 = 1 04 00 + eRENDERDOC_API_Version_1_4_1 = 10401, // RENDERDOC_API_1_4_1 = 1 04 01 + eRENDERDOC_API_Version_1_4_2 = 10402, // RENDERDOC_API_1_4_2 = 1 04 02 + eRENDERDOC_API_Version_1_5_0 = 10500, // RENDERDOC_API_1_5_0 = 1 05 00 + eRENDERDOC_API_Version_1_6_0 = 10600, // RENDERDOC_API_1_6_0 = 1 06 00 +} RENDERDOC_Version; + +// API version changelog: +// +// 1.0.0 - initial release +// 1.0.1 - Bugfix: IsFrameCapturing() was returning false for captures that were triggered +// by keypress or TriggerCapture, instead of Start/EndFrameCapture. +// 1.0.2 - Refactor: Renamed eRENDERDOC_Option_DebugDeviceMode to eRENDERDOC_Option_APIValidation +// 1.1.0 - Add feature: TriggerMultiFrameCapture(). Backwards compatible with 1.0.x since the new +// function pointer is added to the end of the struct, the original layout is identical +// 1.1.1 - Refactor: Renamed remote access to target control (to better disambiguate from remote +// replay/remote server concept in replay UI) +// 1.1.2 - Refactor: Renamed "log file" in function names to just capture, to clarify that these +// are captures and not debug logging files. This is the first API version in the v1.0 +// branch. +// 1.2.0 - Added feature: SetCaptureFileComments() to add comments to a capture file that will be +// displayed in the UI program on load. +// 1.3.0 - Added feature: New capture option eRENDERDOC_Option_AllowUnsupportedVendorExtensions +// which allows users to opt-in to allowing unsupported vendor extensions to function. +// Should be used at the user's own risk. +// Refactor: Renamed eRENDERDOC_Option_VerifyMapWrites to +// eRENDERDOC_Option_VerifyBufferAccess, which now also controls initialisation to +// 0xdddddddd of uninitialised buffer contents. +// 1.4.0 - Added feature: DiscardFrameCapture() to discard a frame capture in progress and stop +// capturing without saving anything to disk. +// 1.4.1 - Refactor: Renamed Shutdown to RemoveHooks to better clarify what is happening +// 1.4.2 - Refactor: Renamed 'draws' to 'actions' in callstack capture option. +// 1.5.0 - Added feature: ShowReplayUI() to request that the replay UI show itself if connected +// 1.6.0 - Added feature: SetCaptureTitle() which can be used to set a title for a +// capture made with StartFrameCapture() or EndFrameCapture() + +typedef struct RENDERDOC_API_1_6_0 +{ + pRENDERDOC_GetAPIVersion GetAPIVersion; + + pRENDERDOC_SetCaptureOptionU32 SetCaptureOptionU32; + pRENDERDOC_SetCaptureOptionF32 SetCaptureOptionF32; + + pRENDERDOC_GetCaptureOptionU32 GetCaptureOptionU32; + pRENDERDOC_GetCaptureOptionF32 GetCaptureOptionF32; + + pRENDERDOC_SetFocusToggleKeys SetFocusToggleKeys; + pRENDERDOC_SetCaptureKeys SetCaptureKeys; + + pRENDERDOC_GetOverlayBits GetOverlayBits; + pRENDERDOC_MaskOverlayBits MaskOverlayBits; + + // Shutdown was renamed to RemoveHooks in 1.4.1. + // These unions allow old code to continue compiling without changes + union + { + pRENDERDOC_Shutdown Shutdown; + pRENDERDOC_RemoveHooks RemoveHooks; + }; + pRENDERDOC_UnloadCrashHandler UnloadCrashHandler; + + // Get/SetLogFilePathTemplate was renamed to Get/SetCaptureFilePathTemplate in 1.1.2. + // These unions allow old code to continue compiling without changes + union + { + // deprecated name + pRENDERDOC_SetLogFilePathTemplate SetLogFilePathTemplate; + // current name + pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate; + }; + union + { + // deprecated name + pRENDERDOC_GetLogFilePathTemplate GetLogFilePathTemplate; + // current name + pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate; + }; + + pRENDERDOC_GetNumCaptures GetNumCaptures; + pRENDERDOC_GetCapture GetCapture; + + pRENDERDOC_TriggerCapture TriggerCapture; + + // IsRemoteAccessConnected was renamed to IsTargetControlConnected in 1.1.1. + // This union allows old code to continue compiling without changes + union + { + // deprecated name + pRENDERDOC_IsRemoteAccessConnected IsRemoteAccessConnected; + // current name + pRENDERDOC_IsTargetControlConnected IsTargetControlConnected; + }; + pRENDERDOC_LaunchReplayUI LaunchReplayUI; + + pRENDERDOC_SetActiveWindow SetActiveWindow; + + pRENDERDOC_StartFrameCapture StartFrameCapture; + pRENDERDOC_IsFrameCapturing IsFrameCapturing; + pRENDERDOC_EndFrameCapture EndFrameCapture; + + // new function in 1.1.0 + pRENDERDOC_TriggerMultiFrameCapture TriggerMultiFrameCapture; + + // new function in 1.2.0 + pRENDERDOC_SetCaptureFileComments SetCaptureFileComments; + + // new function in 1.4.0 + pRENDERDOC_DiscardFrameCapture DiscardFrameCapture; + + // new function in 1.5.0 + pRENDERDOC_ShowReplayUI ShowReplayUI; + + // new function in 1.6.0 + pRENDERDOC_SetCaptureTitle SetCaptureTitle; +} RENDERDOC_API_1_6_0; + +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_0; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_1; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_2; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_0; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_1; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_2; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_2_0; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_3_0; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_0; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_1; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_2; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_5_0; + +////////////////////////////////////////////////////////////////////////////////////////////////// +// RenderDoc API entry point +// +// This entry point can be obtained via GetProcAddress/dlsym if RenderDoc is available. +// +// The name is the same as the typedef - "RENDERDOC_GetAPI" +// +// This function is not thread safe, and should not be called on multiple threads at once. +// Ideally, call this once as early as possible in your application's startup, before doing +// any API work, since some configuration functionality etc has to be done also before +// initialising any APIs. +// +// Parameters: +// version is a single value from the RENDERDOC_Version above. +// +// outAPIPointers will be filled out with a pointer to the corresponding struct of function +// pointers. +// +// Returns: +// 1 - if the outAPIPointers has been filled with a pointer to the API struct requested +// 0 - if the requested version is not supported or the arguments are invalid. +// +typedef int(RENDERDOC_CC *pRENDERDOC_GetAPI)(RENDERDOC_Version version, void **outAPIPointers); + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file diff --git a/project_config.h.in b/project_config.h.in new file mode 100644 index 0000000..c149f90 --- /dev/null +++ b/project_config.h.in @@ -0,0 +1,16 @@ +#define XRLIB_NAME "xrlib" +#define XRLIB_VERSION_MAJOR @xrlib_VERSION_MAJOR@ +#define XRLIB_VERSION_MINOR @xrlib_VERSION_MINOR@ +#define XRLIB_VERSION_PATCH @xrlib_VERSION_PATCH@ + +#ifdef XR_USE_PLATFORM_ANDROID + #define XRLIB_ASSETS_FOLDER "" +#else + #define XRLIB_ASSETS_FOLDER "assets/" + // #define XRVK_VULKAN_DEBUG2_ENABLE 1 + // #define XRVK_VULKAN_DEBUG3_ENABLE 1 +#endif + +#define XRLIB_SHADERS_FOLDER XRLIB_ASSETS_FOLDER "shaders/" +#define XRLIB_MODELS_FOLDER XRLIB_ASSETS_FOLDER "models/" +#define XRLIB_TEXTURES_FOLDER XRLIB_ASSETS_FOLDER "textures/" diff --git a/src/ext/EXT_hand_tracking.cpp b/src/ext/EXT_hand_tracking.cpp new file mode 100644 index 0000000..b9e4b1b --- /dev/null +++ b/src/ext/EXT_hand_tracking.cpp @@ -0,0 +1,142 @@ +/* + * Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) + * Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +namespace xrlib::EXT +{ + CHandTracking::SJointVelocities::SJointVelocities( void *pNextLeft, void *pNextRight ) + { + left.next = pNextLeft; + left.jointCount = XR_HAND_JOINT_COUNT_EXT; + left.jointVelocities = leftVelocities.data(); + + right.next = pNextRight; + right.jointCount = XR_HAND_JOINT_COUNT_EXT; + right.jointVelocities = rightVelocities.data(); + } + + CHandTracking::SJointLocations::SJointLocations( SJointVelocities &jointVelocities ) + { + left.next = &jointVelocities.left; + left.isActive = XR_FALSE; + left.jointCount = XR_HAND_JOINT_COUNT_EXT; + left.jointLocations = leftJointLocations.data(); + + right.next = &jointVelocities.right; + right.isActive = XR_FALSE; + right.jointCount = XR_HAND_JOINT_COUNT_EXT; + right.jointLocations = rightJointLocations.data(); + } + + CHandTracking::SJointLocations::SJointLocations( void *pNextLeft, void *pNextRight ) + { + left.next = pNextLeft; + left.isActive = XR_FALSE; + left.jointCount = XR_HAND_JOINT_COUNT_EXT; + left.jointLocations = leftJointLocations.data(); + + right.next = pNextRight; + right.isActive = XR_FALSE; + right.jointCount = XR_HAND_JOINT_COUNT_EXT; + right.jointLocations = rightJointLocations.data(); + } + + CHandTracking::CHandTracking( XrInstance xrInstance ) : + ExtBase( xrInstance, XR_EXT_HAND_TRACKING_EXTENSION_NAME) + { + // Initialize function pointers + XrResult result = INIT_PFN( xrInstance, xrLocateHandJointsEXT ); + assert( XR_UNQUALIFIED_SUCCESS( result ) ); + } + + CHandTracking::~CHandTracking() + { + PFN_xrDestroyHandTrackerEXT xrDestroyHandTrackerEXT = nullptr; + XrResult result = INIT_PFN( m_xrInstance, xrDestroyHandTrackerEXT ); + + if ( XR_UNQUALIFIED_SUCCESS( result ) ) + { + for ( auto &handTracker : m_vecHandTrackers ) + if ( handTracker != XR_NULL_HANDLE ) xrDestroyHandTrackerEXT( handTracker ); + } + } + + XrResult CHandTracking::Init( XrSession xrSession, XrHandJointSetEXT leftHandJointSet, void *pNextLeft, XrHandJointSetEXT rightHandJointSet, void *pNextRight ) + { + assert( xrSession != XR_NULL_HANDLE ); + m_xrSession = xrSession; + + // Create hand trackers + PFN_xrCreateHandTrackerEXT xrCreateHandTrackerEXT = nullptr; + XR_RETURN_ON_ERROR( INIT_PFN( m_xrInstance, xrCreateHandTrackerEXT ) ); + + XrHandTrackerCreateInfoEXT handTrackerCI { XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT }; + handTrackerCI.next = pNextLeft; + handTrackerCI.hand = XR_HAND_LEFT_EXT; + handTrackerCI.handJointSet = leftHandJointSet; + XR_RETURN_ON_ERROR( xrCreateHandTrackerEXT( m_xrSession, &handTrackerCI, &m_vecHandTrackers[ XR_LEFT ] ) ); + + handTrackerCI.next = pNextRight; + handTrackerCI.hand = XR_HAND_RIGHT_EXT; + handTrackerCI.handJointSet = rightHandJointSet; + XR_RETURN_ON_ERROR( xrCreateHandTrackerEXT( m_xrSession, &handTrackerCI, &m_vecHandTrackers[ XR_RIGHT ] ) ); + + return XR_SUCCESS; + } + + XrResult CHandTracking::LocateHandJoints( SJointLocations *outHandJointLocations, XrSpace baseSpace, XrTime time, void *pNextLeft, void *pNextRight ) + { + XR_RETURN_ON_ERROR( LocateHandJoints( &outHandJointLocations->left, XrHandEXT::XR_HAND_LEFT_EXT, baseSpace, time, pNextLeft ) ); + XR_RETURN_ON_ERROR( LocateHandJoints( &outHandJointLocations->right, XrHandEXT::XR_HAND_RIGHT_EXT, baseSpace, time, pNextRight ) ); + + return XR_SUCCESS; + } + + XrResult CHandTracking::LocateHandJoints( XrHandJointLocationsEXT *outHandJointLocations, XrHandEXT hand, XrSpace baseSpace, XrTime time, void *pNext ) + { + XrHandJointsLocateInfoEXT xrHandJointsLocateInfo { XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT }; + xrHandJointsLocateInfo.next = pNext, + xrHandJointsLocateInfo.baseSpace = baseSpace; + xrHandJointsLocateInfo.time = time; + + return xrLocateHandJointsEXT( + ( hand == XrHandEXT::XR_HAND_LEFT_EXT ) ? m_vecHandTrackers[ XR_LEFT ] : m_vecHandTrackers[ XR_RIGHT ], + &xrHandJointsLocateInfo, + outHandJointLocations ); + } + + XrSystemHandTrackingPropertiesEXT CHandTracking::GenerateSystemProperties( void *pNext ) + { + XrSystemHandTrackingPropertiesEXT systemProps { XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT }; + systemProps.next = pNext; + systemProps.supportsHandTracking = XR_FALSE; + + return systemProps; + } + + XrHandTrackerEXT *CHandTracking::GetHandTracker( XrHandEXT hand ) + { + switch ( hand ) + { + case XR_HAND_LEFT_EXT: + return &m_vecHandTrackers[ XR_LEFT ]; + break; + case XR_HAND_RIGHT_EXT: + return &m_vecHandTrackers[ XR_RIGHT ]; + break; + case XR_HAND_MAX_ENUM_EXT: + default: + break; + } + + return nullptr; + } + + + +} // namespace xrlib::EXT diff --git a/src/input.cpp b/src/input.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/instance.cpp b/src/instance.cpp new file mode 100644 index 0000000..5dd3131 --- /dev/null +++ b/src/instance.cpp @@ -0,0 +1,484 @@ +/* + * Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) + * Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +namespace xrlib +{ + #ifdef XR_USE_PLATFORM_ANDROID + CInstance::CInstance( struct android_app *pAndroidApp, const std::string &sAppName, const XrVersion32 unAppVersion, const ELogLevel eMinLogLevel ): + m_pAndroidApp( pAndroidApp ), + m_sAppName( sAppName ), + m_unAppVersion( unAppVersion ), + m_eMinLogLevel( eMinLogLevel ) + #else + CInstance::CInstance( const std::string &sAppName, const XrVersion32 unAppVersion, const ELogLevel eMinLogLevel ) : + m_sAppName( sAppName), + m_unAppVersion( unAppVersion ), + m_eMinLogLevel( eMinLogLevel ) + #endif + { + if ( sAppName.empty() ) + { + m_sAppName = "XrApp"; + LogWarning( "", "No application name provided. Was set to: %s", m_sAppName.c_str() ); + } + else if ( sAppName.size() > XR_MAX_APPLICATION_NAME_SIZE ) + { + m_sAppName = sAppName.substr( 0, XR_MAX_APPLICATION_NAME_SIZE ); + LogWarning( "", "Provided application name is too long. Truncated to: %s", m_sAppName.c_str() ); + } + } + + CInstance::~CInstance() + { + if ( m_xrInstance != XR_NULL_HANDLE ) + xrDestroyInstance( m_xrInstance ); + + #ifdef XR_USE_PLATFORM_ANDROID + m_pAndroidApp->activity->vm->DetachCurrentThread(); + #endif + } + + XrResult CInstance::Init( + std::vector< const char * > vecInstanceExtensions, + std::vector< const char * > vecAPILayers, + const XrInstanceCreateFlags createFlags, + const void *pNext ) + { + #ifdef XR_USE_PLATFORM_ANDROID + // For Android, we need to initialize the android openxr loader + XR_RETURN_ON_ERROR( InitAndroidLoader() ); + #endif + + XrResult xrResult = XR_ERROR_INITIALIZATION_FAILED; + + // Set openxr instance info + XrInstanceCreateInfo instanceCI { XR_TYPE_INSTANCE_CREATE_INFO }; + instanceCI.next = pNext; + instanceCI.createFlags = createFlags; + instanceCI.applicationInfo = {}; + + if ( !StringCopy(instanceCI.applicationInfo.applicationName, XR_MAX_APPLICATION_NAME_SIZE, m_sAppName ) ) + return xrResult; + + if ( !StringCopy( instanceCI.applicationInfo.engineName, XR_MAX_ENGINE_NAME_SIZE, XRLIB_NAME ) ) + return xrResult; + + instanceCI.applicationInfo.applicationVersion = m_unAppVersion; + instanceCI.applicationInfo.engineVersion = XR_MAKE_VERSION32( XRLIB_VERSION_MAJOR, XRLIB_VERSION_MINOR, XRLIB_VERSION_PATCH ); + instanceCI.applicationInfo.apiVersion = XR_MAKE_VERSION( 1, 0, 0 ); // XR_CURRENT_API_VERSION; + + // Retrieve the supported extensions by the runtime + std::vector< XrExtensionProperties > vecExtensionProperties; + if ( !XR_SUCCEEDED( GetSupportedExtensions( vecExtensionProperties ) ) ) + { + return XR_ERROR_RUNTIME_UNAVAILABLE; + } + + if ( CheckLogLevelVerbose( m_eMinLogLevel ) ) + LogVerbose( "", "This runtime supports %i available extensions:", vecExtensionProperties.size() ); + + // Remove any unsupported graphics api extensions from requested extensions (only Vulkan is supported) + FilterOutUnsupportedGraphicsApis( vecInstanceExtensions ); + FilterOutUnsupportedExtensions( vecInstanceExtensions ); + + // Get the supported extension names by the runtime so we can check for vulkan support + std::vector< std::string > vecSupportedExtensionNames; + GetExtensionNames( vecSupportedExtensionNames, vecExtensionProperties ); + + // Add extensions to create instance info + instanceCI.enabledExtensionCount = (uint32_t) vecInstanceExtensions.size(); + instanceCI.enabledExtensionNames = vecInstanceExtensions.data(); + + // Cache enabled extensions + for ( auto &xrExtensionProperty : vecExtensionProperties ) + { + std::string sExtensionName = std::string( xrExtensionProperty.extensionName ); + bool bFound = std::find( vecInstanceExtensions.begin(), vecInstanceExtensions.end(), sExtensionName ) != vecInstanceExtensions.end(); + + if ( bFound ) + { + m_vecEnabledExtensions.push_back( sExtensionName ); + } + + if ( CheckLogLevelVerbose( m_eMinLogLevel ) ) + { + std::string sEnableTag = bFound ? "[WILL ENABLE]" : ""; + LogVerbose( "", "\t%s (ver. %i) %s", &xrExtensionProperty.extensionName, xrExtensionProperty.extensionVersion, sEnableTag.c_str() ); + } + } + + // Retrieve the supported api layers by the runtime + std::vector< XrApiLayerProperties > vecApiLayerProperties; + if ( !XR_SUCCEEDED( GetSupportedApiLayers( vecApiLayerProperties ) ) ) + { + return XR_ERROR_RUNTIME_UNAVAILABLE; + } + + if ( CheckLogLevelVerbose( m_eMinLogLevel ) ) + LogVerbose( "", "There are %i openxr api layers available:", vecApiLayerProperties.size() ); + + // Prepare app requested api layers + FilterOutUnsupportedApiLayers( vecAPILayers ); + + // Cache enabled api layers + for ( auto &xrApiLayerProperty : vecApiLayerProperties ) + { + std::string sApiLayerName = std::string( xrApiLayerProperty.layerName ); + bool bFound = std::find( vecAPILayers.begin(), vecAPILayers.end(), sApiLayerName ) != vecAPILayers.end(); + + if ( bFound ) + { + m_vecEnabledApiLayers.push_back( sApiLayerName ); + } + + if ( CheckLogLevelVerbose( m_eMinLogLevel ) ) + { + std::string sEnableTag = bFound ? "[WILL ENABLE]" : ""; + + LogVerbose( "", "\t%s (ver. %i) %s", xrApiLayerProperty.layerName, xrApiLayerProperty.specVersion, sEnableTag.c_str() ); + LogVerbose( "", "\t\t%s\n", xrApiLayerProperty.description ); + } + } + + // Add api layers to create instance info + instanceCI.enabledApiLayerCount = (uint32_t) vecAPILayers.size(); + instanceCI.enabledApiLayerNames = vecAPILayers.data(); + + // Create openxr instance + xrResult = xrCreateInstance( &instanceCI, &m_xrInstance ); + if ( !XR_UNQUALIFIED_SUCCESS( xrResult ) ) + { + LogError( "", "Error creating openxr instance: %s", XrEnumToString( xrResult ) ); + return xrResult; + } + + if ( CheckLogLevelVerbose( m_eMinLogLevel ) ) + LogVerbose( "", "OpenXR instance created. Handle (%" PRIu64 ")", ( uint64_t ) m_xrInstance ); + + + // Get instance properties + xrResult = xrGetInstanceProperties( m_xrInstance, &m_xrInstanceProperties ); + if ( !XR_UNQUALIFIED_SUCCESS( xrResult ) ) + { + LogError( "", "Error getting active openxr instance properties (%i)", xrResult ); + return xrResult; + } + + if ( CheckLogLevelVerbose( m_eMinLogLevel ) ) + LogVerbose( + "", + "OpenXR runtime %s version %i.%i.%i is now active for this instance.", + m_xrInstanceProperties.runtimeName, + XR_VERSION_MAJOR( m_xrInstanceProperties.runtimeVersion ), + XR_VERSION_MINOR( m_xrInstanceProperties.runtimeVersion ), + XR_VERSION_PATCH( m_xrInstanceProperties.runtimeVersion ) ); + + // Get user's system info + XrSystemGetInfo xrSystemGetInfo { XR_TYPE_SYSTEM_GET_INFO }; + xrSystemGetInfo.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY; + + xrResult = xrGetSystem( m_xrInstance, &xrSystemGetInfo, &m_xrSystemId ); + if ( !XR_UNQUALIFIED_SUCCESS( xrResult ) ) + { + LogError( "", "Error getting user's system id (%s)", XrEnumToString( xrResult ) ); + return xrResult; + } + + xrResult = xrGetSystemProperties( m_xrInstance, m_xrSystemId, &m_xrSystemProperties ); + if ( !XR_UNQUALIFIED_SUCCESS( xrResult ) ) + { + LogError( "", "Error getting user's system info (%s)", XrEnumToString( xrResult ) ); + return xrResult; + } + + xrGetSystemProperties( m_xrInstance, m_xrSystemId, &m_xrSystemProperties ); + if ( CheckLogLevelVerbose( m_eMinLogLevel ) ) + LogVerbose( "", "Active tracking system is %s (Vendor Id %" PRIu32")", &m_xrSystemProperties.systemName[0], m_xrSystemProperties.vendorId ); + + // Show supported view configurations + if ( CheckLogLevelVerbose( m_eMinLogLevel ) ) + { + auto vecSupportedViewConfigurations = GetSupportedViewConfigurations(); + + LogVerbose( "", "This runtime supports %i view configuration(s):", vecSupportedViewConfigurations.size() ); + for ( auto &xrViewConfigurationType : vecSupportedViewConfigurations ) + { + LogVerbose( "", "\t%s", XrEnumToString( xrViewConfigurationType ) ); + } + } + + #ifdef XR_USE_PLATFORM_ANDROID + // For android, we need to pass the android state management function to the xrProvider + m_pAndroidApp->onAppCmd = app_handle_cmd; + #endif + + return xrResult; + } + + bool CInstance::IsApiLayerEnabled( std::string &sApiLayerName ) + { + return FindStringInVector( m_vecEnabledApiLayers, sApiLayerName ); + } + + XrResult CInstance::GetSupportedApiLayers( std::vector< XrApiLayerProperties > &vecApiLayers ) + { + uint32_t unCount = 0; + XrResult xrResult = xrEnumerateApiLayerProperties( 0, &unCount, nullptr ); + + if ( XR_UNQUALIFIED_SUCCESS( xrResult ) ) + { + if ( unCount == 0 ) + return XR_SUCCESS; + + vecApiLayers.clear(); + for ( uint32_t i = 0; i < unCount; ++i ) + { + vecApiLayers.push_back( XrApiLayerProperties { XR_TYPE_API_LAYER_PROPERTIES, nullptr } ); + } + + return xrEnumerateApiLayerProperties( unCount, &unCount, vecApiLayers.data() ); + } + + return xrResult; + } + + XrResult CInstance::GetSupportedApiLayers( std::vector< std::string > &vecApiLayers ) + { + std::vector< XrApiLayerProperties > vecApiLayerProperties; + XrResult xrResult = GetSupportedApiLayers( vecApiLayerProperties ); + + if ( XR_UNQUALIFIED_SUCCESS( xrResult ) ) + { + GetApilayerNames( vecApiLayers, vecApiLayerProperties ); + } + + return xrResult; + } + + bool CInstance::IsExtensionEnabled( const char *extensionName ) + { + std::string sName = extensionName; + return IsExtensionEnabled( sName ); + } + + bool CInstance::IsExtensionEnabled( std::string &sExtensionName ) + { + return FindStringInVector( m_vecEnabledExtensions, sExtensionName ); + } + + XrResult CInstance::GetSupportedExtensions( std::vector< XrExtensionProperties > &outExtensions, const char *pcharApiLayerName ) + { + uint32_t unCount = 0; + XrResult xrResult = xrEnumerateInstanceExtensionProperties( pcharApiLayerName, 0, &unCount, nullptr ); + + if ( XR_UNQUALIFIED_SUCCESS( xrResult ) ) + { + if ( unCount == 0 ) + return XR_SUCCESS; + + outExtensions.clear(); + for ( uint32_t i = 0; i < unCount; ++i ) + { + outExtensions.push_back( XrExtensionProperties { XR_TYPE_EXTENSION_PROPERTIES, nullptr } ); + } + + return xrEnumerateInstanceExtensionProperties( pcharApiLayerName, unCount, &unCount, outExtensions.data() ); + } + + return xrResult; + } + + XrResult CInstance::GetSupportedExtensions( std::vector< std::string > &outExtensions, const char *pcharApiLayerName ) + { + std::vector< XrExtensionProperties > vecExtensionProperties; + XrResult xrResult = GetSupportedExtensions( vecExtensionProperties ); + + if ( XR_UNQUALIFIED_SUCCESS( xrResult ) ) + { + GetExtensionNames( outExtensions, vecExtensionProperties ); + } + + return xrResult; + } + + XrResult CInstance::FilterForSupportedExtensions( std::vector< std::string > &vecRequestedExtensionNames ) + { + // Get supported extensions from active runtime + std::vector< std::string > vecSupportedExtensions; + XrResult xrResult = GetSupportedExtensions( vecSupportedExtensions ); + + if ( XR_UNQUALIFIED_SUCCESS( xrResult ) ) + { + for ( std::vector< std::string >::iterator it = vecRequestedExtensionNames.begin(); it != vecRequestedExtensionNames.end(); ) + { + // Find requested extension is the runtime's list of supported extensions + bool bFound = false; + for ( auto &sExtensionName : vecSupportedExtensions ) + { + if ( sExtensionName == *it ) + { + bFound = true; + break; + } + } + + // If the requested extension isn't found, delete it + if ( !bFound ) + { + vecRequestedExtensionNames.erase( it ); + } + } + + // shrink the requested extension vector in case we needed to delete values from it + vecRequestedExtensionNames.shrink_to_fit(); + } + + return xrResult; + } + + XrResult CInstance::FilterOutUnsupportedExtensions( std::vector< const char * > &vecExtensionNames ) + { + std::vector< XrExtensionProperties > vecSupportedExtensions; + XR_RETURN_ON_ERROR( GetSupportedExtensions( vecSupportedExtensions ) ) + + for ( auto it = vecExtensionNames.begin(); it != vecExtensionNames.end(); it++ ) + { + // Check if our requested extension exists + bool bFound = false; + for ( auto &supportedExt : vecSupportedExtensions ) + { + if ( std::strcmp( supportedExt.extensionName, *it ) == 0 ) + { + bFound = true; + break; + } + } + + // If not supported by the current openxr runtime, then remove it from our list + if ( !bFound ) + { + vecExtensionNames.erase( it-- ); + } + } + + return XR_SUCCESS; + } + + XrResult CInstance::FilterOutUnsupportedApiLayers( std::vector< const char * > &vecApiLayerNames ) + { + std::vector< XrApiLayerProperties > vecSupportedApiLayers; + XR_RETURN_ON_ERROR( GetSupportedApiLayers( vecSupportedApiLayers ) ) + + for ( auto it = vecApiLayerNames.begin(); it != vecApiLayerNames.end(); it++ ) + { + // Check if our requested extension exists + bool bFound = false; + for ( auto &supportedApiLayer : vecSupportedApiLayers ) + { + if ( std::strcmp( supportedApiLayer.layerName, *it ) == 0 ) + { + bFound = true; + break; + } + } + + // If not supported by the current openxr runtime, then remove it from our list + if ( !bFound ) + { + vecApiLayerNames.erase( it-- ); + } + } + + return XR_SUCCESS; + } + + void CInstance::FilterOutUnsupportedGraphicsApis( std::vector< const char * > &vecExtensionNames ) + { + // @todo - capture from openxr_platform.h + std::vector< const char * > vecUnsupportedGraphicsApis { "XR_KHR_opengl_enable", "XR_KHR_opengl_es_enable", "XR_KHR_D3D11_enable", "XR_KHR_D3D12_enable", "XR_MNDX_egl_enable" }; + + for ( auto it = vecExtensionNames.begin(); it != vecExtensionNames.end(); it++ ) + { + // Check if there's an unsupported graphics api in the input vector + bool bFound = false; + for ( auto &unsupportedExt : vecUnsupportedGraphicsApis ) + { + if ( std::strcmp( unsupportedExt, *it ) == 0 ) + { + bFound = true; + break; + } + } + + // If an unsupported graphics api is found in the list, then remove it + if ( bFound ) + { + vecExtensionNames.erase( it-- ); + } + } + } + + std::vector< XrViewConfigurationType > CInstance::GetSupportedViewConfigurations() + { + // Check if there's a valid openxr instance + if ( m_xrInstance == XR_NULL_HANDLE ) + return std::vector< XrViewConfigurationType >(); + + // Get number of view configurations supported by the runtime + uint32_t unViewConfigurationsNum = 0; + XrResult xrResult = xrEnumerateViewConfigurations( m_xrInstance, m_xrSystemId, unViewConfigurationsNum, &unViewConfigurationsNum, nullptr ); + + if ( !XR_UNQUALIFIED_SUCCESS( xrResult ) ) + { + xrlib::LogError( "", "Error getting supported view configuration types from the runtime (%s)", XrEnumToString( xrResult ) ); + return std::vector< XrViewConfigurationType >(); + } + + std::vector< XrViewConfigurationType > vecViewConfigurations( unViewConfigurationsNum ); + xrResult = xrEnumerateViewConfigurations( m_xrInstance, m_xrSystemId, unViewConfigurationsNum, &unViewConfigurationsNum, vecViewConfigurations.data() ); + + if ( !XR_UNQUALIFIED_SUCCESS( xrResult ) ) + return std::vector< XrViewConfigurationType >(); + + return vecViewConfigurations; + } + + void CInstance::GetApilayerNames( std::vector< std::string > &outApiLayerNames, const std::vector< XrApiLayerProperties > &vecApilayerProperties ) + { + for ( auto &apiLayerProperty : vecApilayerProperties ) + { + outApiLayerNames.push_back( apiLayerProperty.layerName ); + } + } + + void CInstance::GetExtensionNames( std::vector< std::string > &outExtensionNames, const std::vector< XrExtensionProperties > &vecExtensionProperties ) + { + for ( auto &extensionProperty : vecExtensionProperties ) + { + outExtensionNames.push_back( extensionProperty.extensionName ); + } + } + + const XrSystemProperties *CInstance::GetXrSystemProperties( bool bUpdate, void *pNext ) + { + assert( m_xrInstance != XR_NULL_HANDLE ); + assert( m_xrSystemId != XR_NULL_SYSTEM_ID ); + + if ( bUpdate ) + { + m_xrSystemProperties.next = pNext; + XrResult xrResult = xrGetSystemProperties( m_xrInstance, m_xrSystemId, &m_xrSystemProperties ); + if ( !XR_UNQUALIFIED_SUCCESS( xrResult ) ) + LogWarning( "", "Error updating user's system info (%s)", XrEnumToString( xrResult ) ); + } + + return &m_xrSystemProperties; + } + +} diff --git a/src/session.cpp b/src/session.cpp new file mode 100644 index 0000000..ea94609 --- /dev/null +++ b/src/session.cpp @@ -0,0 +1,495 @@ +/* + * Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) + * Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +namespace xrlib +{ + CSession::CSession( CInstance *pInstance ) : + m_pInstance( pInstance ) + { + assert( pInstance ); + + m_pVulkan = new CVulkan( this ); + } + + CSession::~CSession() + { + if ( m_xrAppSpace != XR_NULL_HANDLE ) + xrDestroySpace( m_xrAppSpace ); + + if ( m_xrSession != XR_NULL_HANDLE ) + xrDestroySession( m_xrSession ); + + if ( m_pVulkan ) + delete m_pVulkan; + } + + XrResult CSession::Init( + VkSurfaceKHR *pSurface, + XrSessionCreateFlags flgAdditionalCreateInfo, + void *pVkInstanceNext, + void *pXrVkInstanceNext, + void *pVkLogicalDeviceNext, + void *pXrLogicalDeviceNext ) + { + XR_RETURN_ON_ERROR( InitVulkan( pSurface, pVkInstanceNext, pXrVkInstanceNext, pVkLogicalDeviceNext, pXrLogicalDeviceNext ) ); + XR_RETURN_ON_ERROR( CreateXrSession( flgAdditionalCreateInfo ) ); + XR_RETURN_ON_ERROR( CreateAppSpace( xrAppReferencePose, xrAppReferenceSpaceType ) ); + + return XR_SUCCESS; + } + + XrResult + CSession::InitVulkan( + VkSurfaceKHR *pSurface, + void *pVkInstanceNext, + void *pXrVkInstanceNext, + void *pVkLogicalDeviceNext, + void *pXrLogicalDeviceNext ) + { + XrResult xrResult = m_pVulkan->Init( pSurface, pVkInstanceNext, pXrVkInstanceNext, pVkLogicalDeviceNext, pXrLogicalDeviceNext ); + if ( !XR_UNQUALIFIED_SUCCESS( xrResult ) ) + { + LogError( "", "Unable to initialize Vulkan resources: %s", XrEnumToString( xrResult ) ); + return xrResult; + } + + return XR_SUCCESS; + } + + XrResult CSession::CreateXrSession( XrSessionCreateFlags flgAdditionalCreateInfo, void *pNext ) + { + // Check if there's a valid openxr instance + if ( GetAppInstance()->GetXrSystemId() == XR_NULL_SYSTEM_ID || m_pVulkan->GetGraphicsBinding()->device == VK_NULL_HANDLE ) + return XR_ERROR_CALL_ORDER_INVALID; + + m_pVulkan->GetGraphicsBinding()->next = pNext; + + XrSessionCreateInfo xrSessionCreateInfo { XR_TYPE_SESSION_CREATE_INFO }; + xrSessionCreateInfo.next = m_pVulkan->GetGraphicsBinding(); + xrSessionCreateInfo.systemId = GetAppInstance()->GetXrSystemId(); + xrSessionCreateInfo.createFlags = flgAdditionalCreateInfo; + + XrResult xrResult = xrCreateSession( m_pInstance->GetXrInstance(), &xrSessionCreateInfo, &m_xrSession ); + if ( !XR_UNQUALIFIED_SUCCESS( xrResult ) ) + { + LogError( "", "Unable to create openxr session: %s", XrEnumToString( xrResult ) ); + return xrResult; + } + + return XR_SUCCESS; + } + + XrResult CSession::CreateAppSpace( XrPosef referencePose, XrReferenceSpaceType referenceSpaceType, void *pNext ) + { + // Log session supported reference space types (debug only) + if ( CheckLogLevelDebug( m_pInstance->GetMinLogLevel() ) ) + { + auto vecSupportedReferenceSpaceTypes = GetSupportedReferenceSpaceTypes(); + + LogDebug( "", "This session supports %i reference space type(s):", vecSupportedReferenceSpaceTypes.size() ); + for ( auto &xrReferenceSpaceType : vecSupportedReferenceSpaceTypes ) + { + LogDebug( "", "\t%s", XrEnumToString( xrReferenceSpaceType ) ); + } + } + + // Create reference space + XrReferenceSpaceCreateInfo xrReferenceSpaceCreateInfo { XR_TYPE_REFERENCE_SPACE_CREATE_INFO }; + xrReferenceSpaceCreateInfo.next = pNext; + xrReferenceSpaceCreateInfo.poseInReferenceSpace = referencePose; + xrReferenceSpaceCreateInfo.referenceSpaceType = referenceSpaceType; + XR_RETURN_ON_ERROR( xrCreateReferenceSpace( m_xrSession, &xrReferenceSpaceCreateInfo, &m_xrAppSpace ) ) + + if ( CheckLogLevelDebug( m_pInstance->GetMinLogLevel() ) ) + LogDebug( "", "Reference space of type (%s) created with handle (%" PRIu64 ").", XrEnumToString( xrAppReferenceSpaceType ), (uint64_t) m_xrAppSpace ); + + return XR_SUCCESS; + } + + XrResult CSession::Start( void *pOtherBeginInfo ) + { + // Check if session was initialized correctly + if ( m_xrSession == XR_NULL_HANDLE ) + return XR_ERROR_CALL_ORDER_INVALID; + + // Begin session + XrSessionBeginInfo xrSessionBeginInfo { XR_TYPE_SESSION_BEGIN_INFO }; + xrSessionBeginInfo.next = pOtherBeginInfo; + xrSessionBeginInfo.primaryViewConfigurationType = xrViewConfigurationType; + + XrResult xrResult = xrBeginSession( m_xrSession, &xrSessionBeginInfo ); + + if ( !XR_UNQUALIFIED_SUCCESS( xrResult ) ) + { + LogError( "", "Unable to start session: %s", XrEnumToString( xrResult ) ); + return xrResult; + } + + LogInfo( "", "OpenXR session started (%" PRIu64 ") with view configuration type: %s", (uint64_t) m_xrSession, XrEnumToString( xrViewConfigurationType ) ); + return XR_SUCCESS; + } + + XrResult CSession::Poll( XrEventDataBaseHeader *outEventData ) + { + #ifdef XR_USE_PLATFORM_ANDROID + // For android we need in addition, to poll android events to proceed to the session ready state + while ( true ) + { + // We'll wait indefinitely (-1ms) for android poll results until we get to the android app resumed state + // and/or the openxr session has started. + const int nTimeout = ( !m_pInstance->androidAppState.Resumed && IsSessionRunning() ) ? -1 : 0; + + int nEvents; + struct android_poll_source *androidPollSource; + if ( ALooper_pollAll( nTimeout, nullptr, &nEvents, (void **) &androidPollSource ) < 0 ) + { + break; + } + + // Process android event + if ( androidPollSource ) + { + androidPollSource->process( m_pInstance->GetAndroidApp(), androidPollSource ); + } + + } + #endif + + XrEventDataBuffer xrEventDataBuffer { XR_TYPE_EVENT_DATA_BUFFER }; + XrResult result = xrPollEvent( m_pInstance->GetXrInstance(), &xrEventDataBuffer ); + if ( !XR_SUCCEEDED( result ) ) + return result; + + // Internal event processing + outEventData->type = xrEventDataBuffer.type; + outEventData = reinterpret_cast< XrEventDataBaseHeader* >( &xrEventDataBuffer ); + + if ( outEventData->type == XR_TYPE_EVENT_DATA_EVENTS_LOST ) + { + // Report any lost events + const XrEventDataEventsLost *const eventsLost = reinterpret_cast< const XrEventDataEventsLost * >( outEventData ); + LogWarning( "", "Poll events warning - there are %i events lost", eventsLost->lostEventCount ); + } + else if ( outEventData->type == XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED ) + { + // Update session state + const XrEventDataSessionStateChanged xrSessionStateChangedEvent = *reinterpret_cast< const XrEventDataSessionStateChanged * >( outEventData ); + LogInfo( "", "OpenXR session state changed from %s to %s", XrEnumToString( m_xrSessionState ), XrEnumToString( xrSessionStateChangedEvent.state ) ); + m_xrSessionState = xrSessionStateChangedEvent.state; + } + + return XR_SUCCESS; + } + + XrResult CSession::End( bool bRequestToExit ) + { + // Check if session was initialized correctly + if ( m_xrSession == XR_NULL_HANDLE ) + return XR_ERROR_CALL_ORDER_INVALID; + + XrResult xrResult = XR_SUCCESS; + if ( bRequestToExit ) + { + xrResult = xrRequestExitSession( m_xrSession ); + if ( !XR_UNQUALIFIED_SUCCESS( xrResult ) ) + { + LogError( "", "Error requesting runtime to end session: %s", XrEnumToString( xrResult ) ); + return xrResult; + } + } + + xrResult = xrEndSession( m_xrSession ); + if ( !XR_UNQUALIFIED_SUCCESS( xrResult ) ) + { + LogError( "", "Unable to end session: %s", XrEnumToString( xrResult ) ); + return xrResult; + } + + LogInfo( "", "OpenXR session ended." ); + return XR_SUCCESS; + } + + XrResult CSession::StartFrame( XrFrameState *pFrameState, void *pWaitFrameNext, void *pBeginFrameNext ) + { + // @todo: debug only asserts + + XrFrameWaitInfo xrWaitFrameInfo { XR_TYPE_FRAME_WAIT_INFO, pWaitFrameNext }; + xrWaitFrameInfo.next = pWaitFrameNext; + XR_RETURN_ON_ERROR( xrWaitFrame( m_xrSession, &xrWaitFrameInfo, pFrameState ) ); + + XrFrameBeginInfo xrBeginFrameInfo { XR_TYPE_FRAME_BEGIN_INFO, pBeginFrameNext }; + XR_RETURN_ON_ERROR( xrBeginFrame( m_xrSession, &xrBeginFrameInfo ) ); + + return XR_SUCCESS; + } + + XrResult CSession::UpdateEyeStates( + std::vector< XrView > &outEyeViews, + std::vector< XrMatrix4x4f > &outEyeProjections, + XrViewState *outEyeViewsState, + XrFrameState *pFrameState, + XrSpace space, + const float fNearZ, + const float fFarZ, + XrViewConfigurationType viewConfigurationType, + void *pNext ) + { + // @todo: debug assert only as this is called per frame - views and projections count *must* match + + XrViewLocateInfo xrFrameSpaceTimeInfo { XR_TYPE_VIEW_LOCATE_INFO }; + xrFrameSpaceTimeInfo.next = pNext; + xrFrameSpaceTimeInfo.displayTime = pFrameState->predictedDisplayTime; + xrFrameSpaceTimeInfo.space = space; + xrFrameSpaceTimeInfo.viewConfigurationType = viewConfigurationType; + + uint32_t unFoundViews; + XR_RETURN_ON_ERROR( xrLocateViews( m_xrSession, &xrFrameSpaceTimeInfo, outEyeViewsState, (uint32_t) outEyeViews.size(), &unFoundViews, outEyeViews.data() ) ); + + for ( uint32_t i = 0; i < outEyeViews.size(); i++ ) + XrMatrix4x4f_CreateProjectionFov( &outEyeProjections[ i ], GRAPHICS_VULKAN, outEyeViews[ i ].fov, fNearZ, fFarZ ); + + return XR_SUCCESS; + } + + XrResult CSession::AcquireFrameImage( uint32_t *outImageIndex, XrSwapchain swapchain, void *pNext ) + { + if ( !pNext ) + { + // Acquire info is optional + XR_RETURN_ON_ERROR( xrAcquireSwapchainImage( swapchain, nullptr, outImageIndex ) ); + } + else + { + XrSwapchainImageAcquireInfo acquireInfo { XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, pNext }; + XR_RETURN_ON_ERROR( xrAcquireSwapchainImage( swapchain, &acquireInfo, outImageIndex ) ); + } + + return XR_SUCCESS; + } + + XrResult CSession::AcquireFrameImage( + uint32_t *outColorImageIndex, + uint32_t *outDepthImageIndex, + XrSwapchain colorSwapchain, + XrSwapchain depthSwapchain, + void *pColorAcquireNext, + void *pDepthAcquireNext ) + { + + XR_RETURN_ON_ERROR( AcquireFrameImage( outColorImageIndex, colorSwapchain, pColorAcquireNext ) ); + XR_RETURN_ON_ERROR( AcquireFrameImage( outDepthImageIndex, depthSwapchain, pDepthAcquireNext ) ); + + return XR_SUCCESS; + } + + XrResult CSession::WaitForFrameImage( + XrSwapchain swapchain, + XrDuration duration, + void *pNext ) + { + XrSwapchainImageWaitInfo xrWaitInfo { XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO }; + xrWaitInfo.next = pNext; + xrWaitInfo.timeout = duration; + + return xrWaitSwapchainImage( swapchain, &xrWaitInfo ); + } + + XrResult CSession::WaitForFrameImage( + XrSwapchain colorSwapchain, + XrSwapchain depthSwapchain, + XrDuration duration, + void *pNext ) + { + XrSwapchainImageWaitInfo xrWaitInfo { XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO }; + xrWaitInfo.next = pNext; + xrWaitInfo.timeout = duration; + + XR_RETURN_ON_ERROR( WaitForFrameImage( colorSwapchain, duration, pNext ) ); + XR_RETURN_ON_ERROR( WaitForFrameImage( depthSwapchain, duration, pNext ) ); + + return XR_SUCCESS; + } + + XrResult CSession::ReleaseFrameImage( XrSwapchain swapchain, void *pNext ) + { + if ( !pNext ) + { + // release info is optional + XR_RETURN_ON_ERROR( xrReleaseSwapchainImage( swapchain, nullptr ) ); + } + else + { + XrSwapchainImageReleaseInfo releaseInfo { XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO, pNext }; + XR_RETURN_ON_ERROR( xrReleaseSwapchainImage( swapchain, &releaseInfo ) ); + } + + return XR_SUCCESS; + } + + XrResult CSession::ReleaseFrameImage( XrSwapchain colorSwapchain, XrSwapchain depthSwapchain, void *pNext ) + { + XR_RETURN_ON_ERROR( ReleaseFrameImage( colorSwapchain, pNext ) ); + XR_RETURN_ON_ERROR( ReleaseFrameImage( depthSwapchain, pNext ) ); + return XR_SUCCESS; + } + + XrResult CSession::EndFrame( + XrFrameState *pFrameState, + std::vector< XrCompositionLayerBaseHeader * > &vecFrameLayers, + XrEnvironmentBlendMode blendMode, + void *pNext ) + { + XrFrameEndInfo xrEndFrameInfo { XR_TYPE_FRAME_END_INFO }; + xrEndFrameInfo.next = pNext; + xrEndFrameInfo.displayTime = pFrameState->predictedDisplayTime; + xrEndFrameInfo.environmentBlendMode = blendMode; + xrEndFrameInfo.layerCount = (uint32_t) vecFrameLayers.size(); + xrEndFrameInfo.layers = vecFrameLayers.data(); + + return xrEndFrame( m_xrSession, &xrEndFrameInfo ); + } + + std::vector< XrReferenceSpaceType > CSession::GetSupportedReferenceSpaceTypes() + { + assert( m_xrSession != XR_NULL_HANDLE ); + + // Get number of reference space types supported by the runtime + uint32_t unReferenceSpaceTypesNum = 0; + + XrResult xrResult = xrEnumerateReferenceSpaces( m_xrSession, unReferenceSpaceTypesNum, &unReferenceSpaceTypesNum, nullptr ); + if ( !XR_UNQUALIFIED_SUCCESS( xrResult ) ) + { + LogError( "", "Error getting supported reference space types from the runtime: %s", XrEnumToString( xrResult ) ); + return std::vector< XrReferenceSpaceType >(); + } + + std::vector< XrReferenceSpaceType > vecViewReferenceSpaceTypes( unReferenceSpaceTypesNum ); + xrResult = xrEnumerateReferenceSpaces( m_xrSession, unReferenceSpaceTypesNum, &unReferenceSpaceTypesNum, vecViewReferenceSpaceTypes.data() ); + + if ( !XR_UNQUALIFIED_SUCCESS( xrResult ) ) + { + LogError( "", "Error getting supported reference space types from the runtime: %s", XrEnumToString( xrResult ) ); + return std::vector< XrReferenceSpaceType >(); + } + + return vecViewReferenceSpaceTypes; + } + + XrResult CSession::GetSupportedTextureFormats( std::vector< int64_t > &outSupportedFormats ) + { + // Check if session was initialized correctly + if ( m_xrSession == XR_NULL_HANDLE ) + return XR_ERROR_CALL_ORDER_INVALID; + + // Clear out vector + outSupportedFormats.clear(); + + // Select a swapchain format + uint32_t unSwapchainFormatNum = 0; + XR_RETURN_ON_ERROR( xrEnumerateSwapchainFormats( m_xrSession, unSwapchainFormatNum, &unSwapchainFormatNum, nullptr ) ); + + outSupportedFormats.resize( unSwapchainFormatNum, 0 ); + XR_RETURN_ON_ERROR( xrEnumerateSwapchainFormats( m_xrSession, unSwapchainFormatNum, &unSwapchainFormatNum, outSupportedFormats.data() ) ); + + // Log supported swapchain formats + //if ( GetMinLogLevel() == ELogLevel::LogVerbose ) + //{ + // LogVerbose( "", "Runtime supports the following vulkan formats: " ); + // for ( auto supportedTextureFormat : outSupportedFormats ) + // { + // LogVerbose( "", "\t%i", supportedTextureFormat ); + // } + //} + + return XR_SUCCESS; + } + + int64_t CSession::SelectColorTextureFormat( const std::vector< int64_t > &vecRequestedFormats ) + { + // Check if session was initialized correctly + if ( m_xrSession == XR_NULL_HANDLE ) + return XR_ERROR_CALL_ORDER_INVALID; + + // Retrieve this session's supported formats + std::vector< int64_t > vecSupportedFormats; + if ( !XR_UNQUALIFIED_SUCCESS( GetSupportedTextureFormats( vecSupportedFormats ) ) ) + return 0; + + // If there are no requested texture formats, choose the first one from the runtime + if ( vecRequestedFormats.empty() ) + { + // Get first format + for ( auto textureFormat : vecSupportedFormats ) + { + if ( !m_pVulkan->IsDepthFormat( (VkFormat) textureFormat ) ) + return textureFormat; + } + } + else + { + // Find matching texture format with runtime's supported ones + for ( auto supportedFormat : vecSupportedFormats ) + { + if ( m_pVulkan->IsDepthFormat( (VkFormat) supportedFormat ) ) + continue; + + for ( auto requestedFormat : vecRequestedFormats ) + { + // Return selected format if a match is found + if ( requestedFormat == supportedFormat ) + return supportedFormat; + } + } + } + + return 0; + } + + int64_t CSession::SelectDepthTextureFormat( const std::vector< int64_t > &vecRequestedFormats ) + { + // Check if session was initialized correctly + if ( m_xrSession == XR_NULL_HANDLE ) + return XR_ERROR_CALL_ORDER_INVALID; + + // Retrieve this session's supported formats + std::vector< int64_t > vecSupportedFormats; + if ( !XR_UNQUALIFIED_SUCCESS( GetSupportedTextureFormats( vecSupportedFormats ) ) ) + return 0; + + // If there are no requested texture formats, choose the first one from the runtime + if ( vecRequestedFormats.empty() ) + { + // Get first format + for ( auto textureFormat : vecSupportedFormats ) + { + if ( m_pVulkan->IsDepthFormat( (VkFormat) textureFormat ) ) + return textureFormat; + } + } + else + { + // Find matching texture format with runtime's supported ones + for ( auto supportedFormat : vecSupportedFormats ) + { + if ( !m_pVulkan->IsDepthFormat( (VkFormat) supportedFormat ) ) + continue; + + for ( auto requestedFormat : vecRequestedFormats ) + { + // Return selected format if a match is found + if ( requestedFormat == supportedFormat ) + return supportedFormat; + } + } + } + + return 0; + } +} + diff --git a/src/vulkan.cpp b/src/vulkan.cpp new file mode 100644 index 0000000..03941cd --- /dev/null +++ b/src/vulkan.cpp @@ -0,0 +1,415 @@ +/* + * Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) + * Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + + +namespace xrlib +{ + CVulkan::CVulkan( CSession *pSession ) : + m_pSession( pSession) + { + assert( pSession ); + } + + CVulkan::~CVulkan() + { + // Destroy logical device + if ( m_vkDevice ) + vkDestroyDevice( m_vkDevice, nullptr ); + } + + XrResult CVulkan::Init( + VkSurfaceKHR *pSurface, + void *pVkInstanceNext, + void *pXrVkInstanceNext, + void *pVkLogicalDeviceNext, + void *pXrLogicalDeviceNext ) + { + // Check if there's a valid openxr instance + if ( GetAppInstance()->GetXrInstance() == XR_NULL_HANDLE ) + return XR_ERROR_CALL_ORDER_INVALID; + + // (1) Call *required* function GetVulkanGraphicsRequirements + // This returns the min-and-max vulkan api level required by the active runtime + XR_RETURN_ON_ERROR( GetVulkanGraphicsRequirements() ); + + // (2) Define app info for vulkan + VkApplicationInfo vkApplicationInfo { VK_STRUCTURE_TYPE_APPLICATION_INFO }; + vkApplicationInfo.pApplicationName = GetAppInstance()->GetAppName(); + vkApplicationInfo.applicationVersion = static_cast< uint32_t >( GetAppInstance()->GetAppVersion() ); + vkApplicationInfo.pEngineName = XRLIB_NAME; + vkApplicationInfo.engineVersion = XR_MAKE_VERSION32( XRLIB_VERSION_MAJOR, XRLIB_VERSION_MINOR, XRLIB_VERSION_PATCH ); + vkApplicationInfo.apiVersion = VK_API_VERSION_1_2; + + // (3) Add vulkan layers and extensions the library needs + for ( auto validationLayer : m_vecValidationLayers ) + vecLayers.push_back( validationLayer ); + + for ( auto validationExtension : m_vecValidationExtensions ) + vecExtensions.push_back( validationExtension ); + + #ifdef XRVK_VULKAN_DEBUG_ENABLE + vecExtensions.push_back( VK_EXT_DEBUG_UTILS_EXTENSION_NAME ); + #endif + + #ifdef XRVK_VULKAN_DEBUG2_ENABLE + vecExtensions.push_back( VK_EXT_DEBUG_REPORT_EXTENSION_NAME ); + vecExtensions.push_back( VK_EXT_DEBUG_MARKER_EXTENSION_NAME ); + #endif + + #ifdef XRVK_VULKAN_DEBUG3_ENABLE + vecExtensions.push_back( VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME ); + #endif + + // (4) Create vulkan instance + VkInstanceCreateInfo vkInstanceCreateInfo { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO }; + vkInstanceCreateInfo.pApplicationInfo = &vkApplicationInfo; + vkInstanceCreateInfo.enabledLayerCount = (uint32_t) vecLayers.size(); + vkInstanceCreateInfo.ppEnabledLayerNames = vecLayers.empty() ? nullptr : vecLayers.data(); + vkInstanceCreateInfo.enabledExtensionCount = (uint32_t) vecExtensions.size(); + vkInstanceCreateInfo.ppEnabledExtensionNames = vecExtensions.empty() ? nullptr : vecExtensions.data(); + + XrVulkanInstanceCreateInfoKHR xrVulkanInstanceCreateInfo { XR_TYPE_VULKAN_INSTANCE_CREATE_INFO_KHR }; + xrVulkanInstanceCreateInfo.next = pXrVkInstanceNext; + xrVulkanInstanceCreateInfo.systemId = GetAppInstance()->GetXrSystemId(); + xrVulkanInstanceCreateInfo.pfnGetInstanceProcAddr = &vkGetInstanceProcAddr; + xrVulkanInstanceCreateInfo.vulkanCreateInfo = &vkInstanceCreateInfo; + xrVulkanInstanceCreateInfo.vulkanAllocator = nullptr; + + // ... validation + VkDebugUtilsMessengerCreateInfoEXT vkDebugCreateInfo { VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT }; + vkDebugCreateInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + vkDebugCreateInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + vkDebugCreateInfo.pfnUserCallback = Callback_Debug; + + // ... next chain + vkDebugCreateInfo.pNext = pVkInstanceNext; + vkInstanceCreateInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT *) &vkDebugCreateInfo; + + VkResult vkResult = VK_SUCCESS; + XrResult xrResult = CreateVkInstance( vkResult, GetAppInstance()->GetXrInstance(), &xrVulkanInstanceCreateInfo ); + + if ( !XR_UNQUALIFIED_SUCCESS( xrResult ) || vkResult != VK_SUCCESS ) + { + LogError( "Error creating vulkan instance with openxr result (%s) and vulkan result (%i)", XrEnumToString( xrResult ), (int32_t) vkResult ); + return xrResult == XR_SUCCESS ? XR_ERROR_VALIDATION_FAILURE : xrResult; + } + + LogInfo( "", "Vulkan instance successfully created." ); + + // (5) Get the vulkan physical device (gpu) used by the runtime + xrResult = GetVulkanGraphicsPhysicalDevice(); + if ( !XR_UNQUALIFIED_SUCCESS( xrResult ) ) + { + LogError( "Error getting the runtime's vulkan physical device (gpu) with result (%s) ", XrEnumToString( xrResult ) ); + return xrResult; + } + + // (6) Create vulkan logical device + xrResult = CreateVulkanLogicalDevice( pSurface, pVkLogicalDeviceNext, pXrLogicalDeviceNext ); + if ( !XR_UNQUALIFIED_SUCCESS( xrResult ) ) + { + LogError( "Error getting the runtime's vulkan physical device (gpu) with result (%s) ", XrEnumToString( xrResult ) ); + return xrResult; + } + + return XR_SUCCESS; + } + + XrResult CVulkan::GetVulkanGraphicsRequirements() + { + // Get the vulkan graphics requirements (min/max vulkan api version, etc) of the runtime + PFN_xrGetVulkanGraphicsRequirementsKHR xrGetVulkanGraphicsRequirementsKHR = nullptr; + XR_RETURN_ON_ERROR( INIT_PFN( GetAppInstance()->GetXrInstance(), xrGetVulkanGraphicsRequirementsKHR ) ); + + // We'll retrieve the Vulkan1 requirements and use it as the min/max vulkan api + XrGraphicsRequirementsVulkanKHR vulkan1Requirements { XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR }; + XR_RETURN_ON_ERROR( xrGetVulkanGraphicsRequirementsKHR( GetAppInstance()->GetXrInstance(), GetAppInstance()->GetXrSystemId(), &vulkan1Requirements ) ); + m_xrGraphicsRequirements.next = vulkan1Requirements.next; + m_xrGraphicsRequirements.minApiVersionSupported = vulkan1Requirements.minApiVersionSupported; + m_xrGraphicsRequirements.maxApiVersionSupported = vulkan1Requirements.maxApiVersionSupported; + + return XR_SUCCESS; + } + + XrResult CVulkan::CreateVkInstance( VkResult &outVkResult, const XrInstance xrInstance, const XrVulkanInstanceCreateInfoKHR *xrVulkanInstanceCreateInfo ) + { + // Check vulkan extensions required by the runtime + PFN_xrGetVulkanInstanceExtensionsKHR xrGetVulkanInstanceExtensionsKHR = nullptr; + XR_RETURN_ON_ERROR( INIT_PFN( xrInstance, xrGetVulkanInstanceExtensionsKHR ) ); + uint32_t unExtNum = 0; + XR_RETURN_ON_ERROR( xrGetVulkanInstanceExtensionsKHR( xrInstance, xrVulkanInstanceCreateInfo->systemId, 0, &unExtNum, nullptr ) ); + + // Check number of extensions required by the runtime + std::vector< char > vecRuntimeExtensionNames( unExtNum ); + XR_RETURN_ON_ERROR( xrGetVulkanInstanceExtensionsKHR( xrInstance, xrVulkanInstanceCreateInfo->systemId, unExtNum, &unExtNum, vecRuntimeExtensionNames.data() ) ); + + // Combine runtime and app extensions + std::vector< const char * > vecRuntimeExtensions = ConvertDelimitedCharArray( vecRuntimeExtensionNames.data(), ' ' ); + for ( uint32_t i = 0; i < vecRuntimeExtensions.size(); ++i ) + { + vecExtensions.push_back( vecRuntimeExtensions[ i ] ); + } + + VkInstanceCreateInfo vkInstanceCreateInfo { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO }; + memcpy( &vkInstanceCreateInfo, xrVulkanInstanceCreateInfo->vulkanCreateInfo, sizeof( vkInstanceCreateInfo ) ); + vkInstanceCreateInfo.enabledExtensionCount = (uint32_t) vecExtensions.size(); + vkInstanceCreateInfo.ppEnabledExtensionNames = vecExtensions.empty() ? nullptr : vecExtensions.data(); + + auto pfnCreateInstance = (PFN_vkCreateInstance) xrVulkanInstanceCreateInfo->pfnGetInstanceProcAddr( nullptr, "vkCreateInstance" ); + outVkResult = pfnCreateInstance( &vkInstanceCreateInfo, xrVulkanInstanceCreateInfo->vulkanAllocator, &m_vkInstance ); + + return XR_SUCCESS; + } + + XrResult CVulkan::GetVulkanGraphicsPhysicalDevice() + { + assert( m_vkInstance != VK_NULL_HANDLE ); + + PFN_xrGetVulkanGraphicsDeviceKHR xrGetVulkanGraphicsDeviceKHR = nullptr; + XR_RETURN_ON_ERROR( INIT_PFN( GetAppInstance()->GetXrInstance(), xrGetVulkanGraphicsDeviceKHR ) ); + XR_RETURN_ON_ERROR( xrGetVulkanGraphicsDeviceKHR( GetAppInstance()->GetXrInstance(), GetAppInstance()->GetXrSystemId(), m_vkInstance, &m_vkPhysicalDevice ) ); + return XR_SUCCESS; + } + + XrResult CVulkan::CreateVulkanLogicalDevice( VkSurfaceKHR *pSurface, void *pVkLogicalDeviceNext, void *pXrLogicalDeviceNext ) + { + assert( m_vkPhysicalDevice != VK_NULL_HANDLE ); + + // (1) Setup device queues ( one for graphics and one for presentation to a surface/mirror if available) + VkDeviceQueueCreateInfo vkDeviceQueueCI_Graphics { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO }; + float fQueuePriority_Graphics = 1.f; // highest priority for rendering to hmd + vkDeviceQueueCI_Graphics.queueCount = 1; + vkDeviceQueueCI_Graphics.pQueuePriorities = &fQueuePriority_Graphics; + + VkDeviceQueueCreateInfo vkDeviceQueueCI_Transfer { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO }; + float fQueuePriority_Transfer = 0.5f; // mid priority for data transfers + vkDeviceQueueCI_Transfer.queueCount = 1; + vkDeviceQueueCI_Transfer.pQueuePriorities = &fQueuePriority_Transfer; + + VkDeviceQueueCreateInfo vkDeviceQueueCI_Present { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO }; + float fQueuePriority_Present = 0.f; // lowest priority for rendering to a mirror/surface + vkDeviceQueueCI_Present.queueCount = 1; + vkDeviceQueueCI_Present.pQueuePriorities = &fQueuePriority_Present; + + // (2) Retrieve all queue families for the physical device + uint32_t unQueueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties( m_vkPhysicalDevice, &unQueueFamilyCount, nullptr ); + std::vector< VkQueueFamilyProperties > vecQueueFamilyProps( unQueueFamilyCount ); + vkGetPhysicalDeviceQueueFamilyProperties( m_vkPhysicalDevice, &unQueueFamilyCount, vecQueueFamilyProps.data() ); + + bool bGraphicsFamilyFound = false; + bool bPresentFamilyFound = false; + bool bTransferFamilyFound = false; + + for ( uint32_t i = 0; i < unQueueFamilyCount; ++i ) + { + // Find an appropriate graphics queue + if ( !bGraphicsFamilyFound && ( ( vecQueueFamilyProps[ i ].queueFlags & VK_QUEUE_GRAPHICS_BIT ) != 0u ) ) + { + m_vkQueueIndex_GraphicsFamily = vkDeviceQueueCI_Graphics.queueFamilyIndex = i; + bGraphicsFamilyFound = true; + } + + // Find an appropriate transfer queue + if ( !bTransferFamilyFound && ( ( vecQueueFamilyProps[ i ].queueFlags & VK_QUEUE_TRANSFER_BIT ) != 0u ) ) + { + m_vkQueueIndex_TransferFamily = vkDeviceQueueCI_Transfer.queueFamilyIndex = i; + bTransferFamilyFound = true; + } + + // Find an appropriate present queue + if ( !bPresentFamilyFound && pSurface ) + { + vkGetPhysicalDeviceSurfaceSupportKHR( m_vkPhysicalDevice, i, *pSurface, &m_vkSupportsSurfacePresent ); + if ( m_vkSupportsSurfacePresent ) + { + m_vkQueueIndex_PresentFamily = vkDeviceQueueCI_Present.queueFamilyIndex = i; + bPresentFamilyFound = true; + } + } + else + { + m_vkSupportsSurfacePresent = VK_FALSE; + if ( bGraphicsFamilyFound && bTransferFamilyFound ) + break; + } + + if ( bGraphicsFamilyFound && bPresentFamilyFound && bTransferFamilyFound ) + break; + } + + #if defined( _WIN32 ) + vecLogicalDeviceExtensions.push_back( VK_KHR_SWAPCHAIN_EXTENSION_NAME ); + #endif + + // (3) Setup logical device + vkPhysicalDeviceFeatures.samplerAnisotropy = VK_TRUE; + // VkPhysicalDeviceFeatures2 physical_features2 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 }; + // physical_features2.features.samplerAnisotropy = VK_TRUE; + // vkGetPhysicalDeviceFeatures2( m_SharedState.vkPhysicalDevice, &physical_features2 ); + + //// log warning(s) if feature(s) is/are not supported + // if ( physical_features2.features.samplerAnisotropy == VK_FALSE || m_SharedState.vkPhysicalDeviceFeatures.samplerAnisotropy == VK_FALSE ) + //{ + // LogWarning( "features.samplerAnisotropy is not available in this gpu, application may not run or render as intended." ); + // } + + std::vector< VkDeviceQueueCreateInfo > vecDeviceQueueCIs; + vecDeviceQueueCIs.push_back( vkDeviceQueueCI_Graphics ); + + bool bTransferIsSameAsGraphics = vkDeviceQueueCI_Graphics.queueFamilyIndex == vkDeviceQueueCI_Transfer.queueFamilyIndex; + if ( !bTransferIsSameAsGraphics ) + vecDeviceQueueCIs.push_back( vkDeviceQueueCI_Transfer ); + + bool bPresentIsUnique = ( vkDeviceQueueCI_Graphics.queueFamilyIndex != vkDeviceQueueCI_Present.queueFamilyIndex ) && + ( vkDeviceQueueCI_Transfer.queueFamilyIndex != vkDeviceQueueCI_Present.queueFamilyIndex ); + + if ( m_vkSupportsSurfacePresent && !bPresentIsUnique ) + vecDeviceQueueCIs.push_back( vkDeviceQueueCI_Present ); + + VkDeviceCreateInfo vkLogicalDeviceCI { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; + vkLogicalDeviceCI.pNext = pVkLogicalDeviceNext; //&physical_features2; + vkLogicalDeviceCI.queueCreateInfoCount = (uint32_t) vecDeviceQueueCIs.size(); + vkLogicalDeviceCI.pQueueCreateInfos = vecDeviceQueueCIs.data(); + vkLogicalDeviceCI.enabledLayerCount = 0; + vkLogicalDeviceCI.ppEnabledLayerNames = nullptr; + vkLogicalDeviceCI.enabledExtensionCount = (uint32_t) vecLogicalDeviceExtensions.size(); + vkLogicalDeviceCI.ppEnabledExtensionNames = vecLogicalDeviceExtensions.empty() ? nullptr : vecLogicalDeviceExtensions.data(); + vkLogicalDeviceCI.pEnabledFeatures = &vkPhysicalDeviceFeatures; + + XrVulkanDeviceCreateInfoKHR xrVulkanDeviceCreateInfo { XR_TYPE_VULKAN_DEVICE_CREATE_INFO_KHR }; + xrVulkanDeviceCreateInfo.next = pXrLogicalDeviceNext; + xrVulkanDeviceCreateInfo.systemId = GetAppInstance()->GetXrSystemId(); + xrVulkanDeviceCreateInfo.pfnGetInstanceProcAddr = &vkGetInstanceProcAddr; + xrVulkanDeviceCreateInfo.vulkanCreateInfo = &vkLogicalDeviceCI; + xrVulkanDeviceCreateInfo.vulkanPhysicalDevice = m_vkPhysicalDevice; + xrVulkanDeviceCreateInfo.vulkanAllocator = nullptr; + + // (4) Create logical device + VkResult vkResult = VK_SUCCESS; + XrResult xrResult = CreateVkDevice( vkResult, &xrVulkanDeviceCreateInfo ); + if ( !XR_UNQUALIFIED_SUCCESS( xrResult ) || vkResult != VK_SUCCESS ) + { + LogError( "", "Error creating vulkan device with openxr result (%s) and vulkan result (%i)", xrlib::XrEnumToString( xrResult ), (int32_t) vkResult ); + return xrResult == XR_SUCCESS ? XR_ERROR_VALIDATION_FAILURE : xrResult; + } + + LogInfo( "", "Vulkan (logical) device successfully created." ); + + // (5) Get device queue(s) + vkGetDeviceQueue( m_vkDevice, m_vkQueueIndex_GraphicsFamily, 0, &m_vkQueue_Graphics ); + + if ( !bTransferIsSameAsGraphics ) + { + vkGetDeviceQueue( m_vkDevice, m_vkQueueIndex_TransferFamily, 0, &m_vkQueue_Transfer ); + } + else + { + m_vkQueue_Transfer = m_vkQueue_Graphics; + } + + + if ( m_vkSupportsSurfacePresent ) + { + if ( !bPresentIsUnique ) + m_vkQueue_Present = m_vkQueue_Graphics; + else + vkGetDeviceQueue( m_vkDevice, m_vkQueueIndex_PresentFamily, 0, &m_vkQueue_Present ); + } + + // (6) Create graphics binding that we will use to create an openxr session + m_xrGraphicsBinding.instance = m_vkInstance; + m_xrGraphicsBinding.physicalDevice = m_vkPhysicalDevice; + m_xrGraphicsBinding.device = m_vkDevice; + m_xrGraphicsBinding.queueFamilyIndex = m_vkQueueIndex_GraphicsFamily; + m_xrGraphicsBinding.queueIndex = m_vkQueueIndex_Graphics; + + return XR_SUCCESS; + } + + XrResult CVulkan::CreateVkDevice( VkResult &outVkResult, const XrVulkanDeviceCreateInfoKHR *xrVulkanDeviceCreateInfo ) + { + assert( xrVulkanDeviceCreateInfo ); + assert( m_vkInstance != VK_NULL_HANDLE ); + assert( m_vkPhysicalDevice != VK_NULL_HANDLE ); + + PFN_xrGetVulkanDeviceExtensionsKHR xrGetVulkanDeviceExtensionsKHR = nullptr; + XR_RETURN_ON_ERROR( INIT_PFN( GetAppInstance()->GetXrInstance(), xrGetVulkanDeviceExtensionsKHR ) ); + + // Get number of device extensions needed by the runtime + uint32_t unDeviceExtensionsNum = 0; + XR_RETURN_ON_ERROR( xrGetVulkanDeviceExtensionsKHR( GetAppInstance()->GetXrInstance(), xrVulkanDeviceCreateInfo->systemId, 0, &unDeviceExtensionsNum, nullptr ) ); + + std::vector< char > vecRuntimeLogicalDeviceExtensionNames( unDeviceExtensionsNum ); + XR_RETURN_ON_ERROR( xrGetVulkanDeviceExtensionsKHR( + GetAppInstance()->GetXrInstance(), xrVulkanDeviceCreateInfo->systemId, unDeviceExtensionsNum, &unDeviceExtensionsNum, vecRuntimeLogicalDeviceExtensionNames.data() ) ); + + // Merge runtime and app's required extensions + std::vector< const char * > vecRuntimeLogicalDeviceExtensions = ConvertDelimitedCharArray( vecRuntimeLogicalDeviceExtensionNames.data(), ' ' ); + for ( uint32_t i = 0; i < vecRuntimeLogicalDeviceExtensions.size(); ++i ) + { + vecLogicalDeviceExtensions.push_back( vecRuntimeLogicalDeviceExtensions[ i ] ); + } + + memcpy( &vkPhysicalDeviceFeatures, xrVulkanDeviceCreateInfo->vulkanCreateInfo->pEnabledFeatures, sizeof( vkPhysicalDeviceFeatures ) ); + + #if !defined( XR_USE_PLATFORM_ANDROID ) + // Mute validation error with Meta PC runtime + VkPhysicalDeviceFeatures metaRequiredFeatures {}; + vkGetPhysicalDeviceFeatures( m_vkPhysicalDevice, &metaRequiredFeatures ); + if ( metaRequiredFeatures.shaderStorageImageMultisample == VK_TRUE ) + { + vkPhysicalDeviceFeatures.shaderStorageImageMultisample = VK_TRUE; + } + #endif + + VkDeviceCreateInfo vkDeviceCreateInfo { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; + memcpy( &vkDeviceCreateInfo, xrVulkanDeviceCreateInfo->vulkanCreateInfo, sizeof( vkDeviceCreateInfo ) ); + vkDeviceCreateInfo.pEnabledFeatures = &vkPhysicalDeviceFeatures; + vkDeviceCreateInfo.enabledExtensionCount = (uint32_t) vecLogicalDeviceExtensions.size(); + vkDeviceCreateInfo.ppEnabledExtensionNames = vecLogicalDeviceExtensions.empty() ? nullptr : vecLogicalDeviceExtensions.data(); + + auto pfnCreateDevice = (PFN_vkCreateDevice) xrVulkanDeviceCreateInfo->pfnGetInstanceProcAddr( m_vkInstance, "vkCreateDevice" ); + outVkResult = pfnCreateDevice( m_vkPhysicalDevice, &vkDeviceCreateInfo, xrVulkanDeviceCreateInfo->vulkanAllocator, &m_vkDevice ); + + return XR_SUCCESS; + } + + VKAPI_ATTR VkBool32 VKAPI_CALL CVulkan::Callback_Debug( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData, + void *pUserData ) + { + if ( messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT ) + { + LogDebug( "Vulkan", "%s", pCallbackData->pMessage ); + } + + return VK_FALSE; + } + + const bool CVulkan::IsDepthFormat( VkFormat vkFormat ) + { + if ( vkFormat == VK_FORMAT_D16_UNORM || + vkFormat == VK_FORMAT_X8_D24_UNORM_PACK32 || + vkFormat == VK_FORMAT_D32_SFLOAT || + vkFormat == VK_FORMAT_D16_UNORM_S8_UINT || + vkFormat == VK_FORMAT_D24_UNORM_S8_UINT || + vkFormat == VK_FORMAT_D32_SFLOAT_S8_UINT ) + { + return true; + } + + return false; + } + +} + diff --git a/src/xrvk/buffer.cpp b/src/xrvk/buffer.cpp new file mode 100644 index 0000000..122f863 --- /dev/null +++ b/src/xrvk/buffer.cpp @@ -0,0 +1,137 @@ +/* + * Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) + * Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +namespace xrlib +{ + CDeviceBuffer::CDeviceBuffer( CSession *pSession ) : m_pSession( pSession ) + { + assert( pSession ); + assert( pSession->GetVulkan()->GetVkPhysicalDevice() != VK_NULL_HANDLE ); + assert( pSession->GetVulkan()->GetVkLogicalDevice() != VK_NULL_HANDLE ); + } + + CDeviceBuffer::~CDeviceBuffer() + { + UnmapMemory(); + + if ( m_vkBufferInfo.buffer != VK_NULL_HANDLE ) + vkDestroyBuffer( GetLogicalDevice(), m_vkBufferInfo.buffer, nullptr ); + + if ( m_vkDeviceMemory != VK_NULL_HANDLE ) + vkFreeMemory( GetLogicalDevice(), m_vkDeviceMemory, nullptr ); + } + + VkResult CDeviceBuffer::Init( + VkBufferUsageFlags usageFlags, + VkMemoryPropertyFlags memPropFlags, + VkDeviceSize unSize, + void *pData, + VkAllocationCallbacks *pCallbacks ) + { + assert( usageFlags != 0 ); + assert( memPropFlags != 0 ); + + // Create vulkan buffer + VkBufferCreateInfo bufferCI { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufferCI.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferCI.usage = usageFlags; + bufferCI.size = unSize; + + VkResult result = vkCreateBuffer( GetLogicalDevice(), &bufferCI, pCallbacks, &m_vkBufferInfo.buffer ); + if ( result != VK_SUCCESS ) + return result; + + // Get buffer memory requirements + VkMemoryRequirements memReqs; + VkMemoryAllocateInfo memAllocInfo { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; + vkGetBufferMemoryRequirements( GetLogicalDevice(), m_vkBufferInfo.buffer, &memReqs ); + + // Find memory type matching memory requirement for the buffer and provided memory property(ies) + memAllocInfo.allocationSize = memReqs.size; + memAllocInfo.memoryTypeIndex = FindMemoryType( memPropFlags, memReqs.memoryTypeBits ); + + // If buffer is for shader use, allocate required flag + VkMemoryAllocateFlagsInfoKHR memAllocFlags {}; + if ( usageFlags & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT ) + { + memAllocFlags.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR; + memAllocFlags.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR; + memAllocInfo.pNext = &memAllocFlags; + } + + result = vkAllocateMemory( GetLogicalDevice(), &memAllocInfo, pCallbacks, &m_vkDeviceMemory ); + if ( result != VK_SUCCESS ) + return result; + + m_vkMemoryAlignment = memReqs.alignment; + m_vkMemorySize = memReqs.size; + + // If buffer data's provided, copy to internal cache + if ( pData ) + { + result = vkMapMemory( GetLogicalDevice(), m_vkDeviceMemory, 0, m_vkMemorySize, 0, &m_pData ); + if ( result != VK_SUCCESS ) + return result; + + + memcpy( m_pData, pData, m_vkMemorySize ); + if ( ( memPropFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT ) == 0 ) + { + result = FlushMemory(); + if ( result != VK_SUCCESS ) + return result; + } + + UnmapMemory(); + } + + // Bind buffer to device memory + return vkBindBufferMemory( GetLogicalDevice(), m_vkBufferInfo.buffer, m_vkDeviceMemory, 0 ); + } + + uint32_t CDeviceBuffer::FindMemoryType( VkMemoryPropertyFlags memPropFlags, uint32_t unBits ) + { + VkPhysicalDeviceMemoryProperties memProps; + vkGetPhysicalDeviceMemoryProperties( GetPhysicalDevice(), &memProps ); + + for ( uint32_t i = 0; i < memProps.memoryTypeCount; i++ ) + if ( ( unBits & ( 1 << i ) ) && ( memProps.memoryTypes[ i ].propertyFlags & memPropFlags ) == memPropFlags ) + return i; + + throw std::runtime_error( "FATAL: Unable to find requested memory type for device's physical device." ); + } + + VkResult CDeviceBuffer::FlushMemory( VkDeviceSize unSize, VkDeviceSize unOffset, uint32_t unRangeCount ) + { + VkMappedMemoryRange mappedRange { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE }; + mappedRange.memory = m_vkDeviceMemory; + mappedRange.offset = unOffset; + mappedRange.size = unSize; + return vkFlushMappedMemoryRanges( GetLogicalDevice(), unRangeCount, &mappedRange ); + } + + void CDeviceBuffer::UnmapMemory() + { + if ( !m_pData ) + return; + + vkUnmapMemory( GetLogicalDevice(), m_vkDeviceMemory ); + m_pData = nullptr; + } + + VkPhysicalDevice CDeviceBuffer::GetPhysicalDevice() + { + return m_pSession->GetVulkan()->GetVkPhysicalDevice(); + } + + VkDevice CDeviceBuffer::GetLogicalDevice() + { + return m_pSession->GetVulkan()->GetVkLogicalDevice(); + } + +} // namespace xrlib diff --git a/src/xrvk/mesh.cpp b/src/xrvk/mesh.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/xrvk/pipeline.cpp b/src/xrvk/pipeline.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/xrvk/primitive.cpp b/src/xrvk/primitive.cpp new file mode 100644 index 0000000..d7b7779 --- /dev/null +++ b/src/xrvk/primitive.cpp @@ -0,0 +1,410 @@ +/* + * Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) + * Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +namespace xrlib +{ + CPrimitive::CPrimitive( CSession *pSession, uint32_t drawPriority, EAssetMotionType motionType, VkPipeline graphicsPipeline, XrSpace space ) : + m_pSession( pSession ), + unDrawPriority( drawPriority ), + pipeline( graphicsPipeline ) + { + assert( pSession ); + + // Create device memory buffers + m_pIndexBuffer = new CDeviceBuffer( pSession ); + m_pVertexBuffer = new CDeviceBuffer( pSession ); + m_pInstanceBuffer = new CDeviceBuffer( pSession ); + + // Pre-fill first instance + instances.push_back( SInstanceState { motionType, space } ); // pre-fill first instance + instanceMatrices.push_back( XrMatrix4x4f() ); + XrMatrix4x4f_CreateTranslationRotationScale( &instanceMatrices[ 0 ], GetPosition( 0 ), GetOrientation( 0 ), GetScale( 0 ) ); + } + + CPrimitive::~CPrimitive() + { + Reset(); + DeleteBuffers(); + } + + VkResult CPrimitive::InitBuffers() + { + if ( m_pIndexBuffer ) + delete m_pIndexBuffer; + + m_pIndexBuffer = new CDeviceBuffer( m_pSession ); + VkResult result = InitBuffer( m_pIndexBuffer, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, sizeof( unsigned short ) * m_vecIndices.size(), m_vecIndices.data() ); + if ( result != VK_SUCCESS ) + return result; + + if ( m_pVertexBuffer ) + delete m_pVertexBuffer; + + m_pVertexBuffer = new CDeviceBuffer( m_pSession ); + result = InitBuffer( m_pVertexBuffer, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, sizeof( XrVector3f ) * m_vecVertices.size(), m_vecVertices.data() ); + if ( result != VK_SUCCESS ) + return result; + + // todo assert for debug, matrices must be at least 1 + if ( m_pInstanceBuffer ) + delete m_pInstanceBuffer; + + m_pInstanceBuffer = new CDeviceBuffer( m_pSession ); + return InitBuffer( m_pInstanceBuffer, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, sizeof( XrMatrix4x4f ) * instanceMatrices.size(), instanceMatrices.data() ); + } + + VkResult CPrimitive::InitBuffer( CDeviceBuffer *pBuffer, VkBufferUsageFlags usageFlags, VkDeviceSize unSize, void *pData, VkMemoryPropertyFlags memPropFlags, VkAllocationCallbacks *pCallbacks ) + { + // @todo debug assert. + // assert( pBuffer ); + + return pBuffer->Init( usageFlags, memPropFlags, unSize, pData, pCallbacks ); + } + + void CPrimitive::ResetScale( float x, float y, float z, uint32_t unInstanceIndex ) + { + // @todo - debug assert. ideally no checks here other than debug assert for perf + instances[ unInstanceIndex ].scale = { x, y, z }; + } + + void CPrimitive::ResetScale( float fScale, uint32_t unInstanceIndex ) + { + // @todo - debug assert. ideally no checks here other than debug assert for perf + instances[ unInstanceIndex ].scale = { fScale, fScale, fScale }; + } + + void CPrimitive::Scale( float fPercent, uint32_t unInstanceIndex ) + { + // @todo - debug assert. ideally no checks here other than debug assert for perf + instances[ unInstanceIndex ].scale.x *= fPercent; + instances[ unInstanceIndex ].scale.y *= fPercent; + instances[ unInstanceIndex ].scale.z *= fPercent; + } + + void CPrimitive::AddTri( XrVector3f v1, XrVector3f v2, XrVector3f v3 ) + { + AddVertex( v1 ); + AddVertex( v2 ); + AddVertex( v3 ); + + } + + void CPrimitive::AddQuadCW( XrVector3f v1, XrVector3f v2, XrVector3f v3, XrVector3f v4 ) + { + AddVertex( v1 ); + AddVertex( v2 ); + AddVertex( v4 ); + + AddVertex( v2 ); + AddVertex( v3 ); + AddVertex( v4 ); + } + + void CPrimitive::AddQuadCCW( XrVector3f v1, XrVector3f v2, XrVector3f v3, XrVector3f v4 ) + { + AddVertex( v4 ); + AddVertex( v3 ); + AddVertex( v2 ); + + AddVertex( v2 ); + AddVertex( v1 ); + AddVertex( v4 ); + } + + void CPrimitive::AddIndex( unsigned short index ) + { + m_vecIndices.push_back( index ); + } + + void CPrimitive::AddVertex( XrVector3f vertex ) + { + m_vecVertices.push_back( vertex ); + } + + void CPrimitive::Reset() + { + ResetIndices(); + ResetVertices(); + } + + void CPrimitive::ResetIndices() + { + m_vecIndices.clear(); + } + + void CPrimitive::ResetVertices() + { + m_vecVertices.clear(); } + + CDeviceBuffer *CPrimitive::UpdateBuffer( VkCommandBuffer transferCmdBuffer ) + { + // Calculate buffer size + VkDeviceSize bufferSize = instanceMatrices.size() * sizeof( XrMatrix4x4f ); + + // Create staging buffer + CDeviceBuffer *pStagingBuffer = new CDeviceBuffer( m_pSession ); + pStagingBuffer->Init( + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + bufferSize, + instanceMatrices.data() ); + + // Copy buffer + VkBufferCopy bufferCopyRegion = {}; + bufferCopyRegion.size = bufferSize; + vkCmdCopyBuffer( transferCmdBuffer, pStagingBuffer->GetVkBuffer(), m_pInstanceBuffer->GetVkBuffer(), 1, &bufferCopyRegion ); + + return pStagingBuffer; + } + + uint32_t CPrimitive::AddInstance( uint32_t unCount ) + { + for ( size_t i = 0; i < unCount; i++ ) + { + instances.push_back( SInstanceState() ); + instanceMatrices.push_back( XrMatrix4x4f() ); + } + + if ( m_pInstanceBuffer ) + delete m_pInstanceBuffer; + + m_pInstanceBuffer = new CDeviceBuffer( m_pSession ); + VkResult result = InitBuffer( m_pInstanceBuffer, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, sizeof( XrMatrix4x4f ) * instanceMatrices.size(), nullptr ); + assert( result == VK_SUCCESS ); + + return GetInstanceCount(); + } + + void CPrimitive::UpdateModelMatrix( uint32_t unInstanceIndex, XrSpace baseSpace, XrTime time, bool bForceUpdate ) + { + if ( instances[ unInstanceIndex ].motionType == EAssetMotionType::STATIC_INIT ) + { + bForceUpdate = true; + instances[ unInstanceIndex ].motionType = EAssetMotionType::STATIC_NO_INIT; + } + + if ( instances[ unInstanceIndex ].motionType == EAssetMotionType::DYNAMIC || bForceUpdate ) + { + if ( instances[ unInstanceIndex ].space != XR_NULL_HANDLE && baseSpace != XR_NULL_HANDLE ) + { + XrSpaceLocation spaceLocation { XR_TYPE_SPACE_LOCATION }; + if ( XR_UNQUALIFIED_SUCCESS ( xrLocateSpace( instances[ unInstanceIndex ].space, baseSpace, time, &spaceLocation ) ) ) + { + if ( spaceLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT ) + instances[ unInstanceIndex ].pose.orientation = spaceLocation.pose.orientation; + + if ( spaceLocation.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT ) + instances[ unInstanceIndex ].pose.position = spaceLocation.pose.position; + } + } + + XrMatrix4x4f_CreateTranslationRotationScale( + &instanceMatrices[ unInstanceIndex ], + &instances[ unInstanceIndex ].pose.position, + &instances[ unInstanceIndex ].pose.orientation, + &instances[ unInstanceIndex ].scale ); + } + } + + XrMatrix4x4f *CPrimitive::GetModelMatrix( uint32_t unInstanceIndex, bool bRefresh ) + { + if ( bRefresh ) + XrMatrix4x4f_CreateTranslationRotationScale( + &instanceMatrices[ unInstanceIndex ], + &instances[ unInstanceIndex ].pose.position, + &instances[ unInstanceIndex ].pose.orientation, + &instances[ unInstanceIndex ].scale ); + + return &instanceMatrices[ unInstanceIndex ]; + } + + void CPrimitive::DeleteBuffers() + { + if ( m_pIndexBuffer ) + delete m_pIndexBuffer; + + if ( m_pVertexBuffer ) + delete m_pVertexBuffer; + + if ( m_pInstanceBuffer ) + delete m_pInstanceBuffer; + } + + CColoredPrimitive::CColoredPrimitive( + CSession *pSession, + uint32_t drawPriority, + EAssetMotionType motionType, + float fAlpha, + VkPipeline graphicsPipeline, + XrSpace space ) + : CPrimitive( pSession, drawPriority, motionType, graphicsPipeline, space ) + { + + } + + CColoredPrimitive::~CColoredPrimitive() {} + + void CColoredPrimitive::AddVertex( XrVector3f vertex ) + { + AddColoredVertex( vertex, k_ColorMagenta ); + } + + void CColoredPrimitive::AddColoredVertex( XrVector3f vertex, XrVector3f color, float fAlpha ) + { + m_vecVertices.push_back( { vertex, { color.x, color.y, color.z, fAlpha } } ); + } + + void CColoredPrimitive::AddTri( XrVector3f v1, XrVector3f v2, XrVector3f v3 ) + { + AddColoredTri( v1, v2, v3, k_ColorMagenta ); + } + + void CColoredPrimitive::AddColoredTri( XrVector3f v1, XrVector3f v2, XrVector3f v3, XrVector3f color, float fAlpha ) + { + m_vecVertices.push_back( { v1, { color.x, color.y, color.z, fAlpha } } ); + m_vecVertices.push_back( { v2, { color.x, color.y, color.z, fAlpha } } ); + m_vecVertices.push_back( { v3, { color.x, color.y, color.z, fAlpha } } ); + } + + void CColoredPrimitive::AddQuadCW( XrVector3f v1, XrVector3f v2, XrVector3f v3, XrVector3f v4 ) + { + AddColoredQuadCW( v1, v2, v3, v4, k_ColorMagenta ); + } + + void CColoredPrimitive::AddColoredQuadCW( XrVector3f v1, XrVector3f v2, XrVector3f v3, XrVector3f v4, XrVector3f color, float fAlpha ) + { + m_vecVertices.push_back( { v1, { color.x, color.y, color.z, fAlpha } } ); + m_vecVertices.push_back( { v2, { color.x, color.y, color.z, fAlpha } } ); + m_vecVertices.push_back( { v4, { color.x, color.y, color.z, fAlpha } } ); + + m_vecVertices.push_back( { v2, { color.x, color.y, color.z, fAlpha } } ); + m_vecVertices.push_back( { v3, { color.x, color.y, color.z, fAlpha } } ); + m_vecVertices.push_back( { v4, { color.x, color.y, color.z, fAlpha } } ); + } + + void CColoredPrimitive::AddQuadCCW( XrVector3f v1, XrVector3f v2, XrVector3f v3, XrVector3f v4 ) + { + AddColoredQuadCCW( v1, v2, v3, v4, k_ColorMagenta ); + } + + void CColoredPrimitive::AddColoredQuadCCW( XrVector3f v1, XrVector3f v2, XrVector3f v3, XrVector3f v4, XrVector3f color, float fAlpha ) + { + m_vecVertices.push_back( { v4, { color.x, color.y, color.z, fAlpha } } ); + m_vecVertices.push_back( { v3, { color.x, color.y, color.z, fAlpha } } ); + m_vecVertices.push_back( { v2, { color.x, color.y, color.z, fAlpha } } ); + + m_vecVertices.push_back( { v2, { color.x, color.y, color.z, fAlpha } } ); + m_vecVertices.push_back( { v1, { color.x, color.y, color.z, fAlpha } } ); + m_vecVertices.push_back( { v4, { color.x, color.y, color.z, fAlpha } } ); + } + + void CColoredPrimitive::Recolor( XrVector3f color, float fAlpha ) + { + for ( auto &vertex : m_vecVertices ) + vertex.color = { color.x, color.y, color.z, fAlpha }; + } + + VkResult CColoredPrimitive::InitBuffers() + { + VkResult result = InitBuffer( m_pIndexBuffer, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, sizeof( unsigned short ) * m_vecIndices.size(), m_vecIndices.data() ); + if ( result != VK_SUCCESS ) + return result; + + result = InitBuffer( m_pVertexBuffer, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, sizeof( SColoredVertex ) * m_vecVertices.size(), m_vecVertices.data() ); + if ( result != VK_SUCCESS ) + return result; + + // todo assert for debug, matrices must be at least 1 + return InitBuffer( m_pInstanceBuffer, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, sizeof( XrMatrix4x4f ) * instanceMatrices.size(), instanceMatrices.data() ); + } + + CPyramid::CPyramid( CSession *pSession, uint32_t drawPriority, EAssetMotionType EAssetMotionType, VkPipeline graphicsPipeline, XrSpace space ) + : CPrimitive( pSession, drawPriority, EAssetMotionType, graphicsPipeline, space ) + + { + const XrVector3f VertPyramid_Tip { 0.f, 0.f, -0.5f }; + const XrVector3f VertPyramid_Top { 0.f, 0.5f, 0.5f }; + const XrVector3f VertPyramid_Base_Left { -0.5f, -0.5f, 0.5f }; + const XrVector3f VertPyramid_Base_Right { 0.5f, -0.5f, 0.5f }; + + // Add Vertices + AddTri( VertPyramid_Base_Left, VertPyramid_Top, VertPyramid_Tip ); // left (port) + AddTri( VertPyramid_Base_Right, VertPyramid_Top, VertPyramid_Base_Left ); // back + AddTri( VertPyramid_Tip, VertPyramid_Top, VertPyramid_Base_Right ); // right (starboard) + AddTri( VertPyramid_Base_Left, VertPyramid_Tip, VertPyramid_Base_Right ); // base - this is wound clockwise so the face will end up facing downwards + + // Add indices + // @todo + for ( uint32_t i = 0; i < 12; i++ ) + m_vecIndices.push_back( i ); + } + + CPyramid::~CPyramid() {} + + CColoredPyramid::CColoredPyramid( CSession *pSession, uint32_t drawPriority, EAssetMotionType motionType, float fAlpha, VkPipeline graphicsPipeline, XrSpace space ) + : CColoredPrimitive( pSession, drawPriority, motionType, fAlpha, graphicsPipeline, space ) + { + const XrVector3f VertPyramid_Tip { 0.f, 0.f, -0.5f }; + const XrVector3f VertPyramid_Top { 0.f, 0.5f, 0.5f }; + const XrVector3f VertPyramid_Base_Left { -0.5f, -0.5f, 0.5f }; + const XrVector3f VertPyramid_Base_Right { 0.5f, -0.5f, 0.5f }; + + // Add Vertices + AddColoredTri( VertPyramid_Top, VertPyramid_Base_Left, VertPyramid_Tip, k_ColorRed, fAlpha ); // left (port) + AddColoredTri( VertPyramid_Top, VertPyramid_Base_Right, VertPyramid_Base_Left, k_ColorPurple, fAlpha ); // back + AddColoredTri( VertPyramid_Top, VertPyramid_Tip, VertPyramid_Base_Right, k_ColorGreen, fAlpha ); // right (starboard) + AddColoredTri( VertPyramid_Tip, VertPyramid_Base_Left, VertPyramid_Base_Right, k_ColorBlue, fAlpha ); // base - this is wound clockwise so the face will end up facing downwards + + // Add indices + // @todo + for ( uint32_t i = 0; i < 12; i++ ) + m_vecIndices.push_back( i ); + } + + CColoredPyramid::~CColoredPyramid() {} + + CColoredCube::CColoredCube( CSession *pSession, uint32_t drawPriority, EAssetMotionType motionType, float fAlpha, VkPipeline graphicsPipeline, XrSpace space ) + : CColoredPrimitive( pSession, drawPriority, motionType, fAlpha, graphicsPipeline, space ) + { + // Vertices for a 1x1x1 meter cube. (Left/Right, Top/Bottom, Front/Back) + const XrVector3f LBB { -0.5f, -0.5f, -0.5f }; + const XrVector3f LBF { -0.5f, -0.5f, 0.5f }; + const XrVector3f LTB { -0.5f, 0.5f, -0.5f }; + const XrVector3f LTF { -0.5f, 0.5f, 0.5f }; + const XrVector3f RBB { 0.5f, -0.5f, -0.5f }; + const XrVector3f RBF { 0.5f, -0.5f, 0.5f }; + const XrVector3f RTB { 0.5f, 0.5f, -0.5f }; + const XrVector3f RTF { 0.5f, 0.5f, 0.5f }; + + AddColoredTri( LBB, LTB, LBF, k_ColorRed, fAlpha ); // left (port) + AddColoredTri( LBF, LTB, LTF, k_ColorRed, fAlpha ); + + AddColoredTri( RBF, RTB, RBB, k_ColorRed, fAlpha ); // right (starboard) + AddColoredTri( RTF, RTB, RBF, k_ColorRed, fAlpha ); + + AddColoredTri( RBF, LBB, LBF, k_ColorBlue, fAlpha ); // bottom + AddColoredTri( RBB, LBB, RBF, k_ColorBlue, fAlpha ); + + AddColoredTri( RTF, LTB, RTB, k_ColorTeal, fAlpha ); // top + AddColoredTri( LTF, LTB, RTF, k_ColorTeal, fAlpha ); + + AddColoredTri( RTB, LBB, RBB, k_ColorPurple, fAlpha ); // back + AddColoredTri( LTB, LBB, RTB, k_ColorPurple, fAlpha ); + + AddColoredTri( RTF, LBF, LTF, k_ColorGold, fAlpha ); // front + AddColoredTri( RBF, LBF, RTF, k_ColorGold, fAlpha ); + + // Add indices + // @todo + for ( uint32_t i = 0; i < 36; i++ ) + m_vecIndices.push_back( i ); + } + + CColoredCube::~CColoredCube() {} + +} // namespace xrlib \ No newline at end of file diff --git a/src/xrvk/render.cpp b/src/xrvk/render.cpp new file mode 100644 index 0000000..13f965f --- /dev/null +++ b/src/xrvk/render.cpp @@ -0,0 +1,1030 @@ +/* + * Copyright 2024 Rune Berg (http://runeberg.io | https://github.com/1runeberg) + * Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +namespace xrlib +{ + SShader::~SShader() + { + if ( m_vkLogicalDevice != VK_NULL_HANDLE && m_vkShaderModule != VK_NULL_HANDLE ) + vkDestroyShaderModule( m_vkLogicalDevice, m_vkShaderModule, nullptr ); + } + + #ifdef XR_USE_PLATFORM_ANDROID + VkPipelineShaderStageCreateInfo SShader::Init( + AAssetManager *assetManager, + VkDevice vkLogicalDevice, + VkShaderStageFlagBits shaderStage, + VkShaderModuleCreateFlags createFlags, + void *pNext ) + #else + VkPipelineShaderStageCreateInfo SShader::Init( + VkDevice vkLogicalDevice, + VkShaderStageFlagBits shaderStage, + VkShaderModuleCreateFlags createFlags, + void *pNext ) + #endif + { + assert( vkLogicalDevice != VK_NULL_HANDLE ); + assert( shaderStage != 0 ); + + m_vkLogicalDevice = vkLogicalDevice; + + // Read shader from disk + #ifdef XR_USE_PLATFORM_ANDROID + auto spirvShaderCode = ReadBinaryFile( assetManager, m_sFilename ); + #else + auto spirvShaderCode = ReadBinaryFile( m_sFilename ); + #endif + + // Create shader module + VkShaderModuleCreateInfo shaderModuleCI { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; + shaderModuleCI.pNext = pNext; + shaderModuleCI.flags = createFlags; + shaderModuleCI.codeSize = spirvShaderCode.size(); + shaderModuleCI.pCode = reinterpret_cast< const uint32_t * >( spirvShaderCode.data() ); + + if ( vkCreateShaderModule( vkLogicalDevice, &shaderModuleCI, nullptr, &m_vkShaderModule ) != VK_SUCCESS ) + throw std::runtime_error( "failed to create shader module!" ); + + // Assemble shader stage + VkPipelineShaderStageCreateInfo shaderStageCI { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO }; + shaderStageCI.pNext = nullptr; + shaderStageCI.flags = 0; + shaderStageCI.stage = shaderStage; + shaderStageCI.module = m_vkShaderModule; + shaderStageCI.pName = m_sEntrypoint.c_str(); + + return shaderStageCI; + } + + SShaderSet::SShaderSet( + std::string sVertexShaderFilename, + std::string sFragmentShaderFilename, + std::string sVertexShaderEntrypoint, + std::string sFragmentShaderEntrypoint ) + { + assert( !sVertexShaderFilename.empty() ); + assert( !sFragmentShaderFilename.empty() ); + assert( !sVertexShaderEntrypoint.empty() ); + assert( !sFragmentShaderEntrypoint.empty() ); + + vertexShader = new SShader( sVertexShaderFilename, sVertexShaderEntrypoint ); + fragmentShader = new SShader( sFragmentShaderFilename, sFragmentShaderEntrypoint ); + } + + SShaderSet::~SShaderSet() + { + if ( vertexShader ) + delete vertexShader; + + if ( fragmentShader ) + delete fragmentShader; + } + + #ifdef XR_USE_PLATFORM_ANDROID + + void SShaderSet::Init( + AAssetManager *assetManager, + VkDevice vkLogicalDevice, + VkShaderStageFlagBits vertexShaderStage, + VkShaderStageFlagBits fragmentShaderStage, + VkShaderModuleCreateFlags vertexShaderCreateFlags, + VkShaderModuleCreateFlags fragmentShaderCreateFlags, + void *pVertexNext, + void *pFragmentNext ) + { + stages.clear(); + + stages.push_back( vertexShader->Init( assetManager, vkLogicalDevice, vertexShaderStage, vertexShaderCreateFlags, pVertexNext ) ); + stages.push_back( fragmentShader->Init( assetManager, vkLogicalDevice, fragmentShaderStage, fragmentShaderCreateFlags, pFragmentNext ) ); + } + + #else + + void SShaderSet::Init( + VkDevice vkLogicalDevice, + VkShaderStageFlagBits vertexShaderStage, + VkShaderStageFlagBits fragmentShaderStage, + VkShaderModuleCreateFlags vertexShaderCreateFlags, + VkShaderModuleCreateFlags fragmentShaderCreateFlags, + void *pVertexNext, + void *pFragmentNext ) + { + stages.clear(); + + stages.push_back( vertexShader->Init( vkLogicalDevice, vertexShaderStage, vertexShaderCreateFlags, pVertexNext ) ); + stages.push_back( fragmentShader->Init( vkLogicalDevice, fragmentShaderStage, fragmentShaderCreateFlags, pFragmentNext ) ); + } + + #endif + + CStereoRender::CStereoRender( CSession *pSession, VkFormat vkColorFormat, VkFormat vkDepthFormat ) + : m_pSession( pSession), + m_vkColorFormat( vkColorFormat ), + m_vkDepthFormat( vkDepthFormat ) + { + // Validate session + assert( pSession ); + assert( pSession->GetXrSession() != XR_NULL_HANDLE ); + + // Validate session's current view configuration + assert( pSession->xrViewConfigurationType == XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO ); + + // Validate texture formats + std::vector< int64_t > supportedFormats; + assert ( XR_UNQUALIFIED_SUCCESS( pSession->GetSupportedTextureFormats( supportedFormats ) ) ); + + bool bFoundColor = false; + bool bFoundDepth = false; + + for ( auto &supportedFormat : supportedFormats ) + { + if ( bFoundColor && bFoundDepth ) + break; + + if ( !bFoundColor && ( (VkFormat) supportedFormat == vkColorFormat ) ) + bFoundColor = true; + + if ( !bFoundDepth && ( (VkFormat) supportedFormat == vkDepthFormat ) ) + bFoundDepth = true; + } + + assert( bFoundColor && bFoundDepth ); + assert( !pSession->GetVulkan()->IsDepthFormat( vkColorFormat ) ); + assert( pSession->GetVulkan()->IsDepthFormat( vkDepthFormat ) ); + }; + + CStereoRender::~CStereoRender() + { + if ( GetLogicalDevice() != VK_NULL_HANDLE ) + { + // Destroy render views + for ( auto &renderTarget : m_vecMultiviewRenderTargets ) + { + if ( renderTarget.vkColorImageDescriptor.imageView != VK_NULL_HANDLE ) + vkDestroyImageView( GetLogicalDevice(), renderTarget.vkColorImageDescriptor.imageView, nullptr ); + + if ( renderTarget.vkDepthImageView != VK_NULL_HANDLE ) + vkDestroyImageView(GetLogicalDevice(), renderTarget.vkDepthImageView, nullptr ); + } + + // Destroy render pass + for ( auto renderPass : vecRenderPasses ) + { + if ( renderPass != VK_NULL_HANDLE ) + vkDestroyRenderPass( GetLogicalDevice(), renderPass, nullptr ); + } + + // Destroy samplers, frame buffers and fences + for ( auto &renderTarget : m_vecMultiviewRenderTargets ) + { + if ( renderTarget.vkColorImageDescriptor.sampler != VK_NULL_HANDLE ) + vkDestroySampler( GetLogicalDevice(), renderTarget.vkColorImageDescriptor.sampler, nullptr ); + + if ( renderTarget.vkFrameBuffer != VK_NULL_HANDLE ) + vkDestroyFramebuffer( GetLogicalDevice(), renderTarget.vkFrameBuffer, nullptr ); + + if ( renderTarget.vkRenderCommandFence != VK_NULL_HANDLE ) + vkDestroyFence( GetLogicalDevice(), renderTarget.vkRenderCommandFence, nullptr ); + } + } + + if ( m_xrColorSwapchain != XR_NULL_HANDLE ) + xrDestroySwapchain( m_xrColorSwapchain ); + + if ( m_xrDepthSwapchain != XR_NULL_HANDLE ) + xrDestroySwapchain( m_xrDepthSwapchain ); + + if ( m_vkTransferCommandPool != VK_NULL_HANDLE ) + vkDestroyCommandPool( GetLogicalDevice(), m_vkTransferCommandPool, nullptr ); + + if ( m_vkRenderCommandPool != VK_NULL_HANDLE ) + vkDestroyCommandPool( GetLogicalDevice(), m_vkRenderCommandPool, nullptr ); + } + + XrResult CStereoRender::Init( uint32_t unTextureFaceCount, uint32_t unTextureMipCount ) + { + // Check if openxr session is valid + if ( m_pSession->GetXrSession() == XR_NULL_HANDLE ) + return XR_ERROR_CALL_ORDER_INVALID; + + // Create swapchains + XR_RETURN_ON_ERROR( CreateSwapchains( unTextureFaceCount, unTextureMipCount ) ); + XR_RETURN_ON_ERROR( CreateSwapchainImages( m_vecSwapchainColorImages, m_xrColorSwapchain ) ); + XR_RETURN_ON_ERROR( CreateSwapchainImages( m_vecSwapchainDepthImages, m_xrDepthSwapchain ) ); + + // Create command pools + VkCommandPoolCreateInfo commandPoolCI { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; + commandPoolCI.queueFamilyIndex = GetAppSession()->GetVulkan()->GetVkQueueIndex_GraphicsFamily(); + commandPoolCI.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + VkResult result = vkCreateCommandPool( GetLogicalDevice(), &commandPoolCI, nullptr, &m_vkRenderCommandPool ); + assert( result == VK_SUCCESS ); + + commandPoolCI.queueFamilyIndex = GetAppSession()->GetVulkan()->GetVkQueueIndex_TransferFamily(); + commandPoolCI.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + result = vkCreateCommandPool( GetLogicalDevice(), &commandPoolCI, nullptr, &m_vkTransferCommandPool ); + assert( result == VK_SUCCESS ); + + return XR_SUCCESS; + } + + XrResult CStereoRender::CreateSwapchains( uint32_t unFaceCount, uint32_t unMipCount ) + { + assert( m_pSession->GetVulkan()->GetVkPhysicalDevice() != VK_NULL_HANDLE ); + + // (1) Update eye view configurations + uint32_t unEyeConfigNum = 0; + m_vecEyeConfigs.clear(); + + // Get number of configuration views for this session + XR_RETURN_ON_ERROR( xrEnumerateViewConfigurationViews( + GetAppInstance()->GetXrInstance(), + GetAppInstance()->GetXrSystemId(), + XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, + unEyeConfigNum, + &unEyeConfigNum, + nullptr ) ); + + if ( unEyeConfigNum != k_EyeCount ) + return XR_ERROR_VALIDATION_FAILURE; + + // Update eye view configurations + m_vecEyeConfigs.resize( unEyeConfigNum, { XR_TYPE_VIEW_CONFIGURATION_VIEW } ); + XR_RETURN_ON_ERROR( xrEnumerateViewConfigurationViews( + GetAppInstance()->GetXrInstance(), + GetAppInstance()->GetXrSystemId(), + XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, + unEyeConfigNum, + &unEyeConfigNum, + m_vecEyeConfigs.data() ) ); + + + // (2) Create eye views that will contain up to date fov, pose, etc + m_vecEyeViews.resize( unEyeConfigNum, { XR_TYPE_VIEW } ); + + // (3) Validate image array support + VkPhysicalDeviceProperties vkPhysicalDeviceProps; + vkGetPhysicalDeviceProperties( m_pSession->GetVulkan()->GetVkPhysicalDevice(), &vkPhysicalDeviceProps ); + + if ( vkPhysicalDeviceProps.limits.maxImageArrayLayers < k_EyeCount ) + { + LogError( "", "Device does not support image arrays." ); + return XR_ERROR_VALIDATION_FAILURE; + } + + // (4) Create color swapchain + XrSwapchainCreateInfo xrSwapchainCreateInfo { XR_TYPE_SWAPCHAIN_CREATE_INFO }; + xrSwapchainCreateInfo.format = m_vkColorFormat; + xrSwapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT; + xrSwapchainCreateInfo.arraySize = k_EyeCount; + xrSwapchainCreateInfo.width = m_unTextureWidth = m_vecEyeConfigs[ 0 ].recommendedImageRectWidth; + xrSwapchainCreateInfo.height = m_unTextureHeight = m_vecEyeConfigs[ 0 ].recommendedImageRectHeight; + xrSwapchainCreateInfo.mipCount = unMipCount; + xrSwapchainCreateInfo.faceCount = unFaceCount; + xrSwapchainCreateInfo.sampleCount = m_vecEyeConfigs[ 0 ].recommendedSwapchainSampleCount; + + XR_RETURN_ON_ERROR( xrCreateSwapchain( m_pSession->GetXrSession(), &xrSwapchainCreateInfo, &m_xrColorSwapchain ) ); + + // (5) Create depth swapchain + xrSwapchainCreateInfo.format = m_vkDepthFormat; + xrSwapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + + XR_RETURN_ON_ERROR( xrCreateSwapchain( m_pSession->GetXrSession(), &xrSwapchainCreateInfo, &m_xrDepthSwapchain ) ); + + return XR_SUCCESS; + } + + XrResult CStereoRender::CreateSwapchainImages( std::vector< XrSwapchainImageVulkan2KHR > &outSwapchainImages, XrSwapchain xrSwapchain ) + { + // Validate swpachain + if ( xrSwapchain == XR_NULL_HANDLE ) + return XR_ERROR_CALL_ORDER_INVALID; + + // Determine number of swapchain images generated by the runtime + uint32_t unNumOfSwapchainImages = 0; + XR_RETURN_ON_ERROR( xrEnumerateSwapchainImages( xrSwapchain, unNumOfSwapchainImages, &unNumOfSwapchainImages, nullptr ) ); + + // Create swapchain images + outSwapchainImages.clear(); + outSwapchainImages.resize( unNumOfSwapchainImages, { XR_TYPE_SWAPCHAIN_IMAGE_VULKAN2_KHR } ); + + XR_RETURN_ON_ERROR ( xrEnumerateSwapchainImages( + xrSwapchain, + unNumOfSwapchainImages, + &unNumOfSwapchainImages, + reinterpret_cast< XrSwapchainImageBaseHeader * >( outSwapchainImages.data() ) ) ); + + // Log swapchain creation + if ( CheckLogLevelVerbose( GetMinLogLevel() ) ) + LogVerbose( "", "Swapchain created with %i images.", unNumOfSwapchainImages ); + + return XR_SUCCESS; + } + + XrResult CStereoRender::InitDefaultMultiviewRendering( VkRenderPass &outRenderPass ) + { + XR_RETURN_ON_ERROR( PrepareDefaultMultiviewRendering() ); + XR_RETURN_ON_ERROR( AddDefaultMultiviewRenderPass() ); + + outRenderPass = vecRenderPasses.back(); + XR_RETURN_ON_ERROR( CreateDefaultMultiviewFramebuffers( outRenderPass ) ); + + return XR_SUCCESS; + } + + XrResult CStereoRender::PrepareDefaultMultiviewRendering() + { + XR_RETURN_ON_ERROR( CreateMultiviewRenderTargets() ); + + VkSamplerCreateInfo samplerCI = GenerateImageSamplerCI(); + XR_RETURN_ON_ERROR( CreateRenderTargetSamplers( samplerCI ) ); + + return XR_SUCCESS; + } + + XrResult CStereoRender::CreateMultiviewRenderTargets( + VkImageViewCreateFlags colorCreateFlags, + VkImageViewCreateFlags depthCreateFlags, + void *pColorNext, + void *pDepthNext, + VkAllocationCallbacks *pCallbacks ) + { + // Call order validation + if ( m_vecSwapchainColorImages.empty() || m_vecSwapchainDepthImages.empty() ) + return XR_ERROR_CALL_ORDER_INVALID; + + if ( GetLogicalDevice() == VK_NULL_HANDLE ) + return XR_ERROR_CALL_ORDER_INVALID; + + if ( m_vkRenderCommandPool == VK_NULL_HANDLE ) + return XR_ERROR_CALL_ORDER_INVALID; + + // Swapchain image count + uint32_t unSwapchainImageCount = (uint32_t) m_vecSwapchainColorImages.size(); + if ( unSwapchainImageCount != (uint32_t) m_vecSwapchainDepthImages.size() ) + { + LogError( "", "Error creating multiview render targets: color & depth swpachin must be of equal length!" ); + return XR_ERROR_VALIDATION_FAILURE; + } + + // Create render targets + for ( uint32_t i = 0; i < unSwapchainImageCount; i++ ) + { + m_vecMultiviewRenderTargets.push_back( {} ); + m_vecMultiviewRenderTargets.back().vkColorTexture = m_vecSwapchainColorImages[ i ].image; + + // Color image view + VkImageViewCreateInfo imageViewCI { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; + imageViewCI.pNext = pColorNext; + imageViewCI.flags = colorCreateFlags; + imageViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; + imageViewCI.format = m_vkColorFormat; + + imageViewCI.subresourceRange = {}; + imageViewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageViewCI.subresourceRange.baseMipLevel = 0; + imageViewCI.subresourceRange.levelCount = 1; + imageViewCI.subresourceRange.baseArrayLayer = 0; + imageViewCI.subresourceRange.layerCount = k_EyeCount; + + imageViewCI.image = m_vecMultiviewRenderTargets.back().vkColorTexture; + + VkResult result = vkCreateImageView( + GetLogicalDevice(), + &imageViewCI, + pCallbacks, + &m_vecMultiviewRenderTargets.back().vkColorImageDescriptor.imageView ); + assert( result == VK_SUCCESS ); + + // Depth image view + m_vecMultiviewRenderTargets.back().vkDepthTexture = m_vecSwapchainDepthImages[ i ].image; + + imageViewCI.pNext = NULL; + imageViewCI.flags = depthCreateFlags; + imageViewCI.format = m_vkDepthFormat; + imageViewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + + imageViewCI.image = m_vecMultiviewRenderTargets.back().vkDepthTexture; + + result = vkCreateImageView( + GetLogicalDevice(), + &imageViewCI, + pCallbacks, + &m_vecMultiviewRenderTargets.back().vkDepthImageView ); + assert( result == VK_SUCCESS ); + + // Command buffers + VkCommandBufferAllocateInfo commandBufferAlloc { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; + commandBufferAlloc.pNext = nullptr; + commandBufferAlloc.commandPool = m_vkRenderCommandPool; + commandBufferAlloc.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + commandBufferAlloc.commandBufferCount = 1; + result = vkAllocateCommandBuffers( GetLogicalDevice(), &commandBufferAlloc, &m_vecMultiviewRenderTargets.back().vkRenderCommandBuffer ); + assert( result == VK_SUCCESS ); + + commandBufferAlloc.commandPool = m_vkTransferCommandPool; + result = vkAllocateCommandBuffers( GetLogicalDevice(), &commandBufferAlloc, &m_vecMultiviewRenderTargets.back().vkTransferCommandBuffer ); + assert( result == VK_SUCCESS ); + + // Fences + VkFenceCreateInfo fenceCI { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO }; + result = vkCreateFence( GetLogicalDevice(), &fenceCI, nullptr, &m_vecMultiviewRenderTargets.back().vkRenderCommandFence ); + assert( result == VK_SUCCESS ); + + result = vkCreateFence( GetLogicalDevice(), &fenceCI, nullptr, &m_vecMultiviewRenderTargets.back().vkTransferCommandFence ); + assert( result == VK_SUCCESS ); + } + + return XR_SUCCESS; + } + + XrResult CStereoRender::CreateRenderTargetSamplers( VkSamplerCreateInfo &samplerCI, VkAllocationCallbacks *pCallbacks ) + { + if ( m_vecMultiviewRenderTargets.size() != m_vecSwapchainColorImages.size() ) + return XR_ERROR_CALL_ORDER_INVALID; + + if ( GetLogicalDevice() == VK_NULL_HANDLE ) + return XR_ERROR_CALL_ORDER_INVALID; + + if ( samplerCI.sType != VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO ) + return XR_ERROR_VALIDATION_FAILURE; + + for ( auto &renderTarget : m_vecMultiviewRenderTargets ) + { + VkResult result = vkCreateSampler( GetLogicalDevice(), &samplerCI, pCallbacks, &renderTarget.vkColorImageDescriptor.sampler ); + assert( result == VK_SUCCESS ); + } + + return XR_SUCCESS; + } + + XrResult CStereoRender::AddDefaultMultiviewRenderPass() + { + if ( m_vecMultiviewRenderTargets.size() != m_vecSwapchainColorImages.size() ) + return XR_ERROR_CALL_ORDER_INVALID; + + if ( GetLogicalDevice() == VK_NULL_HANDLE ) + return XR_ERROR_CALL_ORDER_INVALID; + + // Define subpass + std::vector< VkSubpassDescription > vecSubpassDescriptions = { GenerateSubpassDescription() }; + std::vector< VkSubpassDependency > vecSubpassDependencies; + + vecSubpassDependencies.push_back( {} ); + vecSubpassDependencies.back().srcSubpass = VK_SUBPASS_EXTERNAL; + vecSubpassDependencies.back().dstSubpass = 0; + vecSubpassDependencies.back().srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + vecSubpassDependencies.back().dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + vecSubpassDependencies.back().srcAccessMask = VK_ACCESS_MEMORY_READ_BIT; + vecSubpassDependencies.back().dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + vecSubpassDependencies.back().dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; + + vecSubpassDependencies.push_back( {} ); + vecSubpassDependencies.back().srcSubpass = 0; + vecSubpassDependencies.back().dstSubpass = VK_SUBPASS_EXTERNAL; + vecSubpassDependencies.back().srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + vecSubpassDependencies.back().dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + vecSubpassDependencies.back().srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + vecSubpassDependencies.back().dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; + vecSubpassDependencies.back().dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; + + // Create renderpass + std::vector< VkAttachmentDescription > vecAtachmentDescriptions = { GenerateColorAttachmentDescription(), GenerateDepthAttachmentDescription() }; + VkRenderPassCreateInfo renderPassCI = GenerateRenderPassCI( vecAtachmentDescriptions, vecSubpassDescriptions, vecSubpassDependencies ); + + // Add multiview to renderpass + VkRenderPassMultiviewCreateInfo multiviewCI = GenerateMultiviewCI(); + renderPassCI.pNext = &multiviewCI; + + assert( AddRenderPass( &renderPassCI ) == VK_SUCCESS ); + return XR_SUCCESS; + } + + XrResult CStereoRender::CreateDefaultMultiviewFramebuffers( VkRenderPass vkRenderPass ) + { + assert( vkRenderPass != VK_NULL_HANDLE ); + + if ( m_vecMultiviewRenderTargets.empty() ) + return XR_ERROR_CALL_ORDER_INVALID; + + if ( GetLogicalDevice() == VK_NULL_HANDLE ) + return XR_ERROR_CALL_ORDER_INVALID; + + for ( auto &renderTarget : m_vecMultiviewRenderTargets ) + { + std::array< VkImageView, 2 > arrImageViews { VK_NULL_HANDLE, VK_NULL_HANDLE }; + renderTarget.SetImageViewArray( arrImageViews ); + + VkFramebufferCreateInfo frameBufferCI = GenerateMultiviewFrameBufferCI( arrImageViews, vkRenderPass ); + VkResult result = vkCreateFramebuffer( GetLogicalDevice(), &frameBufferCI, nullptr, &renderTarget.vkFrameBuffer); + assert( result == VK_SUCCESS ); + } + + return XR_SUCCESS; + } + + VkAttachmentDescription CStereoRender::GenerateColorAttachmentDescription() + { + VkAttachmentDescription outDesc {}; + outDesc.format = m_vkColorFormat; + outDesc.samples = VK_SAMPLE_COUNT_1_BIT; + outDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + outDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + outDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + outDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + outDesc.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + outDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + return outDesc; + } + + VkAttachmentDescription CStereoRender::GenerateDepthAttachmentDescription() + { + VkAttachmentDescription outDesc {}; + outDesc.format = m_vkDepthFormat; + outDesc.samples = VK_SAMPLE_COUNT_1_BIT; + outDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + outDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + outDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + outDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + outDesc.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + outDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + return outDesc; + } + + VkSubpassDescription CStereoRender::GenerateSubpassDescription() + { + VkSubpassDescription outDesc {}; + outDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + outDesc.pDepthStencilAttachment = GetDepthAttachmentReference(); + outDesc.colorAttachmentCount = 1; + outDesc.pColorAttachments = GetColorAttachmentReference(); + + return outDesc; + } + + VkRenderPassMultiviewCreateInfo CStereoRender::GenerateMultiviewCI() + { + VkRenderPassMultiviewCreateInfo multiviewCI { VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO }; + multiviewCI.pNext = nullptr; + multiviewCI.subpassCount = 1; + + multiviewCI.pViewMasks = &this->k_unStereoViewMask; + multiviewCI.correlationMaskCount = 1; + multiviewCI.pCorrelationMasks = &this->k_unStereoConcurrentMask; + + multiviewCI.dependencyCount = 0; + multiviewCI.pViewOffsets = nullptr; + + return multiviewCI; + } + + VkRenderPassCreateInfo + CStereoRender::GenerateRenderPassCI( + std::vector< VkAttachmentDescription > &vecAttachmentDescriptions, + std::vector< VkSubpassDescription > &vecSubpassDescriptions, + std::vector< VkSubpassDependency > &vecSubpassDependencies, + const VkRenderPassCreateFlags vkCreateFlags, + const void *pNext ) + { + VkRenderPassCreateInfo renderPassCI { VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO }; + renderPassCI.pNext = pNext; + renderPassCI.flags = vkCreateFlags; + renderPassCI.attachmentCount = vecAttachmentDescriptions.empty() ? 0 : (uint32_t) vecAttachmentDescriptions.size(); + renderPassCI.pAttachments = vecAttachmentDescriptions.empty() ? nullptr : vecAttachmentDescriptions.data(); + renderPassCI.subpassCount = vecSubpassDescriptions.empty() ? 0 : (uint32_t) vecSubpassDescriptions.size(); + renderPassCI.pSubpasses = vecSubpassDescriptions.empty() ? nullptr : vecSubpassDescriptions.data(); + renderPassCI.dependencyCount = vecSubpassDependencies.empty() ? 0 : (uint32_t) vecSubpassDependencies.size(); + renderPassCI.pDependencies = vecSubpassDependencies.empty() ? nullptr : vecSubpassDependencies.data(); + + return renderPassCI; + } + + VkFramebufferCreateInfo CStereoRender::GenerateMultiviewFrameBufferCI( + std::array< VkImageView, 2 > &arrImageViews, + VkRenderPass vkRenderPass, + VkFramebufferCreateFlags vkCreateFlags, + const void *pNext ) + { + assert( vkRenderPass != VK_NULL_HANDLE ); + + VkFramebufferCreateInfo frameBufferCI { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO }; + frameBufferCI.pNext = pNext; + frameBufferCI.flags = vkCreateFlags; + frameBufferCI.width = m_unTextureWidth; + frameBufferCI.height = m_unTextureHeight; + frameBufferCI.renderPass = vkRenderPass; + frameBufferCI.attachmentCount = (uint32_t) arrImageViews.size(); + frameBufferCI.pAttachments = arrImageViews.data(); + frameBufferCI.layers = 1; // must be one when using multiview + + return frameBufferCI; + } + + VkSamplerCreateInfo CStereoRender::GenerateImageSamplerCI( VkSamplerCreateFlags flags, void *pNext ) + { + VkSamplerCreateInfo samplerCI { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO }; + samplerCI.pNext = pNext; + samplerCI.flags = flags; + samplerCI.magFilter = VK_FILTER_LINEAR; + samplerCI.minFilter = VK_FILTER_LINEAR; + samplerCI.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerCI.addressModeV = samplerCI.addressModeU; + samplerCI.addressModeW = samplerCI.addressModeU; + samplerCI.mipLodBias = 0.0f; + samplerCI.minLod = 0.0f; + samplerCI.maxLod = 1.0f; + samplerCI.anisotropyEnable = VK_TRUE; + samplerCI.maxAnisotropy = 1.0f; + samplerCI.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + + return samplerCI; + } + + VkPipelineColorBlendAttachmentState CStereoRender::GenerateColorBlendAttachment() + { + VkPipelineColorBlendAttachmentState colorBlendAttachment {}; + colorBlendAttachment.blendEnable = 0; + colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; + colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; + colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + + return colorBlendAttachment; + } + + VkPipelineLayoutCreateInfo CStereoRender::GeneratePipelineLayoutCI( + std::vector< VkPushConstantRange > &vecPushConstantRanges, + std::vector< VkDescriptorSetLayout > &vecDescriptorSetLayouts, + VkPipelineLayoutCreateFlags createFlags, + void *pNext ) + { + VkPipelineLayoutCreateInfo pipelineCI { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO }; + pipelineCI.pNext = pNext; + pipelineCI.flags = createFlags; + pipelineCI.pushConstantRangeCount = vecPushConstantRanges.empty() ? 0 : (uint32_t) vecPushConstantRanges.size(); + pipelineCI.pPushConstantRanges = vecPushConstantRanges.empty() ? nullptr : vecPushConstantRanges.data(); + pipelineCI.setLayoutCount = vecDescriptorSetLayouts.empty() ? 0 : (uint32_t) vecDescriptorSetLayouts.size(); + pipelineCI.pSetLayouts = vecDescriptorSetLayouts.empty() ? nullptr : vecDescriptorSetLayouts.data(); + + return pipelineCI; + } + + VkPipelineVertexInputStateCreateInfo CStereoRender::GeneratePipelineStateCI_VertexInput( + std::vector< VkVertexInputBindingDescription > &vecVertexBindingDescriptions, + std::vector< VkVertexInputAttributeDescription > &vecVertexAttributeDescriptions, + VkPipelineVertexInputStateCreateFlags createFlags, + void *pNext ) + { + VkPipelineVertexInputStateCreateInfo outVertexInputCI { VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO }; + outVertexInputCI.pNext = pNext; + outVertexInputCI.flags = createFlags; + outVertexInputCI.vertexBindingDescriptionCount = vecVertexBindingDescriptions.empty() ? 0 : (uint32_t) vecVertexBindingDescriptions.size(); + outVertexInputCI.pVertexBindingDescriptions = vecVertexBindingDescriptions.empty() ? nullptr : vecVertexBindingDescriptions.data(); + outVertexInputCI.vertexAttributeDescriptionCount = vecVertexAttributeDescriptions.empty() ? 0 : (uint32_t) vecVertexAttributeDescriptions.size(); + outVertexInputCI.pVertexAttributeDescriptions = vecVertexAttributeDescriptions.empty() ? nullptr : vecVertexAttributeDescriptions.data(); + + return outVertexInputCI; + } + + VkPipelineInputAssemblyStateCreateInfo CStereoRender::GeneratePipelineStateCI_Assembly( + VkPrimitiveTopology topology, + VkBool32 primitiveRestartEnable, + VkPipelineInputAssemblyStateCreateFlags createFlags, + void *pNext ) + { + VkPipelineInputAssemblyStateCreateInfo outInputAssemblyCI { VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO }; + outInputAssemblyCI.pNext = pNext; + outInputAssemblyCI.flags = createFlags; + outInputAssemblyCI.topology = topology; + outInputAssemblyCI.primitiveRestartEnable = primitiveRestartEnable; + + return outInputAssemblyCI; + } + + VkPipelineTessellationStateCreateInfo CStereoRender::GeneratePipelineStateCI_TesselationCI( + uint32_t patchControlPoints, + VkPipelineTessellationStateCreateFlags createFlags, + void *pNext ) + { + VkPipelineTessellationStateCreateInfo tesselationCI { VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO }; + tesselationCI.pNext = pNext; + tesselationCI.flags = createFlags; + tesselationCI.patchControlPoints = patchControlPoints; + + return tesselationCI; + } + + VkPipelineViewportStateCreateInfo CStereoRender::GeneratePipelineStateCI_ViewportCI( + std::vector< VkViewport > &vecViewports, + std::vector< VkRect2D > &vecScissors, + VkPipelineViewportStateCreateFlags createFlags, void *pNext ) + { + VkPipelineViewportStateCreateInfo viewportCI { VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO }; + viewportCI.pNext = pNext; + viewportCI.flags = createFlags; + viewportCI.viewportCount = vecViewports.empty() ? 0 : (uint32_t) vecViewports.size(); + viewportCI.pViewports = vecViewports.empty() ? nullptr : vecViewports.data(); + viewportCI.scissorCount = vecScissors.empty() ? 0 : (uint32_t) vecScissors.size(); + viewportCI.pScissors = vecScissors.empty() ? nullptr : vecScissors.data(); + + return viewportCI; + } + + VkPipelineRasterizationStateCreateInfo CStereoRender::GeneratePipelineStateCI_RasterizationCI( + VkPolygonMode polygonMode, + VkCullModeFlags cullMode, + VkFrontFace frontFace, + float lineWidth, + VkBool32 depthClampEnable, + float depthBiasClamp, + VkBool32 depthBiasEnable, + float depthBiasConstantFactor, + float depthBiasSlopeFactor, + VkBool32 rasterizerDiscardEnable, + VkPipelineRasterizationStateCreateFlags createFlags, + void *pNext ) + { + VkPipelineRasterizationStateCreateInfo rasterizationCI { VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO }; + rasterizationCI.pNext = pNext; + rasterizationCI.flags = createFlags; + rasterizationCI.polygonMode = polygonMode; + rasterizationCI.cullMode = cullMode; + rasterizationCI.frontFace = frontFace; + rasterizationCI.lineWidth = lineWidth; + rasterizationCI.depthClampEnable = depthClampEnable; + rasterizationCI.depthBiasEnable = depthBiasEnable; + rasterizationCI.depthBiasClamp = depthBiasClamp; + rasterizationCI.depthBiasConstantFactor = depthBiasConstantFactor; + rasterizationCI.depthBiasSlopeFactor = depthBiasSlopeFactor; + rasterizationCI.rasterizerDiscardEnable = rasterizerDiscardEnable; + + return rasterizationCI; + } + + VkPipelineMultisampleStateCreateInfo CStereoRender::GeneratePipelineStateCI_MultisampleCI( + VkSampleCountFlagBits rasterizationSamples, + VkBool32 sampleShadingEnable, + float minSampleShading, + VkSampleMask *pSampleMask, + VkBool32 alphaToCoverageEnable, + VkBool32 alphaToOneEnable, + VkPipelineMultisampleStateCreateFlags createFlags, + void *pNext ) + { + VkPipelineMultisampleStateCreateInfo multisampleCI { VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO }; + multisampleCI.pNext = pNext; + multisampleCI.flags = createFlags; + multisampleCI.rasterizationSamples = rasterizationSamples; + multisampleCI.sampleShadingEnable = sampleShadingEnable; + multisampleCI.minSampleShading = minSampleShading; + multisampleCI.pSampleMask = pSampleMask; + multisampleCI.alphaToCoverageEnable = alphaToCoverageEnable; + multisampleCI.alphaToOneEnable = alphaToOneEnable; + + return multisampleCI; + } + + VkPipelineDepthStencilStateCreateInfo CStereoRender::GeneratePipelineStateCI_DepthStencilCI( + VkBool32 depthTestEnable, + VkBool32 depthWriteEnable, + VkCompareOp depthCompareOp, + VkBool32 depthBoundsTestEnable, + VkBool32 stencilTestEnable, + VkStencilOpState front, + VkStencilOpState back, + float minDepthBounds, + float maxDepthBounds, + VkPipelineDepthStencilStateCreateFlags createFlags, + void *pNext ) + { + VkPipelineDepthStencilStateCreateInfo depthStencilCI { VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO }; + depthStencilCI.pNext = pNext; + depthStencilCI.flags = createFlags; + depthStencilCI.depthTestEnable = depthTestEnable; + depthStencilCI.depthWriteEnable = depthWriteEnable; + depthStencilCI.depthCompareOp = depthCompareOp; + depthStencilCI.depthBoundsTestEnable = depthBoundsTestEnable; + depthStencilCI.stencilTestEnable = stencilTestEnable; + depthStencilCI.front = front; + depthStencilCI.back = back; + depthStencilCI.minDepthBounds = minDepthBounds; + depthStencilCI.maxDepthBounds = maxDepthBounds; + + return depthStencilCI; + } + + VkPipelineColorBlendStateCreateInfo CStereoRender::GeneratePipelineStateCI_ColorBlendCI( + std::vector< VkPipelineColorBlendAttachmentState > &vecColorBlenAttachmentStates, + VkBool32 logicOpEnable, + VkLogicOp logicOp, + VkPipelineColorBlendStateCreateFlags createFlags, + void *pNext ) + { + VkPipelineColorBlendStateCreateInfo colorBlendCI { VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO }; + colorBlendCI.pNext = pNext; + colorBlendCI.flags = createFlags; + colorBlendCI.attachmentCount = vecColorBlenAttachmentStates.empty() ? 0 : (uint32_t)vecColorBlenAttachmentStates.size(); + colorBlendCI.pAttachments = vecColorBlenAttachmentStates.empty() ? nullptr : vecColorBlenAttachmentStates.data(); + colorBlendCI.logicOpEnable = logicOpEnable; + colorBlendCI.logicOp = logicOp; + + return colorBlendCI; + } + + VkPipelineDynamicStateCreateInfo CStereoRender::GeneratePipelineStateCI_DynamicStateCI( + std::vector< VkDynamicState > &vecDynamicStates, + VkPipelineDynamicStateCreateFlags createFlags, + void *pNext ) + { + VkPipelineDynamicStateCreateInfo dynamicStateCI { VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO }; + dynamicStateCI.pNext = pNext; + dynamicStateCI.flags = createFlags; + dynamicStateCI.dynamicStateCount = vecDynamicStates.empty() ? 0 : (uint32_t) vecDynamicStates.size(); + dynamicStateCI.pDynamicStates = vecDynamicStates.empty() ? nullptr : vecDynamicStates.data(); + + return dynamicStateCI; + } + + VkResult CStereoRender::CreateGraphicsPipeline( + VkPipeline &outPipeline, + VkPipelineLayout pipelineLayout, + VkRenderPass renderPass, + std::vector< VkPipelineShaderStageCreateInfo > &vecShaderStages, + const VkPipelineVertexInputStateCreateInfo *pVertexInputCI, + const VkPipelineInputAssemblyStateCreateInfo *pInputAssemblyCI, + const VkPipelineTessellationStateCreateInfo *pTessellationCI, + const VkPipelineViewportStateCreateInfo *pViewportStateCI, + const VkPipelineRasterizationStateCreateInfo *pRasterizationCI, + const VkPipelineMultisampleStateCreateInfo *pMultisampleCI, + const VkPipelineDepthStencilStateCreateInfo *pDepthStencilCI, + const VkPipelineColorBlendStateCreateInfo *pColorBlendCI, + const VkPipelineDynamicStateCreateInfo *pDynamicStateCI, + const VkPipelineCache pipelineCache, + const uint32_t unSubpassIndex, + const VkPipelineCreateFlags createFlags, + const void *pNext, + const VkAllocationCallbacks *pCallbacks ) + { + assert( GetLogicalDevice() != VK_NULL_HANDLE ); + + VkGraphicsPipelineCreateInfo pipelineCI { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; + pipelineCI.pNext = pNext; + pipelineCI.flags = createFlags; + + pipelineCI.layout = pipelineLayout; + pipelineCI.renderPass = renderPass; + pipelineCI.subpass = unSubpassIndex; + + pipelineCI.stageCount = (uint32_t) vecShaderStages.size(); + pipelineCI.pStages = vecShaderStages.data(); + pipelineCI.pVertexInputState = pVertexInputCI; + pipelineCI.pInputAssemblyState = pInputAssemblyCI; + pipelineCI.pTessellationState = pTessellationCI; + pipelineCI.pViewportState = pViewportStateCI; + pipelineCI.pRasterizationState = pRasterizationCI; + pipelineCI.pMultisampleState = pMultisampleCI; + pipelineCI.pDepthStencilState = pDepthStencilCI; + pipelineCI.pColorBlendState = pColorBlendCI; + pipelineCI.pDynamicState = pDynamicStateCI; + + return vkCreateGraphicsPipelines( GetLogicalDevice(), pipelineCache, 1, &pipelineCI, pCallbacks, &outPipeline ); + } + + VkResult CStereoRender::AddRenderPass( VkRenderPassCreateInfo *renderPassCI, VkAllocationCallbacks *pAllocator ) + { + assert( renderPassCI ); + assert( renderPassCI->sType == VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO ); + + vecRenderPasses.push_back( VK_NULL_HANDLE ); + + VkResult result = vkCreateRenderPass( GetLogicalDevice(), renderPassCI, nullptr, &vecRenderPasses.back() ); + if ( result != VK_SUCCESS ) + { + vecRenderPasses.pop_back(); + return result; + } + + return VK_SUCCESS; + } + + void CStereoRender::BeginDraw( + const uint32_t unSwpachainImageIndex, + std::vector< VkClearValue > &vecClearValues, + const bool startCommandBufferRecording, + const VkRenderPass renderpass, + const VkSubpassContents subpass) + { + // @todo - debug assert swapchain image index vs size of render targets + + if ( startCommandBufferRecording ) + { + // Set command buffer to recording + VkCommandBufferBeginInfo cmdBeginInfo { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + vkBeginCommandBuffer( m_vecMultiviewRenderTargets[ unSwpachainImageIndex ].vkRenderCommandBuffer, &cmdBeginInfo ); + // @todo assert on VkResult for debug only + } + + if ( renderpass != VK_NULL_HANDLE ) + { + // Set render pass begin info + VkRenderPassBeginInfo renderPassBeginInfo { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO }; + renderPassBeginInfo.clearValueCount = (uint32_t) vecClearValues.size(); + renderPassBeginInfo.pClearValues = vecClearValues.data(); + renderPassBeginInfo.renderPass = renderpass; + renderPassBeginInfo.framebuffer = m_vecMultiviewRenderTargets[ unSwpachainImageIndex ].vkFrameBuffer; + renderPassBeginInfo.renderArea.offset = { 0, 0 }; + renderPassBeginInfo.renderArea.extent = GetTextureExtent(); + + // (3) Start render pass + vkCmdBeginRenderPass( m_vecMultiviewRenderTargets[ unSwpachainImageIndex ].vkRenderCommandBuffer, &renderPassBeginInfo, subpass ); + } + } + + void CStereoRender::SubmitDraw( + const uint32_t unSwpachainImageIndex, + std::vector< CDeviceBuffer * > &vecStagingBuffers, + const uint32_t timeoutNs, + const VkCommandBufferResetFlags transferBufferResetFlags, const VkCommandBufferResetFlags renderBufferResetFlags ) + { + // End render recording + vkCmdEndRenderPass( m_vecMultiviewRenderTargets[ unSwpachainImageIndex ].vkRenderCommandBuffer ); + vkEndCommandBuffer( m_vecMultiviewRenderTargets[ unSwpachainImageIndex ].vkRenderCommandBuffer ); + + // Wait for any data transfer operations to finish + // @todo start recording for next frame + vkWaitForFences( GetLogicalDevice(), 1, &m_vecMultiviewRenderTargets[ unSwpachainImageIndex ].vkTransferCommandFence, VK_TRUE, timeoutNs ); + vkResetFences( GetLogicalDevice(), 1, &m_vecMultiviewRenderTargets[ unSwpachainImageIndex ].vkTransferCommandFence ); + vkResetCommandBuffer( m_vecMultiviewRenderTargets[ unSwpachainImageIndex ].vkTransferCommandBuffer, transferBufferResetFlags ); + + // Clear/free staging buffer memory + for ( CDeviceBuffer *pStagingBuffer : vecStagingBuffers ) + delete pStagingBuffer; + + vecStagingBuffers.clear(); + + // Execute render commands (requires exclusive access to vkQueue) + // safest after wait swapchain image + VkSubmitInfo submitInfo { VK_STRUCTURE_TYPE_SUBMIT_INFO }; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &m_vecMultiviewRenderTargets[ unSwpachainImageIndex ].vkRenderCommandBuffer; + vkQueueSubmit( GetAppSession()->GetVulkan()->GetVkQueue_Graphics(), 1, &submitInfo, m_vecMultiviewRenderTargets[ unSwpachainImageIndex ].vkRenderCommandFence ); + + // Wait for rendering to finish + // @todo move to separate thread - or at start unSwapchainIndex from prior frame + vkWaitForFences( GetLogicalDevice(), 1, &m_vecMultiviewRenderTargets[ unSwpachainImageIndex ].vkRenderCommandFence, VK_TRUE, timeoutNs ); + vkResetFences( GetLogicalDevice(), 1, &m_vecMultiviewRenderTargets[ unSwpachainImageIndex ].vkRenderCommandFence ); + vkResetCommandBuffer( m_vecMultiviewRenderTargets[ unSwpachainImageIndex ].vkRenderCommandBuffer, renderBufferResetFlags ); + } + + void CStereoRender::BeginBufferUpdates( const uint32_t unSwpachainImageIndex ) + { + VkCommandBufferBeginInfo beginInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer( m_vecMultiviewRenderTargets[ unSwpachainImageIndex ].vkTransferCommandBuffer, &beginInfo ); + } + + void CStereoRender::SubmitBufferUpdates( const uint32_t unSwpachainImageIndex ) + { + vkEndCommandBuffer( m_vecMultiviewRenderTargets[ unSwpachainImageIndex ].vkTransferCommandBuffer ); + + VkSubmitInfo submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &m_vecMultiviewRenderTargets[ unSwpachainImageIndex ].vkTransferCommandBuffer; + + vkQueueSubmit( GetAppSession()->GetVulkan()->GetVkQueue_Transfer(), 1, &submitInfo, m_vecMultiviewRenderTargets[ unSwpachainImageIndex ].vkTransferCommandFence ); + } + + void CStereoRender::CalculateViewMatrices( std::vector< XrMatrix4x4f > &outViewMatrices, const XrVector3f *eyeScale ) + { + std::vector< XrMatrix4x4f > eyeViews; + eyeViews.resize( outViewMatrices.size() ); + for ( uint32_t i = 0; i < outViewMatrices.size(); i++ ) + { + XrMatrix4x4f_CreateTranslationRotationScale( &eyeViews[ i ], &m_vecEyeViews[ i ].pose.position, &m_vecEyeViews[ i ].pose.orientation, eyeScale ); + XrMatrix4x4f_InvertRigidBody( &outViewMatrices[ i ], &eyeViews[ i ] ); + } + } + + VkPhysicalDevice CStereoRender::GetPhysicalDevice() + { + return m_pSession->GetVulkan()->GetVkPhysicalDevice(); + } + + VkDevice CStereoRender::GetLogicalDevice() + { + return m_pSession->GetVulkan()->GetVkLogicalDevice(); + } + +} // namespace xrlib diff --git a/third_party/openxr b/third_party/openxr new file mode 160000 index 0000000..f90488c --- /dev/null +++ b/third_party/openxr @@ -0,0 +1 @@ +Subproject commit f90488c4fb1537f4256d09d4a4d3ad5543ebaf24