├── .appveyor.yml ├── .clang-format ├── .clang-tidy ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CMakeLists.txt ├── CMakeSettings.json ├── LICENSE ├── README.md ├── cmake └── fx-gltfConfig.cmake.in ├── examples ├── thirdparty │ └── CLI11 │ │ └── CLI11.hpp └── viewer │ ├── .clang-format │ ├── Assets │ ├── Environment │ │ ├── LUT.png │ │ ├── diffuse_negx.png │ │ ├── diffuse_negy.png │ │ ├── diffuse_negz.png │ │ ├── diffuse_posx.png │ │ ├── diffuse_posy.png │ │ ├── diffuse_posz.png │ │ ├── specular_negx_0.png │ │ ├── specular_negx_1.png │ │ ├── specular_negx_2.png │ │ ├── specular_negx_3.png │ │ ├── specular_negx_4.png │ │ ├── specular_negx_5.png │ │ ├── specular_negx_6.png │ │ ├── specular_negy_0.png │ │ ├── specular_negy_1.png │ │ ├── specular_negy_2.png │ │ ├── specular_negy_3.png │ │ ├── specular_negy_4.png │ │ ├── specular_negy_5.png │ │ ├── specular_negy_6.png │ │ ├── specular_negz_0.png │ │ ├── specular_negz_1.png │ │ ├── specular_negz_2.png │ │ ├── specular_negz_3.png │ │ ├── specular_negz_4.png │ │ ├── specular_negz_5.png │ │ ├── specular_negz_6.png │ │ ├── specular_posx_0.png │ │ ├── specular_posx_1.png │ │ ├── specular_posx_2.png │ │ ├── specular_posx_3.png │ │ ├── specular_posx_4.png │ │ ├── specular_posx_5.png │ │ ├── specular_posx_6.png │ │ ├── specular_posy_0.png │ │ ├── specular_posy_1.png │ │ ├── specular_posy_2.png │ │ ├── specular_posy_3.png │ │ ├── specular_posy_4.png │ │ ├── specular_posy_5.png │ │ ├── specular_posy_6.png │ │ ├── specular_posz_0.png │ │ ├── specular_posz_1.png │ │ ├── specular_posz_2.png │ │ ├── specular_posz_3.png │ │ ├── specular_posz_4.png │ │ ├── specular_posz_5.png │ │ └── specular_posz_6.png │ └── ground_plane.gltf │ ├── DirectX │ ├── D3DConstants.h │ ├── D3DDeviceResources.cpp │ ├── D3DDeviceResources.h │ ├── D3DEngine.cpp │ ├── D3DEngine.h │ ├── D3DEnvironmentIBL.cpp │ ├── D3DEnvironmentIBL.h │ ├── D3DFrameResources.cpp │ ├── D3DFrameResources.h │ ├── D3DGraph.h │ ├── D3DMesh.cpp │ ├── D3DMesh.h │ ├── D3DMeshInstance.h │ ├── D3DOrbitCamera.cpp │ ├── D3DOrbitCamera.h │ ├── D3DRenderContext.h │ ├── D3DTexture.cpp │ ├── D3DTexture.h │ ├── D3DTextureSet.cpp │ ├── D3DTextureSet.h │ ├── D3DUploadBuffer.h │ ├── D3DUtil.h │ ├── Shaders │ │ ├── Common.hlsli │ │ ├── Default.hlsl │ │ └── Sky.hlsl │ └── d3dx12.h │ ├── Engine.h │ ├── EngineOptions.h │ ├── ImageData.h │ ├── Logger.h │ ├── MaterialData.h │ ├── MeshData.h │ ├── Platform │ ├── COMUtil.h │ ├── Mouse.cpp │ ├── Mouse.h │ └── platform.h │ ├── README.md │ ├── ShaderOptions.h │ ├── StepTimer.h │ ├── StringFormatter.h │ ├── screenshots │ ├── screenshot00.png │ ├── screenshot01.png │ ├── screenshot02.png │ ├── screenshot03.png │ ├── screenshot04.png │ ├── screenshot05.png │ ├── screenshot06.png │ └── screenshot07.png │ ├── stdafx.cpp │ ├── stdafx.h │ ├── viewer.cpp │ ├── viewer.sln │ └── viewer.vcxproj ├── include └── fx │ └── gltf.h └── test ├── .clang-format ├── CMakeLists.txt ├── CMakeSettings.json ├── data └── unιcode-ρath │ ├── Box.gltf │ └── Box0.bin ├── src ├── unit-base64.cpp ├── unit-exceptions.cpp ├── unit-roundtrip.cpp ├── unit-saveload.cpp ├── unit.cpp ├── utility.cpp └── utility.h └── thirdparty ├── catch2 ├── LICENSE.txt └── catch.hpp └── nlohmann ├── LICENSE.MIT └── json.hpp /.appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | 3 | os: 4 | - Visual Studio 2017 5 | 6 | clone_depth: 10 7 | 8 | environment: 9 | matrix: 10 | - language_standard: "14" 11 | - language_standard: "17" 12 | 13 | init: [] 14 | 15 | install: 16 | - git submodule update --init --recursive 17 | 18 | build_script: 19 | - cmake . -G"Visual Studio 15 2017 Win64" -DFX_GLTF_INSTALL=OFF -DTEST_LanguageStandard="%language_standard%" 20 | - cmake --build . --config Release 21 | 22 | test_script: 23 | - ctest --output-on-failure -C Release -V 24 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | IndentWidth: 4 3 | UseTab: Never 4 | 5 | AccessModifierOffset: -4 6 | AlignAfterOpenBracket: AlwaysBreak 7 | AlignTrailingComments: false 8 | AlignOperands: false 9 | AlwaysBreakTemplateDeclarations: true 10 | AllowAllParametersOfDeclarationOnNextLine: true 11 | AllowShortBlocksOnASingleLine: false 12 | AllowShortFunctionsOnASingleLine: Empty 13 | AllowShortIfStatementsOnASingleLine: false 14 | AllowShortLoopsOnASingleLine: false 15 | 16 | BinPackArguments: false 17 | BinPackParameters: true 18 | 19 | BreakBeforeBraces: Allman 20 | BreakConstructorInitializersBeforeComma: true 21 | 22 | ColumnLimit: 0 23 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 24 | ConstructorInitializerIndentWidth: 4 25 | ContinuationIndentWidth: 4 26 | Cpp11BracedListStyle: false 27 | 28 | IndentCaseLabels: false 29 | IndentPPDirectives: BeforeHash 30 | 31 | MaxEmptyLinesToKeep: 1 32 | NamespaceIndentation: Inner 33 | FixNamespaceComments: true 34 | 35 | DerivePointerAlignment: false 36 | PointerAlignment: Middle 37 | 38 | SortIncludes: true 39 | SpaceBeforeParens: ControlStatements 40 | Standard: Cpp11 41 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | HeaderFilterRegex: .* 2 | 3 | Checks: > 4 | -*, 5 | 6 | bugprone-*, 7 | 8 | cppcoreguidelines-*, 9 | -cppcoreguidelines-avoid-magic-numbers, 10 | -cppcoreguidelines-non-private-member-variables-in-classes, 11 | -cppcoreguidelines-pro-type-reinterpret-cast, 12 | -cppcoreguidelines-macro-usage, 13 | 14 | modernize-*, 15 | -modernize-use-auto, 16 | -modernize-use-trailing-return-type, 17 | 18 | misc-*, 19 | -misc-non-private-member-variables-in-classes, 20 | 21 | readability-*, 22 | -readability-magic-numbers, 23 | -readability-named-parameter, 24 | -readability-uppercase-literal-suffix, 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific files 2 | .vs 3 | *.suo 4 | *.user 5 | *.sln.docstates 6 | 7 | build 8 | 9 | examples/viewer/x64 -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test/data/glTF-Sample-Models"] 2 | path = test/data/glTF-Sample-Models 3 | url = https://github.com/KhronosGroup/glTF-Sample-Models.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | dist: xenial 4 | sudo: required 5 | group: edge 6 | 7 | git: 8 | depth: 10 9 | 10 | matrix: 11 | include: 12 | 13 | # Linux / GCC 14 | 15 | - os: linux 16 | compiler: gcc 17 | env: 18 | - COMPILER=g++-6 19 | - TEST_LANGUAGE_STANDARD=14 20 | addons: 21 | apt: 22 | sources: ['ubuntu-toolchain-r-test'] 23 | packages: ['g++-6', 'ninja-build'] 24 | 25 | - os: linux 26 | compiler: gcc 27 | env: 28 | - COMPILER=g++-7 29 | - TEST_LANGUAGE_STANDARD=17 30 | addons: 31 | apt: 32 | sources: ['ubuntu-toolchain-r-test'] 33 | packages: ['g++-7', 'ninja-build'] 34 | 35 | - os: linux 36 | compiler: gcc 37 | env: 38 | - COMPILER=g++-8 39 | - TEST_LANGUAGE_STANDARD=17 40 | addons: 41 | apt: 42 | sources: ['ubuntu-toolchain-r-test'] 43 | packages: ['g++-8', 'ninja-build'] 44 | 45 | # Linux / Clang 46 | 47 | - os: linux 48 | compiler: clang 49 | env: 50 | - COMPILER=clang++-5.0 51 | - TEST_LANGUAGE_STANDARD=14 52 | addons: 53 | apt: 54 | sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-xenial-5.0'] 55 | packages: ['g++-7', 'clang-5.0', 'ninja-build'] 56 | 57 | - os: linux 58 | compiler: clang 59 | env: 60 | - COMPILER=clang++-6.0 61 | - TEST_LANGUAGE_STANDARD=17 62 | addons: 63 | apt: 64 | sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-xenial-6.0'] 65 | packages: ['g++-7', 'clang-6.0', 'ninja-build'] 66 | 67 | - os: linux 68 | compiler: clang 69 | env: 70 | - COMPILER=clang++-7 71 | - TEST_LANGUAGE_STANDARD=17 72 | addons: 73 | apt: 74 | sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-xenial-7'] 75 | packages: ['g++-8', 'clang-7', 'ninja-build'] 76 | 77 | - os: linux 78 | compiler: clang 79 | env: 80 | - COMPILER=clang++-8 81 | - TEST_LANGUAGE_STANDARD=17 82 | addons: 83 | apt: 84 | sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-xenial-8'] 85 | packages: ['g++-8', 'clang-8', 'ninja-build'] 86 | 87 | 88 | ################ 89 | # build script # 90 | ################ 91 | 92 | script: 93 | # make sure CXX is correctly set 94 | - if [[ "${COMPILER}" != "" ]]; then export CXX=${COMPILER}; fi 95 | 96 | # show OS/compiler version 97 | - uname -a 98 | - $CXX --version 99 | 100 | # compile and execute unit tests 101 | - mkdir -p build && cd build 102 | - cmake .. ${CMAKE_OPTIONS} -DFX_GLTF_INSTALL=OFF -DTEST_LanguageStandard=${TEST_LANGUAGE_STANDARD} -GNinja 103 | - cmake --build . --config Release 104 | - ctest -C Release -V -j 105 | - cd .. 106 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | project(fx-gltf VERSION 2.0.0) 3 | 4 | cmake_policy(SET CMP0054 NEW) 5 | 6 | if (POLICY CMP0077) 7 | cmake_policy(SET CMP0077 NEW) 8 | endif() 9 | 10 | option(FX_GLTF_BUILD_TESTS "If the tests should be built" ON) 11 | option(FX_GLTF_INSTALL "If the library should be installed" ON) 12 | option(FX_GLTF_USE_INSTALLED_DEPS "If installed or repo local dependencies should be used" OFF) 13 | 14 | # Force use of installed deps if installing 15 | if(FX_GLTF_INSTALL) 16 | set(FX_GLTF_USE_INSTALLED_DEPS ON) 17 | endif() 18 | 19 | ## 20 | ## Config 21 | ## 22 | set(FX_GLTF_LIB_TARGET_NAME ${PROJECT_NAME}) 23 | set(FX_GLTF_TARGETS_EXPORT_NAME ${PROJECT_NAME}Targets) 24 | set(FX_GLTF_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include/) 25 | set(FX_GLTF_INSTALL_INCLUDE_DIR ${CMAKE_INSTALL_PREFIX}/include) 26 | set(FX_GLTF_INSTALL_CONFIG_DIR ${CMAKE_INSTALL_PREFIX}/lib/cmake/${PROJECT_NAME}) 27 | set(FX_GLTF_LICENSE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE) 28 | set(FX_GLTF_TARGETS_FILE ${PROJECT_NAME}Targets.cmake) 29 | set(FX_GLTF_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake) 30 | set(FX_GLTF_CONFIG_VERSION_FILE ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake) 31 | set(FX_GLTF_CONFIG_TEMPLATE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/cmake/${PROJECT_NAME}Config.cmake.in) 32 | 33 | ## 34 | ## Library target 35 | ## 36 | add_library(${FX_GLTF_LIB_TARGET_NAME} INTERFACE) 37 | add_library(${PROJECT_NAME}::${FX_GLTF_LIB_TARGET_NAME} ALIAS ${FX_GLTF_LIB_TARGET_NAME}) 38 | target_include_directories(${FX_GLTF_LIB_TARGET_NAME} 39 | INTERFACE 40 | $ 41 | $ 42 | ) 43 | target_compile_features(${FX_GLTF_LIB_TARGET_NAME} INTERFACE cxx_std_14) 44 | 45 | ## Dependencies 46 | if(FX_GLTF_USE_INSTALLED_DEPS) 47 | find_package(nlohmann_json 3.9.1 REQUIRED) 48 | target_link_libraries(${FX_GLTF_LIB_TARGET_NAME} INTERFACE nlohmann_json::nlohmann_json) 49 | endif() 50 | 51 | ## 52 | ## Testing 53 | ## 54 | include(CTest) 55 | 56 | if(FX_GLTF_BUILD_TESTS AND BUILD_TESTING) 57 | enable_testing() 58 | add_subdirectory(test) 59 | endif() 60 | 61 | ## 62 | ## Install 63 | ## 64 | if(FX_GLTF_INSTALL) 65 | include(CMakePackageConfigHelpers) 66 | write_basic_package_version_file( 67 | ${FX_GLTF_CONFIG_VERSION_FILE} 68 | VERSION ${PROJECT_VERSION} 69 | COMPATIBILITY SameMajorVersion 70 | ) 71 | configure_package_config_file( 72 | ${FX_GLTF_CONFIG_TEMPLATE_FILE} 73 | ${FX_GLTF_CONFIG_FILE} 74 | INSTALL_DESTINATION ${FX_GLTF_INSTALL_CONFIG_DIR} 75 | ) 76 | 77 | install( 78 | TARGETS ${FX_GLTF_LIB_TARGET_NAME} 79 | EXPORT ${FX_GLTF_TARGETS_EXPORT_NAME} 80 | ) 81 | install( 82 | DIRECTORY ${FX_GLTF_INCLUDE_DIR} 83 | DESTINATION ${FX_GLTF_INSTALL_INCLUDE_DIR} 84 | ) 85 | install( 86 | EXPORT ${FX_GLTF_TARGETS_EXPORT_NAME} 87 | FILE ${FX_GLTF_TARGETS_FILE} 88 | NAMESPACE ${PROJECT_NAME}:: 89 | DESTINATION ${FX_GLTF_INSTALL_CONFIG_DIR} 90 | ) 91 | install( 92 | FILES 93 | ${FX_GLTF_CONFIG_FILE} 94 | ${FX_GLTF_CONFIG_VERSION_FILE} 95 | DESTINATION ${FX_GLTF_INSTALL_CONFIG_DIR} 96 | ) 97 | install( 98 | FILES ${FX_GLTF_LICENSE_FILE} 99 | DESTINATION ${CMAKE_INSTALL_PREFIX} 100 | ) 101 | 102 | ## 103 | ## Export 104 | ## 105 | export( 106 | EXPORT ${FX_GLTF_TARGETS_EXPORT_NAME} 107 | FILE ${CMAKE_CURRENT_BINARY_DIR}/${FX_GLTF_TARGETS_FILE} 108 | NAMESPACE ${PROJECT_NAME}:: 109 | ) 110 | export(PACKAGE ${PROJECT_NAME}) 111 | endif() 112 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x64-Release", 5 | "generator": "Ninja", 6 | "configurationType": "Release", 7 | "inheritEnvironments": [ 8 | "msvc_x64_x64" 9 | ], 10 | "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", 11 | "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", 12 | "cmakeCommandArgs": "-DFX_GLTF_INSTALL=OFF", 13 | "buildCommandArgs": "-v", 14 | "ctestCommandArgs": "", 15 | "enableClangTidyCodeAnalysis": true 16 | }, 17 | { 18 | "name": "x64-Debug", 19 | "generator": "Ninja", 20 | "configurationType": "Debug", 21 | "inheritEnvironments": [ 22 | "msvc_x64_x64" 23 | ], 24 | "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", 25 | "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", 26 | "cmakeCommandArgs": "-DFX_GLTF_INSTALL=OFF", 27 | "buildCommandArgs": "-v", 28 | "ctestCommandArgs": "", 29 | "enableClangTidyCodeAnalysis": true 30 | }, 31 | { 32 | "name": "x64-Release-ASAN", 33 | "generator": "Ninja", 34 | "configurationType": "RelWithDebInfo", 35 | "inheritEnvironments": [ 36 | "msvc_x64_x64" 37 | ], 38 | "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", 39 | "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", 40 | "cmakeCommandArgs": "-DFX_GLTF_INSTALL=OFF", 41 | "buildCommandArgs": "-v", 42 | "ctestCommandArgs": "", 43 | "enableClangTidyCodeAnalysis": true, 44 | "addressSanitizerEnabled": true 45 | } 46 | ] 47 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2022 Jesse Yurkovich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cmake/fx-gltfConfig.cmake.in: -------------------------------------------------------------------------------- 1 | get_filename_component(fx-gltf_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) 2 | include(CMakeFindDependencyMacro) 3 | 4 | find_dependency(nlohmann_json 3.9.1 REQUIRED) 5 | 6 | if(NOT TARGET fx-gltf::fx-gltf) 7 | include("${fx-gltf_CMAKE_DIR}/fx-gltfTargets.cmake") 8 | endif() 9 | -------------------------------------------------------------------------------- /examples/viewer/.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | IndentWidth: 4 3 | UseTab: Never 4 | 5 | AccessModifierOffset: -4 6 | AlignAfterOpenBracket: AlwaysBreak 7 | AlignTrailingComments: false 8 | AlignOperands: false 9 | AlwaysBreakTemplateDeclarations: true 10 | AllowAllParametersOfDeclarationOnNextLine: true 11 | AllowShortBlocksOnASingleLine: false 12 | AllowShortFunctionsOnASingleLine: Empty 13 | AllowShortIfStatementsOnASingleLine: false 14 | AllowShortLoopsOnASingleLine: false 15 | 16 | BinPackArguments: false 17 | BinPackParameters: true 18 | 19 | BreakBeforeBraces: Allman 20 | BreakConstructorInitializersBeforeComma: true 21 | 22 | ColumnLimit: 0 23 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 24 | ConstructorInitializerIndentWidth: 4 25 | ContinuationIndentWidth: 4 26 | Cpp11BracedListStyle: false 27 | 28 | IndentCaseBlocks: true 29 | IndentCaseLabels: false 30 | IndentPPDirectives: BeforeHash 31 | 32 | MaxEmptyLinesToKeep: 1 33 | NamespaceIndentation: All 34 | FixNamespaceComments: true 35 | 36 | DerivePointerAlignment: false 37 | PointerAlignment: Middle 38 | 39 | SortIncludes: true 40 | SpaceBeforeParens: ControlStatements 41 | Standard: Cpp11 42 | 43 | IncludeCategories: 44 | - Regex: '.*(stdafx).*' 45 | Priority: -2 46 | - Regex: '^<.*' 47 | Priority: -1 48 | - Regex: '^".*' 49 | Priority: 0 50 | -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/LUT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/LUT.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/diffuse_negx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/diffuse_negx.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/diffuse_negy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/diffuse_negy.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/diffuse_negz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/diffuse_negz.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/diffuse_posx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/diffuse_posx.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/diffuse_posy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/diffuse_posy.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/diffuse_posz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/diffuse_posz.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negx_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negx_0.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negx_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negx_1.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negx_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negx_2.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negx_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negx_3.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negx_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negx_4.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negx_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negx_5.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negx_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negx_6.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negy_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negy_0.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negy_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negy_1.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negy_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negy_2.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negy_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negy_3.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negy_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negy_4.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negy_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negy_5.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negy_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negy_6.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negz_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negz_0.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negz_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negz_1.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negz_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negz_2.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negz_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negz_3.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negz_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negz_4.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negz_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negz_5.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_negz_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_negz_6.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posx_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posx_0.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posx_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posx_1.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posx_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posx_2.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posx_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posx_3.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posx_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posx_4.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posx_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posx_5.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posx_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posx_6.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posy_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posy_0.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posy_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posy_1.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posy_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posy_2.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posy_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posy_3.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posy_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posy_4.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posy_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posy_5.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posy_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posy_6.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posz_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posz_0.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posz_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posz_1.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posz_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posz_2.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posz_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posz_3.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posz_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posz_4.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posz_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posz_5.png -------------------------------------------------------------------------------- /examples/viewer/Assets/Environment/specular_posz_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/Assets/Environment/specular_posz_6.png -------------------------------------------------------------------------------- /examples/viewer/Assets/ground_plane.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "accessors" : [ 3 | { 4 | "bufferView" : 0, 5 | "componentType" : 5125, 6 | "count" : 6, 7 | "max" : [ 8 | 3 9 | ], 10 | "min" : [ 11 | 0 12 | ], 13 | "type" : "SCALAR" 14 | }, 15 | { 16 | "bufferView" : 1, 17 | "componentType" : 5126, 18 | "count" : 4, 19 | "max" : [ 20 | 1.0, 21 | 0.0, 22 | 1.0 23 | ], 24 | "min" : [ 25 | -1.0, 26 | 0.0, 27 | -1.0 28 | ], 29 | "type" : "VEC3" 30 | }, 31 | { 32 | "bufferView" : 2, 33 | "componentType" : 5126, 34 | "count" : 4, 35 | "max" : [ 36 | 0.0, 37 | 1.0, 38 | -0.0 39 | ], 40 | "min" : [ 41 | 0.0, 42 | 1.0, 43 | -0.0 44 | ], 45 | "type" : "VEC3" 46 | } 47 | ], 48 | "asset" : { 49 | "generator" : "Khronos Blender glTF 2.0 exporter", 50 | "version" : "2.0" 51 | }, 52 | "bufferViews" : [ 53 | { 54 | "buffer" : 0, 55 | "byteLength" : 24, 56 | "byteOffset" : 0, 57 | "target" : 34963 58 | }, 59 | { 60 | "buffer" : 0, 61 | "byteLength" : 48, 62 | "byteOffset" : 24, 63 | "target" : 34962 64 | }, 65 | { 66 | "buffer" : 0, 67 | "byteLength" : 48, 68 | "byteOffset" : 72, 69 | "target" : 34962 70 | } 71 | ], 72 | "buffers" : [ 73 | { 74 | "byteLength" : 120, 75 | "uri" : "data:application/octet-stream;base64,AAAAAAEAAAACAAAAAAAAAAMAAAABAAAAAACAvwAAAAAAAIA/AACAPwAAAAAAAIC/AACAvwAAAAAAAIC/AACAPwAAAAAAAIA/AAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACA" 76 | } 77 | ], 78 | "meshes" : [ 79 | { 80 | "name" : "Plane", 81 | "primitives" : [ 82 | { 83 | "attributes" : { 84 | "NORMAL" : 2, 85 | "POSITION" : 1 86 | }, 87 | "indices" : 0 88 | } 89 | ] 90 | } 91 | ], 92 | "nodes" : [ 93 | { 94 | "mesh" : 0, 95 | "name" : "Plane" 96 | } 97 | ], 98 | "scene" : 0, 99 | "scenes" : [ 100 | { 101 | "name" : "Scene", 102 | "nodes" : [ 103 | 0 104 | ] 105 | } 106 | ] 107 | } 108 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/D3DConstants.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include 9 | 10 | // The odd ordering of fields is to keep alignment correct 11 | struct Light 12 | { 13 | DirectX::XMFLOAT3 Strength{ 0.5f, 0.5f, 0.5f }; 14 | float FalloffStart{ 1.0f }; // point/spot light only 15 | DirectX::XMFLOAT3 Direction{ 0.0f, -1.0f, 0.0f }; // directional/spot light only 16 | float FalloffEnd{ 10.0f }; // point/spot light only 17 | DirectX::XMFLOAT3 Position{ 0.0f, 0.0f, 0.0f }; // point/spot light only 18 | float SpotPower{ 64.0f }; // spot light only 19 | }; 20 | 21 | struct SceneConstantBuffer 22 | { 23 | DirectX::XMMATRIX ViewProj{}; 24 | DirectX::XMVECTOR Camera{}; 25 | 26 | Light DirectionalLight{}; 27 | Light PointLights[2]{}; 28 | }; 29 | 30 | struct MeshConstantBuffer 31 | { 32 | DirectX::XMMATRIX WorldViewProj{}; 33 | DirectX::XMMATRIX World{}; 34 | 35 | int MaterialIndex{}; 36 | }; 37 | 38 | struct MeshShaderData 39 | { 40 | DirectX::XMFLOAT4 MeshAutoColor{}; 41 | 42 | int BaseColorIndex{}; 43 | DirectX::XMFLOAT4 BaseColorFactor{}; 44 | 45 | int NormalIndex{}; 46 | float NormalScale{}; 47 | 48 | int MetalRoughIndex{}; 49 | float RoughnessFactor{}; 50 | float MetallicFactor{}; 51 | 52 | int AOIndex{}; 53 | float AOStrength{}; 54 | 55 | int EmissiveIndex{}; 56 | DirectX::XMFLOAT3 EmissiveFactor{}; 57 | }; 58 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/D3DDeviceResources.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include "d3dx12.h" 12 | 13 | #include "D3DFrameResources.h" 14 | 15 | // Provides an interface for an application that owns DeviceResources to be notified of the device being lost or created. 16 | struct ID3DDeviceNotify 17 | { 18 | virtual void OnDeviceLost() = 0; 19 | virtual void OnDeviceRestored() = 0; 20 | }; 21 | 22 | // Controls all the DirectX device resources. 23 | class D3DDeviceResources 24 | { 25 | public: 26 | static const unsigned int c_AllowTearing = 0x1; 27 | 28 | D3DDeviceResources(UINT backBufferCount, D3D_FEATURE_LEVEL minFeatureLevel, DXGI_FORMAT backBufferFormat = DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT depthBufferFormat = DXGI_FORMAT_D32_FLOAT, unsigned int flags = 0); 29 | 30 | D3DDeviceResources(D3DDeviceResources const &) = delete; 31 | D3DDeviceResources(D3DDeviceResources &&) = delete; 32 | D3DDeviceResources & operator=(D3DDeviceResources const &) = delete; 33 | D3DDeviceResources & operator=(D3DDeviceResources &&) = delete; 34 | 35 | ~D3DDeviceResources(); 36 | 37 | void CreateDeviceResources(); 38 | void CreateWindowSizeDependentResources(); 39 | void CreateUploadBuffers(std::size_t sceneCount, std::size_t meshCount); 40 | 41 | void SetWindow(HWND window, int width, int height) noexcept; 42 | bool WindowSizeChanged(int width, int height); 43 | void HandleDeviceLost(); 44 | void RegisterDeviceNotify(ID3DDeviceNotify * deviceNotify) noexcept 45 | { 46 | m_deviceNotify = deviceNotify; 47 | } 48 | 49 | void PrepareCommandList(); 50 | void ExecuteCommandList(); 51 | 52 | void Prepare(D3D12_RESOURCE_STATES beforeState = D3D12_RESOURCE_STATE_PRESENT); 53 | void Present(D3D12_RESOURCE_STATES beforeState = D3D12_RESOURCE_STATE_RENDER_TARGET); 54 | void WaitForGpu(); 55 | 56 | // Device Accessors. 57 | RECT GetOutputSize() const noexcept 58 | { 59 | return m_outputSize; 60 | } 61 | 62 | // Direct3D Accessors. 63 | ID3D12Device * GetD3DDevice() const noexcept 64 | { 65 | return m_d3dDevice.Get(); 66 | } 67 | 68 | IDXGISwapChain3 * GetSwapChain() const noexcept 69 | { 70 | return m_swapChain.Get(); 71 | } 72 | 73 | D3D_FEATURE_LEVEL GetDeviceFeatureLevel() const noexcept 74 | { 75 | return m_d3dFeatureLevel; 76 | } 77 | 78 | ID3D12Resource * GetDepthStencil() const noexcept 79 | { 80 | return m_depthStencil.Get(); 81 | } 82 | 83 | ID3D12CommandQueue * GetCommandQueue() const noexcept 84 | { 85 | return m_commandQueue.Get(); 86 | } 87 | 88 | ID3D12GraphicsCommandList * GetCommandList() const noexcept 89 | { 90 | return m_commandList.Get(); 91 | } 92 | 93 | DXGI_FORMAT GetBackBufferFormat() const noexcept 94 | { 95 | return m_backBufferFormat; 96 | } 97 | 98 | DXGI_FORMAT GetDepthBufferFormat() const noexcept 99 | { 100 | return m_depthBufferFormat; 101 | } 102 | 103 | D3D12_VIEWPORT GetScreenViewport() const noexcept 104 | { 105 | return m_screenViewport; 106 | } 107 | 108 | D3D12_RECT GetScissorRect() const noexcept 109 | { 110 | return m_scissorRect; 111 | } 112 | 113 | UINT GetCurrentFrameIndex() const noexcept 114 | { 115 | return m_backBufferIndex; 116 | } 117 | 118 | UINT GetBackBufferCount() const noexcept 119 | { 120 | return m_backBufferCount; 121 | } 122 | 123 | unsigned int GetDeviceOptions() const noexcept 124 | { 125 | return m_options; 126 | } 127 | 128 | D3DFrameResource & GetCurrentFrameResource() 129 | { 130 | return m_frameResources[m_backBufferIndex]; 131 | } 132 | 133 | CD3DX12_CPU_DESCRIPTOR_HANDLE GetRenderTargetView() const 134 | { 135 | return CD3DX12_CPU_DESCRIPTOR_HANDLE(m_rtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart(), m_backBufferIndex, m_rtvDescriptorSize); 136 | } 137 | 138 | CD3DX12_CPU_DESCRIPTOR_HANDLE GetDepthStencilView() const 139 | { 140 | return CD3DX12_CPU_DESCRIPTOR_HANDLE(m_dsvDescriptorHeap->GetCPUDescriptorHandleForHeapStart()); 141 | } 142 | 143 | private: 144 | void MoveToNextFrame(); 145 | void GetAdapter(IDXGIAdapter1 ** ppAdapter); 146 | 147 | const static size_t MAX_BACK_BUFFER_COUNT = 3; 148 | 149 | UINT m_backBufferIndex; 150 | 151 | // clang-format off 152 | // Direct3D objects. 153 | Microsoft::WRL::ComPtr m_d3dDevice; 154 | Microsoft::WRL::ComPtr m_commandQueue; 155 | Microsoft::WRL::ComPtr m_commandList; 156 | 157 | std::vector m_frameResources; 158 | 159 | // Swap chain objects. 160 | Microsoft::WRL::ComPtr m_dxgiFactory; 161 | Microsoft::WRL::ComPtr m_swapChain; 162 | Microsoft::WRL::ComPtr m_depthStencil; 163 | 164 | // Presentation fence objects. 165 | Microsoft::WRL::ComPtr m_fence; 166 | Microsoft::WRL::Wrappers::Event m_fenceEvent; 167 | 168 | // Direct3D rendering objects. 169 | UINT m_rtvDescriptorSize; 170 | Microsoft::WRL::ComPtr m_rtvDescriptorHeap; 171 | Microsoft::WRL::ComPtr m_dsvDescriptorHeap; 172 | D3D12_VIEWPORT m_screenViewport; 173 | D3D12_RECT m_scissorRect; 174 | 175 | // Direct3D properties. 176 | UINT m_backBufferCount; 177 | DXGI_FORMAT m_backBufferFormat; 178 | DXGI_FORMAT m_depthBufferFormat; 179 | D3D_FEATURE_LEVEL m_d3dMinFeatureLevel; 180 | D3D_FEATURE_LEVEL m_d3dFeatureLevel; 181 | 182 | // Cached device properties. 183 | HWND m_window; 184 | RECT m_outputSize; 185 | 186 | // DeviceResources options (see flags above) 187 | uint32_t m_options; 188 | 189 | // The IDeviceNotify can be held directly as it owns the DeviceResources. 190 | ID3DDeviceNotify * m_deviceNotify; 191 | // clang-format on 192 | }; 193 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/D3DEngine.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "D3DDeviceResources.h" 14 | #include "D3DEnvironmentIBL.h" 15 | #include "D3DMesh.h" 16 | #include "D3DMeshInstance.h" 17 | #include "D3DOrbitCamera.h" 18 | #include "D3DTexture.h" 19 | #include "Engine.h" 20 | #include "EngineOptions.h" 21 | #include "ShaderOptions.h" 22 | 23 | class D3DEngine : public Engine, public ID3DDeviceNotify 24 | { 25 | public: 26 | explicit D3DEngine(EngineOptions const & config); 27 | 28 | D3DEngine(D3DEngine const &) = delete; 29 | D3DEngine(D3DEngine &&) = delete; 30 | D3DEngine & operator=(D3DEngine const &) = delete; 31 | D3DEngine & operator=(D3DEngine &&) = delete; 32 | 33 | ~D3DEngine() override; 34 | 35 | void InitializeCore(HWND window) override; 36 | 37 | void Update(float elapsedTime) noexcept override; 38 | void Render() override; 39 | 40 | void ChangeWindowSizeCore(int width, int height) override; 41 | 42 | // ID3DDeviceNotify 43 | void OnDeviceLost() override; 44 | void OnDeviceRestored() override; 45 | 46 | private: 47 | // clang-format off 48 | fx::gltf::Document m_gltfScene{}; 49 | fx::gltf::Document m_gltfGround{}; 50 | 51 | std::unique_ptr m_deviceResources{}; 52 | Microsoft::WRL::ComPtr m_rootSignature{}; 53 | Microsoft::WRL::ComPtr m_cbvHeap{}; 54 | 55 | Microsoft::WRL::ComPtr m_pipelineStateSky{}; 56 | std::unordered_map> m_pipelineStateMap{}; 57 | 58 | D3DOrbitCamera m_camera{}; 59 | 60 | Light m_directionalLight{}; 61 | std::array m_pointLights{}; 62 | 63 | D3DEnvironmentIBL m_environment{}; 64 | std::vector m_textures{}; 65 | std::vector m_meshes{}; 66 | std::vector m_meshInstances{}; 67 | D3DMesh m_groundMesh{}; 68 | 69 | float m_curRotationAngleRad{}; 70 | float m_autoScaleFactor{}; 71 | Util::BBox m_boundingBox{}; 72 | Util::BBox m_transformedBox{}; 73 | // clang-format on 74 | 75 | void CreateDeviceDependentResources(); 76 | void CreateWindowSizeDependentResources(); 77 | 78 | void PrepareRender(); 79 | void CompleteRender(); 80 | 81 | void BuildEnvironmentMaps(); 82 | void BuildScene(); 83 | 84 | void BuildRootSignature(); 85 | void BuildDescriptorHeaps(); 86 | void BuildPipelineStateObjects(); 87 | void BuildUploadBuffers(); 88 | 89 | void CompileShaderPerumutation(std::string const & entryPoint, ShaderOptions options, D3D12_GRAPHICS_PIPELINE_STATE_DESC & psoDescTemplate); 90 | }; 91 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/D3DEnvironmentIBL.cpp: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #include "stdafx.h" 7 | #include 8 | 9 | #include "D3DEnvironmentIBL.h" 10 | #include "D3DTextureSet.h" 11 | #include "D3DUtil.h" 12 | #include "ImageData.h" 13 | #include "StringFormatter.h" 14 | 15 | void D3DEnvironmentIBL::Create(D3DDeviceResources const * deviceResources) 16 | { 17 | D3DTextureSet diffuseSet; 18 | D3DTextureSet specularSet; 19 | D3DTextureSet lutSet; 20 | 21 | std::vector diffuseTextures; 22 | std::vector specularTextures; 23 | 24 | for (auto axis : { "x", "y", "z" }) 25 | { 26 | for (auto dir : { "pos", "neg" }) 27 | { 28 | diffuseTextures.emplace_back( 29 | fx::common::StringFormatter::Format("Assets/Environment/diffuse_{0}{1}.png", dir, axis)); 30 | 31 | for (auto mipIndex : { 0, 1, 2, 3, 4, 5, 6 }) 32 | { 33 | specularTextures.emplace_back( 34 | fx::common::StringFormatter::Format("Assets/Environment/specular_{0}{1}_{2}.png", dir, axis, mipIndex)); 35 | } 36 | } 37 | } 38 | 39 | diffuseSet.Initialize(diffuseTextures); 40 | specularSet.Initialize(specularTextures); 41 | lutSet.Initialize({ ImageData("Assets/Environment/LUT.png") }); 42 | 43 | diffuseSet.LoadToMemory(deviceResources, m_data.DiffuseBuffer, m_data.DiffuseUploadBuffer, 6, 1); 44 | specularSet.LoadToMemory(deviceResources, m_data.SpecularBuffer, m_data.SpecularUploadBuffer, 6, 7); 45 | lutSet.LoadToMemory(deviceResources, m_data.LUTBuffer, m_data.LUTUploadBuffer, 1, 1); 46 | } 47 | 48 | void D3DEnvironmentIBL::CreateSRV(ID3D12Device * device, CD3DX12_CPU_DESCRIPTOR_HANDLE & descriptor) 49 | { 50 | D3D12_SHADER_RESOURCE_VIEW_DESC diffuseDesc = {}; 51 | diffuseDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; 52 | diffuseDesc.Format = m_data.DiffuseBuffer->GetDesc().Format; 53 | diffuseDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE; 54 | diffuseDesc.Texture2D.MostDetailedMip = 0; 55 | diffuseDesc.Texture2D.MipLevels = m_data.DiffuseBuffer->GetDesc().MipLevels; 56 | diffuseDesc.Texture2D.ResourceMinLODClamp = 0.0f; 57 | 58 | D3D12_SHADER_RESOURCE_VIEW_DESC specularDesc = {}; 59 | specularDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; 60 | specularDesc.Format = m_data.SpecularBuffer->GetDesc().Format; 61 | specularDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE; 62 | specularDesc.Texture2D.MostDetailedMip = 0; 63 | specularDesc.Texture2D.MipLevels = m_data.SpecularBuffer->GetDesc().MipLevels; 64 | specularDesc.Texture2D.ResourceMinLODClamp = 0.0f; 65 | 66 | D3D12_SHADER_RESOURCE_VIEW_DESC lutDesc = {}; 67 | lutDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; 68 | lutDesc.Format = m_data.LUTBuffer->GetDesc().Format; 69 | lutDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; 70 | lutDesc.Texture2D.MostDetailedMip = 0; 71 | lutDesc.Texture2D.MipLevels = m_data.LUTBuffer->GetDesc().MipLevels; 72 | lutDesc.Texture2D.ResourceMinLODClamp = 0.0f; 73 | 74 | const uint32_t size = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); 75 | 76 | device->CreateShaderResourceView(m_data.DiffuseBuffer.Get(), &diffuseDesc, descriptor); 77 | descriptor.Offset(1, size); 78 | device->CreateShaderResourceView(m_data.SpecularBuffer.Get(), &specularDesc, descriptor); 79 | descriptor.Offset(1, size); 80 | device->CreateShaderResourceView(m_data.LUTBuffer.Get(), &lutDesc, descriptor); 81 | descriptor.Offset(1, size); 82 | } 83 | 84 | void D3DEnvironmentIBL::FinishUpload() 85 | { 86 | m_data.DiffuseUploadBuffer.Reset(); 87 | m_data.SpecularUploadBuffer.Reset(); 88 | m_data.LUTUploadBuffer.Reset(); 89 | } 90 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/D3DEnvironmentIBL.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include "D3DDeviceResources.h" 9 | 10 | class D3DEnvironmentIBL 11 | { 12 | public: 13 | void Create(D3DDeviceResources const * deviceResources); 14 | 15 | void CreateSRV(ID3D12Device * device, CD3DX12_CPU_DESCRIPTOR_HANDLE & descriptor); 16 | 17 | void FinishUpload(); 18 | 19 | private: 20 | struct D3DEnvironmentData 21 | { 22 | Microsoft::WRL::ComPtr DiffuseBuffer{}; 23 | Microsoft::WRL::ComPtr SpecularBuffer{}; 24 | Microsoft::WRL::ComPtr LUTBuffer{}; 25 | 26 | Microsoft::WRL::ComPtr DiffuseUploadBuffer{}; 27 | Microsoft::WRL::ComPtr SpecularUploadBuffer{}; 28 | Microsoft::WRL::ComPtr LUTUploadBuffer{}; 29 | }; 30 | 31 | D3DEnvironmentData m_data{}; 32 | }; 33 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/D3DFrameResources.cpp: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #include "stdafx.h" 7 | 8 | #include "D3DFrameResources.h" 9 | #include "D3DUtil.h" 10 | 11 | D3DFrameResource::D3DFrameResource(ID3D12Device * device) 12 | { 13 | COMUtil::ThrowIfFailed(device->CreateCommandAllocator( 14 | D3D12_COMMAND_LIST_TYPE_DIRECT, 15 | IID_PPV_ARGS(CommandAllocator.GetAddressOf()))); 16 | } 17 | 18 | void D3DFrameResource::AllocateBuffers(ID3D12Device * device, std::size_t sceneCount, std::size_t meshCount) 19 | { 20 | SceneCB = std::make_unique>(device, sceneCount, true); 21 | MeshCB = std::make_unique>(device, meshCount, true); 22 | MeshDataBuffer = std::make_unique>(device, meshCount, false); 23 | } 24 | 25 | void D3DFrameResource::Reset() 26 | { 27 | CommandAllocator.Reset(); 28 | RenderTarget.Reset(); 29 | SceneCB->Reset(); 30 | MeshCB->Reset(); 31 | MeshDataBuffer->Reset(); 32 | } 33 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/D3DFrameResources.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include "D3DConstants.h" 9 | #include "D3DUploadBuffer.h" 10 | 11 | struct D3DFrameResource 12 | { 13 | explicit D3DFrameResource(ID3D12Device * device); 14 | 15 | D3DFrameResource(D3DFrameResource const &) = delete; 16 | D3DFrameResource(D3DFrameResource &&) = default; 17 | 18 | D3DFrameResource & operator=(const D3DFrameResource &) = delete; 19 | D3DFrameResource & operator=(D3DFrameResource &&) = delete; 20 | ~D3DFrameResource() = default; 21 | 22 | void AllocateBuffers(ID3D12Device * device, std::size_t sceneCount, std::size_t meshCount); 23 | 24 | void Reset(); 25 | 26 | // clang-format off 27 | Microsoft::WRL::ComPtr CommandAllocator{}; 28 | Microsoft::WRL::ComPtr RenderTarget{}; 29 | UINT64 Fence{}; 30 | 31 | std::unique_ptr> SceneCB{}; 32 | std::unique_ptr> MeshCB{}; 33 | std::unique_ptr> MeshDataBuffer{}; 34 | // clang-format on 35 | }; 36 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/D3DGraph.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include 9 | #include 10 | 11 | namespace Graph 12 | { 13 | struct Node 14 | { 15 | DirectX::XMMATRIX CurrentTransform{}; 16 | int32_t CameraIndex = -1; 17 | int32_t MeshIndex = -1; 18 | }; 19 | 20 | void Visit(fx::gltf::Document const & doc, uint32_t nodeIndex, DirectX::XMMATRIX const & parentTransform, std::vector & graphNodes) 21 | { 22 | Node & graphNode = graphNodes[nodeIndex]; 23 | graphNode.CurrentTransform = parentTransform; 24 | 25 | fx::gltf::Node const & node = doc.nodes[nodeIndex]; 26 | if (node.matrix != fx::gltf::defaults::IdentityMatrix) 27 | { 28 | const DirectX::XMFLOAT4X4 local(node.matrix.data()); 29 | graphNode.CurrentTransform = DirectX::XMLoadFloat4x4(&local) * graphNode.CurrentTransform; 30 | } 31 | else 32 | { 33 | if (node.translation != fx::gltf::defaults::NullVec3) 34 | { 35 | const DirectX::XMFLOAT3 local(node.translation.data()); 36 | graphNode.CurrentTransform = DirectX::XMMatrixTranslationFromVector(DirectX::XMLoadFloat3(&local)) * graphNode.CurrentTransform; 37 | } 38 | 39 | if (node.scale != fx::gltf::defaults::IdentityVec3) 40 | { 41 | const DirectX::XMFLOAT3 local(node.scale.data()); 42 | graphNode.CurrentTransform = DirectX::XMMatrixScalingFromVector(DirectX::XMLoadFloat3(&local)) * graphNode.CurrentTransform; 43 | } 44 | 45 | if (node.rotation != fx::gltf::defaults::IdentityRotation) 46 | { 47 | const DirectX::XMFLOAT4 local(node.rotation.data()); 48 | graphNode.CurrentTransform = DirectX::XMMatrixRotationQuaternion(DirectX::XMLoadFloat4(&local)) * graphNode.CurrentTransform; 49 | } 50 | } 51 | 52 | if (node.camera >= 0) 53 | { 54 | graphNode.CameraIndex = node.camera; 55 | } 56 | else 57 | { 58 | if (node.mesh >= 0) 59 | { 60 | graphNode.MeshIndex = node.mesh; 61 | } 62 | 63 | for (auto childIndex : node.children) 64 | { 65 | Visit(doc, childIndex, graphNode.CurrentTransform, graphNodes); 66 | } 67 | } 68 | } 69 | } // namespace Graph 70 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/D3DMesh.cpp: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #include "stdafx.h" 7 | #include 8 | #include 9 | 10 | #include "D3DMesh.h" 11 | #include "MeshData.h" 12 | 13 | uint32_t D3DMesh::CurrentMeshPartId = 1; 14 | 15 | void D3DMesh::Create( 16 | fx::gltf::Document const & doc, std::size_t meshIndex, D3DDeviceResources const * deviceResources) 17 | { 18 | ID3D12Device * device = deviceResources->GetD3DDevice(); 19 | ID3D12GraphicsCommandList * commandList = deviceResources->GetCommandList(); 20 | 21 | const D3D12_HEAP_PROPERTIES defaultHeapProperties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT); 22 | const D3D12_HEAP_PROPERTIES uploadHeapProperties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD); 23 | 24 | m_meshParts.resize(doc.meshes[meshIndex].primitives.size()); 25 | for (std::size_t i = 0; i < doc.meshes[meshIndex].primitives.size(); i++) 26 | { 27 | MeshData mesh(doc, meshIndex, i); 28 | MeshData::BufferInfo const & vBuffer = mesh.VertexBuffer(); 29 | MeshData::BufferInfo const & nBuffer = mesh.NormalBuffer(); 30 | MeshData::BufferInfo const & tBuffer = mesh.TangentBuffer(); 31 | MeshData::BufferInfo const & cBuffer = mesh.TexCoord0Buffer(); 32 | MeshData::BufferInfo const & iBuffer = mesh.IndexBuffer(); 33 | if (!vBuffer.HasData() || !nBuffer.HasData() || !iBuffer.HasData()) 34 | { 35 | throw std::runtime_error("Only meshes with vertex, normal, and index buffers are supported"); 36 | } 37 | 38 | const std::size_t totalBufferSize = 39 | static_cast(vBuffer.TotalSize) + 40 | static_cast(nBuffer.TotalSize) + 41 | static_cast(tBuffer.TotalSize) + 42 | static_cast(cBuffer.TotalSize) + 43 | static_cast(iBuffer.TotalSize); 44 | 45 | D3DMeshPart & meshPart = m_meshParts[i]; 46 | const CD3DX12_RESOURCE_DESC resourceDescV = CD3DX12_RESOURCE_DESC::Buffer(totalBufferSize); 47 | COMUtil::ThrowIfFailed(device->CreateCommittedResource( 48 | &defaultHeapProperties, 49 | D3D12_HEAP_FLAG_NONE, 50 | &resourceDescV, 51 | D3D12_RESOURCE_STATE_COPY_DEST, 52 | nullptr, 53 | IID_PPV_ARGS(meshPart.DefaultBuffer.ReleaseAndGetAddressOf()))); 54 | 55 | COMUtil::ThrowIfFailed(device->CreateCommittedResource( 56 | &uploadHeapProperties, 57 | D3D12_HEAP_FLAG_NONE, 58 | &resourceDescV, 59 | D3D12_RESOURCE_STATE_GENERIC_READ, 60 | nullptr, 61 | IID_PPV_ARGS(meshPart.UploadBuffer.ReleaseAndGetAddressOf()))); 62 | 63 | uint8_t * bufferStart{}; 64 | uint32_t offset{}; 65 | const CD3DX12_RANGE readRange(0, 0); // We do not intend to read from this resource on the CPU. 66 | COMUtil::ThrowIfFailed(meshPart.UploadBuffer->Map(0, &readRange, reinterpret_cast(&bufferStart))); 67 | 68 | // Copy vertex buffer to upload... 69 | std::memcpy(bufferStart, vBuffer.Data, vBuffer.TotalSize); 70 | meshPart.Buffers[SlotVertex].BufferLocation = meshPart.DefaultBuffer->GetGPUVirtualAddress(); 71 | meshPart.Buffers[SlotVertex].StrideInBytes = vBuffer.DataStride; 72 | meshPart.Buffers[SlotVertex].SizeInBytes = vBuffer.TotalSize; 73 | offset += vBuffer.TotalSize; 74 | 75 | // Copy normal buffer to upload... 76 | std::memcpy(bufferStart + offset, nBuffer.Data, nBuffer.TotalSize); 77 | meshPart.Buffers[SlotNormal].BufferLocation = meshPart.DefaultBuffer->GetGPUVirtualAddress() + offset; 78 | meshPart.Buffers[SlotNormal].StrideInBytes = nBuffer.DataStride; 79 | meshPart.Buffers[SlotNormal].SizeInBytes = nBuffer.TotalSize; 80 | offset += nBuffer.TotalSize; 81 | 82 | if (tBuffer.HasData()) 83 | { 84 | // Copy tangent buffer to upload... 85 | std::memcpy(bufferStart + offset, tBuffer.Data, tBuffer.TotalSize); 86 | meshPart.Buffers[SlotTangent].BufferLocation = meshPart.DefaultBuffer->GetGPUVirtualAddress() + offset; 87 | meshPart.Buffers[SlotTangent].StrideInBytes = tBuffer.DataStride; 88 | meshPart.Buffers[SlotTangent].SizeInBytes = tBuffer.TotalSize; 89 | offset += tBuffer.TotalSize; 90 | } 91 | 92 | if (cBuffer.HasData()) 93 | { 94 | // Copy tex-coord buffer to upload... 95 | std::memcpy(bufferStart + offset, cBuffer.Data, cBuffer.TotalSize); 96 | meshPart.Buffers[SlotTexCoord0].BufferLocation = meshPart.DefaultBuffer->GetGPUVirtualAddress() + offset; 97 | meshPart.Buffers[SlotTexCoord0].StrideInBytes = cBuffer.DataStride; 98 | meshPart.Buffers[SlotTexCoord0].SizeInBytes = cBuffer.TotalSize; 99 | offset += cBuffer.TotalSize; 100 | } 101 | 102 | // Copy index buffer to upload... 103 | std::memcpy(bufferStart + offset, iBuffer.Data, iBuffer.TotalSize); 104 | meshPart.IndexBufferView.BufferLocation = meshPart.DefaultBuffer->GetGPUVirtualAddress() + offset; 105 | meshPart.IndexBufferView.Format = Util::GetFormat(iBuffer.Accessor); 106 | meshPart.IndexBufferView.SizeInBytes = iBuffer.TotalSize; 107 | meshPart.IndexCount = iBuffer.Accessor->count; 108 | 109 | // Copy from upload to default... 110 | commandList->CopyBufferRegion(meshPart.DefaultBuffer.Get(), 0, meshPart.UploadBuffer.Get(), 0, totalBufferSize); 111 | const CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(meshPart.DefaultBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER | D3D12_RESOURCE_STATE_INDEX_BUFFER); 112 | commandList->ResourceBarrier(1, &barrier); 113 | 114 | Util::BBox boundingBox{}; 115 | boundingBox.Min = DirectX::XMFLOAT3(vBuffer.Accessor->min.data()); 116 | boundingBox.Max = DirectX::XMFLOAT3(vBuffer.Accessor->max.data()); 117 | Util::UnionBBox(m_boundingBox, boundingBox); 118 | 119 | meshPart.UploadBuffer->Unmap(0, nullptr); 120 | 121 | // Set material properties for this mesh piece... 122 | meshPart.ShaderData.MeshAutoColor = Util::HSVtoRBG(std::fmodf(CurrentMeshPartId++ * 0.618033988749895f, 1.0), 0.65f, 0.65f); 123 | meshPart.SetMaterial(mesh.Material()); 124 | if (tBuffer.HasData()) 125 | { 126 | meshPart.ShaderConfig |= ShaderOptions::HAS_TANGENTS; 127 | } 128 | } 129 | } 130 | 131 | void D3DMesh::SetWorldMatrix(DirectX::XMMATRIX const & baseTransform, DirectX::XMFLOAT3 const & centerTranslation, float rotationY, float scalingFactor) 132 | { 133 | const DirectX::XMMATRIX translation = DirectX::XMMatrixTranslationFromVector(DirectX::XMLoadFloat3(¢erTranslation)); 134 | const DirectX::XMMATRIX rotation = DirectX::XMMatrixRotationY(rotationY); 135 | const DirectX::XMMATRIX scale = DirectX::XMMatrixScaling(scalingFactor, scalingFactor, scalingFactor); 136 | DirectX::XMStoreFloat4x4(&m_worldMatrix, baseTransform * translation * rotation * scale); 137 | } 138 | 139 | void D3DMesh::Render(D3DRenderContext & renderContext) 140 | { 141 | MeshConstantBuffer meshParameters{}; 142 | meshParameters.WorldViewProj = DirectX::XMMatrixTranspose(DirectX::XMLoadFloat4x4(&m_worldMatrix) * renderContext.ViewProj); 143 | meshParameters.World = DirectX::XMMatrixTranspose(DirectX::XMLoadFloat4x4(&m_worldMatrix)); 144 | 145 | std::size_t meshCBIndex = 0; 146 | for (auto & meshPart : m_meshParts) 147 | { 148 | // Ensure we can correctly draw this part of the mesh. An optimization would be to reduce the number of calls to SetPipelineState further by sorting the meshes... 149 | const ShaderOptions options = renderContext.OverrideShaderOptions == ShaderOptions::None ? meshPart.ShaderConfig : renderContext.OverrideShaderOptions; 150 | if (options != renderContext.CurrentShaderOptions) 151 | { 152 | renderContext.CommandList->SetPipelineState(renderContext.PipelineStateMap[options].Get()); 153 | renderContext.CurrentShaderOptions = options; 154 | } 155 | 156 | const std::size_t cbIndex = renderContext.CurrentCBIndex + meshCBIndex; 157 | meshParameters.MaterialIndex = static_cast(cbIndex); 158 | renderContext.CurrentFrame.MeshCB->CopyData(cbIndex, meshParameters); 159 | renderContext.CurrentFrame.MeshDataBuffer->CopyData(cbIndex, meshPart.ShaderData); 160 | 161 | renderContext.CommandList->IASetVertexBuffers(0, 4, &meshPart.Buffers[0]); 162 | renderContext.CommandList->IASetIndexBuffer(&meshPart.IndexBufferView); 163 | renderContext.CommandList->SetGraphicsRootConstantBufferView(1, renderContext.CurrentFrame.MeshCB->GetGPUVirtualAddress(cbIndex)); 164 | renderContext.CommandList->DrawIndexedInstanced(meshPart.IndexCount, 1, 0, 0, 0); 165 | 166 | meshCBIndex++; 167 | } 168 | 169 | renderContext.CurrentCBIndex += m_meshParts.size(); 170 | } 171 | 172 | void D3DMesh::FinishUpload() 173 | { 174 | for (auto & meshPart : m_meshParts) 175 | { 176 | meshPart.UploadBuffer.Reset(); 177 | } 178 | } 179 | 180 | void D3DMesh::Reset() 181 | { 182 | for (auto & meshPart : m_meshParts) 183 | { 184 | meshPart.DefaultBuffer.Reset(); 185 | } 186 | } 187 | 188 | void D3DMesh::D3DMeshPart::SetMaterial(MaterialData const & materialData) 189 | { 190 | if (materialData.HasData()) 191 | { 192 | auto material = materialData.Data(); 193 | ShaderData.BaseColorIndex = material.pbrMetallicRoughness.baseColorTexture.index; 194 | ShaderData.BaseColorFactor = DirectX::XMFLOAT4(material.pbrMetallicRoughness.baseColorFactor.data()); 195 | 196 | ShaderData.MetalRoughIndex = material.pbrMetallicRoughness.metallicRoughnessTexture.index; 197 | ShaderData.MetallicFactor = material.pbrMetallicRoughness.metallicFactor; 198 | ShaderData.RoughnessFactor = material.pbrMetallicRoughness.roughnessFactor; 199 | 200 | ShaderData.NormalIndex = material.normalTexture.index; 201 | ShaderData.NormalScale = material.normalTexture.scale; 202 | 203 | ShaderData.AOIndex = material.occlusionTexture.index; 204 | ShaderData.AOStrength = material.occlusionTexture.strength; 205 | 206 | ShaderData.EmissiveIndex = material.emissiveTexture.index; 207 | ShaderData.EmissiveFactor = DirectX::XMFLOAT3(material.emissiveFactor.data()); 208 | 209 | ShaderOptions options = ShaderOptions::None; 210 | if (ShaderData.BaseColorIndex >= 0) 211 | options |= ShaderOptions::HAS_BASECOLORMAP; 212 | if (ShaderData.NormalIndex >= 0) 213 | options |= ShaderOptions::HAS_NORMALMAP; 214 | if (ShaderData.MetalRoughIndex >= 0) 215 | options |= ShaderOptions::HAS_METALROUGHNESSMAP; 216 | if (ShaderData.AOIndex >= 0) 217 | { 218 | options |= ShaderOptions::HAS_OCCLUSIONMAP; 219 | 220 | if (ShaderData.AOIndex == ShaderData.MetalRoughIndex) 221 | { 222 | options |= ShaderOptions::HAS_OCCLUSIONMAP_COMBINED; 223 | } 224 | } 225 | if (ShaderData.EmissiveIndex >= 0) 226 | options |= ShaderOptions::HAS_EMISSIVEMAP; 227 | 228 | if (options == ShaderOptions::None) 229 | { 230 | options = ShaderOptions::USE_FACTORS_ONLY; 231 | } 232 | 233 | ShaderConfig = options; 234 | } 235 | else 236 | { 237 | // Use a default material 238 | ShaderConfig = ShaderOptions::USE_FACTORS_ONLY; 239 | ShaderData.BaseColorFactor = DirectX::XMFLOAT4(0.8f, 0.8f, 0.8f, 1.0f); 240 | ShaderData.MetallicFactor = 0; 241 | ShaderData.RoughnessFactor = 0.5f; 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/D3DMesh.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "D3DDeviceResources.h" 13 | #include "D3DRenderContext.h" 14 | #include "D3DUtil.h" 15 | #include "MaterialData.h" 16 | #include "ShaderOptions.h" 17 | 18 | class D3DMesh 19 | { 20 | public: 21 | void Create( 22 | fx::gltf::Document const & doc, std::size_t meshIndex, D3DDeviceResources const * deviceResources); 23 | 24 | void SetWorldMatrix(DirectX::XMMATRIX const & baseTransform, DirectX::XMFLOAT3 const & centerTranslation, float rotationY, float scalingFactor); 25 | 26 | void Render(D3DRenderContext & renderContext); 27 | 28 | void FinishUpload(); 29 | 30 | void Reset(); 31 | 32 | Util::BBox const & MeshBBox() const noexcept 33 | { 34 | return m_boundingBox; 35 | } 36 | 37 | std::size_t MeshPartCount() const noexcept 38 | { 39 | return m_meshParts.size(); 40 | } 41 | 42 | std::vector GetRequiredShaderOptions() const 43 | { 44 | std::vector requiredShaderOptions{}; 45 | for (auto const & meshPart : m_meshParts) 46 | { 47 | requiredShaderOptions.push_back(meshPart.ShaderConfig); 48 | } 49 | 50 | return requiredShaderOptions; 51 | } 52 | 53 | static const UINT SlotVertex = 0; 54 | static const UINT SlotNormal = 1; 55 | static const UINT SlotTangent = 2; 56 | static const UINT SlotTexCoord0 = 3; 57 | 58 | private: 59 | struct D3DMeshPart 60 | { 61 | Microsoft::WRL::ComPtr DefaultBuffer{}; 62 | Microsoft::WRL::ComPtr UploadBuffer{}; 63 | 64 | // Views 0-3 should match the input-slot order defined by the PSO 65 | std::array Buffers{}; 66 | 67 | D3D12_INDEX_BUFFER_VIEW IndexBufferView{}; 68 | 69 | uint32_t IndexCount{}; 70 | 71 | MeshShaderData ShaderData{}; 72 | ShaderOptions ShaderConfig{}; 73 | 74 | void SetMaterial(MaterialData const & materialData); 75 | }; 76 | 77 | DirectX::XMFLOAT4X4 m_worldMatrix{}; 78 | 79 | std::vector m_meshParts{}; 80 | 81 | Util::BBox m_boundingBox; 82 | 83 | static uint32_t CurrentMeshPartId; 84 | }; 85 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/D3DMeshInstance.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include 9 | 10 | struct D3DMeshInstance 11 | { 12 | DirectX::XMMATRIX Transform; 13 | 14 | uint32_t MeshIndex; 15 | }; 16 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/D3DOrbitCamera.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | 3 | #include "D3DOrbitCamera.h" 4 | 5 | void D3DOrbitCamera::Reset(DirectX::XMFLOAT3 position) 6 | { 7 | constexpr DirectX::XMVECTOR zAxis = { { 0, 0, 1, 0 } }; 8 | constexpr DirectX::XMVECTOR yAxis = { { 0, 1, 0, 0 } }; 9 | 10 | position.y = -position.y; 11 | const DirectX::XMVECTOR original = DirectX::XMLoadFloat3(&position); 12 | 13 | position.y = 0; 14 | const DirectX::XMVECTOR projected = DirectX::XMVector3Normalize(DirectX::XMLoadFloat3(&position)); 15 | 16 | const int xFlip = position.x < 0 ? -1 : 1; 17 | 18 | Reset( 19 | DirectX::XMVectorGetX(DirectX::XMVector3Length(original)), 20 | DirectX::XMVectorGetX(DirectX::XMVector3AngleBetweenNormals(DirectX::XMVector3Normalize(original), yAxis)), 21 | xFlip * DirectX::XMVectorGetX(DirectX::XMVector3AngleBetweenNormals(projected, zAxis))); 22 | } 23 | 24 | void D3DOrbitCamera::Reset(float radius, float phi, float theta) 25 | { 26 | m_radius = 1; 27 | m_phi = DirectX::XM_PIDIV2; 28 | m_theta = 0; 29 | 30 | Update(radius, phi, theta); 31 | } 32 | 33 | void D3DOrbitCamera::Update(float zoomFactor, float deltaPhi, float deltaTheta) 34 | { 35 | Dolly(zoomFactor); 36 | RotateLeft(deltaTheta); 37 | RotateUp(deltaPhi); 38 | 39 | Calculate(); 40 | } 41 | 42 | void D3DOrbitCamera::Update(Mouse::ButtonStateTracker tracker) 43 | { 44 | using ButtonState = Mouse::ButtonStateTracker::ButtonState; 45 | 46 | // Query mouse state... 47 | bool changed = false; 48 | const Mouse::State state = tracker.GetLastState(); 49 | if (tracker.leftButton == ButtonState::PRESSED || tracker.middleButton == ButtonState::PRESSED) 50 | { 51 | TrackLastCursorPosition(state); 52 | } 53 | else if (tracker.leftButton == ButtonState::HELD) 54 | { 55 | RotateLeft(DirectX::XM_2PI * (state.x - m_lastCursorPos.x) / 540.0f); 56 | RotateUp(DirectX::XM_2PI * (state.y - m_lastCursorPos.y) / 540.0f); 57 | 58 | changed = true; 59 | TrackLastCursorPosition(state); 60 | } 61 | else if (tracker.middleButton == ButtonState::HELD) 62 | { 63 | const int deltaY = state.y - m_lastCursorPos.y; 64 | if (deltaY < 0) 65 | { 66 | Dolly(0.95f); 67 | } 68 | else if (deltaY > 0) 69 | { 70 | Dolly(1.0f / 0.95f); 71 | } 72 | 73 | changed = true; 74 | TrackLastCursorPosition(state); 75 | } 76 | 77 | if (changed) 78 | { 79 | Calculate(); 80 | } 81 | } 82 | 83 | void D3DOrbitCamera::SetProjection(float fovAngleY, float aspectRatio, float nearZ, float farZ) 84 | { 85 | const DirectX::XMMATRIX projection = DirectX::XMMatrixPerspectiveFovLH(fovAngleY, aspectRatio, nearZ, farZ); 86 | const DirectX::XMMATRIX viewProj = DirectX::XMLoadFloat4x4(&ViewMatrix) * projection; 87 | 88 | DirectX::XMStoreFloat4x4(&m_projectionMatrix, projection); 89 | DirectX::XMStoreFloat4x4(&ViewProjectionMatrix, viewProj); 90 | } 91 | 92 | void D3DOrbitCamera::Calculate() 93 | { 94 | constexpr DirectX::XMVECTOR Backward = { { 0.0f, 0.0f, 1.0f, 0.0f } }; 95 | constexpr DirectX::XMVECTOR At = { { 0.0f, 0.0f, 0.0f, 0.0f } }; 96 | constexpr DirectX::XMVECTOR Up = { { 0.0f, 1.0f, 0.0f, 0.0 } }; 97 | 98 | // Update camera position... 99 | Position = DirectX::XMVector3Transform(Backward, DirectX::XMMatrixRotationRollPitchYaw(m_phi, m_theta, 0.0f)); 100 | Position = DirectX::XMVectorScale(Position, m_radius); 101 | 102 | // Refresh view and view+projection matrices... 103 | DirectX::XMStoreFloat4x4(&ViewMatrix, DirectX::XMMatrixLookAtLH(Position, At, Up)); 104 | DirectX::XMStoreFloat4x4(&ViewProjectionMatrix, DirectX::XMLoadFloat4x4(&ViewMatrix) * DirectX::XMLoadFloat4x4(&m_projectionMatrix)); 105 | } 106 | 107 | void D3DOrbitCamera::TrackLastCursorPosition(Mouse::State const & state) noexcept 108 | { 109 | m_lastCursorPos.x = state.x; 110 | m_lastCursorPos.y = state.y; 111 | } 112 | 113 | void D3DOrbitCamera::Dolly(float zoomFactor) noexcept 114 | { 115 | m_radius *= zoomFactor; 116 | } 117 | 118 | void D3DOrbitCamera::RotateLeft(float deltaTheta) noexcept 119 | { 120 | m_theta += deltaTheta; 121 | } 122 | 123 | void D3DOrbitCamera::RotateUp(float deltaPhi) noexcept 124 | { 125 | constexpr float Epsilon = 0.000001f; 126 | 127 | m_phi -= deltaPhi; 128 | m_phi = std::clamp(m_phi, -DirectX::XM_PIDIV2 + Epsilon, DirectX::XM_PIDIV2 - Epsilon); 129 | } 130 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/D3DOrbitCamera.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include 9 | #include "Platform/Mouse.h" 10 | 11 | class D3DOrbitCamera 12 | { 13 | public: 14 | void Reset(DirectX::XMFLOAT3 position); 15 | void Reset(float radius, float phi, float theta); 16 | 17 | void Update(Mouse::ButtonStateTracker tracker); 18 | void Update(float zoomFactor, float deltaPhi, float deltaTheta); 19 | 20 | void SetProjection(float fovAngleY, float aspectRatio, float nearZ, float farZ); 21 | 22 | DirectX::XMFLOAT4X4 ViewMatrix{}; 23 | DirectX::XMFLOAT4X4 ViewProjectionMatrix{}; 24 | DirectX::XMVECTOR Position{}; 25 | 26 | private: 27 | DirectX::XMFLOAT4X4 m_projectionMatrix{}; 28 | 29 | float m_radius{}; 30 | float m_phi{}; 31 | float m_theta{}; 32 | 33 | DirectX::XMINT2 m_lastCursorPos{}; 34 | void TrackLastCursorPosition(Mouse::State const & state) noexcept; 35 | 36 | void Calculate(); 37 | 38 | void Dolly(float zoomFactor) noexcept; 39 | void RotateLeft(float deltaTheta) noexcept; 40 | void RotateUp(float deltaPhi) noexcept; 41 | }; 42 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/D3DRenderContext.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "D3DFrameResources.h" 13 | #include "ShaderOptions.h" 14 | 15 | struct D3DRenderContext 16 | { 17 | ID3D12GraphicsCommandList * CommandList; 18 | D3DFrameResource const & CurrentFrame; 19 | DirectX::XMMATRIX const & ViewProj; 20 | std::size_t CurrentCBIndex; 21 | ShaderOptions CurrentShaderOptions; 22 | ShaderOptions OverrideShaderOptions; 23 | std::unordered_map> & PipelineStateMap; 24 | }; 25 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/D3DTexture.cpp: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #include "stdafx.h" 7 | 8 | #include "D3DTexture.h" 9 | #include "D3DTextureSet.h" 10 | #include "D3DUtil.h" 11 | 12 | void D3DTexture::Create(ImageData const & texture, D3DDeviceResources const * deviceResources) 13 | { 14 | D3DTextureSet texSet; 15 | 16 | texSet.Initialize({ texture }); 17 | texSet.LoadToMemory(deviceResources, m_data.DefaultBuffer, m_data.UploadBuffer, 1, 1); 18 | } 19 | 20 | void D3DTexture::CreateSRV(ID3D12Device * device, CD3DX12_CPU_DESCRIPTOR_HANDLE const & descriptor) 21 | { 22 | D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; 23 | srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; 24 | srvDesc.Format = m_data.DefaultBuffer->GetDesc().Format; 25 | srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; 26 | srvDesc.Texture2D.MostDetailedMip = 0; 27 | srvDesc.Texture2D.MipLevels = m_data.DefaultBuffer->GetDesc().MipLevels; 28 | srvDesc.Texture2D.ResourceMinLODClamp = 0.0f; 29 | 30 | device->CreateShaderResourceView(m_data.DefaultBuffer.Get(), &srvDesc, descriptor); 31 | } 32 | 33 | void D3DTexture::FinishUpload() 34 | { 35 | m_data.UploadBuffer.Reset(); 36 | } 37 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/D3DTexture.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include "D3DDeviceResources.h" 9 | #include "ImageData.h" 10 | 11 | class D3DTexture 12 | { 13 | public: 14 | void Create(ImageData const & texture, D3DDeviceResources const * deviceResources); 15 | 16 | void CreateSRV(ID3D12Device * device, CD3DX12_CPU_DESCRIPTOR_HANDLE const & descriptor); 17 | 18 | void FinishUpload(); 19 | 20 | private: 21 | struct D3DTextureData 22 | { 23 | Microsoft::WRL::ComPtr DefaultBuffer{}; 24 | Microsoft::WRL::ComPtr UploadBuffer{}; 25 | }; 26 | 27 | D3DTextureData m_data{}; 28 | }; 29 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/D3DTextureSet.cpp: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #include "stdafx.h" 7 | 8 | #include "D3DTextureSet.h" 9 | #include "D3DUtil.h" 10 | 11 | using Microsoft::WRL::ComPtr; 12 | 13 | void D3DTextureSet::Initialize(std::vector const & textures) 14 | { 15 | m_images.resize(textures.size()); 16 | 17 | ComPtr factory; 18 | COMUtil::ThrowIfFailed(CoCreateInstance(CLSID_WICImagingFactory2, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory))); 19 | 20 | for (std::size_t i = 0; i < textures.size(); i++) 21 | { 22 | Image & image = m_images[i]; 23 | ImageData::ImageInfo const & imageInfo = textures[i].Info(); 24 | 25 | ComPtr decoder; 26 | if (imageInfo.IsBinary()) 27 | { 28 | IWICStream * stream; 29 | COMUtil::ThrowIfFailed(factory->CreateStream(&stream)); 30 | COMUtil::ThrowIfFailed(stream->InitializeFromMemory(reinterpret_cast(const_cast(imageInfo.BinaryData)), static_cast(imageInfo.BinarySize))); 31 | COMUtil::ThrowIfFailed(factory->CreateDecoderFromStream(stream, nullptr, WICDecodeMetadataCacheOnDemand, decoder.GetAddressOf())); 32 | } 33 | else 34 | { 35 | std::wstring texture(imageInfo.FileName.generic_wstring()); 36 | COMUtil::ThrowIfFailed(factory->CreateDecoderFromFilename(texture.c_str(), nullptr, GENERIC_READ, WICDecodeMetadataCacheOnDemand, decoder.GetAddressOf())); 37 | } 38 | 39 | COMUtil::ThrowIfFailed(decoder->GetFrame(0, image.frame.GetAddressOf())); 40 | 41 | WICPixelFormatGUID pixelFormat; 42 | COMUtil::ThrowIfFailed(image.frame->GetSize(&image.width, &image.height)); 43 | COMUtil::ThrowIfFailed(image.frame->GetPixelFormat(&pixelFormat)); 44 | 45 | if (std::memcmp(&pixelFormat, &GUID_WICPixelFormat32bppRGBA, sizeof(GUID)) != 0) 46 | { 47 | COMUtil::ThrowIfFailed(factory->CreateFormatConverter(image.formatConverter.GetAddressOf())); 48 | 49 | BOOL canConvert = FALSE; 50 | COMUtil::ThrowIfFailed(image.formatConverter->CanConvert(pixelFormat, GUID_WICPixelFormat32bppRGBA, &canConvert)); 51 | if (canConvert == FALSE) 52 | { 53 | throw std::runtime_error("Unable to convert texture to RGBA"); 54 | } 55 | 56 | COMUtil::ThrowIfFailed(image.formatConverter->Initialize(image.frame.Get(), GUID_WICPixelFormat32bppRGBA, WICBitmapDitherTypeErrorDiffusion, nullptr, 0, WICBitmapPaletteTypeMedianCut)); 57 | } 58 | 59 | image.size = image.width * image.height * 4; 60 | 61 | m_totalSize += Util::ResourceSize(image.size); 62 | } 63 | } 64 | 65 | void D3DTextureSet::LoadToMemory( 66 | D3DDeviceResources const * deviceResources, 67 | Microsoft::WRL::ComPtr & defaultBuffer, 68 | Microsoft::WRL::ComPtr & uploadBuffer, 69 | UINT16 depthOrArraySize, 70 | UINT16 mipChainLength) 71 | { 72 | ID3D12Device * device = deviceResources->GetD3DDevice(); 73 | ID3D12GraphicsCommandList * commandList = deviceResources->GetCommandList(); 74 | 75 | const D3D12_HEAP_PROPERTIES defaultHeapProperties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT); 76 | const D3D12_HEAP_PROPERTIES uploadHeapProperties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD); 77 | 78 | D3D12_RESOURCE_DESC texDesc{}; 79 | texDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; 80 | texDesc.Alignment = 0; 81 | texDesc.Width = m_images[0].width; 82 | texDesc.Height = m_images[0].height; 83 | texDesc.DepthOrArraySize = depthOrArraySize; 84 | texDesc.MipLevels = mipChainLength; 85 | texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; 86 | texDesc.SampleDesc.Count = 1; 87 | texDesc.SampleDesc.Quality = 0; 88 | texDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; 89 | texDesc.Flags = D3D12_RESOURCE_FLAG_NONE; 90 | 91 | const CD3DX12_RESOURCE_DESC resourceDescV = CD3DX12_RESOURCE_DESC::Buffer(m_totalSize); 92 | COMUtil::ThrowIfFailed(device->CreateCommittedResource( 93 | &defaultHeapProperties, 94 | D3D12_HEAP_FLAG_NONE, 95 | &texDesc, 96 | D3D12_RESOURCE_STATE_COPY_DEST, 97 | nullptr, 98 | IID_PPV_ARGS(defaultBuffer.ReleaseAndGetAddressOf()))); 99 | 100 | COMUtil::ThrowIfFailed(device->CreateCommittedResource( 101 | &uploadHeapProperties, 102 | D3D12_HEAP_FLAG_NONE, 103 | &resourceDescV, 104 | D3D12_RESOURCE_STATE_GENERIC_READ, 105 | nullptr, 106 | IID_PPV_ARGS(uploadBuffer.ReleaseAndGetAddressOf()))); 107 | 108 | uint8_t * bufferStart{}; 109 | const CD3DX12_RANGE readRange(0, 0); // We do not intend to read from this resource on the CPU. 110 | COMUtil::ThrowIfFailed(uploadBuffer->Map(0, &readRange, reinterpret_cast(&bufferStart))); 111 | 112 | const UINT totalSubresources = depthOrArraySize * mipChainLength; 113 | 114 | std::size_t offset = 0; 115 | std::vector subresources(totalSubresources); 116 | for (std::size_t i = 0; i < totalSubresources; i++) 117 | { 118 | Image const & image = m_images[i]; 119 | 120 | // Load the image data right into the upload buffer... 121 | if (image.formatConverter) 122 | { 123 | COMUtil::ThrowIfFailed(image.formatConverter->CopyPixels(nullptr, image.width * 4, image.size, bufferStart + offset)); 124 | } 125 | else 126 | { 127 | COMUtil::ThrowIfFailed(image.frame->CopyPixels(nullptr, image.width * 4, image.size, bufferStart + offset)); 128 | } 129 | 130 | subresources[i].pData = bufferStart + offset; 131 | subresources[i].RowPitch = static_cast(image.width) * 4; 132 | subresources[i].SlicePitch = subresources[i].RowPitch * image.height; 133 | 134 | offset += Util::ResourceSize(image.size); 135 | } 136 | 137 | UpdateSubresources(commandList, defaultBuffer.Get(), uploadBuffer.Get(), 0, 0, totalSubresources, &subresources[0]); 138 | const CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); 139 | commandList->ResourceBarrier(1, &barrier); 140 | } 141 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/D3DTextureSet.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "D3DDeviceResources.h" 15 | #include "ImageData.h" 16 | 17 | class D3DTextureSet 18 | { 19 | public: 20 | void Initialize(std::vector const & textures); 21 | 22 | void LoadToMemory( 23 | D3DDeviceResources const * deviceResources, 24 | Microsoft::WRL::ComPtr & defaultBuffer, 25 | Microsoft::WRL::ComPtr & uploadBuffer, 26 | UINT16 depthOrArraySize, 27 | UINT16 mipChainLength); 28 | 29 | private: 30 | struct Image 31 | { 32 | Microsoft::WRL::ComPtr frame{}; 33 | Microsoft::WRL::ComPtr formatConverter{}; 34 | 35 | uint32_t width{}; 36 | uint32_t height{}; 37 | uint32_t size{}; 38 | }; 39 | 40 | std::vector m_images{}; 41 | 42 | std::size_t m_totalSize{}; 43 | }; 44 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/D3DUploadBuffer.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include 9 | #include "d3dx12.h" 10 | 11 | #include "Platform/COMUtil.h" 12 | 13 | template 14 | class D3DUploadBuffer 15 | { 16 | public: 17 | D3DUploadBuffer(ID3D12Device * device, std::size_t elementCount, bool isConstantBuffer) 18 | { 19 | m_elementByteSize = sizeof(T); 20 | if (isConstantBuffer) 21 | { 22 | m_elementByteSize = (sizeof(T) + 255u) & ~255u; 23 | } 24 | 25 | const D3D12_HEAP_PROPERTIES uploadHeapProperties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD); 26 | const CD3DX12_RESOURCE_DESC resourceDescV = CD3DX12_RESOURCE_DESC::Buffer(m_elementByteSize * elementCount); 27 | COMUtil::ThrowIfFailed(device->CreateCommittedResource( 28 | &uploadHeapProperties, 29 | D3D12_HEAP_FLAG_NONE, 30 | &resourceDescV, 31 | D3D12_RESOURCE_STATE_GENERIC_READ, 32 | nullptr, 33 | IID_PPV_ARGS(&m_uploadBuffer))); 34 | 35 | m_uploadBuffer->Map(0, nullptr, reinterpret_cast(&m_mappedData)); 36 | } 37 | 38 | D3DUploadBuffer(D3DUploadBuffer const &) = delete; 39 | D3DUploadBuffer(D3DUploadBuffer &&) = delete; 40 | D3DUploadBuffer & operator=(D3DUploadBuffer const &) = delete; 41 | D3DUploadBuffer & operator=(D3DUploadBuffer &&) = delete; 42 | 43 | ~D3DUploadBuffer() 44 | { 45 | if (m_uploadBuffer != nullptr) 46 | { 47 | m_uploadBuffer->Unmap(0, nullptr); 48 | } 49 | 50 | m_mappedData = nullptr; 51 | } 52 | 53 | ID3D12Resource * Resource() const noexcept 54 | { 55 | return m_uploadBuffer.Get(); 56 | } 57 | 58 | D3D12_GPU_VIRTUAL_ADDRESS GetGPUVirtualAddress(std::size_t elementIndex) 59 | { 60 | return Resource()->GetGPUVirtualAddress() + (elementIndex * m_elementByteSize); 61 | } 62 | 63 | void CopyData(std::size_t elementIndex, T const & data) noexcept 64 | { 65 | std::memcpy(&m_mappedData[elementIndex * m_elementByteSize], &data, sizeof(T)); 66 | } 67 | 68 | void Reset() 69 | { 70 | m_uploadBuffer.Reset(); 71 | } 72 | 73 | private: 74 | Microsoft::WRL::ComPtr m_uploadBuffer{}; 75 | uint8_t * m_mappedData{}; 76 | 77 | uint32_t m_elementByteSize{}; 78 | }; 79 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/D3DUtil.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "Logger.h" 15 | #include "Platform/COMUtil.h" 16 | 17 | namespace Util 18 | { 19 | struct BBox 20 | { 21 | DirectX::XMFLOAT3 Min{}; 22 | DirectX::XMFLOAT3 Max{}; 23 | DirectX::XMFLOAT3 CenterTranslation{}; 24 | }; 25 | 26 | inline DXGI_FORMAT GetFormat(fx::gltf::Accessor const * accessor) 27 | { 28 | if (accessor->type == fx::gltf::Accessor::Type::Vec3 && accessor->componentType == fx::gltf::Accessor::ComponentType::Float) 29 | { 30 | return DXGI_FORMAT_R32G32B32_FLOAT; 31 | } 32 | else if (accessor->type == fx::gltf::Accessor::Type::Scalar && accessor->componentType == fx::gltf::Accessor::ComponentType::UnsignedInt) 33 | { 34 | return DXGI_FORMAT_R32_UINT; 35 | } 36 | else if (accessor->type == fx::gltf::Accessor::Type::Scalar && accessor->componentType == fx::gltf::Accessor::ComponentType::UnsignedShort) 37 | { 38 | return DXGI_FORMAT_R16_UINT; 39 | } 40 | else 41 | { 42 | throw std::runtime_error("Unknown accessor types"); 43 | } 44 | } 45 | 46 | inline constexpr uint64_t ResourceSize(uint32_t size) noexcept 47 | { 48 | const std::size_t MinResourceSize = 64 * 1024; 49 | return size < MinResourceSize ? MinResourceSize : size; 50 | } 51 | 52 | inline void CenterBBox(BBox & currentBBox) 53 | { 54 | using namespace DirectX; 55 | const DirectX::XMVECTOR min = DirectX::XMLoadFloat3(¤tBBox.Min); 56 | const DirectX::XMVECTOR max = DirectX::XMLoadFloat3(¤tBBox.Max); 57 | const DirectX::XMVECTOR mid = DirectX::XMVectorNegate(0.5f * (min + max)); 58 | 59 | DirectX::XMStoreFloat3(¤tBBox.CenterTranslation, mid); 60 | } 61 | 62 | inline void UnionBBox(BBox & currentBBox, BBox const & other) 63 | { 64 | const DirectX::XMVECTOR cMin = DirectX::XMLoadFloat3(¤tBBox.Min); 65 | const DirectX::XMVECTOR cMax = DirectX::XMLoadFloat3(¤tBBox.Max); 66 | const DirectX::XMVECTOR oMin = DirectX::XMLoadFloat3(&other.Min); 67 | const DirectX::XMVECTOR oMax = DirectX::XMLoadFloat3(&other.Max); 68 | DirectX::XMStoreFloat3(¤tBBox.Min, DirectX::XMVectorMin(cMin, oMin)); 69 | DirectX::XMStoreFloat3(¤tBBox.Max, DirectX::XMVectorMax(cMax, oMax)); 70 | 71 | Util::CenterBBox(currentBBox); 72 | } 73 | 74 | inline BBox TransformBBox(BBox const & currentBBox, DirectX::XMMATRIX const & transform) 75 | { 76 | const DirectX::XMVECTOR newMin = DirectX::XMVector3Transform(DirectX::XMLoadFloat3(¤tBBox.Min), transform); 77 | const DirectX::XMVECTOR newMax = DirectX::XMVector3Transform(DirectX::XMLoadFloat3(¤tBBox.Max), transform); 78 | 79 | BBox newBBox{}; 80 | DirectX::XMStoreFloat3(&newBBox.Min, newMin); 81 | DirectX::XMStoreFloat3(&newBBox.Max, newMax); 82 | Util::CenterBBox(newBBox); 83 | 84 | return newBBox; 85 | } 86 | 87 | inline BBox TransformBBox(BBox const & currentBBox, DirectX::XMFLOAT3 const & centerTranslation, float scalingFactor) 88 | { 89 | const DirectX::XMMATRIX translation = DirectX::XMMatrixTranslationFromVector(DirectX::XMLoadFloat3(¢erTranslation)); 90 | const DirectX::XMMATRIX scale = DirectX::XMMatrixScaling(scalingFactor, scalingFactor, scalingFactor); 91 | 92 | return Util::TransformBBox(currentBBox, translation * scale); 93 | } 94 | 95 | inline DirectX::XMFLOAT4 HSVtoRBG(float hue, float saturation, float value) noexcept 96 | { 97 | DirectX::XMFLOAT4 rgba{}; 98 | 99 | rgba.x = fabsf(hue * 6.0f - 3.0f) - 1.0f; 100 | rgba.y = 2.0f - fabsf(hue * 6.0f - 2.0f); 101 | rgba.z = 2.0f - fabsf(hue * 6.0f - 4.0f); 102 | 103 | rgba.x = std::clamp(rgba.x, 0.0f, 1.0f); 104 | rgba.y = std::clamp(rgba.y, 0.0f, 1.0f); 105 | rgba.z = std::clamp(rgba.z, 0.0f, 1.0f); 106 | 107 | rgba.x = ((rgba.x - 1.0f) * saturation + 1.0f) * value; 108 | rgba.y = ((rgba.y - 1.0f) * saturation + 1.0f) * value; 109 | rgba.z = ((rgba.z - 1.0f) * saturation + 1.0f) * value; 110 | 111 | rgba.w = 1.0f; 112 | return rgba; 113 | } 114 | 115 | inline Microsoft::WRL::ComPtr CompileShader( 116 | std::wstring const & filename, 117 | std::string const & entrypoint, 118 | std::string const & target, 119 | D3D_SHADER_MACRO const * defines) 120 | { 121 | UINT compileFlags = D3DCOMPILE_ENABLE_STRICTNESS; 122 | #if defined(DEBUG) || defined(_DEBUG) 123 | compileFlags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; 124 | #else 125 | compileFlags |= D3DCOMPILE_OPTIMIZATION_LEVEL3; 126 | #endif 127 | 128 | Microsoft::WRL::ComPtr byteCode{}; 129 | Microsoft::WRL::ComPtr errors{}; 130 | const HRESULT hr = 131 | D3DCompileFromFile(filename.c_str(), defines, D3D_COMPILE_STANDARD_FILE_INCLUDE, entrypoint.c_str(), target.c_str(), compileFlags, 0, &byteCode, &errors); 132 | 133 | if (errors != nullptr) 134 | { 135 | Logger::WriteLine(static_cast(errors->GetBufferPointer())); 136 | } 137 | 138 | COMUtil::ThrowIfFailed(hr); 139 | return byteCode; 140 | } 141 | } // namespace Util 142 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/Shaders/Common.hlsli: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | 7 | struct Light 8 | { 9 | float3 Strength; 10 | float FalloffStart; // point/spot light only 11 | float3 Direction; // directional/spot light only 12 | float FalloffEnd; // point/spot light only 13 | float3 Position; // point light only 14 | float SpotPower; // spot light only 15 | }; 16 | 17 | cbuffer SceneConstants : register(b0) 18 | { 19 | float4x4 ViewProj; 20 | float4 Camera; 21 | 22 | Light DirectionalLight; 23 | Light PointLights[2]; 24 | }; 25 | 26 | cbuffer MeshConstants : register(b1) 27 | { 28 | float4x4 WorldViewProj; 29 | float4x4 World; 30 | 31 | int MaterialIndex; 32 | }; 33 | 34 | struct MaterialData 35 | { 36 | float4 MeshAutoColor; 37 | 38 | int BaseColorIndex; 39 | float4 BaseColorFactor; 40 | 41 | int NormalIndex; 42 | float NormalScale; 43 | 44 | int MetalRoughIndex; 45 | float RoughnessFactor; 46 | float MetallicFactor; 47 | 48 | int AOIndex; 49 | float AOStrength; 50 | 51 | int EmissiveIndex; 52 | float3 EmissiveFactor; 53 | }; 54 | 55 | StructuredBuffer MaterialDataBuffer : register(t0, space1); 56 | 57 | TextureCube DiffuseEnvMap : register(t0); 58 | TextureCube SpecularEnvMap : register(t1); 59 | Texture2D BRDF_LUT : register(t2); 60 | 61 | Texture2D Textures[64] : register(t3); 62 | 63 | SamplerState SampAnisotropicWrap : register(s0); 64 | SamplerState SampLinearClamp : register(s1); 65 | 66 | //-------------------------------------------------------------------------------------- 67 | static const float M_PI = 3.141592653589793f; 68 | static const float MinRoughness = 0.04f; 69 | 70 | float4 SRGBtoLINEAR(float4 srgbColor) 71 | { 72 | #if MANUAL_SRGB 73 | return float4(pow(srgbColor.xyz, 2.2), srgbColor.w); 74 | #else 75 | return srgbColor; 76 | #endif 77 | } 78 | 79 | float3 LINEARtoSRGB(float3 linearColor) 80 | { 81 | #if MANUAL_SRGB 82 | return pow(linearColor, 1.0 / 2.2); 83 | #else 84 | return linearColor; 85 | #endif 86 | } 87 | 88 | float3 Diffuse_Lambert(float3 diffuseColor) 89 | { 90 | return diffuseColor / M_PI; 91 | } 92 | 93 | float3 F_Schlick(float3 r0, float3 f90, float LdH) 94 | { 95 | return r0 + (f90 - r0) * pow(clamp(1.0 - LdH, 0.0, 1.0), 5.0); 96 | } 97 | 98 | float G_Smith(float NdL, float NdV, float alphaRoughness) 99 | { 100 | float a2 = alphaRoughness * alphaRoughness; 101 | 102 | float gl = NdL + sqrt(a2 + (1.0 - a2) * (NdL * NdL)); 103 | float gv = NdV + sqrt(a2 + (1.0 - a2) * (NdV * NdV)); 104 | return 1.0f / (gl * gv); // The division by (4.0 * NdL * NdV) is unneeded with this form 105 | } 106 | 107 | float D_GGX(float NdH, float alphaRoughness) 108 | { 109 | float a2 = alphaRoughness * alphaRoughness; 110 | float f = (NdH * a2 - NdH) * NdH + 1.0; 111 | return a2 / (M_PI * f * f); 112 | } 113 | 114 | float3 IBLContribution(float3 diffuseColor, float3 specularColor, float perceptualRoughness, float NdV, float3 N, float3 reflection) 115 | { 116 | float mipCount = 7.0f; 117 | float lod = (perceptualRoughness * mipCount); 118 | float3 brdf = SRGBtoLINEAR(BRDF_LUT.Sample(SampLinearClamp, float2(NdV, 1.0 - perceptualRoughness))).rgb; 119 | float3 diffuseLight = SRGBtoLINEAR(DiffuseEnvMap.Sample(SampAnisotropicWrap, N)).rgb; 120 | 121 | float3 specularLight = SRGBtoLINEAR(SpecularEnvMap.SampleLevel(SampAnisotropicWrap, reflection, lod)).rgb; 122 | 123 | float3 diffuse = diffuseLight * diffuseColor; 124 | float3 specular = specularLight * (specularColor * brdf.x + brdf.y); 125 | 126 | return diffuse + specular; 127 | } -------------------------------------------------------------------------------- /examples/viewer/DirectX/Shaders/Default.hlsl: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | 7 | #include "Common.hlsli" 8 | 9 | struct VS_INPUT 10 | { 11 | float3 Pos : POSITION; 12 | float3 Normal : NORMAL; 13 | float4 Tangent : TANGENT; 14 | float2 TexC : TEXCOORD; 15 | }; 16 | 17 | struct PS_INPUT 18 | { 19 | float4 PosH : SV_POSITION; 20 | float3 PosW : POSITION; 21 | float3 NormalW : NORMAL; 22 | float3 TangentW : TANGENT; 23 | float3 BinormalW : BINORMAL; 24 | float2 TexC : TEXCOORD; 25 | }; 26 | 27 | //-------------------------------------------------------------------------------------- 28 | 29 | PS_INPUT StandardVS(VS_INPUT input) 30 | { 31 | PS_INPUT output; 32 | 33 | // Transform to world space + homogeneous clip space... 34 | output.PosW = mul(input.Pos, (float3x3)World); 35 | output.PosH = mul(float4(input.Pos, 1.0f), WorldViewProj); 36 | 37 | // Assumes nonuniform scaling; otherwise, need to use inverse-transpose of world matrix. 38 | output.NormalW = mul(input.Normal, (float3x3)World); 39 | output.TangentW = mul(input.Tangent.xyz, (float3x3)World); 40 | output.BinormalW = cross(output.NormalW, output.TangentW) * input.Tangent.w; 41 | output.TexC = input.TexC; 42 | 43 | return output; 44 | } 45 | 46 | //-------------------------------------------------------------------------------------- 47 | 48 | float4 UberPS(PS_INPUT input) 49 | : SV_Target 50 | { 51 | MaterialData matData = MaterialDataBuffer[MaterialIndex]; 52 | 53 | input.NormalW = normalize(input.NormalW); 54 | 55 | float4 finalColor; 56 | 57 | #if USE_AUTO_COLOR 58 | finalColor = matData.MeshAutoColor; 59 | finalColor += float4(saturate(dot(DirectionalLight.Direction, input.NormalW) * DirectionalLight.Strength), 1.0f); 60 | #else 61 | 62 | #if HAS_BASECOLORMAP 63 | float4 baseColor = SRGBtoLINEAR(Textures[matData.BaseColorIndex].Sample(SampAnisotropicWrap, input.TexC)) * matData.BaseColorFactor; 64 | #else 65 | float4 baseColor = matData.BaseColorFactor; 66 | #endif 67 | 68 | #if HAS_METALROUGHNESSMAP 69 | float4 metalRoughSample = Textures[matData.MetalRoughIndex].Sample(SampAnisotropicWrap, input.TexC); 70 | float perceptualRoughness = metalRoughSample.g * matData.RoughnessFactor; 71 | float metallic = metalRoughSample.b * matData.MetallicFactor; 72 | #else 73 | float perceptualRoughness = matData.RoughnessFactor; 74 | float metallic = matData.MetallicFactor; 75 | #endif 76 | 77 | perceptualRoughness = clamp(perceptualRoughness, MinRoughness, 1.0); 78 | metallic = clamp(metallic, 0.0, 1.0); 79 | 80 | float alphaRoughness = perceptualRoughness * perceptualRoughness; 81 | 82 | float3 f0 = MinRoughness; 83 | float3 diffuseColor = baseColor.rgb * (1.0 - f0) * (1.0 - metallic); 84 | float3 specularColor = lerp(f0, baseColor.rgb, metallic); 85 | 86 | float reflectance = max(max(specularColor.r, specularColor.g), specularColor.b); 87 | 88 | // For typical incident reflectance range (between 4% to 100%) set the grazing reflectance to 100% for typical fresnel effect. 89 | // For very low reflectance range on highly diffuse objects (below 4%), incrementally reduce grazing reflecance to 0%. 90 | float reflectance90 = clamp(reflectance * 25.0, 0.0, 1.0); 91 | float3 specularEnvironmentR0 = specularColor.rgb; 92 | float3 specularEnvironmentR90 = reflectance90; 93 | 94 | #if HAS_TANGENTS 95 | float3x3 TBN = float3x3(normalize(input.TangentW), normalize(input.BinormalW), input.NormalW); 96 | #else 97 | float3 pos_dx = ddx(input.PosW); 98 | float3 pos_dy = ddy(input.PosW); 99 | float3 tex_dx = ddx(float3(input.TexC, 0.0)); 100 | float3 tex_dy = ddy(float3(input.TexC, 0.0)); 101 | float3 t = (tex_dy.y * pos_dx - tex_dx.y * pos_dy) / (tex_dx.x * tex_dy.y - tex_dy.x * tex_dx.y); 102 | 103 | float3 n = input.NormalW; 104 | t = normalize(t - n * dot(n, t)); 105 | float3 b = normalize(cross(n, t)); 106 | 107 | float3x3 TBN = float3x3(t, b, n); 108 | #endif 109 | 110 | #if HAS_NORMALMAP 111 | float4 normalMapSample = Textures[matData.NormalIndex].Sample(SampAnisotropicWrap, input.TexC); 112 | normalMapSample.g = 1.0f - normalMapSample.g; 113 | 114 | float3 normal = (2.0f * normalMapSample.rgb - 1.0f) * float3(matData.NormalScale, matData.NormalScale, 1.0f); 115 | float3 N = normalize(mul(normal, TBN)); 116 | #else 117 | float3 N = TBN[2].xyz; 118 | #endif 119 | 120 | float3 V = normalize(Camera.xyz - input.PosW); // Vector from surface point to camera 121 | float3 L = normalize(DirectionalLight.Direction); // Vector from surface point to light 122 | float3 H = normalize(L + V); // Half vector between both l and v 123 | float3 reflection = -normalize(reflect(V, N)); 124 | 125 | float NdL = clamp(dot(N, L), 0.001, 1.0); 126 | float NdV = clamp(abs(dot(N, V)), 0.001, 1.0); 127 | float NdH = clamp(dot(N, H), 0.0, 1.0); 128 | float LdH = clamp(dot(L, H), 0.0, 1.0); 129 | 130 | // Calculate the shading terms for the microfacet specular shading model 131 | float3 F = F_Schlick(specularEnvironmentR0, specularEnvironmentR90, LdH); 132 | float G = G_Smith(NdL, NdV, alphaRoughness); 133 | float D = D_GGX(NdH, alphaRoughness); 134 | 135 | // Calculation of analytical lighting contribution 136 | float3 diffuseContrib = (1.0 - F) * Diffuse_Lambert(diffuseColor); 137 | float3 specContrib = F * (G * D); 138 | float3 color = NdL * (diffuseContrib + specContrib); 139 | 140 | // Calculate lighting contribution from image based lighting source (IBL) 141 | #if USE_IBL 142 | color += IBLContribution(diffuseColor, specularColor, perceptualRoughness, NdV, N, reflection); 143 | #endif 144 | 145 | // Apply optional PBR terms for additional (optional) shading 146 | #if HAS_OCCLUSIONMAP 147 | #if HAS_OCCLUSIONMAP_COMBINED 148 | float ao = metalRoughSample.r; 149 | #else 150 | float ao = Textures[matData.AOIndex].Sample(SampAnisotropicWrap, input.TexC).r; 151 | #endif 152 | 153 | color = lerp(color, color * ao, matData.AOStrength); 154 | #endif 155 | 156 | #if HAS_EMISSIVEMAP 157 | float3 emissive = SRGBtoLINEAR(Textures[matData.EmissiveIndex].Sample(SampAnisotropicWrap, input.TexC)).rgb * matData.EmissiveFactor; 158 | color += emissive; 159 | #endif 160 | 161 | finalColor = float4(LINEARtoSRGB(color), baseColor.a); 162 | #endif 163 | 164 | return finalColor; 165 | } 166 | 167 | float4 GroundPS(PS_INPUT input) 168 | : SV_Target 169 | { 170 | const float3 gridColorMajor = 0.0f; 171 | const float3 gridColorMinor = 0.3f; 172 | const float gridSizeMajor = 4; 173 | const float gridSizeMinor = 1; 174 | const float epsilon = 0.05f; 175 | 176 | float4 finalColor = 0.98f; 177 | 178 | float wx = input.PosW.x; 179 | float wz = input.PosW.z; 180 | float x0 = abs(frac(wx / gridSizeMajor - 0.5) - 0.5) / fwidth(wx) * gridSizeMajor / 2.0; 181 | float z0 = abs(frac(wz / gridSizeMajor - 0.5) - 0.5) / fwidth(wz) * gridSizeMajor / 2.0; 182 | 183 | float x1 = abs(frac(wx / gridSizeMinor - 0.5) - 0.5) / fwidth(wx) * gridSizeMinor; 184 | float z1 = abs(frac(wz / gridSizeMinor - 0.5) - 0.5) / fwidth(wz) * gridSizeMinor; 185 | 186 | float v0 = 1.0 - clamp(min(x0, z0), 0.0, 1.0); 187 | float v1 = 1.0 - clamp(min(x1, z1), 0.0, 1.0); 188 | 189 | if (v0 > epsilon) 190 | { 191 | finalColor.rgb = lerp(finalColor.rgb, gridColorMajor, v0); 192 | } 193 | else 194 | { 195 | finalColor.rgb = lerp(finalColor.rgb, gridColorMinor, v1); 196 | } 197 | 198 | float awx = abs(wx); 199 | float awz = abs(wz); 200 | if (awx < epsilon) 201 | { 202 | finalColor.rgb = lerp(float3(0, 1, 0), finalColor.rgb, awx / epsilon); 203 | } 204 | else if (awz < epsilon) 205 | { 206 | finalColor.rgb = lerp(float3(1, 0, 0), finalColor.rgb, awz / epsilon); 207 | } 208 | 209 | finalColor.a *= 1.0 - clamp((length(input.PosW.xz) - 20) / 25.0, 0.1, 1.0); 210 | return finalColor; 211 | } 212 | -------------------------------------------------------------------------------- /examples/viewer/DirectX/Shaders/Sky.hlsl: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | 7 | #include "Common.hlsli" 8 | 9 | struct VS_INPUT 10 | { 11 | float3 PosL : POSITION; 12 | float3 NormalL : NORMAL; 13 | float2 TexC : TEXCOORD; 14 | }; 15 | 16 | struct PS_INPUT 17 | { 18 | float4 PosH : SV_POSITION; 19 | float3 PosL : POSITION; 20 | }; 21 | 22 | PS_INPUT VS(VS_INPUT input) 23 | { 24 | PS_INPUT output; 25 | 26 | // Use local vertex position as cubemap lookup vector. 27 | output.PosL = input.PosL; 28 | 29 | // Transform to world space. 30 | float4 posW = mul(float4(input.PosL, 1.0f), World); 31 | 32 | // Always center sky about camera. 33 | posW.xyz += Camera.xyz; 34 | 35 | // Set z = w so that z/w = 1 (i.e., skydome always on far plane). 36 | output.PosH = mul(posW, ViewProj).xyww; 37 | 38 | return output; 39 | } 40 | 41 | float4 PS(PS_INPUT input) 42 | : SV_Target 43 | { 44 | return Environment.Sample(SampAnisotropicWrap, input.PosL.xy); 45 | } 46 | -------------------------------------------------------------------------------- /examples/viewer/Engine.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include 9 | 10 | #include "DirectX/D3DEngine.h" 11 | #include "EngineOptions.h" 12 | #include "Platform/Mouse.h" 13 | #include "Platform/platform.h" 14 | #include "StepTimer.h" 15 | 16 | class Engine 17 | { 18 | public: 19 | explicit Engine(EngineOptions const & config) 20 | : Config(config) 21 | { 22 | } 23 | 24 | Engine(Engine const &) = delete; 25 | Engine(Engine &&) = delete; 26 | Engine & operator=(Engine const &) = delete; 27 | Engine & operator=(Engine &&) = delete; 28 | 29 | virtual ~Engine() = default; 30 | 31 | const EngineOptions Config; 32 | 33 | Mouse::ButtonStateTracker MouseTracker{}; 34 | 35 | void Initialize(HWND hwnd) 36 | { 37 | const int MaxTextLength = 32; 38 | wchar_t currentWindowText[MaxTextLength] = { 0 }; 39 | GetWindowText(hwnd, currentWindowText, MaxTextLength); 40 | 41 | m_window = hwnd; 42 | m_windowTitle.assign(currentWindowText); 43 | 44 | m_mouse.SetWindow(hwnd); 45 | m_mouse.ResetScrollWheelValue(); 46 | 47 | InitializeCore(hwnd); 48 | } 49 | 50 | void Tick() 51 | { 52 | UpdateStats(); 53 | 54 | m_timer.Tick([this]() { 55 | MouseTracker.Update(m_mouse.GetState()); 56 | 57 | Update(static_cast(m_timer.GetElapsedSeconds())); 58 | }); 59 | 60 | Render(); 61 | } 62 | 63 | void ChangeWindowSize(int width, int height) 64 | { 65 | ChangeWindowSizeCore(width, height); 66 | } 67 | 68 | protected: 69 | virtual void InitializeCore(HWND hwnd) = 0; 70 | virtual void Update(float elapsedTime) noexcept = 0; 71 | virtual void Render() = 0; 72 | virtual void ChangeWindowSizeCore(int width, int height) = 0; 73 | 74 | private: 75 | HWND m_window{}; 76 | std::wstring m_windowTitle{}; 77 | 78 | Mouse m_mouse{}; 79 | StepTimer m_timer{}; 80 | 81 | void UpdateStats() 82 | { 83 | static double prevTime = 0.0f; 84 | 85 | const double currentTime = m_timer.GetTotalSeconds(); 86 | if ((currentTime - prevTime) >= 1.0f) 87 | { 88 | std::wstring fps = std::to_wstring(m_timer.GetFramesPerSecond()); 89 | std::wstring windowText = m_windowTitle + L" : " + fps + L" fps"; 90 | 91 | SetWindowText(m_window, windowText.c_str()); 92 | 93 | prevTime = currentTime; 94 | } 95 | } 96 | }; 97 | -------------------------------------------------------------------------------- /examples/viewer/EngineOptions.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include 9 | 10 | struct EngineOptions 11 | { 12 | int Width{ 960 }; 13 | int Height{ 540 }; 14 | 15 | bool AutoRotate{}; 16 | bool EnableMaterials{}; 17 | bool EnableIBL{}; 18 | bool EnableGround{}; 19 | 20 | float CameraX{ 0 }; 21 | float CameraY{ 0 }; 22 | float CameraZ{ 8.0f }; 23 | 24 | std::string ModelPath{}; 25 | }; 26 | -------------------------------------------------------------------------------- /examples/viewer/ImageData.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include 9 | 10 | #if defined(_MSC_VER) 11 | #if _MSC_VER < 1914 || (!defined(_HAS_CXX17) || (defined(_HAS_CXX17) && _HAS_CXX17 == 0)) 12 | #define VIEWER_EXPERIMENTAL_FILESYSTEM 13 | #endif 14 | #endif 15 | 16 | #ifdef VIEWER_EXPERIMENTAL_FILESYSTEM 17 | #include 18 | #define VIEWER_FILESYSTEM std::experimental::filesystem::v1 19 | #else 20 | #include 21 | #define VIEWER_FILESYSTEM std::filesystem 22 | #endif 23 | 24 | class ImageData 25 | { 26 | public: 27 | struct ImageInfo 28 | { 29 | VIEWER_FILESYSTEM::path FileName{}; 30 | 31 | uint32_t BinarySize{}; 32 | uint8_t const * BinaryData{}; 33 | 34 | bool IsBinary() const noexcept 35 | { 36 | return BinaryData != nullptr; 37 | } 38 | }; 39 | 40 | explicit ImageData(std::string const & texture) 41 | { 42 | m_info.FileName = texture; 43 | } 44 | 45 | ImageData(fx::gltf::Document const & doc, std::size_t textureIndex, std::string const & modelPath) 46 | { 47 | fx::gltf::Image const & image = doc.images[doc.textures[textureIndex].source]; 48 | 49 | const bool isEmbedded = image.IsEmbeddedResource(); 50 | if (!image.uri.empty() && !isEmbedded) 51 | { 52 | m_info.FileName = fx::gltf::detail::GetDocumentRootPath(modelPath) / image.uri; 53 | } 54 | else 55 | { 56 | if (isEmbedded) 57 | { 58 | image.MaterializeData(m_embeddedData); 59 | m_info.BinaryData = &m_embeddedData[0]; 60 | m_info.BinarySize = static_cast(m_embeddedData.size()); 61 | } 62 | else 63 | { 64 | fx::gltf::BufferView const & bufferView = doc.bufferViews[image.bufferView]; 65 | fx::gltf::Buffer const & buffer = doc.buffers[bufferView.buffer]; 66 | 67 | m_info.BinaryData = &buffer.data[bufferView.byteOffset]; 68 | m_info.BinarySize = bufferView.byteLength; 69 | } 70 | } 71 | } 72 | 73 | ImageInfo const & Info() const noexcept 74 | { 75 | return m_info; 76 | } 77 | 78 | private: 79 | ImageInfo m_info{}; 80 | 81 | std::vector m_embeddedData{}; 82 | }; 83 | -------------------------------------------------------------------------------- /examples/viewer/Logger.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include 9 | #include "StringFormatter.h" 10 | 11 | namespace Logger 12 | { 13 | template 14 | static void Write(std::basic_string_view format, Args &&... args) 15 | { 16 | if constexpr (sizeof...(args) > 0) 17 | { 18 | std::cout << fx::common::StringFormatter::Format(format, std::forward(args)...); 19 | } 20 | else 21 | { 22 | std::cout << format; 23 | } 24 | } 25 | 26 | template 27 | static void WriteLine(std::basic_string_view format, Args &&... args) 28 | { 29 | Logger::Write(format, std::forward(args)...); 30 | std::cout << std::endl; 31 | } 32 | } // namespace Logger 33 | -------------------------------------------------------------------------------- /examples/viewer/MaterialData.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include 9 | 10 | class MaterialData 11 | { 12 | public: 13 | void SetData(fx::gltf::Material const & material) 14 | { 15 | m_material = material; 16 | m_hasData = true; 17 | } 18 | 19 | fx::gltf::Material const & Data() const noexcept 20 | { 21 | return m_material; 22 | } 23 | 24 | bool HasData() const 25 | { 26 | return m_hasData && !m_material.pbrMetallicRoughness.empty(); 27 | } 28 | 29 | private: 30 | fx::gltf::Material m_material{}; 31 | bool m_hasData{}; 32 | }; 33 | -------------------------------------------------------------------------------- /examples/viewer/MeshData.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include 9 | #include "MaterialData.h" 10 | 11 | class MeshData 12 | { 13 | public: 14 | struct BufferInfo 15 | { 16 | fx::gltf::Accessor const * Accessor; 17 | 18 | uint8_t const * Data; 19 | uint32_t DataStride; 20 | uint32_t TotalSize; 21 | 22 | bool HasData() const noexcept 23 | { 24 | return Data != nullptr; 25 | } 26 | }; 27 | 28 | MeshData(fx::gltf::Document const & doc, std::size_t meshIndex, std::size_t primitveIndex) 29 | { 30 | fx::gltf::Mesh const & mesh = doc.meshes[meshIndex]; 31 | fx::gltf::Primitive const & primitive = mesh.primitives[primitveIndex]; 32 | 33 | for (auto const & attrib : primitive.attributes) 34 | { 35 | if (attrib.first == "POSITION") 36 | { 37 | m_vertexBuffer = GetData(doc, doc.accessors[attrib.second]); 38 | } 39 | else if (attrib.first == "NORMAL") 40 | { 41 | m_normalBuffer = GetData(doc, doc.accessors[attrib.second]); 42 | } 43 | else if (attrib.first == "TANGENT") 44 | { 45 | m_tangentBuffer = GetData(doc, doc.accessors[attrib.second]); 46 | } 47 | else if (attrib.first == "TEXCOORD_0") 48 | { 49 | m_texCoord0Buffer = GetData(doc, doc.accessors[attrib.second]); 50 | } 51 | } 52 | 53 | m_indexBuffer = GetData(doc, doc.accessors[primitive.indices]); 54 | 55 | if (primitive.material >= 0) 56 | { 57 | m_materialData.SetData(doc.materials[primitive.material]); 58 | } 59 | } 60 | 61 | BufferInfo const & IndexBuffer() const noexcept 62 | { 63 | return m_indexBuffer; 64 | } 65 | 66 | BufferInfo const & VertexBuffer() const noexcept 67 | { 68 | return m_vertexBuffer; 69 | } 70 | 71 | BufferInfo const & NormalBuffer() const noexcept 72 | { 73 | return m_normalBuffer; 74 | } 75 | 76 | BufferInfo const & TangentBuffer() const noexcept 77 | { 78 | return m_tangentBuffer; 79 | } 80 | 81 | BufferInfo const & TexCoord0Buffer() const noexcept 82 | { 83 | return m_texCoord0Buffer; 84 | } 85 | 86 | MaterialData const & Material() const noexcept 87 | { 88 | return m_materialData; 89 | } 90 | 91 | private: 92 | BufferInfo m_indexBuffer{}; 93 | BufferInfo m_vertexBuffer{}; 94 | BufferInfo m_normalBuffer{}; 95 | BufferInfo m_tangentBuffer{}; 96 | BufferInfo m_texCoord0Buffer{}; 97 | 98 | MaterialData m_materialData{}; 99 | 100 | static BufferInfo GetData(fx::gltf::Document const & doc, fx::gltf::Accessor const & accessor) 101 | { 102 | fx::gltf::BufferView const & bufferView = doc.bufferViews[accessor.bufferView]; 103 | fx::gltf::Buffer const & buffer = doc.buffers[bufferView.buffer]; 104 | 105 | const uint32_t dataTypeSize = CalculateDataTypeSize(accessor); 106 | return BufferInfo{ &accessor, &buffer.data[static_cast(bufferView.byteOffset) + accessor.byteOffset], dataTypeSize, accessor.count * dataTypeSize }; 107 | } 108 | 109 | static uint32_t CalculateDataTypeSize(fx::gltf::Accessor const & accessor) noexcept 110 | { 111 | uint32_t elementSize = 0; 112 | switch (accessor.componentType) 113 | { 114 | case fx::gltf::Accessor::ComponentType::Byte: 115 | case fx::gltf::Accessor::ComponentType::UnsignedByte: 116 | elementSize = 1; 117 | break; 118 | case fx::gltf::Accessor::ComponentType::Short: 119 | case fx::gltf::Accessor::ComponentType::UnsignedShort: 120 | elementSize = 2; 121 | break; 122 | case fx::gltf::Accessor::ComponentType::Float: 123 | case fx::gltf::Accessor::ComponentType::UnsignedInt: 124 | elementSize = 4; 125 | break; 126 | } 127 | 128 | switch (accessor.type) 129 | { 130 | case fx::gltf::Accessor::Type::Mat2: 131 | return 4 * elementSize; 132 | break; 133 | case fx::gltf::Accessor::Type::Mat3: 134 | return 9 * elementSize; 135 | break; 136 | case fx::gltf::Accessor::Type::Mat4: 137 | return 16 * elementSize; 138 | break; 139 | case fx::gltf::Accessor::Type::Scalar: 140 | return elementSize; 141 | break; 142 | case fx::gltf::Accessor::Type::Vec2: 143 | return 2 * elementSize; 144 | break; 145 | case fx::gltf::Accessor::Type::Vec3: 146 | return 3 * elementSize; 147 | break; 148 | case fx::gltf::Accessor::Type::Vec4: 149 | return 4 * elementSize; 150 | break; 151 | } 152 | 153 | return 0; 154 | } 155 | }; 156 | -------------------------------------------------------------------------------- /examples/viewer/Platform/COMUtil.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace COMUtil 13 | { 14 | // Helper class for COM exceptions 15 | class com_exception : public std::exception 16 | { 17 | public: 18 | explicit com_exception(HRESULT hr) noexcept 19 | : result(hr) {} 20 | 21 | const char * what() const noexcept override 22 | { 23 | static char s_str[64] = {}; 24 | sprintf_s(s_str, "Failure with HRESULT of %08X", result); 25 | return s_str; 26 | } 27 | 28 | private: 29 | HRESULT result; 30 | }; 31 | 32 | // Helper utility converts D3D API failures into exceptions. 33 | inline void ThrowIfFailed(HRESULT hr) 34 | { 35 | if (FAILED(hr)) 36 | { 37 | throw com_exception(hr); 38 | } 39 | } 40 | 41 | inline void Init() 42 | { 43 | ThrowIfFailed(CoInitializeEx(nullptr, 0)); 44 | } 45 | } // namespace COMUtil -------------------------------------------------------------------------------- /examples/viewer/Platform/Mouse.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include 9 | 10 | class Mouse 11 | { 12 | public: 13 | Mouse() noexcept(false); 14 | Mouse(Mouse && moveFrom) noexcept; 15 | Mouse & operator=(Mouse && moveFrom) noexcept; 16 | 17 | Mouse(Mouse const &) = delete; 18 | Mouse & operator=(Mouse const &) = delete; 19 | 20 | virtual ~Mouse(); 21 | 22 | enum Mode 23 | { 24 | MODE_ABSOLUTE = 0, 25 | MODE_RELATIVE, 26 | }; 27 | 28 | struct State 29 | { 30 | bool leftButton; 31 | bool middleButton; 32 | bool rightButton; 33 | bool xButton1; 34 | bool xButton2; 35 | int x; 36 | int y; 37 | int scrollWheelValue; 38 | Mode positionMode; 39 | }; 40 | 41 | class ButtonStateTracker 42 | { 43 | public: 44 | enum ButtonState 45 | { 46 | UP = 0, // Button is up 47 | HELD = 1, // Button is held down 48 | RELEASED = 2, // Button was just released 49 | PRESSED = 3, // Buton was just pressed 50 | }; 51 | 52 | ButtonState leftButton{}; 53 | ButtonState middleButton{}; 54 | ButtonState rightButton{}; 55 | ButtonState xButton1{}; 56 | ButtonState xButton2{}; 57 | 58 | ButtonStateTracker() noexcept 59 | { 60 | Reset(); 61 | } 62 | 63 | void __cdecl Update(const State & state) noexcept; 64 | 65 | void __cdecl Reset() noexcept; 66 | 67 | State __cdecl GetLastState() const noexcept 68 | { 69 | return lastState; 70 | } 71 | 72 | private: 73 | State lastState{}; 74 | }; 75 | 76 | // Retrieve the current state of the mouse 77 | State __cdecl GetState() const; 78 | 79 | // Resets the accumulated scroll wheel value 80 | void __cdecl ResetScrollWheelValue() noexcept; 81 | 82 | // Sets mouse mode (defaults to absolute) 83 | void __cdecl SetMode(Mode mode); 84 | 85 | // Feature detection 86 | bool __cdecl IsConnected() const noexcept; 87 | 88 | // Cursor visibility 89 | bool __cdecl IsVisible() const; 90 | void __cdecl SetVisible(bool visible); 91 | 92 | void __cdecl SetWindow(HWND window); 93 | static void __cdecl ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam); 94 | 95 | // Singleton 96 | static Mouse & __cdecl Get(); 97 | 98 | private: 99 | class Impl; 100 | 101 | std::unique_ptr pImpl; 102 | }; 103 | -------------------------------------------------------------------------------- /examples/viewer/Platform/platform.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #define WIN32_LEAN_AND_MEAN 9 | 10 | #define NOMINMAX 11 | #define NOSERVICE 12 | #define NOMCX 13 | 14 | #define NODRAWTEXT 15 | #define NOGDI 16 | #define NOBITMAP 17 | 18 | #define NOHELP 19 | 20 | #include 21 | -------------------------------------------------------------------------------- /examples/viewer/README.md: -------------------------------------------------------------------------------- 1 | # glTF Viewer 2 | 3 | An application for demonstrating the practical usage of [fx-gltf](https://github.com/jessey-git/fx-gltf) within the context of a modern rendering project. 4 | 5 | ## Features 6 | * Lightly abstracted DirectX 12 rendering 7 | * Enough abstraction to get out of the way, but not too much so-as to interfere with learning 8 | * Auto mesh coloring for easy debugging/experimentation 9 | * PBR materials with image-based lighting (IBL) 10 | 11 | ![screenshot](screenshots/screenshot00.png) 12 | ![screenshot](screenshots/screenshot01.png) 13 | ![screenshot](screenshots/screenshot02.png) 14 | ![screenshot](screenshots/screenshot03.png) 15 | ![screenshot](screenshots/screenshot04.png) 16 | ![screenshot](screenshots/screenshot05.png) 17 | ![screenshot](screenshots/screenshot06.png) 18 | ![screenshot](screenshots/screenshot07.png) 19 | 20 | ## Usage 21 | ``` 22 | A simple glTF2.0 scene viewer using DirectX 12 23 | Usage: viewer.exe [OPTIONS] file 24 | 25 | Positionals: 26 | file TEXT (REQUIRED) Scene to load (.gltf or .glb) 27 | 28 | Options: 29 | -h,--help Print this help message and exit 30 | --width INT Initial window width 31 | --height INT Initial window height 32 | -r,--rotate Auto rotate model 33 | -m,--materials Enable model materials 34 | -i,--ibl Enable IBL 35 | -g,--ground Enable ground plane 36 | -x FLOAT Camera x position 37 | -y FLOAT Camera y position 38 | -z FLOAT Camera z position 39 | 40 | Controls: 41 | Orbit with left mouse button 42 | Dolly with middle mouse button 43 | ``` 44 | 45 | ## Design 46 | 47 | ### Code 48 | 49 | #### ```MeshData.h``` 50 | * Processes a given ```fx::gltf::Mesh```/```fx::gltf::Mesh::Primitive``` pair using its ```fx::gltf::Buffer``` and ```fx::gltf::Accessor``` information and exposes it in a more graphics-api friendly manner 51 | 52 | #### ```ImageData.h``` 53 | * Processes a given ```fx::gltf::Image``` and fills in the appropriate set of information for actual loading 54 | * The difference between external, embedded, and binary glTF formats are handled here 55 | 56 | #### ```DirectX/D3DMeshData.cpp``` 57 | * Uses ```MeshData``` to build the actual vertex/normal/index etc. buffers for DirectX 12 58 | * Performs the mesh's command list drawing during scene render 59 | 60 | #### ```DirectX/D3DTextureSet.cpp``` 61 | * Uses ```ImageData``` to load in the actual image textures with the WIC API 62 | 63 | #### ```DirectX/D3DGraph.h``` 64 | * Uses ```fx::gltf::Document``` and ```fx::gltf::Node``` to visit each node in the scene-graph 65 | * Applies the node's transformation data so we can use it during update/render 66 | 67 | #### ```DirectX/D3DEngine.cpp``` 68 | * Builds all DirectX 12 resources necessary for rendering 69 | * Uses ```D3DGraph``` to build/traverse the scene-graph, building up ```D3DMeshInstance```s along the way 70 | * Coordinates the update/render sequencing flow 71 | 72 | ### Psuedo call-graph 73 | * Win32Application::Run 74 | * D3DEngine / Engine 75 | * Initialization 76 | * Load and process the glTF document 77 | * Textures and materials 78 | * Mesh pieces 79 | * Scene-graph 80 | * Compile necessary shaders to support the loaded materials 81 | * Establish all DirectX resources (Heaps, PSO's, constant buffers, etc.) 82 | * Update/Render loop 83 | * Set Root Signature, PSO's, constant buffers, structured buffers, etc. 84 | * Draw each mesh instance 85 | * Set mesh constant buffers 86 | * Set mesh vertex/normal/index buffers 87 | * Perform actual draw call 88 | 89 | ### DirectX 12 / glTF Integration Notes 90 | 91 | #### PipelineStateObject Input Layout 92 | Most glTF files today store vertex/normal/tex-coord buffers separately from each other (non interleaved). For example, consider a simple 6 vertex mesh with vertex/normal/tex-coord formats of Vec3/Vec3/Vec2 respectively: 93 | 94 | ```[vvvvvv][nnnnnn][tttttt] == 3 buffers: [72 bytes][72 bytes][48 bytes]``` 95 | 96 | Compare this against an interleaved layout which looks as follows: 97 | 98 | ```[vntvntvntvntvntvnt] == 1 buffer: [192 bytes]``` 99 | 100 | This matters when creating the PipelineStateObject's InputLayout. To properly use glTF data in this format without extra data manipulation, make use of the ```D3D12_INPUT_ELEMENT_DESC.InputSlot``` field. See ```D3DEngine::BuildPipelineStateObjects``` for reference. 101 | 102 | ## Known Issues and TODOs 103 | * General 104 | * Interleaved buffers from glTF are not supported currently 105 | * Scene centering does not seem to be working for all tested models. Some scenes still seem shifted away from 0,0,0 106 | 107 | * DirectX 12 108 | * Minor: Mip-maps are not generated for the textures 109 | * Advanced: A memory-manager with pooling / page management should be implemented 110 | * Advanced: A new set of abstractions for properly batching ResourceBarriers is needed (likely requires refactoring away DeviceResources + FrameResources into finer grained objects) 111 | 112 | * Vulkan 113 | * Needs implementing (patches welcome) 114 | 115 | ## Supported Compilers 116 | * Microsoft Visual C++ 2017 15.6+ (and possibly earlier) 117 | 118 | ## License 119 | 120 | 121 | 122 | Licensed under the MIT License . 123 | 124 | Copyright (c) 2018-2022 Jesse Yurkovich 125 | 126 | Permission is hereby granted, free of charge, to any person obtaining a copy 127 | of this software and associated documentation files (the "Software"), to deal 128 | in the Software without restriction, including without limitation the rights 129 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 130 | copies of the Software, and to permit persons to whom the Software is 131 | furnished to do so, subject to the following conditions: 132 | 133 | The above copyright notice and this permission notice shall be included in all 134 | copies or substantial portions of the Software. 135 | 136 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 137 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 138 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 139 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 140 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 141 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 142 | SOFTWARE. 143 | 144 | ## Used third-party tools 145 | 146 | This software would not be possible without the help of the following resources. 147 | 148 | * [d3dx12.h](https://github.com/Microsoft/DirectX-Graphics-Samples/tree/master/Libraries/D3DX12) containing helper structures and functions for D3D12 149 | * [DirectX-Graphics-Samples](https://github.com/Microsoft/DirectX-Graphics-Samples) for usage examples and API inspiration 150 | * [Xbox-ATG-Samples](https://github.com/Microsoft/Xbox-ATG-Samples) for usage examples and API inspiration 151 | * [CLI11](https://github.com/CLIUtils/CLI11) for command-line parsing 152 | -------------------------------------------------------------------------------- /examples/viewer/ShaderOptions.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | enum class ShaderOptions : uint64_t 13 | { 14 | // clang-format off 15 | None = 0, 16 | USE_AUTO_COLOR = (1u << 0u), 17 | USE_FACTORS_ONLY = (1u << 1u), 18 | USE_MANUAL_SRGB = (1u << 2u), 19 | USE_IBL = (1u << 3u), 20 | HAS_BASECOLORMAP = (1u << 4u), 21 | HAS_TANGENTS = (1u << 5u), 22 | HAS_NORMALMAP = (1u << 6u), 23 | HAS_METALROUGHNESSMAP = (1u << 7u), 24 | HAS_OCCLUSIONMAP = (1u << 8u), 25 | HAS_OCCLUSIONMAP_COMBINED = (1u << 9u), 26 | HAS_EMISSIVEMAP = (1u << 10u), 27 | 28 | IS_GROUND = (1u << 12u), 29 | // clang-format on 30 | }; 31 | 32 | inline constexpr ShaderOptions operator|(ShaderOptions a, ShaderOptions b) noexcept 33 | { 34 | return static_cast(static_cast(a) | static_cast(b)); 35 | } 36 | 37 | inline ShaderOptions & operator|=(ShaderOptions & a, ShaderOptions b) noexcept 38 | { 39 | return reinterpret_cast(reinterpret_cast(a) |= static_cast(b)); 40 | } 41 | 42 | inline constexpr ShaderOptions operator&(ShaderOptions a, ShaderOptions b) noexcept 43 | { 44 | return static_cast(static_cast(a) & static_cast(b)); 45 | } 46 | 47 | inline ShaderOptions & operator&=(ShaderOptions & a, ShaderOptions b) noexcept 48 | { 49 | return reinterpret_cast(reinterpret_cast(a) &= static_cast(b)); 50 | } 51 | 52 | inline constexpr ShaderOptions operator~(ShaderOptions a) noexcept 53 | { 54 | return static_cast(~static_cast(a)); 55 | } 56 | 57 | inline constexpr ShaderOptions operator^(ShaderOptions a, ShaderOptions b) noexcept 58 | { 59 | return static_cast(static_cast(a) ^ static_cast(b)); 60 | } 61 | 62 | inline ShaderOptions & operator^=(ShaderOptions & a, ShaderOptions b) noexcept 63 | { 64 | return reinterpret_cast(reinterpret_cast(a) ^= static_cast(b)); 65 | } 66 | 67 | inline constexpr bool IsSet(ShaderOptions options, ShaderOptions flag) noexcept 68 | { 69 | return (options & flag) == flag; 70 | } 71 | 72 | inline std::vector GetShaderDefines(ShaderOptions options) 73 | { 74 | std::vector defines; 75 | 76 | if (IsSet(options, ShaderOptions::USE_AUTO_COLOR)) 77 | { 78 | defines.emplace_back("USE_AUTO_COLOR"); 79 | } 80 | if (IsSet(options, ShaderOptions::USE_FACTORS_ONLY)) 81 | { 82 | defines.emplace_back("USE_FACTORS_ONLY"); 83 | } 84 | if (IsSet(options, ShaderOptions::USE_MANUAL_SRGB)) 85 | { 86 | defines.emplace_back("USE_MANUAL_SRGB"); 87 | } 88 | if (IsSet(options, ShaderOptions::USE_IBL)) 89 | { 90 | defines.emplace_back("USE_IBL"); 91 | } 92 | if (IsSet(options, ShaderOptions::HAS_BASECOLORMAP)) 93 | { 94 | defines.emplace_back("HAS_BASECOLORMAP"); 95 | } 96 | if (IsSet(options, ShaderOptions::HAS_TANGENTS)) 97 | { 98 | defines.emplace_back("HAS_TANGENTS"); 99 | } 100 | if (IsSet(options, ShaderOptions::HAS_NORMALMAP)) 101 | { 102 | defines.emplace_back("HAS_NORMALMAP"); 103 | } 104 | if (IsSet(options, ShaderOptions::HAS_METALROUGHNESSMAP)) 105 | { 106 | defines.emplace_back("HAS_METALROUGHNESSMAP"); 107 | } 108 | if (IsSet(options, ShaderOptions::HAS_OCCLUSIONMAP)) 109 | { 110 | defines.emplace_back("HAS_OCCLUSIONMAP"); 111 | } 112 | if (IsSet(options, ShaderOptions::HAS_OCCLUSIONMAP_COMBINED)) 113 | { 114 | if (!IsSet(options, ShaderOptions::HAS_METALROUGHNESSMAP)) 115 | { 116 | throw std::runtime_error("Invalid ShaderOptions. HAS_OCCLUSIONMAP_COMBINED requires HAS_METALROUGHNESSMAP"); 117 | } 118 | 119 | defines.emplace_back("HAS_OCCLUSIONMAP_COMBINED"); 120 | } 121 | if (IsSet(options, ShaderOptions::HAS_EMISSIVEMAP)) 122 | { 123 | defines.emplace_back("HAS_EMISSIVEMAP"); 124 | } 125 | if (IsSet(options, ShaderOptions::IS_GROUND)) 126 | { 127 | defines.emplace_back("IS_GROUND"); 128 | } 129 | 130 | return defines; 131 | } -------------------------------------------------------------------------------- /examples/viewer/StepTimer.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include 9 | 10 | #include "Platform/platform.h" 11 | 12 | // Helper class for animation and simulation timing. 13 | class StepTimer 14 | { 15 | public: 16 | StepTimer() noexcept 17 | : m_targetElapsedTicks(TicksPerSecond / 60) 18 | { 19 | QueryPerformanceFrequency(&m_qpcFrequency); 20 | QueryPerformanceCounter(&m_qpcLastTime); 21 | 22 | // Initialize max delta to 1/10 of a second. 23 | m_qpcMaxDelta = m_qpcFrequency.QuadPart / 10; 24 | } 25 | 26 | // Get elapsed time since the previous Update call. 27 | uint64_t GetElapsedTicks() const noexcept 28 | { 29 | return m_elapsedTicks; 30 | } 31 | double GetElapsedSeconds() const noexcept 32 | { 33 | return TicksToSeconds(m_elapsedTicks); 34 | } 35 | 36 | // Get total time since the start of the program. 37 | uint64_t GetTotalTicks() const noexcept 38 | { 39 | return m_totalTicks; 40 | } 41 | double GetTotalSeconds() const noexcept 42 | { 43 | return TicksToSeconds(m_totalTicks); 44 | } 45 | 46 | // Get total number of updates since start of the program. 47 | uint32_t GetFrameCount() const noexcept 48 | { 49 | return m_frameCount; 50 | } 51 | 52 | // Get the current framerate. 53 | uint32_t GetFramesPerSecond() const noexcept 54 | { 55 | return m_framesPerSecond; 56 | } 57 | 58 | // Set whether to use fixed or variable timestep mode. 59 | void SetFixedTimeStep(bool isFixedTimestep) noexcept 60 | { 61 | m_isFixedTimeStep = isFixedTimestep; 62 | } 63 | 64 | // Set how often to call Update when in fixed timestep mode. 65 | void SetTargetElapsedTicks(uint64_t targetElapsed) noexcept 66 | { 67 | m_targetElapsedTicks = targetElapsed; 68 | } 69 | void SetTargetElapsedSeconds(double targetElapsed) noexcept 70 | { 71 | m_targetElapsedTicks = SecondsToTicks(targetElapsed); 72 | } 73 | 74 | // Integer format represents time using 10,000,000 ticks per second. 75 | static const uint64_t TicksPerSecond = 10000000; 76 | 77 | static constexpr double TicksToSeconds(uint64_t ticks) noexcept 78 | { 79 | return static_cast(ticks) / TicksPerSecond; 80 | } 81 | 82 | static constexpr uint64_t SecondsToTicks(double seconds) noexcept 83 | { 84 | return static_cast(seconds * TicksPerSecond); 85 | } 86 | 87 | // After an intentional timing discontinuity (for instance a blocking IO operation) 88 | // call this to avoid having the fixed timestep logic attempt a set of catch-up 89 | // Update calls. 90 | 91 | void ResetElapsedTime() noexcept 92 | { 93 | QueryPerformanceCounter(&m_qpcLastTime); 94 | 95 | m_leftOverTicks = 0; 96 | m_framesPerSecond = 0; 97 | m_framesThisSecond = 0; 98 | m_qpcSecondCounter = 0; 99 | } 100 | 101 | // Update timer state, calling the specified Update function the appropriate number of times. 102 | template 103 | void Tick(TUpdate const & update) 104 | { 105 | // Query the current time. 106 | LARGE_INTEGER currentTime; 107 | QueryPerformanceCounter(¤tTime); 108 | 109 | uint64_t timeDelta = currentTime.QuadPart - m_qpcLastTime.QuadPart; 110 | 111 | m_qpcLastTime = currentTime; 112 | m_qpcSecondCounter += timeDelta; 113 | 114 | // Clamp excessively large time deltas (e.g. after paused in the debugger). 115 | if (timeDelta > m_qpcMaxDelta) 116 | { 117 | timeDelta = m_qpcMaxDelta; 118 | } 119 | 120 | // Convert QPC units into a canonical tick format. This cannot overflow due to the previous clamp. 121 | timeDelta *= TicksPerSecond; 122 | timeDelta /= m_qpcFrequency.QuadPart; 123 | 124 | const uint32_t lastFrameCount = m_frameCount; 125 | 126 | if (m_isFixedTimeStep) 127 | { 128 | // Fixed timestep update logic 129 | 130 | // If the app is running very close to the target elapsed time (within 1/4 of a millisecond) just clamp 131 | // the clock to exactly match the target value. This prevents tiny and irrelevant errors 132 | // from accumulating over time. Without this clamping, a game that requested a 60 fps 133 | // fixed update, running with vsync enabled on a 59.94 NTSC display, would eventually 134 | // accumulate enough tiny errors that it would drop a frame. It is better to just round 135 | // small deviations down to zero to leave things running smoothly. 136 | 137 | if (abs(static_cast(timeDelta - m_targetElapsedTicks)) < TicksPerSecond / 4000) 138 | { 139 | timeDelta = m_targetElapsedTicks; 140 | } 141 | 142 | m_leftOverTicks += timeDelta; 143 | 144 | while (m_leftOverTicks >= m_targetElapsedTicks) 145 | { 146 | m_elapsedTicks = m_targetElapsedTicks; 147 | m_totalTicks += m_targetElapsedTicks; 148 | m_leftOverTicks -= m_targetElapsedTicks; 149 | m_frameCount++; 150 | 151 | update(); 152 | } 153 | } 154 | else 155 | { 156 | // Variable timestep update logic. 157 | m_elapsedTicks = timeDelta; 158 | m_totalTicks += timeDelta; 159 | m_leftOverTicks = 0; 160 | m_frameCount++; 161 | 162 | update(); 163 | } 164 | 165 | // Track the current framerate. 166 | if (m_frameCount != lastFrameCount) 167 | { 168 | m_framesThisSecond++; 169 | } 170 | 171 | if (m_qpcSecondCounter >= static_cast(m_qpcFrequency.QuadPart)) 172 | { 173 | m_framesPerSecond = m_framesThisSecond; 174 | m_framesThisSecond = 0; 175 | m_qpcSecondCounter %= m_qpcFrequency.QuadPart; 176 | } 177 | } 178 | 179 | private: 180 | // Source timing data uses QPC units. 181 | LARGE_INTEGER m_qpcFrequency{}; 182 | LARGE_INTEGER m_qpcLastTime{}; 183 | uint64_t m_qpcMaxDelta{}; 184 | 185 | // Derived timing data uses a canonical tick format. 186 | uint64_t m_elapsedTicks{}; 187 | uint64_t m_totalTicks{}; 188 | uint64_t m_leftOverTicks{}; 189 | 190 | // Members for tracking the framerate. 191 | uint32_t m_frameCount{}; 192 | uint32_t m_framesPerSecond{}; 193 | uint32_t m_framesThisSecond{}; 194 | uint64_t m_qpcSecondCounter{}; 195 | 196 | // Members for configuring fixed timestep mode. 197 | bool m_isFixedTimeStep{}; 198 | uint64_t m_targetElapsedTicks{}; 199 | }; 200 | -------------------------------------------------------------------------------- /examples/viewer/screenshots/screenshot00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/screenshots/screenshot00.png -------------------------------------------------------------------------------- /examples/viewer/screenshots/screenshot01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/screenshots/screenshot01.png -------------------------------------------------------------------------------- /examples/viewer/screenshots/screenshot02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/screenshots/screenshot02.png -------------------------------------------------------------------------------- /examples/viewer/screenshots/screenshot03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/screenshots/screenshot03.png -------------------------------------------------------------------------------- /examples/viewer/screenshots/screenshot04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/screenshots/screenshot04.png -------------------------------------------------------------------------------- /examples/viewer/screenshots/screenshot05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/screenshots/screenshot05.png -------------------------------------------------------------------------------- /examples/viewer/screenshots/screenshot06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/screenshots/screenshot06.png -------------------------------------------------------------------------------- /examples/viewer/screenshots/screenshot07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/examples/viewer/screenshots/screenshot07.png -------------------------------------------------------------------------------- /examples/viewer/stdafx.cpp: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #include "stdafx.h" 7 | -------------------------------------------------------------------------------- /examples/viewer/stdafx.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include "Platform/platform.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #ifdef _DEBUG 24 | #include 25 | #endif 26 | 27 | #include "DirectX/d3dx12.h" 28 | 29 | #include "Logger.h" 30 | #include "StringFormatter.h" 31 | -------------------------------------------------------------------------------- /examples/viewer/viewer.cpp: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #include "stdafx.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define CLI11_BOOST_OPTIONAL 0 14 | #include "CLI11/CLI11.hpp" 15 | 16 | #include "DirectX/D3DEngine.h" 17 | #include "Engine.h" 18 | #include "EngineOptions.h" 19 | #include "Platform/COMUtil.h" 20 | #include "Platform/Mouse.h" 21 | 22 | class Win32Application 23 | { 24 | public: 25 | static int Run(HINSTANCE hInstance, int nCmdShow) 26 | { 27 | EngineOptions options{}; 28 | if (!TryParseCommandLine(options)) 29 | { 30 | return -1; 31 | } 32 | 33 | std::unique_ptr engine = std::make_unique(options); 34 | 35 | // Initialize the window class. 36 | WNDCLASSEX windowClass = { 0 }; 37 | windowClass.cbSize = sizeof(WNDCLASSEX); 38 | windowClass.style = CS_HREDRAW | CS_VREDRAW; 39 | windowClass.lpfnWndProc = WindowProc; 40 | windowClass.hInstance = hInstance; 41 | windowClass.hCursor = LoadCursor(nullptr, IDC_ARROW); 42 | windowClass.lpszClassName = L"viewer"; 43 | RegisterClassEx(&windowClass); 44 | 45 | RECT windowRect = { 0, 0, static_cast(options.Width), static_cast(options.Height) }; 46 | AdjustWindowRect(&windowRect, WS_OVERLAPPEDWINDOW, FALSE); 47 | 48 | // Create the window and store a handle to it. 49 | HWND hwnd = CreateWindow( 50 | windowClass.lpszClassName, 51 | L"viewer", 52 | WS_OVERLAPPEDWINDOW, 53 | CW_USEDEFAULT, 54 | CW_USEDEFAULT, 55 | windowRect.right - windowRect.left, 56 | windowRect.bottom - windowRect.top, 57 | nullptr, // We have no parent window. 58 | nullptr, // We aren't using menus. 59 | hInstance, 60 | engine.get()); 61 | 62 | // Initialize the engine... 63 | engine->Initialize(hwnd); 64 | 65 | ShowWindow(hwnd, nCmdShow); 66 | 67 | // Main loop. 68 | MSG msg{}; 69 | while (msg.message != WM_QUIT) 70 | { 71 | // Process any messages in the queue. 72 | if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) 73 | { 74 | TranslateMessage(&msg); 75 | DispatchMessage(&msg); 76 | } 77 | } 78 | 79 | // Return this part of the WM_QUIT message to Windows. 80 | return static_cast(msg.wParam); 81 | } 82 | 83 | private: 84 | static LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 85 | { 86 | Engine * engine = reinterpret_cast(GetWindowLongPtr(hWnd, GWLP_USERDATA)); 87 | 88 | switch (message) 89 | { 90 | case WM_CREATE: 91 | { 92 | LPCREATESTRUCT pCreateStruct = reinterpret_cast(lParam); 93 | SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast(pCreateStruct->lpCreateParams)); 94 | 95 | Mouse::ProcessMessage(message, wParam, lParam); 96 | } 97 | 98 | return 0; 99 | 100 | case WM_PAINT: 101 | if (engine != nullptr) 102 | { 103 | engine->Tick(); 104 | } 105 | 106 | return 0; 107 | 108 | case WM_SIZE: 109 | if (engine != nullptr) 110 | { 111 | engine->ChangeWindowSize(LOWORD(lParam), HIWORD(lParam)); 112 | } 113 | 114 | return 0; 115 | 116 | case WM_INPUT: 117 | case WM_MOUSEMOVE: 118 | case WM_LBUTTONDOWN: 119 | case WM_LBUTTONUP: 120 | case WM_RBUTTONDOWN: 121 | case WM_RBUTTONUP: 122 | case WM_MBUTTONDOWN: 123 | case WM_MBUTTONUP: 124 | case WM_MOUSEWHEEL: 125 | case WM_XBUTTONDOWN: 126 | case WM_XBUTTONUP: 127 | case WM_MOUSEHOVER: 128 | Mouse::ProcessMessage(message, wParam, lParam); 129 | break; 130 | 131 | case WM_DESTROY: 132 | PostQuitMessage(0); 133 | return 0; 134 | } 135 | 136 | // Handle any messages the switch statement didn't. 137 | return DefWindowProc(hWnd, message, wParam, lParam); 138 | } 139 | 140 | static bool TryParseCommandLine(EngineOptions & options) 141 | { 142 | bool showHelp{}; 143 | 144 | CLI::App app{ "A simple glTF2.0 scene viewer using DirectX 12", "viewer.exe" }; 145 | app.add_option("--width", options.Width, "Initial window width"); 146 | app.add_option("--height", options.Height, "Initial window height"); 147 | app.add_flag("-r,--rotate", options.AutoRotate, "Auto rotate model"); 148 | app.add_flag("-m,--materials", options.EnableMaterials, "Enable model materials"); 149 | app.add_flag("-i,--ibl", options.EnableIBL, "Enable IBL"); 150 | app.add_flag("-g,--ground", options.EnableGround, "Enable ground plane"); 151 | app.add_option("-x", options.CameraX, "Camera x position"); 152 | app.add_option("-y", options.CameraY, "Camera y position"); 153 | app.add_option("-z", options.CameraZ, "Camera z position"); 154 | app.add_option("file", options.ModelPath, "Scene to load (.gltf or .glb)")->required(true); 155 | 156 | int argc; 157 | std::vector args{}; 158 | LPWSTR * argv = CommandLineToArgvW(GetCommandLine(), &argc); 159 | if (argv != nullptr && argc > 0) 160 | { 161 | for (int i = argc - 1; i > 0; i--) 162 | { 163 | std::string arg; 164 | for (auto c : std::wstring_view(argv[i])) 165 | { 166 | arg.push_back(static_cast(c)); 167 | } 168 | 169 | args.emplace_back(std::move(arg)); 170 | } 171 | LocalFree(argv); 172 | } 173 | else 174 | { 175 | showHelp = true; 176 | } 177 | 178 | std::string errorMessage{}; 179 | try 180 | { 181 | app.parse(args); 182 | } 183 | catch (CLI::CallForHelp const &) 184 | { 185 | showHelp = true; 186 | } 187 | catch (CLI::ParseError const & e) 188 | { 189 | errorMessage = e.what(); 190 | showHelp = true; 191 | } 192 | 193 | if (showHelp) 194 | { 195 | std::cout << app.help() << std::endl; 196 | std::cout << "Controls:" << std::endl; 197 | std::cout << " Orbit with left mouse button" << std::endl; 198 | std::cout << " Dolly with middle mouse button" << std::endl; 199 | std::cout << std::endl; 200 | 201 | if (!errorMessage.empty()) 202 | { 203 | std::cout << errorMessage << std::endl; 204 | } 205 | 206 | return false; 207 | } 208 | 209 | return true; 210 | } 211 | }; 212 | 213 | int wmain() 214 | { 215 | int result = -1; 216 | try 217 | { 218 | COMUtil::Init(); 219 | result = Win32Application::Run(GetModuleHandle(nullptr), SW_SHOWDEFAULT); 220 | } 221 | catch (std::exception const & e) 222 | { 223 | std::string message; 224 | fx::FormatException(message, e); 225 | std::cout << message << std::endl; 226 | } 227 | 228 | return result; 229 | } 230 | -------------------------------------------------------------------------------- /examples/viewer/viewer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27428.2037 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "viewer", "viewer.vcxproj", "{D49DC3E4-3F9B-4F15-8767-C2CF5735182F}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Release|x64 = Release|x64 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {D49DC3E4-3F9B-4F15-8767-C2CF5735182F}.Debug|x64.ActiveCfg = Debug|x64 15 | {D49DC3E4-3F9B-4F15-8767-C2CF5735182F}.Debug|x64.Build.0 = Debug|x64 16 | {D49DC3E4-3F9B-4F15-8767-C2CF5735182F}.Release|x64.ActiveCfg = Release|x64 17 | {D49DC3E4-3F9B-4F15-8767-C2CF5735182F}.Release|x64.Build.0 = Release|x64 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {4DBC3027-48C1-4393-A1ED-EC29F1431398} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /examples/viewer/viewer.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | 15.0 15 | {D49DC3E4-3F9B-4F15-8767-C2CF5735182F} 16 | Win32Proj 17 | viewer 18 | 10.0 19 | 20 | 21 | 22 | Application 23 | true 24 | v142 25 | Unicode 26 | 27 | 28 | Application 29 | false 30 | v142 31 | true 32 | Unicode 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | true 48 | NativeRecommendedRules.ruleset 49 | false 50 | true 51 | 52 | 53 | false 54 | NativeRecommendedRules.ruleset 55 | false 56 | 57 | 58 | true 59 | 60 | 61 | 62 | Use 63 | Level3 64 | Disabled 65 | true 66 | _DEBUG;_WINDOWS;%(PreprocessorDefinitions) 67 | true 68 | $(SolutionDir);$(SolutionDir)..\thirdparty;$(SolutionDir)..\..\include;%(AdditionalIncludeDirectories) 69 | stdcpp17 70 | false 71 | 72 | 73 | Console 74 | true 75 | d3d12.lib;d3dcompiler.lib;dxgi.lib;dxguid.lib;%(AdditionalDependencies) 76 | d3d12.dll 77 | 78 | 79 | 80 | 81 | Use 82 | Level3 83 | MaxSpeed 84 | true 85 | true 86 | true 87 | NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 88 | true 89 | $(SolutionDir);$(SolutionDir)..\thirdparty;$(SolutionDir)..\..\include;%(AdditionalIncludeDirectories) 90 | stdcpp17 91 | false 92 | 93 | 94 | Console 95 | true 96 | true 97 | true 98 | d3d12.lib;d3dcompiler.lib;dxgi.lib;dxguid.lib;%(AdditionalDependencies) 99 | d3d12.dll 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | Create 144 | Create 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | $(OutDir)\DirectX\Shaders 161 | 162 | 163 | $(OutDir)\Assets 164 | 165 | 166 | $(OutDir)\Assets\Environment 167 | 168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /test/.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | IndentWidth: 4 3 | UseTab: Never 4 | 5 | AccessModifierOffset: -4 6 | AlignAfterOpenBracket: AlwaysBreak 7 | AlignTrailingComments: false 8 | AlignOperands: false 9 | AlwaysBreakTemplateDeclarations: true 10 | AllowAllParametersOfDeclarationOnNextLine: true 11 | AllowShortBlocksOnASingleLine: false 12 | AllowShortFunctionsOnASingleLine: Empty 13 | AllowShortIfStatementsOnASingleLine: false 14 | AllowShortLoopsOnASingleLine: false 15 | 16 | BinPackArguments: false 17 | BinPackParameters: true 18 | 19 | BreakBeforeBraces: Allman 20 | BreakConstructorInitializersBeforeComma: true 21 | 22 | ColumnLimit: 0 23 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 24 | ConstructorInitializerIndentWidth: 4 25 | ContinuationIndentWidth: 4 26 | Cpp11BracedListStyle: false 27 | 28 | IndentCaseBlocks: true 29 | IndentCaseLabels: false 30 | IndentPPDirectives: BeforeHash 31 | 32 | MaxEmptyLinesToKeep: 1 33 | NamespaceIndentation: All 34 | FixNamespaceComments: true 35 | 36 | DerivePointerAlignment: false 37 | PointerAlignment: Middle 38 | 39 | SortIncludes: true 40 | SpaceBeforeParens: ControlStatements 41 | Standard: Cpp11 42 | 43 | IncludeCategories: 44 | - Regex: '.*(stdafx).*' 45 | Priority: -2 46 | - Regex: '^<.*' 47 | Priority: -1 48 | - Regex: '^".*' 49 | Priority: 0 50 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(THIRDPARTY_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty) 2 | 3 | set(TEST_LanguageStandard "17" CACHE STRING "14 or 17") 4 | 5 | ############################################################################# 6 | # Catch library with the main function to speed up build 7 | ############################################################################# 8 | add_library(catch2_main OBJECT "src/unit.cpp" "src/utility.cpp") 9 | target_include_directories(catch2_main PRIVATE "src") 10 | 11 | if(FX_GLTF_USE_INSTALLED_DEPS) 12 | find_package(Catch2 2.6.0 REQUIRED) 13 | 14 | target_link_libraries(catch2_main PRIVATE nlohmann_json::nlohmann_json) 15 | target_link_libraries(catch2_main PRIVATE Catch2::Catch2) 16 | else() 17 | target_include_directories(catch2_main PRIVATE ${THIRDPARTY_INCLUDE_DIR}) 18 | endif() 19 | 20 | if(TEST_LanguageStandard STREQUAL "17") 21 | target_compile_features(catch2_main PUBLIC cxx_std_17) 22 | elseif(TEST_LanguageStandard STREQUAL "14") 23 | target_compile_features(catch2_main PUBLIC cxx_std_14) 24 | endif() 25 | 26 | if(MSVC) 27 | # Force to always compile with W4 28 | if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") 29 | string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 30 | else() 31 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") 32 | endif() 33 | 34 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /permissive-") 35 | endif() 36 | 37 | ############################################################################# 38 | # One executable for each unit test file 39 | ############################################################################# 40 | 41 | file(GLOB files "src/unit-*.cpp") 42 | foreach(file ${files}) 43 | get_filename_component(file_basename ${file} NAME_WE) 44 | string(REGEX REPLACE "unit-([^$]+)" "test-\\1" testcase ${file_basename}) 45 | 46 | add_executable(${testcase} $ ${file}) 47 | set_target_properties(${testcase} PROPERTIES 48 | COMPILE_DEFINITIONS "$<$:_SCL_SECURE_NO_WARNINGS>" 49 | COMPILE_OPTIONS "$<$:/EHsc>" 50 | ) 51 | 52 | if(TEST_LanguageStandard STREQUAL "17") 53 | target_compile_features(${testcase} PRIVATE cxx_std_17) 54 | elseif(TEST_LanguageStandard STREQUAL "14") 55 | target_compile_features(${testcase} PRIVATE cxx_std_14) 56 | endif() 57 | 58 | target_compile_definitions(${testcase} PRIVATE CATCH_CONFIG_FAST_COMPILE) 59 | target_link_libraries(${testcase} PRIVATE ${FX_GLTF_LIB_TARGET_NAME}) 60 | if(FX_GLTF_USE_INSTALLED_DEPS) 61 | target_link_libraries(${testcase} PRIVATE nlohmann_json::nlohmann_json) 62 | target_link_libraries(${testcase} PRIVATE Catch2::Catch2) 63 | else() 64 | target_include_directories(${testcase} PRIVATE ${THIRDPARTY_INCLUDE_DIR}) 65 | endif() 66 | 67 | if(CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) 68 | target_link_libraries(${testcase} PRIVATE stdc++fs) 69 | endif() 70 | 71 | add_test(NAME "${testcase}" 72 | COMMAND ${testcase} ${CATCH_TEST_FILTER} 73 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 74 | ) 75 | set_tests_properties("${testcase}" PROPERTIES LABELS "default") 76 | 77 | endforeach() -------------------------------------------------------------------------------- /test/CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com//fwlink//?linkid=834763 for more information about this file. 3 | "configurations": [ 4 | { 5 | "name": "x64-Debug", 6 | "generator": "Ninja", 7 | "configurationType": "Debug", 8 | "inheritEnvironments": [ "msvc_x64_x64" ], 9 | "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", 10 | "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", 11 | "cmakeCommandArgs": "", 12 | "buildCommandArgs": "-v", 13 | "ctestCommandArgs": "", 14 | "enableClangTidyCodeAnalysis": true 15 | }, 16 | { 17 | "name": "x64-Release", 18 | "generator": "Ninja", 19 | "configurationType": "Release", 20 | "inheritEnvironments": [ "msvc_x64_x64" ], 21 | "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", 22 | "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", 23 | "cmakeCommandArgs": "", 24 | "buildCommandArgs": "-v", 25 | "ctestCommandArgs": "", 26 | "enableClangTidyCodeAnalysis": true 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /test/data/unιcode-ρath/Box.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "asset": { 3 | "generator": "COLLADA2GLTF", 4 | "version": "2.0" 5 | }, 6 | "scene": 0, 7 | "scenes": [ 8 | { 9 | "nodes": [ 10 | 0 11 | ] 12 | } 13 | ], 14 | "nodes": [ 15 | { 16 | "children": [ 17 | 1 18 | ], 19 | "matrix": [ 20 | 1.0, 21 | 0.0, 22 | 0.0, 23 | 0.0, 24 | 0.0, 25 | 0.0, 26 | -1.0, 27 | 0.0, 28 | 0.0, 29 | 1.0, 30 | 0.0, 31 | 0.0, 32 | 0.0, 33 | 0.0, 34 | 0.0, 35 | 1.0 36 | ] 37 | }, 38 | { 39 | "mesh": 0 40 | } 41 | ], 42 | "meshes": [ 43 | { 44 | "primitives": [ 45 | { 46 | "attributes": { 47 | "NORMAL": 1, 48 | "POSITION": 2 49 | }, 50 | "indices": 0, 51 | "mode": 4, 52 | "material": 0 53 | } 54 | ], 55 | "name": "Mesh" 56 | } 57 | ], 58 | "accessors": [ 59 | { 60 | "bufferView": 0, 61 | "byteOffset": 0, 62 | "componentType": 5123, 63 | "count": 36, 64 | "max": [ 65 | 23 66 | ], 67 | "min": [ 68 | 0 69 | ], 70 | "type": "SCALAR" 71 | }, 72 | { 73 | "bufferView": 1, 74 | "byteOffset": 0, 75 | "componentType": 5126, 76 | "count": 24, 77 | "max": [ 78 | 1.0, 79 | 1.0, 80 | 1.0 81 | ], 82 | "min": [ 83 | -1.0, 84 | -1.0, 85 | -1.0 86 | ], 87 | "type": "VEC3" 88 | }, 89 | { 90 | "bufferView": 1, 91 | "byteOffset": 288, 92 | "componentType": 5126, 93 | "count": 24, 94 | "max": [ 95 | 0.5, 96 | 0.5, 97 | 0.5 98 | ], 99 | "min": [ 100 | -0.5, 101 | -0.5, 102 | -0.5 103 | ], 104 | "type": "VEC3" 105 | } 106 | ], 107 | "materials": [ 108 | { 109 | "pbrMetallicRoughness": { 110 | "baseColorFactor": [ 111 | 0.800000011920929, 112 | 0.0, 113 | 0.0, 114 | 1.0 115 | ], 116 | "metallicFactor": 0.0 117 | }, 118 | "name": "Red" 119 | } 120 | ], 121 | "bufferViews": [ 122 | { 123 | "buffer": 0, 124 | "byteOffset": 576, 125 | "byteLength": 72, 126 | "target": 34963 127 | }, 128 | { 129 | "buffer": 0, 130 | "byteOffset": 0, 131 | "byteLength": 576, 132 | "byteStride": 12, 133 | "target": 34962 134 | } 135 | ], 136 | "buffers": [ 137 | { 138 | "byteLength": 648, 139 | "uri": "Box0.bin" 140 | } 141 | ] 142 | } 143 | -------------------------------------------------------------------------------- /test/data/unιcode-ρath/Box0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessey-git/fx-gltf/7766c237ea81c0bb3759e78e5c0f22854843eef8/test/data/unιcode-ρath/Box0.bin -------------------------------------------------------------------------------- /test/src/unit-base64.cpp: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | TEST_CASE("base64") 15 | { 16 | SECTION("simple") 17 | { 18 | std::vector bytes{}; 19 | std::string base64Text{}; 20 | 21 | base64Text = ""; 22 | REQUIRE(fx::base64::TryDecode(base64Text, bytes)); 23 | REQUIRE(fx::base64::Encode(bytes) == base64Text); 24 | REQUIRE(bytes.empty()); 25 | 26 | base64Text = "TEST"; 27 | REQUIRE(fx::base64::TryDecode(base64Text, bytes)); 28 | REQUIRE(fx::base64::Encode(bytes) == base64Text); 29 | REQUIRE(bytes == std::vector{ 76, 68, 147 }); 30 | 31 | base64Text = "YW55IGNhcm5hbCBwbGVhcw=="; 32 | REQUIRE(fx::base64::TryDecode(base64Text, bytes)); 33 | REQUIRE(fx::base64::Encode(bytes) == base64Text); 34 | REQUIRE(bytes == std::vector{ 97, 110, 121, 32, 99, 97, 114, 110, 97, 108, 32, 112, 108, 101, 97, 115 }); 35 | } 36 | 37 | SECTION("random encode/decode") 38 | { 39 | std::mt19937 gen(29); 40 | std::uniform_int_distribution<> dist(0, 255); 41 | 42 | for (std::size_t iteration = 1; iteration < 1024; iteration++) 43 | { 44 | // Random bytes... 45 | std::vector bytes(iteration); 46 | std::generate(bytes.begin(), bytes.end(), [&dist, &gen] { return static_cast(dist(gen)); }); 47 | 48 | // Roundtrip... 49 | std::string encoded = fx::base64::Encode(bytes); 50 | 51 | std::vector outBytes; 52 | const bool decodeResult = fx::base64::TryDecode(encoded, outBytes); 53 | 54 | REQUIRE(decodeResult); 55 | REQUIRE(bytes == outBytes); 56 | REQUIRE(outBytes.size() == iteration); 57 | } 58 | } 59 | 60 | SECTION("random negative") 61 | { 62 | std::mt19937 gen(29); 63 | std::uniform_int_distribution<> dist(0, 255); 64 | 65 | std::vector invalidChars(192); 66 | for (auto c : fx::base64::detail::DecodeMap) 67 | { 68 | if (c == -1) 69 | { 70 | invalidChars.push_back(c); 71 | } 72 | } 73 | 74 | for (std::size_t iteration = 1; iteration < 1024; iteration++) 75 | { 76 | // Random, valid, encoded string... 77 | std::string encoded(iteration, '\0'); 78 | std::generate(encoded.begin(), encoded.end(), [&dist, &gen] { return fx::base64::detail::EncodeMap[dist(gen) % 64]; }); 79 | 80 | bool decodeResult = false; 81 | std::vector outBytes; 82 | if (encoded.length() % 4 == 0) 83 | { 84 | // Base string should decode fine... 85 | decodeResult = fx::base64::TryDecode(encoded, outBytes); 86 | REQUIRE(decodeResult); 87 | 88 | // Mutate it to force some form of failure... 89 | for (int mutation = 0; mutation < 64; mutation++) 90 | { 91 | std::string invalid = encoded; 92 | std::size_t mutatePosition = dist(gen) % invalid.length(); 93 | invalid[mutatePosition] = invalidChars[dist(gen) % invalidChars.size()]; 94 | 95 | decodeResult = fx::base64::TryDecode(invalid, outBytes); 96 | REQUIRE_FALSE(decodeResult); 97 | } 98 | } 99 | else 100 | { 101 | // Strings whose length are not divisible by 4 should always fail 102 | decodeResult = fx::base64::TryDecode(encoded, outBytes); 103 | REQUIRE_FALSE(decodeResult); 104 | } 105 | } 106 | } 107 | 108 | SECTION("negative") 109 | { 110 | std::vector bytes{}; 111 | std::string base64Text{}; 112 | 113 | base64Text = "A"; 114 | REQUIRE_FALSE(fx::base64::TryDecode(base64Text, bytes)); 115 | REQUIRE(bytes.empty()); 116 | 117 | base64Text = "AA"; 118 | REQUIRE_FALSE(fx::base64::TryDecode(base64Text, bytes)); 119 | REQUIRE(bytes.empty()); 120 | 121 | base64Text = "AAA"; 122 | REQUIRE_FALSE(fx::base64::TryDecode(base64Text, bytes)); 123 | REQUIRE(bytes.empty()); 124 | 125 | base64Text = "AA A"; 126 | REQUIRE_FALSE(fx::base64::TryDecode(base64Text, bytes)); 127 | REQUIRE(bytes.empty()); 128 | 129 | base64Text = "===="; 130 | REQUIRE_FALSE(fx::base64::TryDecode(base64Text, bytes)); 131 | REQUIRE(bytes.empty()); 132 | 133 | base64Text = "A==="; 134 | REQUIRE_FALSE(fx::base64::TryDecode(base64Text, bytes)); 135 | REQUIRE(bytes.empty()); 136 | 137 | base64Text = "A==A"; 138 | REQUIRE_FALSE(fx::base64::TryDecode(base64Text, bytes)); 139 | REQUIRE(bytes.empty()); 140 | 141 | base64Text = "AA=A"; 142 | REQUIRE_FALSE(fx::base64::TryDecode(base64Text, bytes)); 143 | REQUIRE(bytes.empty()); 144 | 145 | base64Text = "AAA["; 146 | REQUIRE_FALSE(fx::base64::TryDecode(base64Text, bytes)); 147 | REQUIRE(bytes.empty()); 148 | 149 | base64Text = "YW55IGNhcm5hbCBwbGVhcw="; 150 | REQUIRE_FALSE(fx::base64::TryDecode(base64Text, bytes)); 151 | REQUIRE(bytes.empty()); 152 | 153 | base64Text = "YW55IGNhcm5hbCBwbGVhcw"; 154 | REQUIRE_FALSE(fx::base64::TryDecode(base64Text, bytes)); 155 | REQUIRE(bytes.empty()); 156 | } 157 | } -------------------------------------------------------------------------------- /test/src/unit-exceptions.cpp: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "utility.h" 14 | 15 | // The matcher class 16 | class ExceptionContainsMatcher : public Catch::MatcherBase 17 | { 18 | public: 19 | explicit ExceptionContainsMatcher(std::string const & text, bool shouldBeNested = false) 20 | : text_(text), shouldBeNested_(shouldBeNested) {} 21 | 22 | bool match(std::exception const & e) const override 23 | { 24 | fx::FormatException(message_, e); 25 | 26 | bool isNestedException = message_.find("nested") != std::string::npos; 27 | bool properlyNested = shouldBeNested_ ? isNestedException : !isNestedException; 28 | return properlyNested && message_.find(text_) != std::string::npos; 29 | } 30 | 31 | std::string describe() const override 32 | { 33 | std::ostringstream str; 34 | str << "Message '" << message_ << "' contains text '" << text_ << "' (should be nested: " << shouldBeNested_ << ")"; 35 | 36 | return str.str(); 37 | } 38 | 39 | private: 40 | std::string text_; 41 | bool shouldBeNested_; 42 | 43 | mutable std::string message_; 44 | }; 45 | 46 | template 47 | void Mutate(nlohmann::json const & incoming, TMutator && mutator, std::string const & text, bool shouldBeNested = false) 48 | { 49 | nlohmann::json mutated = incoming; 50 | mutator(mutated); 51 | 52 | INFO("Expecting exception containing : " << text); 53 | INFO("Mutated json : " << mutated.dump(2)); 54 | 55 | REQUIRE_THROWS_MATCHES(fx::gltf::detail::Create(mutated, {}), fx::gltf::invalid_gltf_document, ExceptionContainsMatcher(text, shouldBeNested)); 56 | } 57 | 58 | TEST_CASE("exceptions") 59 | { 60 | utility::CleanupTestOutputDir(); 61 | utility::CreateTestOutputDir(); 62 | 63 | nlohmann::json json = 64 | R"( 65 | { 66 | "accessors": [ { 67 | "componentType":5126, "count":14, "type":"VEC3", 68 | "sparse": { "count":3, "indices": { "bufferView": 2, "componentType": 5123 }, "values":{ "bufferView": 3 } } 69 | } ], 70 | "animations": [ { 71 | "channels": [ { "sampler": 0, "target": { "path": "rotation" } } ], 72 | "samplers": [ { "input": 6, "interpolation": "LINEAR", "output": 7 } ] 73 | } ], 74 | "asset": { "version": "2.0" }, 75 | "buffers": [ { "byteLength": 10, "uri": "data:application/octet-stream;base64,AAAABB==" } ], 76 | "bufferViews": [ { "buffer": 0, "byteLength": 10 } ], 77 | "cameras": [ { "perspective": { "yfov": 0.6, "znear": 1.0 }, "type": "perspective" } ], 78 | "images" : [ { "uri" : "data:image/jpeg;base64,$$$$" } ], 79 | "materials": [ { "alphaMode": "OPAQUE", "pbrMetallicRoughness": { "baseColorTexture": { "index": 0 }, "metallicRoughnessTexture": { "index": 1 } }, "normalTexture": { "index": 2 }, "occlusionTexture": { "index": 1 }, "emissiveTexture": { "index": 3 } } ], 80 | "meshes": [ { "primitives": [ { "attributes": { "NORMAL": 1, "POSITION": 2 } } ] } ], 81 | "skins": [ { "joints": [ 2 ] } ] 82 | } 83 | )"_json; 84 | 85 | // Sanity check to ensure above json actually starts in a valid, loadable, state 86 | fx::gltf::Document mainDocument = json; 87 | REQUIRE(mainDocument.asset.version == "2.0"); 88 | 89 | SECTION("load : missing required fields") 90 | { 91 | Mutate( 92 | json, [](nlohmann::json & m) { m["accessors"][0].erase("componentType"); }, "componentType"); 93 | Mutate( 94 | json, [](nlohmann::json & m) { m["accessors"][0].erase("count"); }, "count"); 95 | Mutate( 96 | json, [](nlohmann::json & m) { m["accessors"][0].erase("type"); }, "type"); 97 | Mutate( 98 | json, [](nlohmann::json & m) { m["accessors"][0]["sparse"].erase("count"); }, "count"); 99 | Mutate( 100 | json, [](nlohmann::json & m) { m["accessors"][0]["sparse"].erase("indices"); }, "indices"); 101 | Mutate( 102 | json, [](nlohmann::json & m) { m["accessors"][0]["sparse"]["indices"].erase("bufferView"); }, "bufferView"); 103 | Mutate( 104 | json, [](nlohmann::json & m) { m["accessors"][0]["sparse"]["indices"].erase("componentType"); }, "componentType"); 105 | Mutate( 106 | json, [](nlohmann::json & m) { m["accessors"][0]["sparse"].erase("values"); }, "values"); 107 | Mutate( 108 | json, [](nlohmann::json & m) { m["animations"][0].erase("channels"); }, "channels"); 109 | Mutate( 110 | json, [](nlohmann::json & m) { m["animations"][0].erase("samplers"); }, "samplers"); 111 | Mutate( 112 | json, [](nlohmann::json & m) { m["animations"][0]["channels"][0].erase("sampler"); }, "sampler"); 113 | Mutate( 114 | json, [](nlohmann::json & m) { m["animations"][0]["channels"][0].erase("target"); }, "target"); 115 | Mutate( 116 | json, [](nlohmann::json & m) { m["animations"][0]["channels"][0]["target"].erase("path"); }, "path"); 117 | Mutate( 118 | json, [](nlohmann::json & m) { m["animations"][0]["samplers"][0].erase("input"); }, "input"); 119 | Mutate( 120 | json, [](nlohmann::json & m) { m["animations"][0]["samplers"][0].erase("output"); }, "output"); 121 | Mutate( 122 | json, [](nlohmann::json & m) { m["asset"].erase("version"); }, "version"); 123 | Mutate( 124 | json, [](nlohmann::json & m) { m["buffers"][0].erase("byteLength"); }, "byteLength"); 125 | Mutate( 126 | json, [](nlohmann::json & m) { m["bufferViews"][0].erase("buffer"); }, "buffer"); 127 | Mutate( 128 | json, [](nlohmann::json & m) { m["bufferViews"][0].erase("byteLength"); }, "byteLength"); 129 | Mutate( 130 | json, [](nlohmann::json & m) { m["cameras"][0].erase("type"); }, "type"); 131 | Mutate( 132 | json, [](nlohmann::json & m) { m["cameras"][0]["perspective"].erase("yfov"); }, "yfov"); 133 | Mutate( 134 | json, [](nlohmann::json & m) { m["cameras"][0]["perspective"].erase("znear"); }, "znear"); 135 | Mutate( 136 | json, [](nlohmann::json & m) { m["materials"][0]["emissiveTexture"].erase("index"); }, "index"); 137 | Mutate( 138 | json, [](nlohmann::json & m) { m["materials"][0]["pbrMetallicRoughness"]["baseColorTexture"].erase("index"); }, "index"); 139 | Mutate( 140 | json, [](nlohmann::json & m) { m["materials"][0]["pbrMetallicRoughness"]["metallicRoughnessTexture"].erase("index"); }, "index"); 141 | Mutate( 142 | json, [](nlohmann::json & m) { m["materials"][0]["normalTexture"].erase("index"); }, "index"); 143 | Mutate( 144 | json, [](nlohmann::json & m) { m["materials"][0]["occlusionTexture"].erase("index"); }, "index"); 145 | Mutate( 146 | json, [](nlohmann::json & m) { m["meshes"][0].erase("primitives"); }, "primitives"); 147 | Mutate( 148 | json, [](nlohmann::json & m) { m["meshes"][0]["primitives"][0].erase("attributes"); }, "attributes"); 149 | Mutate( 150 | json, [](nlohmann::json & m) { m["skins"][0].erase("joints"); }, "joints"); 151 | } 152 | 153 | SECTION("load : invalid field values") 154 | { 155 | Mutate( 156 | json, [](nlohmann::json & m) { m["accessors"][0]["type"] = "vec3"; }, "accessor.type"); 157 | Mutate( 158 | json, [](nlohmann::json & m) { m["animations"][0]["samplers"][0]["interpolation"] = "linear"; }, "animation.sampler.interpolation"); 159 | Mutate( 160 | json, [](nlohmann::json & m) { m["buffers"][0]["byteLength"] = 0; }, "buffer.byteLength"); 161 | Mutate( 162 | json, [](nlohmann::json & m) { m["buffers"][0]["byteLength"] = 2; }, "malformed base64"); 163 | Mutate( 164 | json, [](nlohmann::json & m) { m["buffers"][0]["uri"] = "data:application/octet-stream;base64,$$$$"; }, "malformed base64"); 165 | Mutate( 166 | json, [](nlohmann::json & m) { m["buffers"][0]["uri"] = "../dir/traversal.bin"; }, "buffer.uri"); 167 | Mutate( 168 | json, [](nlohmann::json & m) { m["buffers"][0]["uri"] = "nonexistant.bin"; }, "buffer.uri"); 169 | Mutate( 170 | json, [](nlohmann::json & m) { m["cameras"][0]["type"] = "D-SLR"; }, "camera.type"); 171 | Mutate( 172 | json, [](nlohmann::json & m) { m["materials"][0]["alphaMode"] = "opaque"; }, "material.alphaMode"); 173 | 174 | std::vector data{}; 175 | REQUIRE_THROWS_MATCHES(mainDocument.images[0].MaterializeData(data), fx::gltf::invalid_gltf_document, ExceptionContainsMatcher("malformed base64")); 176 | } 177 | 178 | SECTION("load : quotas") 179 | { 180 | FX_GLTF_FILESYSTEM::path externalFile{ "data/glTF-Sample-Models/2.0/Box/glTF/Box.gltf" }; 181 | FX_GLTF_FILESYSTEM::path glbFile{ "data/glTF-Sample-Models/2.0/Box/glTF-Binary/Box.glb" }; 182 | 183 | fx::gltf::ReadQuotas readQuotas{}; 184 | readQuotas.MaxBufferByteLength = 500; 185 | REQUIRE_THROWS_MATCHES(fx::gltf::LoadFromText(externalFile, readQuotas), fx::gltf::invalid_gltf_document, ExceptionContainsMatcher("MaxBufferByteLength")); 186 | 187 | readQuotas.MaxFileSize = 500; 188 | REQUIRE_THROWS_MATCHES(fx::gltf::LoadFromBinary(glbFile, readQuotas), fx::gltf::invalid_gltf_document, ExceptionContainsMatcher("MaxFileSize")); 189 | 190 | readQuotas = {}; 191 | readQuotas.MaxBufferCount = 1; 192 | nlohmann::json external = utility::LoadJsonFromFile(externalFile); 193 | external["buffers"].push_back(json["buffers"][0]); // Duplicate so we have 2 identical buffers 194 | REQUIRE_THROWS_MATCHES(fx::gltf::detail::Create(external, { fx::gltf::detail::GetDocumentRootPath(externalFile), readQuotas }), fx::gltf::invalid_gltf_document, ExceptionContainsMatcher("MaxBufferCount")); 195 | } 196 | 197 | SECTION("load : mismatched") 198 | { 199 | REQUIRE_THROWS_MATCHES(fx::gltf::LoadFromText("data/glTF-Sample-Models/2.0/Box/glTF-Binary/Box.glb"), fx::gltf::invalid_gltf_document, ExceptionContainsMatcher("json.exception", true)); 200 | REQUIRE_THROWS_MATCHES(fx::gltf::LoadFromBinary("data/glTF-Sample-Models/2.0/Box/glTF/Box.gltf"), fx::gltf::invalid_gltf_document, ExceptionContainsMatcher("GLB header")); 201 | } 202 | 203 | SECTION("load / save : invalid path") 204 | { 205 | fx::gltf::Document doc; 206 | doc = json; 207 | doc.buffers[0].data.resize(doc.buffers[0].byteLength); 208 | doc.buffers.push_back(doc.buffers[0]); 209 | doc.buffers[0].uri.clear(); 210 | 211 | REQUIRE_THROWS_AS(fx::gltf::LoadFromText("not-exist"), std::system_error); 212 | REQUIRE_THROWS_AS(fx::gltf::LoadFromBinary("not-exist"), std::system_error); 213 | 214 | REQUIRE_THROWS_AS(fx::gltf::Save(doc, utility::GetTestOutputDir(), false), std::system_error); 215 | REQUIRE_THROWS_AS(fx::gltf::Save(doc, utility::GetTestOutputDir(), true), std::system_error); 216 | 217 | doc.buffers[1].uri = "./"; 218 | REQUIRE_THROWS_AS(fx::gltf::Save(doc, utility::GetTestOutputDir() / "nop", true), fx::gltf::invalid_gltf_document); 219 | } 220 | 221 | SECTION("save : invalid buffers") 222 | { 223 | fx::gltf::Document doc; 224 | 225 | INFO("No buffers"); 226 | doc = json; 227 | doc.buffers.clear(); 228 | REQUIRE_THROWS_AS(fx::gltf::Save(doc, utility::GetTestOutputDir() / "nop", false), fx::gltf::invalid_gltf_document); 229 | 230 | INFO("Buffer byteLength = 0"); 231 | doc = json; 232 | doc.buffers[0].byteLength = 0; 233 | REQUIRE_THROWS_AS(fx::gltf::Save(doc, utility::GetTestOutputDir() / "nop", false), fx::gltf::invalid_gltf_document); 234 | 235 | INFO("Buffer byteLength != data size"); 236 | doc = json; 237 | doc.buffers[0].byteLength = 20; 238 | doc.buffers[0].data.resize(10); 239 | REQUIRE_THROWS_AS(fx::gltf::Save(doc, utility::GetTestOutputDir() / "nop", false), fx::gltf::invalid_gltf_document); 240 | 241 | INFO("A second buffer with empty uri"); 242 | doc = json; 243 | doc.buffers.push_back({}); 244 | doc.buffers[0].uri.clear(); 245 | doc.buffers[0].byteLength = 20; 246 | doc.buffers[0].data.resize(20); 247 | doc.buffers[1].uri.clear(); 248 | doc.buffers[1].byteLength = 20; 249 | doc.buffers[1].data.resize(20); 250 | REQUIRE_THROWS_AS(fx::gltf::Save(doc, utility::GetTestOutputDir() / "nop", false), fx::gltf::invalid_gltf_document); 251 | 252 | INFO("Binary save with invalid buffer uri"); 253 | doc = json; 254 | doc.buffers[0].uri = "not empty"; 255 | doc.buffers[0].byteLength = 20; 256 | doc.buffers[0].data.resize(20); 257 | REQUIRE_THROWS_AS(fx::gltf::Save(doc, utility::GetTestOutputDir() / "nop", true), fx::gltf::invalid_gltf_document); 258 | } 259 | 260 | utility::CleanupTestOutputDir(); 261 | } -------------------------------------------------------------------------------- /test/src/unit-saveload.cpp: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "utility.h" 14 | 15 | TEST_CASE("saveload") 16 | { 17 | utility::CleanupTestOutputDir(); 18 | utility::CreateTestOutputDir(); 19 | 20 | SECTION("load text external - save text embedded") 21 | { 22 | FX_GLTF_FILESYSTEM::path originalFile{ "data/glTF-Sample-Models/2.0/Box/glTF/Box.gltf" }; 23 | FX_GLTF_FILESYSTEM::path newFile{ utility::GetTestOutputDir() / "test1.gltf" }; 24 | 25 | fx::gltf::Document originalDocument = fx::gltf::LoadFromText(originalFile); 26 | std::string originalUri = originalDocument.buffers.front().uri; 27 | 28 | originalDocument.buffers.front().SetEmbeddedResource(); 29 | fx::gltf::Save(originalDocument, newFile, false); 30 | 31 | fx::gltf::Document newDocument = fx::gltf::LoadFromText(newFile); 32 | 33 | REQUIRE(newDocument.buffers.front().data == originalDocument.buffers.front().data); 34 | REQUIRE(newDocument.buffers.front().uri != originalUri); 35 | REQUIRE(newDocument.buffers.front().IsEmbeddedResource()); 36 | } 37 | 38 | SECTION("load text external - save binary") 39 | { 40 | FX_GLTF_FILESYSTEM::path originalFile{ "data/glTF-Sample-Models/2.0/Box/glTF/Box.gltf" }; 41 | FX_GLTF_FILESYSTEM::path newFile{ utility::GetTestOutputDir() / "test2.glb" }; 42 | 43 | fx::gltf::Document originalDocument = fx::gltf::LoadFromText(originalFile); 44 | 45 | originalDocument.buffers.front().uri.clear(); 46 | fx::gltf::Save(originalDocument, newFile, true); 47 | 48 | fx::gltf::Document newDocument = fx::gltf::LoadFromBinary(newFile); 49 | 50 | REQUIRE(newDocument.buffers.front().data == originalDocument.buffers.front().data); 51 | REQUIRE(newDocument.buffers.front().uri.empty()); 52 | } 53 | 54 | SECTION("load text embedded - save text external") 55 | { 56 | FX_GLTF_FILESYSTEM::path originalFile{ "data/glTF-Sample-Models/2.0/Box/glTF-Embedded/Box.gltf" }; 57 | FX_GLTF_FILESYSTEM::path newFile{ utility::GetTestOutputDir() / "test3.gltf" }; 58 | 59 | fx::gltf::Document originalDocument = fx::gltf::LoadFromText(originalFile); 60 | std::string originalUri = originalDocument.buffers.front().uri; 61 | 62 | originalDocument.buffers.front().uri = "test3.bin"; 63 | fx::gltf::Save(originalDocument, newFile, false); 64 | 65 | fx::gltf::Document newDocument = fx::gltf::LoadFromText(newFile); 66 | 67 | REQUIRE(newDocument.buffers.front().data == originalDocument.buffers.front().data); 68 | REQUIRE(newDocument.buffers.front().uri != originalUri); 69 | REQUIRE(newDocument.buffers.front().uri == "test3.bin"); 70 | } 71 | 72 | SECTION("load text embedded - save binary") 73 | { 74 | FX_GLTF_FILESYSTEM::path originalFile{ "data/glTF-Sample-Models/2.0/Box/glTF-Embedded/Box.gltf" }; 75 | FX_GLTF_FILESYSTEM::path newFile{ utility::GetTestOutputDir() / "test4.glb" }; 76 | 77 | fx::gltf::Document originalDocument = fx::gltf::LoadFromText(originalFile); 78 | 79 | originalDocument.buffers.front().uri.clear(); 80 | fx::gltf::Save(originalDocument, newFile, true); 81 | 82 | fx::gltf::Document newDocument = fx::gltf::LoadFromBinary(newFile); 83 | 84 | REQUIRE(newDocument.buffers.front().data == originalDocument.buffers.front().data); 85 | REQUIRE(newDocument.buffers.front().uri.empty()); 86 | } 87 | 88 | SECTION("load binary - save text external") 89 | { 90 | FX_GLTF_FILESYSTEM::path originalFile{ "data/glTF-Sample-Models/2.0/Box/glTF-Binary/Box.glb" }; 91 | FX_GLTF_FILESYSTEM::path newFile{ utility::GetTestOutputDir() / "test5.gltf" }; 92 | 93 | fx::gltf::Document originalDocument = fx::gltf::LoadFromBinary(originalFile); 94 | 95 | originalDocument.buffers.front().uri = "test5.bin"; 96 | fx::gltf::Save(originalDocument, newFile, false); 97 | 98 | fx::gltf::Document newDocument = fx::gltf::LoadFromText(newFile); 99 | 100 | REQUIRE(newDocument.buffers.front().data == originalDocument.buffers.front().data); 101 | REQUIRE(newDocument.buffers.front().uri == "test5.bin"); 102 | } 103 | 104 | SECTION("load binary - save text embedded") 105 | { 106 | FX_GLTF_FILESYSTEM::path originalFile{ "data/glTF-Sample-Models/2.0/Box/glTF-Binary/Box.glb" }; 107 | FX_GLTF_FILESYSTEM::path newFile{ utility::GetTestOutputDir() / "test6.gltf" }; 108 | 109 | fx::gltf::Document originalDocument = fx::gltf::LoadFromBinary(originalFile); 110 | 111 | originalDocument.buffers.front().SetEmbeddedResource(); 112 | fx::gltf::Save(originalDocument, newFile, false); 113 | 114 | fx::gltf::Document newDocument = fx::gltf::LoadFromText(newFile); 115 | 116 | REQUIRE(newDocument.buffers.front().data == originalDocument.buffers.front().data); 117 | REQUIRE(newDocument.buffers.front().IsEmbeddedResource()); 118 | } 119 | 120 | SECTION("load binary - save binary + additional as external") 121 | { 122 | FX_GLTF_FILESYSTEM::path originalFile{ "data/glTF-Sample-Models/2.0/Box/glTF-Binary/Box.glb" }; 123 | FX_GLTF_FILESYSTEM::path newFile{ utility::GetTestOutputDir() / "test7.glb" }; 124 | 125 | fx::gltf::Document originalDocument = fx::gltf::LoadFromBinary(originalFile); 126 | 127 | std::vector newBytes = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 128 | originalDocument.buffers.push_back(fx::gltf::Buffer{}); 129 | originalDocument.buffers.back().uri = "test7.bin"; 130 | originalDocument.buffers.back().byteLength = static_cast(newBytes.size()); 131 | originalDocument.buffers.back().data = newBytes; 132 | 133 | fx::gltf::Save(originalDocument, newFile, true); 134 | 135 | fx::gltf::Document newDocument = fx::gltf::LoadFromBinary(newFile); 136 | 137 | REQUIRE(newDocument.buffers.front().data == originalDocument.buffers.front().data); 138 | REQUIRE(newDocument.buffers.back().data == newBytes); 139 | } 140 | 141 | SECTION("load binary - save binary + additional as embedded") 142 | { 143 | FX_GLTF_FILESYSTEM::path originalFile{ "data/glTF-Sample-Models/2.0/Box/glTF-Binary/Box.glb" }; 144 | FX_GLTF_FILESYSTEM::path newFile{ utility::GetTestOutputDir() / "test8.glb" }; 145 | 146 | fx::gltf::Document originalDocument = fx::gltf::LoadFromBinary(originalFile); 147 | 148 | std::vector newBytes = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 149 | originalDocument.buffers.push_back(fx::gltf::Buffer{}); 150 | originalDocument.buffers.back().uri = "test8.bin"; 151 | originalDocument.buffers.back().byteLength = static_cast(newBytes.size()); 152 | originalDocument.buffers.back().data = newBytes; 153 | originalDocument.buffers.back().SetEmbeddedResource(); 154 | 155 | fx::gltf::Save(originalDocument, newFile, true); 156 | 157 | fx::gltf::Document newDocument = fx::gltf::LoadFromBinary(newFile); 158 | 159 | REQUIRE(newDocument.buffers.front().data == originalDocument.buffers.front().data); 160 | REQUIRE(newDocument.buffers.back().data == newBytes); 161 | REQUIRE(newDocument.buffers.back().IsEmbeddedResource()); 162 | } 163 | 164 | SECTION("load text - save text streams") 165 | { 166 | FX_GLTF_FILESYSTEM::path originalFile1{ "data/glTF-Sample-Models/2.0/Box/glTF/Box.gltf" }; 167 | FX_GLTF_FILESYSTEM::path originalFile2{ "data/glTF-Sample-Models/2.0/BoxVertexColors/glTF/BoxVertexColors.gltf" }; 168 | 169 | fx::gltf::Document originalDocument1 = fx::gltf::LoadFromText(originalFile1); 170 | fx::gltf::Document originalDocument2 = fx::gltf::LoadFromText(originalFile2); 171 | 172 | // Roundtrip 2 different files in the same stream... 173 | std::stringstream ss{}; 174 | fx::gltf::Save(originalDocument1, ss, utility::GetTestOutputDir(), false); 175 | fx::gltf::Save(originalDocument2, ss, utility::GetTestOutputDir(), false); 176 | 177 | ss.seekg(0, std::stringstream::beg); 178 | 179 | fx::gltf::Document copy1 = fx::gltf::LoadFromText(ss, utility::GetTestOutputDir()); 180 | fx::gltf::Document copy2 = fx::gltf::LoadFromText(ss, utility::GetTestOutputDir()); 181 | 182 | REQUIRE(copy1.buffers.front().data == originalDocument1.buffers.front().data); 183 | REQUIRE(copy2.buffers.front().data == originalDocument2.buffers.front().data); 184 | } 185 | 186 | SECTION("load binary - save binary streams") 187 | { 188 | FX_GLTF_FILESYSTEM::path originalFile1{ "data/glTF-Sample-Models/2.0/Box/glTF-Binary/Box.glb" }; 189 | FX_GLTF_FILESYSTEM::path originalFile2{ "data/glTF-Sample-Models/2.0/BoxVertexColors/glTF-Binary/BoxVertexColors.glb" }; 190 | 191 | fx::gltf::Document originalDocument1 = fx::gltf::LoadFromBinary(originalFile1); 192 | fx::gltf::Document originalDocument2 = fx::gltf::LoadFromBinary(originalFile2); 193 | 194 | // Roundtrip 2 different files in the same stream... 195 | std::stringstream ss{}; 196 | fx::gltf::Save(originalDocument1, ss, "", true); 197 | fx::gltf::Save(originalDocument2, ss, "", true); 198 | 199 | ss.seekg(0, std::stringstream::beg); 200 | 201 | fx::gltf::Document copy1 = fx::gltf::LoadFromBinary(ss, ""); 202 | fx::gltf::Document copy2 = fx::gltf::LoadFromBinary(ss, ""); 203 | 204 | REQUIRE(copy1.buffers.front().data == originalDocument1.buffers.front().data); 205 | REQUIRE(copy2.buffers.front().data == originalDocument2.buffers.front().data); 206 | } 207 | 208 | SECTION("load from unicode path - save to unicode path") 209 | { 210 | #ifdef __cpp_lib_char8_t 211 | FX_GLTF_FILESYSTEM::path originalFile{ "data/unιcode-ρath/Box.gltf" }; 212 | FX_GLTF_FILESYSTEM::path newFile{ utility::GetTestOutputDir() / "unιcodeFile.glb" }; 213 | #else 214 | FX_GLTF_FILESYSTEM::path originalFile{ FX_GLTF_FILESYSTEM::u8path("data/unιcode-ρath/Box.gltf") }; 215 | FX_GLTF_FILESYSTEM::path newFile{ utility::GetTestOutputDir() / FX_GLTF_FILESYSTEM::u8path("unιcodeFile.glb") }; 216 | #endif 217 | 218 | fx::gltf::Document originalDocument = fx::gltf::LoadFromText(originalFile); 219 | 220 | originalDocument.buffers.front().uri.clear(); 221 | fx::gltf::Save(originalDocument, newFile, true); 222 | 223 | fx::gltf::Document newDocument = fx::gltf::LoadFromBinary(newFile); 224 | 225 | REQUIRE(newDocument.buffers.front().data == originalDocument.buffers.front().data); 226 | REQUIRE(newDocument.buffers.front().uri.empty()); 227 | } 228 | 229 | utility::CleanupTestOutputDir(); 230 | } -------------------------------------------------------------------------------- /test/src/unit.cpp: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | 7 | #define CATCH_CONFIG_CONSOLE_WIDTH 120 8 | #define CATCH_CONFIG_MAIN 9 | #include "catch2/catch.hpp" -------------------------------------------------------------------------------- /test/src/utility.cpp: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | 7 | #include "utility.h" 8 | 9 | #include "catch2/catch.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace utility 16 | { 17 | FX_GLTF_FILESYSTEM::path GetTestOutputDir() 18 | { 19 | return "output"; 20 | } 21 | 22 | void CreateTestOutputDir() 23 | { 24 | std::error_code err{}; 25 | int attempts = 5; 26 | while (--attempts >= 0) 27 | { 28 | if (FX_GLTF_FILESYSTEM::create_directory(GetTestOutputDir(), err)) 29 | { 30 | break; 31 | } 32 | } 33 | } 34 | 35 | void CleanupTestOutputDir() 36 | { 37 | std::error_code err{}; 38 | int attempts = 5; 39 | while (--attempts >= 0) 40 | { 41 | if (FX_GLTF_FILESYSTEM::remove_all(GetTestOutputDir(), err) != static_cast(-1)) 42 | { 43 | break; 44 | } 45 | } 46 | } 47 | 48 | nlohmann::json LoadJsonFromFile(FX_GLTF_FILESYSTEM::path const & filePath) 49 | { 50 | nlohmann::json result{}; 51 | std::ifstream file(filePath); 52 | if (!file.is_open()) 53 | { 54 | throw std::system_error(std::make_error_code(std::errc::no_such_file_or_directory)); 55 | } 56 | 57 | file >> result; 58 | return result; 59 | } 60 | 61 | // https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ 62 | union Float_t 63 | { 64 | explicit Float_t(float num = 0.0f) 65 | : f(num) {} 66 | bool Negative() const 67 | { 68 | return i < 0; 69 | } 70 | 71 | int32_t i; 72 | float f; 73 | }; 74 | 75 | bool AlmostEqual(float A, float B) 76 | { 77 | // Check if the numbers are really close -- needed 78 | // when comparing numbers near zero. 79 | float absDiff = std::fabs(A - B); 80 | if (absDiff <= 1e-5f) 81 | { 82 | return true; 83 | } 84 | 85 | float absA = std::fabs(A); 86 | float absB = std::fabs(B); 87 | float largest = (absB > absA) ? absB : absA; 88 | const float maxDiff = largest * FLT_EPSILON; 89 | 90 | if (absDiff <= maxDiff) 91 | { 92 | return true; 93 | } 94 | 95 | Float_t uA(A); 96 | Float_t uB(B); 97 | 98 | // Different signs means they do not match. 99 | if (uA.Negative() != uB.Negative()) 100 | { 101 | return false; 102 | } 103 | 104 | // Find the difference in ULPs. 105 | int ulpsDiff = abs(uA.i - uB.i); 106 | 107 | return ulpsDiff <= 2; 108 | } 109 | 110 | nlohmann::json DiffAndFilter(nlohmann::json const & current, nlohmann::json const & original) 111 | { 112 | nlohmann::json filtered{}; 113 | nlohmann::json diff = nlohmann::json::diff(current, original); 114 | 115 | for (auto & element : diff) 116 | { 117 | std::string path = element["path"].get(); 118 | 119 | if (element["op"] == "add") 120 | { 121 | if ((path.find("emissiveFactor") != std::string::npos && element["value"].get>() == std::vector{ 0.0f, 0.0f, 0.0f }) || 122 | (path.find("baseColorFactor") != std::string::npos && element["value"].get>() == std::vector{ 1.0f, 1.0f, 1.0f, 1.0f }) || 123 | (path.find("translation") != std::string::npos && element["value"].get>() == std::vector{ 0.0f, 0.0f, 0.0f }) || 124 | (path.find("rotation") != std::string::npos && element["value"].get>() == std::vector{ 0.0f, 0.0f, 0.0f, 1.0f }) || 125 | (path.find("mode") != std::string::npos && element["value"] == 4) || 126 | (path.find("byteOffset") != std::string::npos && element["value"] == 0) || 127 | (path.find("interpolation") != std::string::npos && element["value"] == "LINEAR") || 128 | (path.find("wrapS") != std::string::npos && element["value"] == 10497) || 129 | (path.find("wrapT") != std::string::npos && element["value"] == 10497) || 130 | (path.find("doubleSided") != std::string::npos && element["value"] == false) || 131 | (path.find("metallicFactor") != std::string::npos && element["value"] == 1.0f) || 132 | (path.find("roughnessFactor") != std::string::npos && element["value"] == 1.0f) || 133 | (path.find("strength") != std::string::npos && element["value"] == 1.0f) || 134 | (path.find("alphaMode") != std::string::npos && element["value"] == "OPAQUE") || 135 | (path.find("texCoord") != std::string::npos && element["value"] == 0)) 136 | { 137 | continue; 138 | } 139 | 140 | if (path.find("scale") != std::string::npos) 141 | { 142 | if ((element["value"].is_array() && element["value"].get>() == std::vector{ 1.0f, 1.0f, 1.0f }) || 143 | (element["value"].is_number() && element["value"].get() == 1.0f)) 144 | { 145 | continue; 146 | } 147 | } 148 | } 149 | else if (element["op"] == "replace") 150 | { 151 | if (path.find("nodes") != std::string::npos) 152 | { 153 | if (element["value"].find("rotation") != element["value"].end()) 154 | { 155 | if (element["value"]["rotation"].get>() == std::vector{ 0.0f, 0.0f, 0.0f, 1.0f }) 156 | { 157 | continue; 158 | } 159 | } 160 | } 161 | 162 | if (element["value"].is_number_float()) 163 | { 164 | // The text-based json diff says our numbers are mismatched but that could be 165 | // because the text actually has more digits of precision than what can fit in 166 | // a float. Let's just double-check aginst current and original... 167 | float currentValue = current[nlohmann::json::json_pointer(path)].get(); 168 | float originalValue = original[nlohmann::json::json_pointer(path)].get(); 169 | if (AlmostEqual(currentValue, originalValue)) 170 | { 171 | continue; 172 | } 173 | } 174 | 175 | if (element["value"].empty()) 176 | { 177 | continue; 178 | } 179 | } 180 | 181 | filtered.push_back(element); 182 | } 183 | 184 | return filtered; 185 | } 186 | } // namespace utility -------------------------------------------------------------------------------- /test/src/utility.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------ 2 | // Copyright(c) 2018-2022 Jesse Yurkovich 3 | // Licensed under the MIT License . 4 | // See the LICENSE file in the repo root for full license information. 5 | // ------------------------------------------------------------ 6 | #pragma once 7 | 8 | #include 9 | #include 10 | 11 | #ifndef FX_GLTF_FILESYSTEM 12 | #if defined(__clang__) 13 | #if __clang_major__ < 7 || (defined(__cplusplus) && __cplusplus < 201703L) 14 | #define FX_GLTF_EXPERIMENTAL_FILESYSTEM 15 | #endif 16 | #elif defined(__GNUC__) 17 | #if __GNUC__ < 8 || (defined(__cplusplus) && __cplusplus < 201703L) 18 | #define FX_GLTF_EXPERIMENTAL_FILESYSTEM 19 | #endif 20 | #elif defined(_MSC_VER) 21 | #if _MSC_VER < 1914 || (!defined(_HAS_CXX17) || (defined(_HAS_CXX17) && _HAS_CXX17 == 0)) 22 | #define FX_GLTF_EXPERIMENTAL_FILESYSTEM 23 | #endif 24 | #endif 25 | 26 | #ifdef FX_GLTF_EXPERIMENTAL_FILESYSTEM 27 | #include 28 | #define FX_GLTF_FILESYSTEM std::experimental::filesystem::v1 29 | #else 30 | #include 31 | #define FX_GLTF_FILESYSTEM std::filesystem 32 | #endif 33 | #endif 34 | 35 | namespace utility 36 | { 37 | FX_GLTF_FILESYSTEM::path GetTestOutputDir(); 38 | void CreateTestOutputDir(); 39 | void CleanupTestOutputDir(); 40 | 41 | nlohmann::json LoadJsonFromFile(FX_GLTF_FILESYSTEM::path const & filePath); 42 | 43 | // Many .glTF files contain json elements which are optional. 44 | // When performing roundtrip tests against such files, fx-gltf will 45 | // remove these optional elements in the final json, causing the diff 46 | // to fail. This function performs the diff and filters such cases. 47 | nlohmann::json DiffAndFilter(nlohmann::json const & current, nlohmann::json const & original); 48 | 49 | } // namespace utility -------------------------------------------------------------------------------- /test/thirdparty/catch2/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /test/thirdparty/nlohmann/LICENSE.MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013-2019 Niels Lohmann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | --------------------------------------------------------------------------------