├── .gitmodules ├── .gitattributes ├── assets └── textures │ ├── 001_basecolor.ktx │ ├── 002_basecolor.ktx │ ├── 003_basecolor.ktx │ ├── 004_basecolor.ktx │ └── 005_basecolor.ktx ├── source ├── CMakeLists.txt ├── base │ ├── LinkImpl.cpp │ ├── Keyboard.cpp │ ├── GraphicsMath.hpp │ ├── Gamepad.hpp │ ├── Gamepad.cpp │ ├── Keyboard.hpp │ ├── CMakeLists.txt │ ├── Camera.hpp │ ├── File.hpp │ ├── File.cpp │ ├── Camera.cpp │ ├── GameTimer.cpp │ ├── Mouse.cpp │ ├── GameTimer.hpp │ ├── Mouse.hpp │ ├── Example.hpp │ ├── SimpleMath.cpp │ ├── Example.cpp │ └── SimpleMath.h ├── helloworld │ ├── tvOS │ │ └── LaunchScreen.storyboard │ ├── Info.plist.in │ ├── CMakeLists.txt │ ├── iOS │ │ └── LaunchScreen.storyboard │ └── main.cpp ├── instancing │ ├── tvOS │ │ └── LaunchScreen.storyboard │ ├── Info.plist.in │ ├── CMakeLists.txt │ ├── iOS │ │ └── LaunchScreen.storyboard │ └── main.cpp └── textures │ ├── tvOS │ └── LaunchScreen.storyboard │ ├── Info.plist.in │ ├── iOS │ └── LaunchScreen.storyboard │ ├── CMakeLists.txt │ └── main.cpp ├── vcpkg.json ├── .clang-format ├── cmake ├── FetchExternal.cmake └── CompileShaders.cmake ├── shaders ├── triangle │ └── shader.metal ├── instancing │ └── shader.metal └── textures │ └── shader.metal ├── CMakeLists.txt ├── LICENSE ├── .gitignore ├── .github └── workflows │ └── cmake-single-platform.yml └── README.md /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | source/base/sal.h -linguist-vendored -------------------------------------------------------------------------------- /assets/textures/001_basecolor.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattGuerrette/Metal/HEAD/assets/textures/001_basecolor.ktx -------------------------------------------------------------------------------- /assets/textures/002_basecolor.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattGuerrette/Metal/HEAD/assets/textures/002_basecolor.ktx -------------------------------------------------------------------------------- /assets/textures/003_basecolor.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattGuerrette/Metal/HEAD/assets/textures/003_basecolor.ktx -------------------------------------------------------------------------------- /assets/textures/004_basecolor.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattGuerrette/Metal/HEAD/assets/textures/004_basecolor.ktx -------------------------------------------------------------------------------- /assets/textures/005_basecolor.ktx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattGuerrette/Metal/HEAD/assets/textures/005_basecolor.ktx -------------------------------------------------------------------------------- /source/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | file(GLOB EXAMPLES "*/") 3 | list(REMOVE_ITEM EXAMPLES ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt) 4 | foreach (example ${EXAMPLES}) 5 | get_filename_component(directory ${example} NAME_WLE) 6 | message(STATUS "Adding example ${directory}") 7 | 8 | add_subdirectory(${example}) 9 | endforeach () -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "metal", 3 | "builtin-baseline": "9e8416f15d93fa9347e3291bd8c128dd44200ba5", 4 | "dependencies": [ 5 | { 6 | "name": "directxmath", 7 | "version>=": "2025-04-03" 8 | }, 9 | { 10 | "name": "fmt", 11 | "version>=": "11.0.2#1" 12 | }, 13 | { 14 | "name": "sdl3", 15 | "version>=": "3.2.24" 16 | }, 17 | { 18 | "name": "cgltf", 19 | "version>=": "1.14" 20 | }, 21 | { 22 | "name": "ktx", 23 | "version>=": "4.3.2" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /source/base/LinkImpl.cpp: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////////////////////////////////////////////////////// 3 | // Copyright (c) 2024. Matt Guerrette 4 | // SPDX-License-Identifier: MIT 5 | //////////////////////////////////////////////////////////////////////////////// 6 | 7 | #define NS_PRIVATE_IMPLEMENTATION 8 | #define MTK_PRIVATE_IMPLEMENTATION 9 | #define MTL_PRIVATE_IMPLEMENTATION 10 | #define CA_PRIVATE_IMPLEMENTATION 11 | 12 | #include "AppKit/AppKit.hpp" 13 | #include "Foundation/Foundation.hpp" 14 | #include "Metal/Metal.hpp" 15 | #include "MetalKit/MetalKit.hpp" 16 | #include "QuartzCore/QuartzCore.hpp" 17 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: WebKit 2 | NamespaceIndentation: All 3 | AlignTrailingComments: true 4 | AlignConsecutiveDeclarations: AcrossComments 5 | BinPackParameters: false 6 | ReferenceAlignment: Left 7 | PointerAlignment: Left 8 | AccessModifierOffset: -4 9 | AlwaysBreakTemplateDeclarations: Yes 10 | ColumnLimit: 100 11 | BraceWrapping: 12 | AfterClass: true 13 | AfterControlStatement: true 14 | AfterEnum: true 15 | AfterFunction: true 16 | AfterNamespace: true 17 | AfterObjCDeclaration: true 18 | AfterStruct: true 19 | AfterUnion: true 20 | BeforeCatch: true 21 | BeforeElse: true 22 | BreakBeforeBraces: Custom 23 | AllowShortFunctionsOnASingleLine: None -------------------------------------------------------------------------------- /source/base/Keyboard.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) 2024. Matt Guerrette 3 | // SPDX-License-Identifier: MIT 4 | //////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include "Keyboard.hpp" 7 | 8 | bool Keyboard::isKeyClicked(SDL_Scancode key) 9 | { 10 | return m_currentKeyState[key] && !m_previousKeyState[key]; 11 | } 12 | 13 | bool Keyboard::isKeyPressed(SDL_Scancode key) 14 | { 15 | return m_currentKeyState[key]; 16 | } 17 | 18 | void Keyboard::registerKeyEvent(SDL_KeyboardEvent* event) 19 | { 20 | m_currentKeyState[event->scancode] = event->down; 21 | } 22 | 23 | void Keyboard::update() 24 | { 25 | m_previousKeyState = m_currentKeyState; 26 | } 27 | -------------------------------------------------------------------------------- /cmake/FetchExternal.cmake: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | if (NOT METALCPP_DIR) 4 | FetchContent_Declare(metalcpp 5 | GIT_REPOSITORY "https://github.com/MattGuerrette/metalcpp" 6 | GIT_TAG main 7 | ) 8 | FetchContent_MakeAvailable(metalcpp) 9 | else () 10 | add_subdirectory(${METALCPP_DIR} metalcpp) 11 | endif () 12 | 13 | include_directories(${metalcpp_SOURCE_DIR}) 14 | 15 | set_target_properties( 16 | AppleFrameworksCpp 17 | PROPERTIES FOLDER "External") 18 | 19 | FetchContent_Declare( 20 | imgui 21 | GIT_REPOSITORY https://github.com/ocornut/imgui.git 22 | GIT_TAG master 23 | ) 24 | FetchContent_MakeAvailable(imgui) 25 | include_directories(${imgui_SOURCE_DIR}) 26 | include_directories(${imgui_SOURCE_DIR}/backends) 27 | -------------------------------------------------------------------------------- /shaders/triangle/shader.metal: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace metal; 4 | 5 | struct Vertex { 6 | float4 position [[position]]; 7 | float4 color; 8 | }; 9 | 10 | struct Uniforms { 11 | float4x4 modelViewProjectionMatrix; 12 | }; 13 | 14 | vertex Vertex triangle_vertex( 15 | device const Vertex* vertices [[buffer(0)]], 16 | constant Uniforms* uniforms [[buffer(1)]], 17 | uint vid [[vertex_id]]) 18 | { 19 | Vertex vertexOut; 20 | vertexOut.position = uniforms->modelViewProjectionMatrix * vertices[vid].position; 21 | vertexOut.color = vertices[vid].color; 22 | vertexOut.color *= 1; 23 | 24 | return vertexOut; 25 | } 26 | 27 | fragment half4 triangle_fragment(Vertex vertexIn [[stage_in]]) 28 | { 29 | return half4(vertexIn.color); 30 | } 31 | -------------------------------------------------------------------------------- /shaders/instancing/shader.metal: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace metal; 4 | 5 | struct Vertex { 6 | float4 position [[position]]; 7 | float4 color; 8 | }; 9 | 10 | struct InstanceData { 11 | float4x4 transform; 12 | }; 13 | 14 | vertex Vertex instancing_vertex( 15 | device const Vertex* vertices [[buffer(0)]], 16 | device const InstanceData* instanceData [[buffer(1)]], 17 | uint vid [[vertex_id]], 18 | uint iid [[instance_id]]) 19 | { 20 | Vertex vertexOut; 21 | vertexOut.position = instanceData[iid].transform * vertices[vid].position; 22 | vertexOut.color = vertices[vid].color; 23 | vertexOut.color *= 1; 24 | 25 | return vertexOut; 26 | } 27 | 28 | fragment half4 instancing_fragment(Vertex vertexIn [[stage_in]]) 29 | { 30 | return half4(vertexIn.color); 31 | } 32 | -------------------------------------------------------------------------------- /source/base/GraphicsMath.hpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) 2024. Matt Guerrette 3 | // SPDX-License-Identifier: MIT 4 | //////////////////////////////////////////////////////////////////////////////// 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #ifdef __APPLE__ 11 | #ifdef __arm__ 12 | #define _XM_ARM_NEON_INTRINSICS_ // Apple architecture 13 | #endif 14 | #endif 15 | 16 | #define PAL_STDCPP_COMPAT // Needed to avoid issues with __null definition on Xcode 16+ 17 | #include 18 | #include 19 | 20 | #include "SimpleMath.h" 21 | 22 | using namespace DirectX; 23 | using namespace DirectX::SimpleMath; 24 | 25 | // Needed due to DirectXMath/SAL headers re-defining NULL 26 | // to avoid strict header ordering 27 | #undef NULL 28 | #define NULL 0 -------------------------------------------------------------------------------- /source/base/Gamepad.hpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Matt Guerrette 2024. 3 | // SPDX-License-Identifier: MIT 4 | //////////////////////////////////////////////////////////////////////////////// 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include 11 | 12 | class Gamepad 13 | { 14 | struct GamepadDeleter 15 | { 16 | void operator()(SDL_Gamepad* gamepad) 17 | { 18 | if (gamepad != nullptr) 19 | { 20 | SDL_CloseGamepad(gamepad); 21 | } 22 | } 23 | }; 24 | using UniqueGamepad = std::unique_ptr; 25 | 26 | public: 27 | explicit Gamepad(SDL_JoystickID id); 28 | 29 | float leftThumbstickHorizontal() const; 30 | 31 | float leftThumbstickVertical() const; 32 | 33 | private: 34 | UniqueGamepad m_gamepad; 35 | }; -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.24) 2 | project(Metal) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | set(CMAKE_XCODE_GENERATE_SCHEME TRUE) 6 | enable_language(OBJC OBJCXX) 7 | 8 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 9 | include(FetchExternal) 10 | 11 | find_package(fmt CONFIG REQUIRED) 12 | find_package(SDL3 CONFIG REQUIRED) 13 | find_package(directxmath CONFIG REQUIRED) 14 | find_package(Ktx CONFIG REQUIRED) 15 | 16 | find_path(CGLTF_INCLUDE_DIRS "cgltf.h") 17 | include_directories(${CGLTF_INCLUDE_DIRS}) 18 | 19 | # Add custom shader compilation for generators other than Xcode 20 | # For Xcode, each target will include the Metal shaders as source 21 | # to be compiled into the default library through Xcode 22 | if (NOT CMAKE_GENERATOR MATCHES "Xcode") 23 | include(CompileShaders) 24 | 25 | file(GLOB_RECURSE METAL_SHADERS ${CMAKE_CURRENT_SOURCE_DIR}/shaders/*.metal) 26 | CompileMetalShaders("${METAL_SHADERS}") 27 | endif () 28 | 29 | # Examples 30 | add_subdirectory(source) 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Matt Guerrette 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 | -------------------------------------------------------------------------------- /source/base/Gamepad.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Matt Guerrette 2024. 3 | // SPDX-License-Identifier: MIT 4 | //////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include "Gamepad.hpp" 7 | 8 | #include 9 | 10 | Gamepad::Gamepad(SDL_JoystickID id) 11 | { 12 | SDL_Gamepad* gamepad = SDL_OpenGamepad(id); 13 | if (gamepad == nullptr) 14 | { 15 | throw std::runtime_error(fmt::format("Failed to open gamepad: {}", SDL_GetError())); 16 | } 17 | 18 | m_gamepad.reset(SDL_OpenGamepad(id)); 19 | } 20 | 21 | float Gamepad::leftThumbstickHorizontal() const 22 | { 23 | const auto axisValue = SDL_GetGamepadAxis(m_gamepad.get(), SDL_GAMEPAD_AXIS_LEFTX); 24 | return static_cast(axisValue) / static_cast(std::numeric_limits::max()); 25 | } 26 | 27 | float Gamepad::leftThumbstickVertical() const 28 | { 29 | const auto axisValue = SDL_GetGamepadAxis(m_gamepad.get(), SDL_GAMEPAD_AXIS_LEFTY); 30 | return static_cast(axisValue) / static_cast(std::numeric_limits::max()); 31 | } -------------------------------------------------------------------------------- /source/base/Keyboard.hpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) 2024. Matt Guerrette 3 | // SPDX-License-Identifier: MIT 4 | //////////////////////////////////////////////////////////////////////////////// 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include 11 | 12 | class Keyboard final 13 | { 14 | using KeyState = std::map; 15 | 16 | public: 17 | /// @brief Checks if specified key was clicked this frame. 18 | /// @param [in] key The clicked key. 19 | /// @return True if key was clicked, false otherwise. 20 | bool isKeyClicked(SDL_Scancode key); 21 | 22 | /// @brief Checks if specified key is pressed this frame. 23 | /// @param [in] key The pressed key. 24 | /// @return True if pressed, false otherwise. 25 | bool isKeyPressed(SDL_Scancode key); 26 | 27 | /// @brief Registers key event. 28 | /// @param [in] event The key event. 29 | void registerKeyEvent(SDL_KeyboardEvent* event); 30 | 31 | /// @brief Updates the state cache for next frame. 32 | void update(); 33 | 34 | private: 35 | KeyState m_previousKeyState; ///< Previous frame key-state. 36 | KeyState m_currentKeyState; ///< Current frame key-state. 37 | }; 38 | -------------------------------------------------------------------------------- /source/base/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_library(base STATIC 3 | Camera.cpp 4 | Keyboard.cpp 5 | Keyboard.hpp 6 | Mouse.cpp 7 | GameTimer.cpp 8 | GameTimer.cpp 9 | SimpleMath.cpp 10 | Example.cpp 11 | Example.hpp 12 | Gamepad.cpp 13 | File.cpp 14 | File.hpp 15 | ${imgui_SOURCE_DIR}/imgui.cpp 16 | ${imgui_SOURCE_DIR}/imgui_draw.cpp 17 | ${imgui_SOURCE_DIR}/imgui_tables.cpp 18 | ${imgui_SOURCE_DIR}/imgui_widgets.cpp 19 | ${imgui_SOURCE_DIR}/backends/imgui_impl_sdl3.cpp 20 | ${imgui_SOURCE_DIR}/backends/imgui_impl_metal.mm 21 | ) 22 | 23 | set_target_properties(base PROPERTIES 24 | XCODE_GENERATE_SCHEME YES 25 | XCODE_ATTRIBUTE_TVOS_DEPLOYMENT_TARGET 17.0 26 | XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET 17.0 27 | XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET 14.0 28 | XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC NO) 29 | 30 | target_compile_definitions(base PRIVATE -DIMGUI_IMPL_METAL_CPP) 31 | target_include_directories(base PUBLIC .) 32 | target_link_libraries(base PUBLIC fmt::fmt SDL3::SDL3 Microsoft::DirectXMath AppleFrameworksCpp 33 | "-framework Foundation" 34 | "-framework Metal" 35 | "-framework MetalKit" 36 | "-framework QuartzCore") 37 | -------------------------------------------------------------------------------- /source/base/Camera.hpp: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////////////////////////////////////////////////////// 3 | // Copyright (c) 2024. Matt Guerrette 4 | // SPDX-License-Identifier: MIT 5 | //////////////////////////////////////////////////////////////////////////////// 6 | 7 | #import "GraphicsMath.hpp" 8 | 9 | XM_ALIGNED_STRUCT(16) CameraUniforms 10 | { 11 | Matrix view; 12 | Matrix projection; 13 | Matrix viewProjection; 14 | Matrix invProjection; 15 | Matrix invView; 16 | Matrix invViewProjection; 17 | }; 18 | 19 | /// @todo Improve this Camera class to use Quaternion rotation 20 | class Camera 21 | { 22 | public: 23 | Camera(Vector3 position, 24 | Vector3 direction, 25 | Vector3 up, 26 | float fov, 27 | float aspectRatio, 28 | float nearPlane, 29 | float farPlane); 30 | 31 | [[nodiscard]] const CameraUniforms& uniforms() const; 32 | 33 | void setProjection(float fov, float aspect, float zNear, float zFar); 34 | 35 | private: 36 | void updateBasisVectors(Vector3 direction); 37 | 38 | void updateUniforms(); 39 | 40 | CameraUniforms m_uniforms {}; 41 | Vector3 m_position; 42 | Vector3 m_direction; 43 | Vector3 m_up; 44 | float m_fieldOfView; 45 | float m_aspectRatio; 46 | float m_nearPlane; 47 | float m_farPlane; 48 | }; 49 | -------------------------------------------------------------------------------- /source/base/File.hpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) 2024. Matt Guerrette 3 | // SPDX-License-Identifier: MIT 4 | //////////////////////////////////////////////////////////////////////////////// 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | namespace SDL 14 | { 15 | struct IOStreamDeleter 16 | { 17 | void operator()(SDL_IOStream* stream) const 18 | { 19 | if (stream != nullptr) 20 | { 21 | SDL_CloseIO(stream); 22 | } 23 | } 24 | }; 25 | using IOStreamPtr = std::unique_ptr; 26 | } 27 | 28 | class File 29 | { 30 | public: 31 | /// @brief Opens specified file for reading/ 32 | /// @note This file should be located within the app resource folder. 33 | /// @param [in] fileName The file located in resource folder to open for reading. 34 | explicit File(const std::string& fileName); 35 | File(const File& file) = delete; 36 | File& operator=(const File& file) = delete; 37 | 38 | /// @brief Accesses the SDL IO stream handle. 39 | /// @return The SDL_IOStream handle. 40 | [[nodiscard]] SDL_IOStream* stream() const; 41 | 42 | /// @brief Reads all bytes from file. 43 | /// @return File contents as a vector of bytes. 44 | [[nodiscard]] std::vector readAll() const; 45 | 46 | private: 47 | SDL::IOStreamPtr m_stream; ///< SDL Read/Write stream handle. 48 | }; -------------------------------------------------------------------------------- /shaders/textures/shader.metal: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace metal; 4 | 5 | struct VertexIn { 6 | float4 position; 7 | float2 uv; 8 | }; 9 | 10 | struct VertexOut { 11 | float4 position [[position]]; 12 | float2 uv; 13 | uint32_t textureIndex; 14 | }; 15 | 16 | typedef struct 17 | { 18 | array, 5> textures; 19 | device float4x4* transforms; 20 | } ArgumentBuffer; 21 | 22 | vertex VertexOut texture_vertex( 23 | device const VertexIn* vertices [[buffer(0)]], 24 | const device ArgumentBuffer& argBuffer[[buffer(1)]], 25 | uint vid [[vertex_id]], 26 | uint iid [[instance_id]]) 27 | { 28 | VertexOut vertexOut; 29 | vertexOut.position = argBuffer.transforms[iid] * vertices[vid].position; 30 | vertexOut.uv = vertices[vid].uv; 31 | vertexOut.textureIndex = iid; 32 | 33 | // Using KTX textures the orientation will be incompatible 34 | // Flip here. Maybe eventually 'ktx create' command will support specifying 35 | // the coordinate space. 36 | vertexOut.uv.y = 1.0 - vertexOut.uv.y; 37 | return vertexOut; 38 | } 39 | 40 | fragment half4 texture_fragment(VertexOut vertexIn [[stage_in]], const device ArgumentBuffer& argBuffer[[buffer(2)]]) 41 | { 42 | constexpr sampler colorSampler(mip_filter::linear, 43 | mag_filter::linear, 44 | min_filter::linear); 45 | 46 | half4 colorSample = argBuffer.textures[vertexIn.textureIndex].sample(colorSampler, vertexIn.uv.xy); 47 | 48 | return half4(colorSample); 49 | } 50 | 51 | -------------------------------------------------------------------------------- /source/helloworld/tvOS/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /source/instancing/tvOS/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /source/textures/tvOS/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | .DS_Store 6 | .idea/ 7 | 8 | vcpkg_installed 9 | 10 | ## Build generated 11 | cmake-build*/ 12 | _build*/ 13 | build*/ 14 | DerivedData/ 15 | 16 | ## Various settings 17 | *.pbxuser 18 | !default.pbxuser 19 | *.mode1v3 20 | !default.mode1v3 21 | *.mode2v3 22 | !default.mode2v3 23 | *.perspectivev3 24 | !default.perspectivev3 25 | xcuserdata/ 26 | 27 | ## Other 28 | *.moved-aside 29 | *.xccheckout 30 | *.xcscmblueprint 31 | 32 | ## Obj-C/Swift specific 33 | *.hmap 34 | *.ipa 35 | *.dSYM.zip 36 | *.dSYM 37 | 38 | # CocoaPods 39 | # 40 | # We recommend against adding the Pods directory to your .gitignore. However 41 | # you should judge for yourself, the pros and cons are mentioned at: 42 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 43 | # 44 | # Pods/ 45 | 46 | # Carthage 47 | # 48 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 49 | # Carthage/Checkouts 50 | 51 | Carthage/Build 52 | 53 | # fastlane 54 | # 55 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 56 | # screenshots whenever they are needed. 57 | # For more information about the recommended setup visit: 58 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 59 | 60 | fastlane/report.xml 61 | fastlane/Preview.html 62 | fastlane/screenshots/**/*.png 63 | fastlane/test_output 64 | 65 | # Code Injection 66 | # 67 | # After new code Injection tools there's a generated folder /iOSInjectionProject 68 | # https://github.com/johnno1962/injectionforxcode 69 | 70 | iOSInjectionProject/ 71 | -------------------------------------------------------------------------------- /source/base/File.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) 2024. Matt Guerrette 3 | // SPDX-License-Identifier: MIT 4 | //////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include "File.hpp" 7 | 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | 14 | namespace 15 | { 16 | std::string pathForResource(const std::string& resourceName) 17 | { 18 | std::filesystem::path resourcePath = SDL_GetBasePath(); 19 | resourcePath.append(resourceName); 20 | 21 | return resourcePath.string(); 22 | } 23 | } // namespace 24 | 25 | File::File(const std::string& fileName) 26 | { 27 | const auto filePath = pathForResource(fileName); 28 | 29 | m_stream.reset(SDL_IOFromFile(filePath.c_str(), "rb")); 30 | if (m_stream == nullptr) 31 | { 32 | throw std::runtime_error( 33 | fmt::format("Failed to open {} for read. SDL_Error: {}", fileName, SDL_GetError())); 34 | } 35 | } 36 | 37 | SDL_IOStream* File::stream() const 38 | { 39 | return m_stream.get(); 40 | } 41 | 42 | std::vector File::readAll() const 43 | { 44 | SDL_SeekIO(m_stream.get(), 0, SDL_IO_SEEK_END); 45 | const auto numBytes = SDL_TellIO(m_stream.get()); 46 | SDL_SeekIO(m_stream.get(), 0, SDL_IO_SEEK_SET); 47 | 48 | std::vector result = {}; 49 | result.resize(numBytes); 50 | 51 | size_t numBytesRead; 52 | void* data = SDL_LoadFile_IO(m_stream.get(), &numBytesRead, false); 53 | if (data == nullptr) 54 | { 55 | return {}; 56 | } 57 | 58 | memcpy(result.data(), data, numBytesRead); 59 | SDL_free(data); 60 | 61 | return result; 62 | } -------------------------------------------------------------------------------- /source/base/Camera.cpp: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////////////////////////////////////////////////////// 3 | // Copyright (c) 2024. Matt Guerrette 4 | // SPDX-License-Identifier: MIT 5 | //////////////////////////////////////////////////////////////////////////////// 6 | 7 | #include "Camera.hpp" 8 | 9 | Camera::Camera(Vector3 position, 10 | Vector3 direction, 11 | Vector3 up, 12 | float fov, 13 | float aspectRatio, 14 | float nearPlane, 15 | float farPlane) 16 | : m_position(position) 17 | , m_direction(direction) 18 | , m_up(up) 19 | , m_fieldOfView(fov) 20 | , m_aspectRatio(aspectRatio) 21 | , m_nearPlane(nearPlane) 22 | , m_farPlane(farPlane) 23 | { 24 | updateBasisVectors(m_direction); 25 | updateUniforms(); 26 | } 27 | 28 | const CameraUniforms& Camera::uniforms() const 29 | { 30 | return m_uniforms; 31 | } 32 | 33 | void Camera::setProjection(float fov, float aspect, float zNear, float zFar) 34 | { 35 | m_fieldOfView = fov; 36 | m_aspectRatio = aspect; 37 | m_nearPlane = zNear; 38 | m_farPlane = zFar; 39 | updateUniforms(); 40 | } 41 | 42 | void Camera::updateBasisVectors(Vector3 direction) 43 | { 44 | m_direction = direction; 45 | m_direction.Normalize(); 46 | 47 | const Vector3 up = m_up; 48 | const Vector3 right = up.Cross(m_direction); 49 | const Vector3 newUp = right.Cross(m_direction); 50 | m_up = up; 51 | } 52 | 53 | void Camera::updateUniforms() 54 | { 55 | m_uniforms.view = Matrix::CreateLookAt(m_position, m_direction, m_up); 56 | m_uniforms.projection = Matrix::CreatePerspectiveFieldOfView( 57 | m_fieldOfView, m_aspectRatio, m_nearPlane, m_farPlane); 58 | m_uniforms.viewProjection = m_uniforms.projection * m_uniforms.view; 59 | } 60 | -------------------------------------------------------------------------------- /source/helloworld/Info.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${MACOSX_BUNDLE_EXECUTABLE_NAME} 9 | CFBundleGetInfoString 10 | ${MACOSX_BUNDLE_INFO_STRING} 11 | CFBundleIconFile 12 | ${MACOSX_BUNDLE_ICON_FILE} 13 | CFBundleIdentifier 14 | ${MACOSX_BUNDLE_GUI_IDENTIFIER} 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleLongVersionString 18 | ${MACOSX_BUNDLE_LONG_VERSION_STRING} 19 | CFBundleName 20 | ${MACOSX_BUNDLE_BUNDLE_NAME} 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | ${MACOSX_BUNDLE_SHORT_VERSION_STRING} 25 | CFBundleSignature 26 | ???? 27 | CFBundleVersion 28 | ${MACOSX_BUNDLE_BUNDLE_VERSION} 29 | CSResourcesFileMapped 30 | 31 | LSRequiresCarbon 32 | 33 | UIRequiresFullScreen 34 | 35 | UILaunchStoryboardName 36 | LaunchScreen.storyboard 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | NSHumanReadableCopyright 43 | ${MACOSX_BUNDLE_COPYRIGHT} 44 | NSHighResolutionCapable 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /source/instancing/Info.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${MACOSX_BUNDLE_EXECUTABLE_NAME} 9 | CFBundleGetInfoString 10 | ${MACOSX_BUNDLE_INFO_STRING} 11 | CFBundleIconFile 12 | ${MACOSX_BUNDLE_ICON_FILE} 13 | CFBundleIdentifier 14 | ${MACOSX_BUNDLE_GUI_IDENTIFIER} 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleLongVersionString 18 | ${MACOSX_BUNDLE_LONG_VERSION_STRING} 19 | CFBundleName 20 | ${MACOSX_BUNDLE_BUNDLE_NAME} 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | ${MACOSX_BUNDLE_SHORT_VERSION_STRING} 25 | CFBundleSignature 26 | ???? 27 | CFBundleVersion 28 | ${MACOSX_BUNDLE_BUNDLE_VERSION} 29 | CSResourcesFileMapped 30 | 31 | LSRequiresCarbon 32 | 33 | UIRequiresFullScreen 34 | 35 | UILaunchStoryboardName 36 | LaunchScreen.storyboard 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | NSHumanReadableCopyright 43 | ${MACOSX_BUNDLE_COPYRIGHT} 44 | NSHighResolutionCapable 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /source/textures/Info.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${MACOSX_BUNDLE_EXECUTABLE_NAME} 9 | CFBundleGetInfoString 10 | ${MACOSX_BUNDLE_INFO_STRING} 11 | CFBundleIconFile 12 | ${MACOSX_BUNDLE_ICON_FILE} 13 | CFBundleIdentifier 14 | ${MACOSX_BUNDLE_GUI_IDENTIFIER} 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleLongVersionString 18 | ${MACOSX_BUNDLE_LONG_VERSION_STRING} 19 | CFBundleName 20 | ${MACOSX_BUNDLE_BUNDLE_NAME} 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | ${MACOSX_BUNDLE_SHORT_VERSION_STRING} 25 | CFBundleSignature 26 | ???? 27 | CFBundleVersion 28 | ${MACOSX_BUNDLE_BUNDLE_VERSION} 29 | CSResourcesFileMapped 30 | 31 | LSRequiresCarbon 32 | 33 | UIRequiresFullScreen 34 | 35 | UILaunchStoryboardName 36 | LaunchScreen.storyboard 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | NSHumanReadableCopyright 43 | ${MACOSX_BUNDLE_COPYRIGHT} 44 | NSHighResolutionCapable 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /source/helloworld/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(EXAMPLE helloworld) 3 | 4 | if (NOT CMAKE_GENERATOR MATCHES "Xcode") 5 | set(RESOURCE_FILES "${PROJECT_BINARY_DIR}/generated/default.metallib") 6 | else () 7 | set(RESOURCE_FILES "") 8 | endif () 9 | 10 | if (${CMAKE_SYSTEM_NAME} MATCHES "iOS") 11 | list(APPEND RESOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/iOS/LaunchScreen.storyboard) 12 | elseif (${CMAKE_SYSTEM_NAME} MATCHES "tvOS") 13 | list(APPEND RESOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/tvOS/LaunchScreen.storyboard) 14 | endif () 15 | 16 | add_executable(${EXAMPLE} 17 | ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp 18 | ${RESOURCE_FILES}) 19 | if (CMAKE_GENERATOR MATCHES "Xcode") 20 | target_sources(${EXAMPLE} PRIVATE ${CMAKE_SOURCE_DIR}/shaders/triangle/shader.metal) 21 | set_source_files_properties( 22 | ${CMAKE_SOURCE_DIR}/shaders/triangle/shader.metal PROPERTIES 23 | LANGUAGE METAL 24 | COMPILE_OPTIONS "-I${CMAKE_SOURCE_DIR}/shaders/triangle" 25 | ) 26 | else () 27 | # Ensure shader library is available 28 | add_dependencies(${EXAMPLE} CompileMetalShaders) 29 | endif () 30 | 31 | target_link_libraries(${EXAMPLE} base) 32 | set_target_properties(${EXAMPLE} 33 | PROPERTIES 34 | MACOSX_BUNDLE TRUE 35 | MACOSX_BUNDLE_BUNDLE_NAME ${EXAMPLE} 36 | MACOSX_BUNDLE_GUI_IDENTIFIER com.github.MattGuerrette.${EXAMPLE} 37 | XCODE_GENERATE_SCHEME YES 38 | XCODE_ATTRIBUTE_TVOS_DEPLOYMENT_TARGET 17.0 39 | XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET 17.0 40 | XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET 14.0 41 | XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES 42 | XCODE_ATTRIBUTE_PRODUCT_NAME ${EXAMPLE} 43 | XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.github.MattGuerrette.${EXAMPLE} 44 | MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in 45 | RESOURCE "${RESOURCE_FILES}") 46 | -------------------------------------------------------------------------------- /source/instancing/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(EXAMPLE instancing) 3 | 4 | if (NOT CMAKE_GENERATOR MATCHES "Xcode") 5 | set(RESOURCE_FILES "${PROJECT_BINARY_DIR}/generated/default.metallib") 6 | else () 7 | set(RESOURCE_FILES "") 8 | endif () 9 | 10 | if (${CMAKE_SYSTEM_NAME} MATCHES "iOS") 11 | list(APPEND RESOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/iOS/LaunchScreen.storyboard) 12 | elseif (${CMAKE_SYSTEM_NAME} MATCHES "tvOS") 13 | list(APPEND RESOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/tvOS/LaunchScreen.storyboard) 14 | endif () 15 | 16 | add_executable(${EXAMPLE} 17 | ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp 18 | ${RESOURCE_FILES}) 19 | 20 | if (CMAKE_GENERATOR MATCHES "Xcode") 21 | target_sources(${EXAMPLE} PRIVATE ${CMAKE_SOURCE_DIR}/shaders/instancing/shader.metal) 22 | set_source_files_properties( 23 | ${CMAKE_SOURCE_DIR}/shaders/instancing/shader.metal PROPERTIES 24 | LANGUAGE METAL 25 | COMPILE_OPTIONS "-I${CMAKE_SOURCE_DIR}/shaders/instancing" 26 | ) 27 | else () 28 | # Ensure shader library is available 29 | add_dependencies(${EXAMPLE} CompileMetalShaders) 30 | endif () 31 | 32 | target_link_libraries(${EXAMPLE} base) 33 | set_target_properties(${EXAMPLE} 34 | PROPERTIES 35 | MACOSX_BUNDLE TRUE 36 | MACOSX_BUNDLE_BUNDLE_NAME ${EXAMPLE} 37 | MACOSX_BUNDLE_GUI_IDENTIFIER com.github.MattGuerrette.${EXAMPLE} 38 | XCODE_GENERATE_SCHEME YES 39 | XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES 40 | XCODE_ATTRIBUTE_TVOS_DEPLOYMENT_TARGET 17.0 41 | XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET 17.0 42 | XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET 14.0 43 | XCODE_ATTRIBUTE_PRODUCT_NAME ${EXAMPLE} 44 | XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.github.MattGuerrette.${EXAMPLE} 45 | MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in 46 | RESOURCE "${RESOURCE_FILES}") 47 | -------------------------------------------------------------------------------- /source/helloworld/iOS/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /source/instancing/iOS/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /source/textures/iOS/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /.github/workflows/cmake-single-platform.yml: -------------------------------------------------------------------------------- 1 | # This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. 2 | # See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml 3 | name: CMake (macOS) 4 | 5 | on: 6 | push: 7 | branches: [ "main" ] 8 | pull_request: 9 | branches: [ "main" ] 10 | 11 | env: 12 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 13 | BUILD_TYPE: Release 14 | 15 | jobs: 16 | build: 17 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 18 | # You can convert this to a matrix build if you need cross-platform coverage. 19 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 20 | runs-on: macos-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - name: Set up Homebrew 26 | id: set-up-homebrew 27 | uses: Homebrew/actions/setup-homebrew@master 28 | 29 | - name: Install Ninja 30 | run: brew install ninja 31 | 32 | - name: Install vcpkg 33 | run: brew install vcpkg 34 | 35 | - name: Clone vcpkg 36 | run: git clone https://github.com/microsoft/vcpkg "${{github.workspace}}/vcpkg"; sh ${{github.workspace}}/vcpkg/bootstrap-vcpkg.sh 37 | 38 | - name: Configure CMake 39 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 40 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 41 | run: cmake -GNinja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_TOOLCHAIN_FILE=${{github.workspace}}/vcpkg/scripts/buildsystems/vcpkg.cmake 42 | 43 | - name: Build 44 | # Build your program with the given configuration 45 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 46 | 47 | 48 | -------------------------------------------------------------------------------- /source/base/GameTimer.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) 2024. Matt Guerrette 3 | // SPDX-License-Identifier: MIT 4 | //////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include "GameTimer.hpp" 7 | 8 | GameTimer::GameTimer() 9 | : m_elapsedTicks(0) 10 | , m_totalTicks(0) 11 | , m_leftOverTicks(0) 12 | , m_frameCount(0) 13 | , m_framesPerSecond(0) 14 | , m_framesThisSecond(0) 15 | , m_qpcSecondCounter(0) 16 | , m_isFixedTimeStep(false) 17 | , m_targetElapsedTicks(0) 18 | { 19 | m_qpcFrequency = SDL_GetPerformanceFrequency(); 20 | m_qpcLastTime = SDL_GetPerformanceCounter(); 21 | 22 | // Max delta 1/10th of a second 23 | m_qpcMaxDelta = m_qpcFrequency / 10; 24 | } 25 | 26 | uint64_t GameTimer::elapsedTicks() const noexcept 27 | { 28 | return m_elapsedTicks; 29 | } 30 | 31 | double GameTimer::elapsedSeconds() const noexcept 32 | { 33 | return ticksToSeconds(m_elapsedTicks); 34 | } 35 | 36 | uint64_t GameTimer::totalTicks() const noexcept 37 | { 38 | return m_totalTicks; 39 | } 40 | 41 | double GameTimer::totalSeconds() const noexcept 42 | { 43 | return ticksToSeconds(m_totalTicks); 44 | } 45 | 46 | uint32_t GameTimer::frameCount() const noexcept 47 | { 48 | return m_frameCount; 49 | } 50 | 51 | uint32_t GameTimer::framesPerSecond() const noexcept 52 | { 53 | return m_framesPerSecond; 54 | } 55 | 56 | void GameTimer::setFixedTimeStep(const bool isFixedTimeStep) noexcept 57 | { 58 | m_isFixedTimeStep = isFixedTimeStep; 59 | } 60 | 61 | void GameTimer::setTargetElapsedTicks(uint64_t targetElapsed) noexcept 62 | { 63 | m_targetElapsedTicks = targetElapsed; 64 | } 65 | 66 | void GameTimer::setTargetElapsedSeconds(const double targetElapsed) noexcept 67 | { 68 | m_targetElapsedTicks = secondsToTicks(targetElapsed); 69 | } 70 | 71 | void GameTimer::resetElapsedTime() 72 | { 73 | m_qpcLastTime = SDL_GetPerformanceCounter(); 74 | 75 | m_leftOverTicks = 0; 76 | m_framesPerSecond = 0; 77 | m_framesThisSecond = 0; 78 | m_qpcSecondCounter = 0; 79 | m_totalTicks = 0; 80 | } 81 | -------------------------------------------------------------------------------- /cmake/CompileShaders.cmake: -------------------------------------------------------------------------------- 1 | 2 | # Find appropriate xcrun and sdk based on platform 3 | 4 | find_program(XCODE_RUN NAMES xcrun REQUIRED) 5 | if (${CMAKE_SYSTEM_NAME} MATCHES "iOS") 6 | set(XCODE_RUN_SDK iphoneos) 7 | set(XCODE_METAL_EXTRA_ARGS "-mios-version-min=16.0") 8 | elseif (${CMAKE_SYSTEM_NAME} MATCHES "tvOS") 9 | set(XCODE_RUN_SDK appletvos) 10 | set(XCODE_METAL_EXTRA_ARGS "-mtvos-version-min=16.0") 11 | else () 12 | set(XCODE_RUN_SDK macosx) 13 | set(XCODE_METAL_EXTRA_ARGS "") 14 | endif () 15 | 16 | 17 | # The following function implements support for compiling multiple 18 | # Metal shaders into a single library file. It achieves this using the following steps: 19 | # 20 | # 1) First, compile all shaders specified in the input list into air object files 21 | # 2) Second, take all air file and bundle them using metal-ar into a single package 22 | # 3) Finally, generate a single uber library containing all compiled shaders 23 | function(CompileMetalShaders SHADER_FILES) 24 | add_custom_target(CompileMetalShaders 25 | DEPENDS "${PROJECT_BINARY_DIR}/generated/default.metallib" 26 | ) 27 | 28 | set(COMPILED_METAL_AIR_FILES "") 29 | foreach (file ${SHADER_FILES}) 30 | get_filename_component(name ${file} NAME_WLE) 31 | get_filename_component(directory ${file} DIRECTORY) 32 | get_filename_component(dirname ${directory} NAME_WLE) 33 | list(APPEND COMPILED_METAL_AIR_FILES ${PROJECT_BINARY_DIR}/generated/${dirname}/${name}.air) 34 | add_custom_command( 35 | OUTPUT "${PROJECT_BINARY_DIR}/generated/${dirname}/${name}.air" 36 | COMMAND ${XCODE_RUN} -sdk ${XCODE_RUN_SDK} metal -c ${file} ${XCODE_METAL_EXTRA_ARGS} -o ${PROJECT_BINARY_DIR}/generated/${dirname}/${name}.air 37 | COMMENT "Compiling ${name} to METAL-AIR" 38 | DEPENDS ${file} ${directory}) 39 | endforeach () 40 | 41 | add_custom_command( 42 | OUTPUT "${PROJECT_BINARY_DIR}/generated/default.metallib" 43 | COMMENT "Compiling ${COMPILED_METAL_AIR_FILES}" 44 | COMMAND ${XCODE_RUN} -sdk ${XCODE_RUN_SDK} metal-ar r ${PROJECT_BINARY_DIR}/generated/default.metal-ar ${COMPILED_METAL_AIR_FILES} 45 | COMMAND ${XCODE_RUN} -sdk ${XCODE_RUN_SDK} metallib -o ${PROJECT_BINARY_DIR}/generated/default.metallib ${PROJECT_BINARY_DIR}/generated/default.metal-ar 46 | DEPENDS ${COMPILED_METAL_AIR_FILES} 47 | ) 48 | endfunction() -------------------------------------------------------------------------------- /source/textures/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(EXAMPLE textures) 3 | 4 | if (NOT CMAKE_GENERATOR MATCHES "Xcode") 5 | set(RESOURCE_FILES "${PROJECT_BINARY_DIR}/generated/default.metallib") 6 | else () 7 | set(RESOURCE_FILES "") 8 | endif () 9 | 10 | if (${CMAKE_SYSTEM_NAME} MATCHES "iOS") 11 | list(APPEND RESOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/iOS/LaunchScreen.storyboard) 12 | elseif (${CMAKE_SYSTEM_NAME} MATCHES "tvOS") 13 | list(APPEND RESOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/tvOS/LaunchScreen.storyboard) 14 | endif () 15 | 16 | list(APPEND RESOURCE_FILES ${CMAKE_SOURCE_DIR}/assets/textures/001_basecolor.ktx) 17 | list(APPEND RESOURCE_FILES ${CMAKE_SOURCE_DIR}/assets/textures/002_basecolor.ktx) 18 | list(APPEND RESOURCE_FILES ${CMAKE_SOURCE_DIR}/assets/textures/003_basecolor.ktx) 19 | list(APPEND RESOURCE_FILES ${CMAKE_SOURCE_DIR}/assets/textures/004_basecolor.ktx) 20 | list(APPEND RESOURCE_FILES ${CMAKE_SOURCE_DIR}/assets/textures/005_basecolor.ktx) 21 | 22 | 23 | add_executable(${EXAMPLE} 24 | ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp 25 | ${RESOURCE_FILES}) 26 | 27 | 28 | if (CMAKE_GENERATOR MATCHES "Xcode") 29 | target_sources(${EXAMPLE} PRIVATE ${CMAKE_SOURCE_DIR}/shaders/textures/shader.metal) 30 | set_source_files_properties( 31 | ${CMAKE_SOURCE_DIR}/shaders/textures/shader.metal PROPERTIES 32 | LANGUAGE METAL 33 | COMPILE_OPTIONS "-I${CMAKE_SOURCE_DIR}/shaders/textures" 34 | ) 35 | 36 | else () 37 | # Ensure shader library is available 38 | add_dependencies(${EXAMPLE} CompileMetalShaders) 39 | endif () 40 | 41 | 42 | target_link_libraries(${EXAMPLE} base KTX::ktx) 43 | target_compile_definitions(${EXAMPLE} PRIVATE USE_KTX_LIBRARY) 44 | 45 | set_target_properties(${EXAMPLE} 46 | PROPERTIES 47 | MACOSX_BUNDLE TRUE 48 | MACOSX_BUNDLE_BUNDLE_NAME ${EXAMPLE} 49 | MACOSX_BUNDLE_GUI_IDENTIFIER com.github.MattGuerrette.${EXAMPLE} 50 | XCODE_GENERATE_SCHEME YES 51 | XCODE_ATTRIBUTE_TVOS_DEPLOYMENT_TARGET 17.0 52 | XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET 17.0 53 | XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET 14.0 54 | XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC NO 55 | XCODE_ATTRIBUTE_PRODUCT_NAME ${EXAMPLE} 56 | XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.github.MattGuerrette.${EXAMPLE} 57 | MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in 58 | RESOURCE "${RESOURCE_FILES}") 59 | -------------------------------------------------------------------------------- /source/base/Mouse.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) 2024. Matt Guerrette 3 | // SPDX-License-Identifier: MIT 4 | //////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include "Mouse.hpp" 7 | 8 | Mouse::Mouse(SDL_Window* window) 9 | : m_window(window) 10 | { 11 | // Enable relative mouse location tracking for deltas 12 | // SDL_SetRelativeMouseMode(SDL_TRUE); 13 | } 14 | 15 | bool Mouse::didLeftClick() const 16 | { 17 | return !m_currentState[SDL_BUTTON_LEFT].isPressed && m_previousState[SDL_BUTTON_LEFT].isPressed; 18 | } 19 | 20 | bool Mouse::didLeftDoubleClick() const 21 | { 22 | return ( 23 | m_currentState[SDL_BUTTON_LEFT].isPressed && m_currentState[SDL_BUTTON_LEFT].isDoubleClick); 24 | } 25 | 26 | bool Mouse::isLeftPressed() const 27 | { 28 | return m_currentState[SDL_BUTTON_LEFT].isPressed; 29 | } 30 | 31 | bool Mouse::didRightClick() const 32 | { 33 | return !m_currentState[SDL_BUTTON_RIGHT].isPressed 34 | && m_previousState[SDL_BUTTON_RIGHT].isPressed; 35 | } 36 | 37 | bool Mouse::didRightDoubleClick() const 38 | { 39 | return (m_currentState[SDL_BUTTON_RIGHT].isPressed 40 | && m_currentState[SDL_BUTTON_RIGHT].isDoubleClick); 41 | } 42 | 43 | int32_t Mouse::x() const 44 | { 45 | return m_locationX; 46 | } 47 | 48 | int32_t Mouse::y() const 49 | { 50 | return m_locationY; 51 | } 52 | 53 | int32_t Mouse::relativeX() const 54 | { 55 | return m_relativeX; 56 | } 57 | 58 | int32_t Mouse::relativeY() const 59 | { 60 | return m_relativeY; 61 | } 62 | 63 | float Mouse::wheelX() const 64 | { 65 | return m_preciseWheelX; 66 | } 67 | 68 | float Mouse::wheelY() const 69 | { 70 | return m_preciseWheelY; 71 | } 72 | 73 | void Mouse::warp() 74 | { 75 | int32_t w, h; 76 | SDL_GetWindowSize(m_window, &w, &h); 77 | SDL_WarpMouseInWindow(m_window, w / 2, h / 2); 78 | } 79 | 80 | void Mouse::registerMouseMotion(SDL_MouseMotionEvent* event) 81 | { 82 | m_locationX = event->x; 83 | m_locationY = event->y; 84 | m_relativeX = event->xrel; 85 | m_relativeY = event->yrel; 86 | } 87 | 88 | void Mouse::registerMouseWheel(SDL_MouseWheelEvent* event) 89 | { 90 | m_preciseWheelX = event->x; 91 | m_preciseWheelY = event->y; 92 | } 93 | 94 | void Mouse::registerMouseButton(SDL_MouseButtonEvent* event) 95 | { 96 | m_currentState[event->button] = { .isDoubleClick = event->clicks > 1, 97 | .isPressed = event->down, 98 | .x = event->x, 99 | .y = event->y }; 100 | } 101 | 102 | void Mouse::update() 103 | { 104 | m_previousState = m_currentState; 105 | // CurrentState = {}; 106 | m_relativeX = 0; 107 | m_relativeY = 0; 108 | } 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![example workflow](https://github.com/MattGuerrette/Metal/actions/workflows/cmake-single-platform.yml/badge.svg) 2 | 3 | # Metal C++ examples 4 | 5 | > **Metal 4 support is required** 6 | 7 | A collection of open source C++ examples for [Metal](https://developer.apple.com/metal) 8 | 9 | ### metal-cpp 10 | 11 | These examples use a modified version of Metal-CPP originally published by Apple. This modified version is maintained 12 | here: 13 | [metal-cpp](https://github.com/MattGuerrette/metalcpp) and includes some additional API bindings not yet supported by 14 | Apple, however 15 | it is actively updated with the latest fixes and additions from Apple as well. 16 | 17 | ## Requirements 18 | 19 | These samples are developed using **C++20** and target the following platforms and os versions: 20 | 21 | | Platform | OS Version | | 22 | |----------|------------|-- | 23 | | macOS | 26.0+ | | 24 | | iOS | 26.0+ | | 25 | | tvOS | 26.0+ |Needs vcpkg support [#12](https://github.com/MattGuerrette/Metal/issues/12) | 26 | 27 | ## Building 28 | 29 | These example applications use [Vcpkg](https://vcpkg.io/en/), [CMake](https://www.cmake.org) and are actively developed using both [CLion](https://www.jetbrains.com/clion/) and [Xcode](https://developer.apple.com/xcode/) 30 | 31 | For those wanting to build the projects from a terminal invoking CMake directly or to 32 | generate the Xcode project files here are some example commands: 33 | 34 | ### Ninja 35 | 36 | ``` 37 | git clone https://github.com/MattGuerrette/Metal.git 38 | cd Metal 39 | mkdir cmake-build-debug 40 | cd cmake-build-debug 41 | cmake .. -GNinja -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake 42 | cmake --build . 43 | ``` 44 | 45 | ### Xcode 46 | 47 | ``` 48 | git clone https://github.com/MattGuerrette/Metal.git 49 | cd Metal 50 | mkdir cmake-build-xcode 51 | cd cmake-build-xcode 52 | cmake .. -GXcode -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake -DCMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM= 53 | cmake --build . 54 | ``` 55 | 56 | For codesigning purposes, you will want to specify the development team ID as shown above to avoid needing 57 | to manually set the development team for each target project. 58 | 59 | ## Table of Contents 60 | 61 | + [Examples](#Examples) 62 | 63 | ## Examples 64 | 65 | ### Basics 66 | 67 | #### [01 - HelloWorld](source/helloworld/) 68 | 69 | Basic example for rendering a colored triangle using Metal 70 | 71 | #### [02 - Instancing](source/instancing/) 72 | 73 | Rendering of multiple cube geometry instances 74 | 75 | #### [03 - Textures](source/textures/) 76 | 77 | Showcases loading of [KTX](https://www.khronos.org/ktx/) compressed textures in ASTC format into 78 | a [MTLHeap](https://developer.apple.com/documentation/metal/mtlheap) and 79 | using [Argument Buffers](https://developer.apple.com/documentation/metal/buffers/improving_cpu_performance_by_using_argument_buffers) 80 | to bindlessly render multiple textures from GPU memory. 81 | -------------------------------------------------------------------------------- /source/base/GameTimer.hpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) 2024. Matt Guerrette 3 | // SPDX-License-Identifier: MIT 4 | //////////////////////////////////////////////////////////////////////////////// 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include 11 | 12 | class GameTimer 13 | { 14 | static constexpr uint64_t s_ticksPerSecond = 10000000; 15 | 16 | public: 17 | GameTimer(); 18 | 19 | ~GameTimer() = default; 20 | 21 | [[nodiscard]] uint64_t elapsedTicks() const noexcept; 22 | 23 | [[nodiscard]] double elapsedSeconds() const noexcept; 24 | 25 | [[nodiscard]] uint64_t totalTicks() const noexcept; 26 | 27 | [[nodiscard]] double totalSeconds() const noexcept; 28 | 29 | [[nodiscard]] uint32_t frameCount() const noexcept; 30 | 31 | [[nodiscard]] uint32_t framesPerSecond() const noexcept; 32 | 33 | void setFixedTimeStep(bool isFixedTimeStep) noexcept; 34 | 35 | void setTargetElapsedTicks(uint64_t targetElapsed) noexcept; 36 | 37 | void setTargetElapsedSeconds(double targetElapsed) noexcept; 38 | 39 | void resetElapsedTime(); 40 | 41 | template 42 | void tick(const TUpdate& update) 43 | { 44 | const uint64_t currentTime = SDL_GetPerformanceCounter(); 45 | 46 | uint64_t delta = currentTime - m_qpcLastTime; 47 | m_qpcLastTime = currentTime; 48 | m_qpcSecondCounter += delta; 49 | 50 | delta = std::clamp(delta, static_cast(0), m_qpcMaxDelta); 51 | delta *= s_ticksPerSecond; 52 | delta /= m_qpcFrequency; 53 | 54 | const uint32_t lastFrameCount = m_frameCount; 55 | if (m_isFixedTimeStep) 56 | { 57 | if (static_cast(std::abs(static_cast(delta - m_targetElapsedTicks))) 58 | < s_ticksPerSecond / 4000) 59 | { 60 | delta = m_targetElapsedTicks; 61 | } 62 | 63 | m_leftOverTicks += delta; 64 | while (m_leftOverTicks >= m_targetElapsedTicks) 65 | { 66 | m_elapsedTicks = m_targetElapsedTicks; 67 | m_totalTicks += m_targetElapsedTicks; 68 | m_leftOverTicks -= m_targetElapsedTicks; 69 | m_frameCount++; 70 | 71 | update(); 72 | } 73 | } 74 | else 75 | { 76 | m_elapsedTicks = delta; 77 | m_totalTicks += delta; 78 | m_leftOverTicks = 0; 79 | m_frameCount++; 80 | 81 | update(); 82 | } 83 | 84 | if (m_frameCount != lastFrameCount) 85 | { 86 | m_framesThisSecond++; 87 | } 88 | 89 | if (m_qpcSecondCounter >= m_qpcFrequency) 90 | { 91 | m_framesPerSecond = m_framesThisSecond; 92 | m_framesThisSecond = 0; 93 | m_qpcSecondCounter %= m_qpcFrequency; 94 | } 95 | } 96 | 97 | static constexpr double ticksToSeconds(const uint64_t ticks) noexcept 98 | { 99 | return static_cast(ticks) / s_ticksPerSecond; 100 | } 101 | 102 | static constexpr uint64_t secondsToTicks(const double seconds) noexcept 103 | { 104 | return static_cast(seconds * s_ticksPerSecond); 105 | } 106 | 107 | private: 108 | uint64_t m_qpcFrequency; 109 | uint64_t m_qpcLastTime; 110 | uint64_t m_qpcMaxDelta; 111 | uint64_t m_qpcSecondCounter; 112 | uint64_t m_elapsedTicks; 113 | uint64_t m_totalTicks; 114 | uint64_t m_leftOverTicks; 115 | uint32_t m_frameCount; 116 | uint32_t m_framesPerSecond; 117 | uint32_t m_framesThisSecond; 118 | bool m_isFixedTimeStep; 119 | uint64_t m_targetElapsedTicks; 120 | }; 121 | -------------------------------------------------------------------------------- /source/base/Mouse.hpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) 2024. Matt Guerrette 3 | // SPDX-License-Identifier: MIT 4 | //////////////////////////////////////////////////////////////////////////////// 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include 11 | 12 | class Mouse final 13 | { 14 | struct ButtonState 15 | { 16 | bool isDoubleClick; 17 | bool isPressed; 18 | float x; 19 | float y; 20 | }; 21 | static constexpr int32_t s_mouseButtons = 3; 22 | using MouseButtonState = std::array; 23 | 24 | public: 25 | /// @brief Constructor 26 | /// @param [in] window The window used for warping mouse cursor. 27 | explicit Mouse(SDL_Window* window); 28 | 29 | /// @brief Checks if a left mouse button click has occurred. 30 | /// @return True if left button clicked, false otherwise. 31 | [[nodiscard]] bool didLeftClick() const; 32 | 33 | /// @brief Checks if a left mouse button double-click as occurred. 34 | /// @return True if left button double-clicked, false otherwise. 35 | [[nodiscard]] bool didLeftDoubleClick() const; 36 | 37 | /// @brief Checks if a right mouse button click has occurred. 38 | /// @return True if right button clicked, false otherwise. 39 | [[nodiscard]] bool didRightClick() const; 40 | 41 | /// @brief Checks if left mouse button is currently pressed 42 | /// @return True if pressed, false otherwise 43 | [[nodiscard]] bool isLeftPressed() const; 44 | 45 | /// @brief Checks if a right mouse button double-click has occurred. 46 | /// @return True if right button double-clicked, false otherwise. 47 | [[nodiscard]] bool didRightDoubleClick() const; 48 | 49 | /// @brief Gets the mouse X coordinate location. 50 | /// @return X coordinate. 51 | [[nodiscard]] int32_t x() const; 52 | 53 | /// @brief Gets the mouse Y coordinate location. 54 | /// @return Y coordinate. 55 | [[nodiscard]] int32_t y() const; 56 | 57 | /// @brief Gets the mouse relative movement in X coordinate. 58 | /// @return Relative mouse movement in X coordinate space. 59 | [[nodiscard]] int32_t relativeX() const; 60 | 61 | /// @brief Gets the mouse relative movement in Y coordinate. 62 | /// @return Relative mouse movement in Y coordinate space. 63 | [[nodiscard]] int32_t relativeY() const; 64 | 65 | /// @brief Gets the precise scroll-wheel movement in X axis. 66 | /// @return Precise scroll-wheel movement in X axis. 67 | [[nodiscard]] float wheelX() const; 68 | 69 | /// @brief Gets the precise scroll-wheel movement in Y axis. 70 | /// @return Precise scroll-wheel movement in Y axis. 71 | [[nodiscard]] float wheelY() const; 72 | 73 | /// @brief Constrains (warps) the mouse cursor to center of window. 74 | void warp(); 75 | 76 | /// @brief Registers mouse motion event. 77 | /// @param [in] event The mouse motion event. 78 | void registerMouseMotion(SDL_MouseMotionEvent* event); 79 | 80 | /// @brief Register mouse wheel event. 81 | /// @param [in] event The mouse wheel event. 82 | void registerMouseWheel(SDL_MouseWheelEvent* event); 83 | 84 | /// @brief Registers mouse button event. 85 | /// @param [in] event The mouse button event. 86 | void registerMouseButton(SDL_MouseButtonEvent* event); 87 | 88 | /// @brief Updates the state cache for next frame. 89 | void update(); 90 | 91 | private: 92 | SDL_Window* m_window; ///< Window used to warp cursor to. 93 | float m_locationX {}; ///< X mouse location. 94 | float m_locationY {}; ///< Y mouse location. 95 | int32_t m_relativeX {}; ///< X mouse location relative to last frame. 96 | int32_t m_relativeY {}; ///< Y mouse location relative to last frame. 97 | float m_preciseWheelX {}; ///< Scroll-wheel delta X (precise). 98 | float m_preciseWheelY {}; ///< Scroll-wheel delta Y (precise). 99 | MouseButtonState m_currentState {}; ///< Current frame mouse button state. 100 | MouseButtonState m_previousState {}; ///< Previous frame mouse button state. 101 | }; -------------------------------------------------------------------------------- /source/base/Example.hpp: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////////////////////////////////////////////////////// 3 | // Copyright (c) 2024. Matt Guerrette 4 | // SPDX-License-Identifier: MIT 5 | //////////////////////////////////////////////////////////////////////////////// 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #include 12 | 13 | #ifdef SDL_PLATFORM_MACOS 14 | #include 15 | #endif 16 | 17 | #include 18 | #include 19 | 20 | #include "GameTimer.hpp" 21 | #include "Gamepad.hpp" 22 | #include "Keyboard.hpp" 23 | #include "Mouse.hpp" 24 | 25 | namespace SDL 26 | { 27 | struct WindowDeleter 28 | { 29 | void operator()(SDL_Window* window) const 30 | { 31 | if (window != nullptr) 32 | { 33 | SDL_DestroyWindow(window); 34 | } 35 | } 36 | }; 37 | using WindowPtr = std::unique_ptr; 38 | 39 | struct MetalViewDeleter 40 | { 41 | void operator()(void* view) const 42 | { 43 | if (view != nullptr) 44 | { 45 | SDL_Metal_DestroyView(view); 46 | } 47 | } 48 | }; 49 | using MetalView = std::unique_ptr; 50 | } 51 | 52 | class Example : public CA::MetalDisplayLinkDelegate 53 | { 54 | public: 55 | explicit Example(const char* title, int32_t width, int32_t height); 56 | 57 | ~Example() override; 58 | 59 | int run([[maybe_unused]] int argc, [[maybe_unused]] char** argv); 60 | 61 | void quit(); 62 | 63 | void metalDisplayLinkNeedsUpdate( 64 | CA::MetalDisplayLink* displayLink, CA::MetalDisplayLinkUpdate* update) override; 65 | 66 | protected: 67 | static constexpr int s_bufferCount = 3; 68 | static constexpr int s_multisampleCount = 4; 69 | static constexpr MTL::PixelFormat s_defaultPixelFormat = MTL::PixelFormatBGRA8Unorm_sRGB; 70 | 71 | virtual bool onLoad() = 0; 72 | 73 | virtual void onUpdate(const GameTimer& timer) = 0; 74 | 75 | virtual void onSetupUi(const GameTimer& timer); 76 | 77 | virtual void onResize(uint32_t width, uint32_t height) = 0; 78 | 79 | virtual void onRender( 80 | CA::MetalDrawable* drawable, MTL4::CommandBuffer* commandBuffer, const GameTimer& timer) 81 | = 0; 82 | 83 | [[nodiscard]] SDL_Window* window() const; 84 | 85 | [[nodiscard]] int32_t windowWidth() const; 86 | 87 | [[nodiscard]] int32_t windowHeight() const; 88 | 89 | [[nodiscard]] const Keyboard& keyboard() const; 90 | 91 | [[nodiscard]] const Mouse& mouse() const; 92 | 93 | [[nodiscard]] uint32_t frameIndex() const; 94 | 95 | [[nodiscard]] MTL::Device* device() const; 96 | 97 | [[nodiscard]] MTL4::CommandQueue* commandQueue() const; 98 | 99 | [[nodiscard]] MTL::DepthStencilState* depthStencilState() const; 100 | 101 | [[nodiscard]] MTL::Library* shaderLibrary() const; 102 | 103 | [[nodiscard]] MTL4::CommandBuffer* commandBuffer() const; 104 | 105 | [[nodiscard]] MTL4::CommandAllocator* commandAllocator() const; 106 | 107 | [[nodiscard]] MTL::Texture* msaaTexture() const; 108 | 109 | [[nodiscard]] MTL::Texture* depthStencilTexture() const; 110 | 111 | [[nodiscard]] CA::MetalLayer* metalLayer() const; 112 | 113 | [[nodisacrd]] MTL4::RenderPassDescriptor* defaultRenderPassDescriptor( 114 | CA::MetalDrawable* drawable) const; 115 | 116 | #ifdef SDL_PLATFORM_MACOS 117 | [[nodiscard]] virtual NS::Menu* createMenuBar(); 118 | #endif 119 | 120 | private: 121 | SDL::WindowPtr m_window; 122 | SDL::MetalView m_view; 123 | uint32_t m_defaultWidth; 124 | uint32_t m_defaultHeight; 125 | GameTimer m_timer; 126 | bool m_running; 127 | 128 | #pragma region Input Handling 129 | std::unique_ptr m_keyboard; 130 | std::unique_ptr m_mouse; 131 | std::unique_ptr m_gamepad; 132 | #pragma endregion 133 | 134 | #pragma region Metal Resources 135 | NS::SharedPtr m_displayLink; 136 | NS::SharedPtr m_device; 137 | NS::SharedPtr m_commandQueue; 138 | NS::SharedPtr m_commandBuffer; 139 | NS::SharedPtr m_commandAllocator[s_bufferCount]; 140 | NS::SharedPtr m_msaaTexture; 141 | NS::SharedPtr m_depthStencilTexture; 142 | NS::SharedPtr m_depthStencilState; 143 | NS::SharedPtr m_shaderLibrary; 144 | #pragma endregion 145 | 146 | #pragma region Sync Primitives 147 | uint32_t m_currentFrameIndex = 0; 148 | uint32_t m_frameNumber = 0; 149 | NS::SharedPtr m_sharedEvent; 150 | #pragma endregion 151 | 152 | void createFrameResources(int32_t width, int32_t height); 153 | }; 154 | -------------------------------------------------------------------------------- /source/base/SimpleMath.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------------- 2 | // SimpleMath.cpp -- Simplified C++ Math wrapper for DirectXMath 3 | // 4 | // Copyright (c) Microsoft Corporation. 5 | // Licensed under the MIT License. 6 | // 7 | // http://go.microsoft.com/fwlink/?LinkId=248929 8 | // http://go.microsoft.com/fwlink/?LinkID=615561 9 | //------------------------------------------------------------------------------------- 10 | 11 | #include "SimpleMath.h" 12 | 13 | /**************************************************************************** 14 | * 15 | * Constants 16 | * 17 | ****************************************************************************/ 18 | 19 | namespace DirectX 20 | { 21 | namespace SimpleMath 22 | { 23 | const Vector2 Vector2::Zero = { 0.f, 0.f }; 24 | 25 | const Vector2 Vector2::One = { 1.f, 1.f }; 26 | 27 | const Vector2 Vector2::UnitX = { 1.f, 0.f }; 28 | 29 | const Vector2 Vector2::UnitY = { 0.f, 1.f }; 30 | 31 | const Vector3 Vector3::Zero = { 0.f, 0.f, 0.f }; 32 | 33 | const Vector3 Vector3::One = { 1.f, 1.f, 1.f }; 34 | 35 | const Vector3 Vector3::UnitX = { 1.f, 0.f, 0.f }; 36 | 37 | const Vector3 Vector3::UnitY = { 0.f, 1.f, 0.f }; 38 | 39 | const Vector3 Vector3::UnitZ = { 0.f, 0.f, 1.f }; 40 | 41 | const Vector3 Vector3::Up = { 0.f, 1.f, 0.f }; 42 | 43 | const Vector3 Vector3::Down = { 0.f, -1.f, 0.f }; 44 | 45 | const Vector3 Vector3::Right = { 1.f, 0.f, 0.f }; 46 | 47 | const Vector3 Vector3::Left = { -1.f, 0.f, 0.f }; 48 | 49 | const Vector3 Vector3::Forward = { 0.f, 0.f, -1.f }; 50 | 51 | const Vector3 Vector3::Backward = { 0.f, 0.f, 1.f }; 52 | 53 | const Vector4 Vector4::Zero = { 0.f, 0.f, 0.f, 0.f }; 54 | 55 | const Vector4 Vector4::One = { 1.f, 1.f, 1.f, 1.f }; 56 | 57 | const Vector4 Vector4::UnitX = { 1.f, 0.f, 0.f, 0.f }; 58 | 59 | const Vector4 Vector4::UnitY = { 0.f, 1.f, 0.f, 0.f }; 60 | 61 | const Vector4 Vector4::UnitZ = { 0.f, 0.f, 1.f, 0.f }; 62 | 63 | const Vector4 Vector4::UnitW = { 0.f, 0.f, 0.f, 1.f }; 64 | 65 | const Matrix Matrix::Identity 66 | = { 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f }; 67 | 68 | const Quaternion Quaternion::Identity = { 0.f, 0.f, 0.f, 1.f }; 69 | } 70 | } 71 | 72 | using namespace DirectX; 73 | using namespace DirectX::SimpleMath; 74 | 75 | /**************************************************************************** 76 | * 77 | * Quaternion 78 | * 79 | ****************************************************************************/ 80 | 81 | void Quaternion::RotateTowards( 82 | const Quaternion& target, float maxAngle, Quaternion& result) const noexcept 83 | { 84 | const XMVECTOR T = XMLoadFloat4(this); 85 | 86 | // We can use the conjugate here instead of inverse assuming q1 & q2 are normalized. 87 | const XMVECTOR R = XMQuaternionMultiply(XMQuaternionConjugate(T), target); 88 | 89 | const float rs = XMVectorGetW(R); 90 | const XMVECTOR L = XMVector3Length(R); 91 | const float angle = 2.f * atan2f(XMVectorGetX(L), rs); 92 | if (angle > maxAngle) 93 | { 94 | const XMVECTOR delta = XMQuaternionRotationAxis(R, maxAngle); 95 | const XMVECTOR Q = XMQuaternionMultiply(delta, T); 96 | XMStoreFloat4(&result, Q); 97 | } 98 | else 99 | { 100 | // Don't overshoot. 101 | result = target; 102 | } 103 | } 104 | 105 | void Quaternion::FromToRotation( 106 | const Vector3& fromDir, const Vector3& toDir, Quaternion& result) noexcept 107 | { 108 | // Melax, "The Shortest Arc Quaternion", Game Programming Gems, Charles River Media (2000). 109 | 110 | const XMVECTOR F = XMVector3Normalize(fromDir); 111 | const XMVECTOR T = XMVector3Normalize(toDir); 112 | 113 | const float dot = XMVectorGetX(XMVector3Dot(F, T)); 114 | if (dot >= 1.f) 115 | { 116 | result = Identity; 117 | } 118 | else if (dot <= -1.f) 119 | { 120 | XMVECTOR axis = XMVector3Cross(F, Vector3::Right); 121 | if (XMVector3NearEqual(XMVector3LengthSq(axis), g_XMZero, g_XMEpsilon)) 122 | { 123 | axis = XMVector3Cross(F, Vector3::Up); 124 | } 125 | 126 | const XMVECTOR Q = XMQuaternionRotationAxis(axis, XM_PI); 127 | XMStoreFloat4(&result, Q); 128 | } 129 | else 130 | { 131 | const XMVECTOR C = XMVector3Cross(F, T); 132 | XMStoreFloat4(&result, C); 133 | 134 | const float s = sqrtf((1.f + dot) * 2.f); 135 | result.x /= s; 136 | result.y /= s; 137 | result.z /= s; 138 | result.w = s * 0.5f; 139 | } 140 | } 141 | 142 | void Quaternion::LookRotation( 143 | const Vector3& forward, const Vector3& up, Quaternion& result) noexcept 144 | { 145 | Quaternion q1; 146 | FromToRotation(Vector3::Forward, forward, q1); 147 | 148 | const XMVECTOR C = XMVector3Cross(forward, up); 149 | if (XMVector3NearEqual(XMVector3LengthSq(C), g_XMZero, g_XMEpsilon)) 150 | { 151 | // forward and up are co-linear 152 | result = q1; 153 | return; 154 | } 155 | 156 | const XMVECTOR U = XMQuaternionMultiply(q1, Vector3::Up); 157 | 158 | Quaternion q2; 159 | FromToRotation(U, up, q2); 160 | 161 | XMStoreFloat4(&result, XMQuaternionMultiply(q2, q1)); 162 | } 163 | 164 | /**************************************************************************** 165 | * 166 | * Viewport 167 | * 168 | ****************************************************************************/ 169 | 170 | #if defined(__d3d11_h__) || defined(__d3d11_x_h__) 171 | static_assert(sizeof(DirectX::SimpleMath::Viewport) == sizeof(D3D11_VIEWPORT), "Size mismatch"); 172 | static_assert(offsetof(DirectX::SimpleMath::Viewport, x) == offsetof(D3D11_VIEWPORT, TopLeftX), 173 | "Layout mismatch"); 174 | static_assert(offsetof(DirectX::SimpleMath::Viewport, y) == offsetof(D3D11_VIEWPORT, TopLeftY), 175 | "Layout mismatch"); 176 | static_assert(offsetof(DirectX::SimpleMath::Viewport, width) == offsetof(D3D11_VIEWPORT, Width), 177 | "Layout mismatch"); 178 | static_assert(offsetof(DirectX::SimpleMath::Viewport, height) == offsetof(D3D11_VIEWPORT, Height), 179 | "Layout mismatch"); 180 | static_assert( 181 | offsetof(DirectX::SimpleMath::Viewport, minDepth) == offsetof(D3D11_VIEWPORT, MinDepth), 182 | "Layout mismatch"); 183 | static_assert( 184 | offsetof(DirectX::SimpleMath::Viewport, maxDepth) == offsetof(D3D11_VIEWPORT, MaxDepth), 185 | "Layout mismatch"); 186 | #endif 187 | 188 | #if defined(__d3d12_h__) || defined(__d3d12_x_h__) || defined(__XBOX_D3D12_X__) 189 | static_assert(sizeof(DirectX::SimpleMath::Viewport) == sizeof(D3D12_VIEWPORT), "Size mismatch"); 190 | static_assert(offsetof(DirectX::SimpleMath::Viewport, x) == offsetof(D3D12_VIEWPORT, TopLeftX), 191 | "Layout mismatch"); 192 | static_assert(offsetof(DirectX::SimpleMath::Viewport, y) == offsetof(D3D12_VIEWPORT, TopLeftY), 193 | "Layout mismatch"); 194 | static_assert(offsetof(DirectX::SimpleMath::Viewport, width) == offsetof(D3D12_VIEWPORT, Width), 195 | "Layout mismatch"); 196 | static_assert(offsetof(DirectX::SimpleMath::Viewport, height) == offsetof(D3D12_VIEWPORT, Height), 197 | "Layout mismatch"); 198 | static_assert( 199 | offsetof(DirectX::SimpleMath::Viewport, minDepth) == offsetof(D3D12_VIEWPORT, MinDepth), 200 | "Layout mismatch"); 201 | static_assert( 202 | offsetof(DirectX::SimpleMath::Viewport, maxDepth) == offsetof(D3D12_VIEWPORT, MaxDepth), 203 | "Layout mismatch"); 204 | #endif 205 | 206 | #if defined(__dxgi1_2_h__) || defined(__d3d11_x_h__) || defined(__d3d12_x_h__) \ 207 | || defined(__XBOX_D3D12_X__) 208 | RECT Viewport::ComputeDisplayArea(DXGI_SCALING scaling, 209 | UINT backBufferWidth, 210 | UINT backBufferHeight, 211 | int outputWidth, 212 | int outputHeight) noexcept 213 | { 214 | RECT rct = {}; 215 | 216 | switch (int(scaling)) 217 | { 218 | case DXGI_SCALING_STRETCH: 219 | // Output fills the entire window area 220 | rct.top = 0; 221 | rct.left = 0; 222 | rct.right = outputWidth; 223 | rct.bottom = outputHeight; 224 | break; 225 | 226 | case 2 /*DXGI_SCALING_ASPECT_RATIO_STRETCH*/: 227 | // Output fills the window area but respects the original aspect ratio, using pillar boxing 228 | // or letter boxing as required Note: This scaling option is not supported for legacy Win32 229 | // windows swap chains 230 | { 231 | assert(backBufferHeight > 0); 232 | const float aspectRatio = float(backBufferWidth) / float(backBufferHeight); 233 | 234 | // Horizontal fill 235 | float scaledWidth = float(outputWidth); 236 | float scaledHeight = float(outputWidth) / aspectRatio; 237 | if (scaledHeight >= float(outputHeight)) 238 | { 239 | // Do vertical fill 240 | scaledWidth = float(outputHeight) * aspectRatio; 241 | scaledHeight = float(outputHeight); 242 | } 243 | 244 | const float offsetX = (float(outputWidth) - scaledWidth) * 0.5f; 245 | const float offsetY = (float(outputHeight) - scaledHeight) * 0.5f; 246 | 247 | rct.left = static_cast(offsetX); 248 | rct.top = static_cast(offsetY); 249 | rct.right = static_cast(offsetX + scaledWidth); 250 | rct.bottom = static_cast(offsetY + scaledHeight); 251 | 252 | // Clip to display window 253 | rct.left = std::max(0, rct.left); 254 | rct.top = std::max(0, rct.top); 255 | rct.right = std::min(outputWidth, rct.right); 256 | rct.bottom = std::min(outputHeight, rct.bottom); 257 | } 258 | break; 259 | 260 | case DXGI_SCALING_NONE: 261 | default: 262 | // Output is displayed in the upper left corner of the window area 263 | rct.top = 0; 264 | rct.left = 0; 265 | rct.right = std::min(static_cast(backBufferWidth), outputWidth); 266 | rct.bottom = std::min(static_cast(backBufferHeight), outputHeight); 267 | break; 268 | } 269 | 270 | return rct; 271 | } 272 | #endif 273 | 274 | RECT Viewport::ComputeTitleSafeArea(UINT backBufferWidth, UINT backBufferHeight) noexcept 275 | { 276 | const float safew = (float(backBufferWidth) + 19.f) / 20.f; 277 | const float safeh = (float(backBufferHeight) + 19.f) / 20.f; 278 | 279 | RECT rct; 280 | rct.left = static_cast(safew); 281 | rct.top = static_cast(safeh); 282 | rct.right = static_cast(float(backBufferWidth) - safew + 0.5f); 283 | rct.bottom = static_cast(float(backBufferHeight) - safeh + 0.5f); 284 | 285 | return rct; 286 | } 287 | -------------------------------------------------------------------------------- /source/helloworld/main.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) 2024. Matt Guerrette 3 | // SPDX-License-Identifier: MIT 4 | //////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | #include "Camera.hpp" 13 | #include "Example.hpp" 14 | 15 | #include 16 | 17 | using namespace DirectX; 18 | 19 | XM_ALIGNED_STRUCT(16) Vertex 20 | { 21 | Vector4 position; 22 | Vector4 color; 23 | }; 24 | 25 | XM_ALIGNED_STRUCT(16) Uniforms 26 | { 27 | [[maybe_unused]] Matrix modelViewProjection; 28 | }; 29 | constexpr size_t g_alignedUniformSize = sizeof(Uniforms) + 0xFF & -0x100; 30 | 31 | class HelloWorld final : public Example 32 | { 33 | public: 34 | HelloWorld(); 35 | 36 | ~HelloWorld() override; 37 | 38 | bool onLoad() override; 39 | 40 | void onUpdate(const GameTimer& timer) override; 41 | 42 | void onRender(CA::MetalDrawable* drawable, 43 | MTL4::CommandBuffer* commandBuffer, 44 | const GameTimer& timer) override; 45 | 46 | void onResize(uint32_t width, uint32_t height) override; 47 | 48 | private: 49 | void createArgumentTable(); 50 | 51 | void createResidencySet(); 52 | 53 | void createBuffers(); 54 | 55 | void createPipelineState(); 56 | 57 | void updateUniforms() const; 58 | 59 | NS::SharedPtr m_pipelineState; 60 | NS::SharedPtr m_vertexBuffer; 61 | NS::SharedPtr m_indexBuffer; 62 | NS::SharedPtr m_argumentTable; 63 | NS::SharedPtr m_residencySet; 64 | std::array, s_bufferCount> m_uniformBuffer; 65 | std::unique_ptr m_mainCamera; 66 | float m_rotationY = 0.0F; 67 | }; 68 | 69 | HelloWorld::HelloWorld() 70 | : Example("Hello, Metal", 800, 600) 71 | { 72 | } 73 | 74 | HelloWorld::~HelloWorld() = default; 75 | 76 | bool HelloWorld::onLoad() 77 | { 78 | const auto width = windowWidth(); 79 | const auto height = windowHeight(); 80 | const float aspect = static_cast(width) / static_cast(height); 81 | constexpr float fov = XMConvertToRadians(75.0F); 82 | constexpr float near = 0.01F; 83 | constexpr float far = 1000.0F; 84 | 85 | m_mainCamera = std::make_unique(XMFLOAT3 { 0.0F, 0.0F, 0.0F }, 86 | XMFLOAT3 { 0.0F, 0.0F, -1.0F }, XMFLOAT3 { 0.0F, 1.0F, 0.0F }, fov, aspect, near, far); 87 | 88 | createBuffers(); 89 | 90 | createArgumentTable(); 91 | 92 | createResidencySet(); 93 | 94 | createPipelineState(); 95 | 96 | return true; 97 | } 98 | 99 | void HelloWorld::onUpdate(const GameTimer& timer) 100 | { 101 | const auto elapsed = static_cast(timer.elapsedSeconds()); 102 | 103 | updateUniforms(); 104 | 105 | m_rotationY += elapsed; 106 | } 107 | 108 | void HelloWorld::onRender(CA::MetalDrawable* drawable, 109 | MTL4::CommandBuffer* commandBuffer, 110 | [[maybe_unused]] const GameTimer& timer) 111 | { 112 | 113 | NS::SharedPtr passDescriptor 114 | = NS::TransferPtr(defaultRenderPassDescriptor(drawable)); 115 | 116 | MTL4::RenderCommandEncoder* commandEncoder 117 | = commandBuffer->renderCommandEncoder(passDescriptor.get()); 118 | 119 | commandEncoder->pushDebugGroup(MTLSTR("Triangle Rendering")); 120 | 121 | const auto currentFrameIndex = frameIndex(); 122 | const auto uniformBufferOffset = g_alignedUniformSize * currentFrameIndex; 123 | 124 | commandEncoder->setRenderPipelineState(m_pipelineState.get()); 125 | commandEncoder->setDepthStencilState(depthStencilState()); 126 | commandEncoder->setFrontFacingWinding(MTL::WindingCounterClockwise); 127 | commandEncoder->setCullMode(MTL::CullModeNone); 128 | 129 | commandEncoder->setArgumentTable(m_argumentTable.get(), MTL::RenderStageVertex); 130 | 131 | m_argumentTable->setAddress(m_vertexBuffer->gpuAddress(), 0); 132 | 133 | commandEncoder->drawIndexedPrimitives(MTL::PrimitiveTypeTriangle, 134 | m_indexBuffer->length() / sizeof(uint16_t), MTL::IndexTypeUInt16, 135 | m_indexBuffer->gpuAddress(), m_indexBuffer->length()); 136 | 137 | commandEncoder->popDebugGroup(); 138 | 139 | commandEncoder->endEncoding(); 140 | } 141 | 142 | void HelloWorld::onResize(const uint32_t width, const uint32_t height) 143 | { 144 | const float aspect = static_cast(width) / static_cast(height); 145 | constexpr float fov = XMConvertToRadians(75.0F); 146 | constexpr float near = 0.01F; 147 | constexpr float far = 1000.0F; 148 | m_mainCamera->setProjection(fov, aspect, near, far); 149 | } 150 | 151 | void HelloWorld::createResidencySet() 152 | { 153 | NS::Error* error = nullptr; 154 | 155 | NS::SharedPtr residencySetDescriptor 156 | = NS::TransferPtr(MTL::ResidencySetDescriptor::alloc()->init()); 157 | m_residencySet 158 | = NS::TransferPtr(device()->newResidencySet(residencySetDescriptor.get(), &error)); 159 | if (error != nullptr) 160 | { 161 | throw std::runtime_error(fmt::format( 162 | "Failed to create residence set: {}", error->localizedFailureReason()->utf8String())); 163 | } 164 | m_residencySet->addAllocation(m_vertexBuffer.get()); 165 | m_residencySet->addAllocation(m_indexBuffer.get()); 166 | for (uint32_t i = 0; i < s_bufferCount; i++) 167 | { 168 | m_residencySet->addAllocation(m_uniformBuffer[i].get()); 169 | } 170 | 171 | commandQueue()->addResidencySet(m_residencySet.get()); 172 | commandQueue()->addResidencySet(metalLayer()->residencySet()); 173 | m_residencySet->commit(); 174 | } 175 | 176 | void HelloWorld::createArgumentTable() 177 | { 178 | NS::Error* error = nullptr; 179 | 180 | NS::SharedPtr argTableDescriptor 181 | = NS::TransferPtr(MTL4::ArgumentTableDescriptor::alloc()->init()); 182 | argTableDescriptor->setMaxBufferBindCount(2); 183 | 184 | m_argumentTable = NS::TransferPtr(device()->newArgumentTable(argTableDescriptor.get(), &error)); 185 | if (error != nullptr) 186 | { 187 | throw std::runtime_error(fmt::format( 188 | "Failed to create argument table: {}", error->localizedFailureReason()->utf8String())); 189 | } 190 | } 191 | 192 | void HelloWorld::createPipelineState() 193 | { 194 | NS::SharedPtr vertexDescriptor 195 | = NS::TransferPtr(MTL::VertexDescriptor::alloc()->init()); 196 | 197 | // Position 198 | vertexDescriptor->attributes()->object(0)->setFormat(MTL::VertexFormatFloat4); 199 | vertexDescriptor->attributes()->object(0)->setOffset(0); 200 | vertexDescriptor->attributes()->object(0)->setBufferIndex(0); 201 | 202 | // Color 203 | vertexDescriptor->attributes()->object(1)->setFormat(MTL::VertexFormatFloat4); 204 | vertexDescriptor->attributes()->object(1)->setOffset(offsetof(Vertex, color)); 205 | vertexDescriptor->attributes()->object(1)->setBufferIndex(0); 206 | 207 | vertexDescriptor->layouts()->object(0)->setStepFunction(MTL::VertexStepFunctionPerVertex); 208 | vertexDescriptor->layouts()->object(0)->setStride(sizeof(Vertex)); 209 | 210 | NS::SharedPtr pipelineDescriptor 211 | = NS::TransferPtr(MTL4::RenderPipelineDescriptor::alloc()->init()); 212 | 213 | NS::SharedPtr vertexFunction 214 | = NS::TransferPtr(MTL4::LibraryFunctionDescriptor::alloc()->init()); 215 | vertexFunction->setLibrary(shaderLibrary()); 216 | vertexFunction->setName(MTLSTR("triangle_vertex")); 217 | pipelineDescriptor->setVertexFunctionDescriptor(vertexFunction.get()); 218 | 219 | NS::SharedPtr fragmentFunction 220 | = NS::TransferPtr(MTL4::LibraryFunctionDescriptor::alloc()->init()); 221 | fragmentFunction->setLibrary(shaderLibrary()); 222 | fragmentFunction->setName(MTLSTR("triangle_fragment")); 223 | pipelineDescriptor->setFragmentFunctionDescriptor(fragmentFunction.get()); 224 | 225 | pipelineDescriptor->setVertexDescriptor(vertexDescriptor.get()); 226 | pipelineDescriptor->setRasterSampleCount(4); 227 | pipelineDescriptor->colorAttachments()->object(0)->setPixelFormat(s_defaultPixelFormat); 228 | 229 | NS::SharedPtr compilerTaskOptions 230 | = NS::TransferPtr(MTL4::CompilerTaskOptions::alloc()->init()); 231 | 232 | NS::Error* error = nullptr; 233 | NS::SharedPtr compilerDescriptor 234 | = NS::TransferPtr(MTL4::CompilerDescriptor::alloc()->init()); 235 | NS::SharedPtr compiler 236 | = NS::TransferPtr(device()->newCompiler(compilerDescriptor.get(), &error)); 237 | if (error != nullptr) 238 | { 239 | throw std::runtime_error(fmt::format( 240 | "Failed to create shader compiler: {}", error->localizedFailureReason()->utf8String())); 241 | } 242 | 243 | m_pipelineState = NS::TransferPtr(compiler->newRenderPipelineState( 244 | pipelineDescriptor.get(), compilerTaskOptions.get(), &error)); 245 | if (error != nullptr) 246 | { 247 | throw std::runtime_error(fmt::format( 248 | "Failed to create pipeline state: {}", error->localizedFailureReason()->utf8String())); 249 | } 250 | } 251 | 252 | void HelloWorld::createBuffers() 253 | { 254 | constexpr std::array vertices 255 | = std::to_array({ { .position = { 0, 1, 0, 1 }, .color = { 1, 0, 0, 1 } }, 256 | { .position = { -1, -1, 0, 1 }, .color = { 0, 1, 0, 1 } }, 257 | { .position = { 1, -1, 0, 1 }, .color = { 0, 0, 1, 1 } } }); 258 | constexpr size_t vertexBufferLength = vertices.size() * sizeof(Vertex); 259 | 260 | constexpr std::array indices = std::to_array({ 0, 1, 2 }); 261 | constexpr size_t indexBufferLength = indices.size() * sizeof(uint16_t); 262 | 263 | m_vertexBuffer = NS::TransferPtr(device()->newBuffer( 264 | vertices.data(), vertexBufferLength, MTL::ResourceCPUCacheModeDefaultCache)); 265 | m_vertexBuffer->setLabel(NS::String::string("Vertices", NS::ASCIIStringEncoding)); 266 | 267 | m_indexBuffer = NS::TransferPtr(device()->newBuffer( 268 | indices.data(), indexBufferLength, MTL::ResourceOptionCPUCacheModeDefault)); 269 | m_indexBuffer->setLabel(NS::String::string("Indices", NS::ASCIIStringEncoding)); 270 | 271 | for (auto index = 0; std::cmp_less(index, s_bufferCount); index++) 272 | { 273 | const auto label = fmt::format("Uniform: {}", index); 274 | const NS::SharedPtr nsLabel 275 | = NS::TransferPtr(NS::String::string(label.c_str(), NS::ASCIIStringEncoding)); 276 | m_uniformBuffer[index] = NS::TransferPtr(device()->newBuffer( 277 | g_alignedUniformSize * s_bufferCount, MTL::ResourceOptionCPUCacheModeDefault)); 278 | m_uniformBuffer[index]->setLabel(nsLabel.get()); 279 | } 280 | } 281 | 282 | void HelloWorld::updateUniforms() const 283 | { 284 | const auto currentFrameIndex = frameIndex(); 285 | 286 | auto position = Vector3(0.0F, 0.0, -10.0F); 287 | auto rotationX = 0.0F; 288 | auto rotationY = m_rotationY; 289 | auto scaleFactor = 3.0F; 290 | 291 | const Vector3 xAxis = Vector3::Right; 292 | const Vector3 yAxis = Vector3::Up; 293 | 294 | const Matrix xRot = Matrix::CreateFromAxisAngle(xAxis, rotationX); 295 | const Matrix yRot = Matrix::CreateFromAxisAngle(yAxis, rotationY); 296 | const Matrix rotation = xRot * yRot; 297 | const Matrix translation = Matrix::CreateTranslation(position); 298 | const Matrix scale = Matrix::CreateScale(scaleFactor); 299 | const Matrix model = scale * rotation * translation; 300 | const CameraUniforms cameraUniforms = m_mainCamera->uniforms(); 301 | 302 | Uniforms uniforms {}; 303 | uniforms.modelViewProjection = model * cameraUniforms.viewProjection; 304 | 305 | const size_t uniformBufferOffset = g_alignedUniformSize * currentFrameIndex; 306 | 307 | auto* buffer = static_cast(this->m_uniformBuffer[currentFrameIndex]->contents()); 308 | memcpy(buffer + uniformBufferOffset, &uniforms, sizeof(uniforms)); 309 | 310 | m_argumentTable->setAddress(m_uniformBuffer[currentFrameIndex]->gpuAddress(), 1); 311 | } 312 | 313 | int main(const int argc, char** argv) 314 | { 315 | int result = EXIT_FAILURE; 316 | try 317 | { 318 | const auto example = std::make_unique(); 319 | result = example->run(argc, argv); 320 | } 321 | catch (const std::runtime_error&) 322 | { 323 | fmt::println("Exiting..."); 324 | } 325 | 326 | return result; 327 | } 328 | -------------------------------------------------------------------------------- /source/instancing/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////////////////////////////////////////////////////// 3 | // Copyright (c) 2024. Matt Guerrette 4 | // SPDX-License-Identifier: MIT 5 | //////////////////////////////////////////////////////////////////////////////// 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | 14 | #include "Camera.hpp" 15 | #include "Example.hpp" 16 | 17 | #include 18 | 19 | XM_ALIGNED_STRUCT(16) Vertex 20 | { 21 | Vector4 position; 22 | Vector4 color; 23 | }; 24 | 25 | XM_ALIGNED_STRUCT(16) Uniforms 26 | { 27 | [[maybe_unused]] Matrix modelViewProjection; 28 | }; 29 | 30 | XM_ALIGNED_STRUCT(16) InstanceData 31 | { 32 | Matrix transform; 33 | }; 34 | 35 | class Instancing final : public Example 36 | { 37 | static constexpr int s_instanceCount = 3; 38 | 39 | public: 40 | Instancing(); 41 | 42 | ~Instancing() override; 43 | 44 | bool onLoad() override; 45 | 46 | void onUpdate(const GameTimer& timer) override; 47 | 48 | void onRender(CA::MetalDrawable* drawable, 49 | MTL4::CommandBuffer* commandBuffer, 50 | const GameTimer& timer) override; 51 | 52 | void onResize(uint32_t width, uint32_t height) override; 53 | 54 | private: 55 | void createArgumentTable(); 56 | 57 | void createResidencySet(); 58 | 59 | void createBuffers(); 60 | 61 | void createPipelineState(); 62 | 63 | void updateUniforms() const; 64 | 65 | NS::SharedPtr m_pipelineState; 66 | NS::SharedPtr m_vertexBuffer; 67 | NS::SharedPtr m_indexBuffer; 68 | NS::SharedPtr m_argumentTable; 69 | NS::SharedPtr m_residencySet; 70 | std::array, s_instanceCount> m_instanceBuffer; 71 | std::unique_ptr m_mainCamera; 72 | float m_rotationX = 0.0F; 73 | float m_rotationY = 0.0F; 74 | }; 75 | 76 | Instancing::Instancing() 77 | : Example("Instancing", 800, 600) 78 | { 79 | } 80 | 81 | Instancing::~Instancing() = default; 82 | 83 | bool Instancing::onLoad() 84 | { 85 | const auto width = windowWidth(); 86 | const auto height = windowHeight(); 87 | 88 | const float aspect = static_cast(width) / static_cast(height); 89 | constexpr float fov = XMConvertToRadians(75.0f); 90 | constexpr float near = 0.01F; 91 | constexpr float far = 1000.0F; 92 | 93 | m_mainCamera = std::make_unique(XMFLOAT3 { 0.0F, 0.0F, 0.0F }, 94 | XMFLOAT3 { 0.0F, 0.0F, -1.0F }, XMFLOAT3 { 0.0F, 1.0F, 0.0F }, fov, aspect, near, far); 95 | 96 | createBuffers(); 97 | 98 | createArgumentTable(); 99 | 100 | createResidencySet(); 101 | 102 | createPipelineState(); 103 | 104 | return true; 105 | } 106 | 107 | void Instancing::onResize(uint32_t width, uint32_t height) 108 | { 109 | const float aspect = static_cast(width) / static_cast(height); 110 | constexpr float fov = XMConvertToRadians(75.0f); 111 | constexpr float near = 0.01F; 112 | constexpr float far = 1000.0F; 113 | m_mainCamera->setProjection(fov, aspect, near, far); 114 | } 115 | 116 | void Instancing::onUpdate(const GameTimer& timer) 117 | { 118 | const auto elapsed = static_cast(timer.elapsedSeconds()); 119 | 120 | m_rotationX += elapsed; 121 | m_rotationY += elapsed; 122 | 123 | updateUniforms(); 124 | } 125 | 126 | void Instancing::onRender(CA::MetalDrawable* drawable, 127 | MTL4::CommandBuffer* commandBuffer, 128 | [[maybe_unused]] const GameTimer& timer) 129 | { 130 | NS::SharedPtr passDescriptor 131 | = NS::TransferPtr(defaultRenderPassDescriptor(drawable)); 132 | 133 | MTL4::RenderCommandEncoder* commandEncoder 134 | = commandBuffer->renderCommandEncoder(passDescriptor.get()); 135 | 136 | commandEncoder->pushDebugGroup(MTLSTR("Instanced Rendering")); 137 | 138 | commandEncoder->setRenderPipelineState(m_pipelineState.get()); 139 | commandEncoder->setDepthStencilState(depthStencilState()); 140 | commandEncoder->setFrontFacingWinding(MTL::WindingCounterClockwise); 141 | commandEncoder->setCullMode(MTL::CullModeNone); 142 | commandEncoder->setArgumentTable(m_argumentTable.get(), MTL::RenderStageVertex); 143 | 144 | m_argumentTable->setAddress(m_vertexBuffer->gpuAddress(), 0); 145 | 146 | commandEncoder->drawIndexedPrimitives(MTL::PrimitiveTypeTriangle, 147 | m_indexBuffer->length() / sizeof(uint16_t), MTL::IndexTypeUInt16, 148 | m_indexBuffer->gpuAddress(), m_indexBuffer->length(), s_instanceCount); 149 | 150 | commandEncoder->popDebugGroup(); 151 | 152 | commandEncoder->endEncoding(); 153 | } 154 | 155 | void Instancing::createResidencySet() 156 | { 157 | NS::Error* error = nullptr; 158 | 159 | NS::SharedPtr residencySetDescriptor 160 | = NS::TransferPtr(MTL::ResidencySetDescriptor::alloc()->init()); 161 | m_residencySet 162 | = NS::TransferPtr(device()->newResidencySet(residencySetDescriptor.get(), &error)); 163 | if (error != nullptr) 164 | { 165 | throw std::runtime_error(fmt::format( 166 | "Failed to create residence set: {}", error->localizedFailureReason()->utf8String())); 167 | } 168 | 169 | m_residencySet->addAllocation(m_vertexBuffer.get()); 170 | m_residencySet->addAllocation(m_indexBuffer.get()); 171 | for (uint32_t i = 0; i < s_bufferCount; i++) 172 | { 173 | m_residencySet->addAllocation(m_instanceBuffer[i].get()); 174 | } 175 | 176 | commandQueue()->addResidencySet(m_residencySet.get()); 177 | commandQueue()->addResidencySet(metalLayer()->residencySet()); 178 | m_residencySet->commit(); 179 | } 180 | 181 | void Instancing::createArgumentTable() 182 | { 183 | NS::Error* error = nullptr; 184 | 185 | NS::SharedPtr argTableDescriptor 186 | = NS::TransferPtr(MTL4::ArgumentTableDescriptor::alloc()->init()); 187 | argTableDescriptor->setMaxBufferBindCount(2); 188 | 189 | m_argumentTable = NS::TransferPtr(device()->newArgumentTable(argTableDescriptor.get(), &error)); 190 | if (error != nullptr) 191 | { 192 | throw std::runtime_error(fmt::format( 193 | "Failed to create argument table: {}", error->localizedFailureReason()->utf8String())); 194 | } 195 | } 196 | 197 | void Instancing::createPipelineState() 198 | { 199 | NS::SharedPtr vertexDescriptor 200 | = NS::TransferPtr(MTL::VertexDescriptor::alloc()->init()); 201 | 202 | // Position 203 | vertexDescriptor->attributes()->object(0)->setFormat(MTL::VertexFormatFloat4); 204 | vertexDescriptor->attributes()->object(0)->setOffset(0); 205 | vertexDescriptor->attributes()->object(0)->setBufferIndex(0); 206 | 207 | // Color 208 | vertexDescriptor->attributes()->object(1)->setFormat(MTL::VertexFormatFloat4); 209 | vertexDescriptor->attributes()->object(1)->setOffset(offsetof(Vertex, color)); 210 | vertexDescriptor->attributes()->object(1)->setBufferIndex(0); 211 | 212 | vertexDescriptor->layouts()->object(0)->setStepFunction(MTL::VertexStepFunctionPerVertex); 213 | vertexDescriptor->layouts()->object(0)->setStride(sizeof(Vertex)); 214 | 215 | NS::SharedPtr pipelineDescriptor 216 | = NS::TransferPtr(MTL4::RenderPipelineDescriptor::alloc()->init()); 217 | 218 | NS::SharedPtr vertexFunction 219 | = NS::TransferPtr(MTL4::LibraryFunctionDescriptor::alloc()->init()); 220 | vertexFunction->setLibrary(shaderLibrary()); 221 | vertexFunction->setName(MTLSTR("instancing_vertex")); 222 | pipelineDescriptor->setVertexFunctionDescriptor(vertexFunction.get()); 223 | 224 | NS::SharedPtr fragmentFunction 225 | = NS::TransferPtr(MTL4::LibraryFunctionDescriptor::alloc()->init()); 226 | fragmentFunction->setLibrary(shaderLibrary()); 227 | fragmentFunction->setName(MTLSTR("instancing_fragment")); 228 | pipelineDescriptor->setFragmentFunctionDescriptor(fragmentFunction.get()); 229 | 230 | pipelineDescriptor->setVertexDescriptor(vertexDescriptor.get()); 231 | pipelineDescriptor->setRasterSampleCount(4); 232 | pipelineDescriptor->colorAttachments()->object(0)->setPixelFormat(s_defaultPixelFormat); 233 | 234 | NS::SharedPtr compilerTaskOptions 235 | = NS::TransferPtr(MTL4::CompilerTaskOptions::alloc()->init()); 236 | 237 | NS::Error* error = nullptr; 238 | NS::SharedPtr compilerDescriptor 239 | = NS::TransferPtr(MTL4::CompilerDescriptor::alloc()->init()); 240 | NS::SharedPtr compiler 241 | = NS::TransferPtr(device()->newCompiler(compilerDescriptor.get(), &error)); 242 | if (error != nullptr) 243 | { 244 | throw std::runtime_error(fmt::format( 245 | "Failed to create shader compiler: {}", error->localizedFailureReason()->utf8String())); 246 | } 247 | 248 | m_pipelineState = NS::TransferPtr(compiler->newRenderPipelineState( 249 | pipelineDescriptor.get(), compilerTaskOptions.get(), &error)); 250 | if (error != nullptr) 251 | { 252 | throw std::runtime_error(fmt::format( 253 | "Failed to create pipeline state: {}", error->localizedFailureReason()->utf8String())); 254 | } 255 | } 256 | 257 | void Instancing::createBuffers() 258 | { 259 | constexpr std::array vertices 260 | = std::to_array({ { .position = { -1, 1, 1, 1 }, .color = { 0, 1, 1, 1 } }, 261 | { .position = { -1, -1, 1, 1 }, .color = { 0, 0, 1, 1 } }, 262 | { .position = { 1, -1, 1, 1 }, .color = { 1, 0, 1, 1 } }, 263 | { .position = { 1, 1, 1, 1 }, .color = { 1, 1, 1, 1 } }, 264 | { .position = { -1, 1, -1, 1 }, .color = { 0, 1, 0, 1 } }, 265 | { .position = { -1, -1, -1, 1 }, .color = { 0, 0, 0, 1 } }, 266 | { .position = { 1, -1, -1, 1 }, .color = { 1, 0, 0, 1 } }, 267 | { .position = { 1, 1, -1, 1 }, .color = { 1, 1, 0, 1 } } }); 268 | constexpr size_t vertexBufferLength = vertices.size() * sizeof(Vertex); 269 | 270 | constexpr std::array indices = std::to_array({ 3, 2, 6, 6, 7, 3, 4, 5, 1, 1, 0, 4, 4, 271 | 0, 3, 3, 7, 4, 1, 5, 6, 6, 2, 1, 0, 1, 2, 2, 3, 0, 7, 6, 5, 5, 4, 7 }); 272 | constexpr size_t indexBufferLength = indices.size() * sizeof(uint16_t); 273 | 274 | m_vertexBuffer = NS::TransferPtr(device()->newBuffer( 275 | vertices.data(), vertexBufferLength, MTL::ResourceCPUCacheModeDefaultCache)); 276 | m_vertexBuffer->setLabel(NS::String::string("Vertices", NS::ASCIIStringEncoding)); 277 | 278 | m_indexBuffer = NS::TransferPtr(device()->newBuffer( 279 | indices.data(), indexBufferLength, MTL::ResourceOptionCPUCacheModeDefault)); 280 | m_indexBuffer->setLabel(NS::String::string("Indices", NS::ASCIIStringEncoding)); 281 | 282 | constexpr size_t instanceDataSize 283 | = static_cast(s_bufferCount * s_instanceCount) * sizeof(InstanceData); 284 | for (auto index = 0; std::cmp_less(index, s_bufferCount); ++index) 285 | { 286 | const auto label = fmt::format("Instance Buffer: {}", index); 287 | const NS::SharedPtr nsLabel 288 | = NS::TransferPtr(NS::String::string(label.c_str(), NS::ASCIIStringEncoding)); 289 | m_instanceBuffer[index] = NS::TransferPtr( 290 | device()->newBuffer(instanceDataSize, MTL::ResourceOptionCPUCacheModeDefault)); 291 | m_instanceBuffer[index]->setLabel(nsLabel.get()); 292 | } 293 | } 294 | 295 | void Instancing::updateUniforms() const 296 | { 297 | const auto currentFrameIndex = frameIndex(); 298 | 299 | MTL::Buffer* instanceBuffer = m_instanceBuffer[currentFrameIndex].get(); 300 | 301 | auto* instanceData = static_cast(instanceBuffer->contents()); 302 | for (auto index = 0; std::cmp_less(index, s_instanceCount); ++index) 303 | { 304 | auto position = Vector3(-5.0F + 5.0F * static_cast(index), 0.0F, -10.0F); 305 | auto rotationX = m_rotationX; 306 | auto rotationY = m_rotationY; 307 | auto scaleFactor = 1.0F; 308 | 309 | const Vector3 xAxis = Vector3::Right; 310 | const Vector3 yAxis = Vector3::Up; 311 | 312 | const Matrix xRot = Matrix::CreateFromAxisAngle(xAxis, rotationX); 313 | const Matrix yRot = Matrix::CreateFromAxisAngle(yAxis, rotationY); 314 | const Matrix rotation = xRot * yRot; 315 | const Matrix translation = Matrix::CreateTranslation(position); 316 | const Matrix scale = Matrix::CreateScale(scaleFactor); 317 | const Matrix model = scale * rotation * translation; 318 | const CameraUniforms cameraUniforms = m_mainCamera->uniforms(); 319 | 320 | instanceData[index].transform = model * cameraUniforms.viewProjection; 321 | } 322 | 323 | m_argumentTable->setAddress(m_instanceBuffer[currentFrameIndex]->gpuAddress(), 1); 324 | } 325 | 326 | int main(int argc, char** argv) 327 | { 328 | int result = EXIT_FAILURE; 329 | try 330 | { 331 | const auto example = std::make_unique(); 332 | result = example->run(argc, argv); 333 | } 334 | catch (const std::runtime_error&) 335 | { 336 | fmt::println("Exiting..."); 337 | } 338 | 339 | return result; 340 | } 341 | -------------------------------------------------------------------------------- /source/base/Example.cpp: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////////////////////////////////////////////////////// 3 | // Copyright (c) 2024. Matt Guerrette 4 | // SPDX-License-Identifier: MIT 5 | //////////////////////////////////////////////////////////////////////////////// 6 | 7 | #include "Example.hpp" 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include "imgui.h" 15 | #include "imgui_impl_metal.h" 16 | #include "imgui_impl_sdl3.h" 17 | 18 | #include "File.hpp" 19 | #include "GraphicsMath.hpp" 20 | 21 | Example::Example(const char* title, int32_t width, int32_t height) 22 | { 23 | IMGUI_CHECKVERSION(); 24 | ImGui::CreateContext(); 25 | ImGuiIO& io = ImGui::GetIO(); 26 | io.IniFilename = nullptr; 27 | io.IniSavingRate = 0.0F; 28 | io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls 29 | io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls 30 | ImGui::StyleColorsDark(); 31 | 32 | if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_GAMEPAD)) 33 | { 34 | throw std::runtime_error(fmt::format("Failed to initialize SDL: {}", SDL_GetError())); 35 | } 36 | 37 | int numDisplays = 0; 38 | auto* displays = SDL_GetDisplays(&numDisplays); 39 | assert(numDisplays != 0); 40 | 41 | const auto display = displays[0]; 42 | const auto* const mode = SDL_GetDesktopDisplayMode(display); 43 | int32_t screenWidth = mode->w; 44 | int32_t screenHeight = mode->h; 45 | SDL_free(displays); 46 | 47 | int flags = SDL_WINDOW_FULLSCREEN | SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_METAL; 48 | #ifdef SDL_PLATFORM_MACOS 49 | flags ^= SDL_WINDOW_FULLSCREEN; 50 | flags |= SDL_WINDOW_RESIZABLE; 51 | screenWidth = width; 52 | screenHeight = height; 53 | #endif 54 | 55 | m_window.reset(SDL_CreateWindow(title, screenWidth, screenHeight, flags)); 56 | if (m_window == nullptr) 57 | { 58 | throw std::runtime_error(fmt::format("Failed to create SDL window: {}", SDL_GetError())); 59 | } 60 | m_view.reset(SDL_Metal_CreateView(m_window.get())); 61 | m_running = true; 62 | 63 | m_defaultWidth = width; 64 | m_defaultHeight = height; 65 | 66 | m_device = NS::TransferPtr(MTL::CreateSystemDefaultDevice()); 67 | 68 | auto* layer = static_cast(SDL_Metal_GetLayer(m_view.get())); 69 | layer->setPixelFormat(s_defaultPixelFormat); 70 | layer->setDevice(m_device.get()); 71 | 72 | ImGui_ImplMetal_Init(m_device.get()); 73 | ImGui_ImplSDL3_InitForMetal(m_window.get()); 74 | 75 | m_commandQueue = NS::TransferPtr(m_device->newMTL4CommandQueue()); 76 | m_commandBuffer = NS::TransferPtr(m_device->newCommandBuffer()); 77 | 78 | for (uint32_t i = 0; i < s_bufferCount; i++) 79 | { 80 | m_commandAllocator[i] = NS::TransferPtr(m_device->newCommandAllocator()); 81 | } 82 | 83 | m_sharedEvent = NS::TransferPtr(m_device->newSharedEvent()); 84 | m_sharedEvent->setSignaledValue(m_frameNumber); 85 | m_currentFrameIndex = 0; 86 | 87 | createFrameResources(windowWidth(), windowHeight()); 88 | 89 | // Create a depth stencil state 90 | const NS::SharedPtr depthStencilDescriptor 91 | = NS::TransferPtr(MTL::DepthStencilDescriptor::alloc()->init()); 92 | depthStencilDescriptor->setDepthCompareFunction(MTL::CompareFunctionLess); 93 | depthStencilDescriptor->setDepthWriteEnabled(true); 94 | 95 | m_depthStencilState 96 | = NS::TransferPtr(m_device->newDepthStencilState(depthStencilDescriptor.get())); 97 | 98 | // Load shader Library 99 | // TODO: Showcase how to use Metal archives to erase compilation 100 | m_shaderLibrary = NS::TransferPtr(m_device->newDefaultLibrary()); 101 | 102 | m_keyboard = std::make_unique(); 103 | m_mouse = std::make_unique(m_window.get()); 104 | 105 | m_timer.setFixedTimeStep(false); 106 | m_timer.resetElapsedTime(); 107 | 108 | m_displayLink = NS::TransferPtr(CA::MetalDisplayLink::alloc()->init(layer)); 109 | // Enable 120HZ refresh for devices that support Pro Motion 110 | m_displayLink->setPreferredFrameRateRange({ 60, mode->refresh_rate, mode->refresh_rate }); 111 | m_displayLink->setDelegate(this); 112 | } 113 | 114 | Example::~Example() 115 | { 116 | // Cleanup 117 | ImGui_ImplMetal_Shutdown(); 118 | ImGui_ImplSDL3_Shutdown(); 119 | ImGui::DestroyContext(); 120 | 121 | m_window.reset(); 122 | 123 | SDL_Quit(); 124 | } 125 | 126 | void Example::createFrameResources(const int32_t width, const int32_t height) 127 | { 128 | m_msaaTexture.reset(); 129 | m_depthStencilTexture.reset(); 130 | 131 | // Create a multisample texture 132 | const NS::SharedPtr msaaTextureDescriptor 133 | = NS::RetainPtr(MTL::TextureDescriptor::texture2DDescriptor( 134 | MTL::PixelFormatBGRA8Unorm_sRGB, width, height, false)); 135 | msaaTextureDescriptor->setTextureType(MTL::TextureType2DMultisample); 136 | msaaTextureDescriptor->setSampleCount(s_multisampleCount); // Set sample count for MSAA 137 | msaaTextureDescriptor->setUsage(MTL::TextureUsageRenderTarget); 138 | msaaTextureDescriptor->setStorageMode(MTL::StorageModePrivate); 139 | 140 | m_msaaTexture = NS::TransferPtr(m_device->newTexture(msaaTextureDescriptor.get())); 141 | 142 | // Create a depth stencil texture 143 | const NS::SharedPtr textureDescriptor 144 | = NS::RetainPtr(MTL::TextureDescriptor::texture2DDescriptor( 145 | MTL::PixelFormatDepth32Float_Stencil8, width, height, false)); 146 | textureDescriptor->setTextureType(MTL::TextureType2DMultisample); 147 | textureDescriptor->setSampleCount(s_multisampleCount); 148 | textureDescriptor->setUsage(MTL::TextureUsageRenderTarget); 149 | textureDescriptor->setResourceOptions( 150 | MTL::ResourceOptionCPUCacheModeDefault | MTL::ResourceStorageModePrivate); 151 | textureDescriptor->setStorageMode(MTL::StorageModeMemoryless); 152 | 153 | m_depthStencilTexture = NS::TransferPtr(m_device->newTexture(textureDescriptor.get())); 154 | } 155 | 156 | const Keyboard& Example::keyboard() const 157 | { 158 | return *m_keyboard; 159 | } 160 | 161 | const Mouse& Example::mouse() const 162 | { 163 | return *m_mouse; 164 | } 165 | 166 | MTL::Device* Example::device() const 167 | { 168 | return m_device.get(); 169 | } 170 | 171 | MTL4::CommandQueue* Example::commandQueue() const 172 | { 173 | return m_commandQueue.get(); 174 | } 175 | 176 | MTL::DepthStencilState* Example::depthStencilState() const 177 | { 178 | return m_depthStencilState.get(); 179 | } 180 | 181 | MTL::Library* Example::shaderLibrary() const 182 | { 183 | return m_shaderLibrary.get(); 184 | } 185 | 186 | CA::MetalLayer* Example::metalLayer() const 187 | { 188 | return (CA::MetalLayer*)SDL_Metal_GetLayer(m_view.get()); 189 | } 190 | 191 | MTL4::RenderPassDescriptor* Example::defaultRenderPassDescriptor(CA::MetalDrawable* drawable) const 192 | { 193 | MTL4::RenderPassDescriptor* passDescriptor = MTL4::RenderPassDescriptor::alloc()->init(); 194 | passDescriptor->colorAttachments()->object(0)->setResolveTexture(drawable->texture()); 195 | passDescriptor->colorAttachments()->object(0)->setTexture(msaaTexture()); 196 | passDescriptor->colorAttachments()->object(0)->setLoadAction(MTL::LoadActionClear); 197 | passDescriptor->colorAttachments()->object(0)->setStoreAction( 198 | MTL::StoreActionMultisampleResolve); 199 | passDescriptor->colorAttachments()->object(0)->setClearColor( 200 | MTL::ClearColor(DirectX::Colors::CornflowerBlue.f[0], DirectX::Colors::CornflowerBlue.f[1], 201 | DirectX::Colors::CornflowerBlue.f[2], 1.0)); 202 | passDescriptor->depthAttachment()->setTexture(depthStencilTexture()); 203 | passDescriptor->depthAttachment()->setLoadAction(MTL::LoadActionClear); 204 | passDescriptor->depthAttachment()->setStoreAction(MTL::StoreActionDontCare); 205 | passDescriptor->depthAttachment()->setClearDepth(1.0); 206 | passDescriptor->stencilAttachment()->setTexture(depthStencilTexture()); 207 | passDescriptor->stencilAttachment()->setLoadAction(MTL::LoadActionClear); 208 | passDescriptor->stencilAttachment()->setStoreAction(MTL::StoreActionDontCare); 209 | passDescriptor->stencilAttachment()->setClearStencil(0); 210 | 211 | return passDescriptor; 212 | } 213 | 214 | MTL4::CommandBuffer* Example::commandBuffer() const 215 | { 216 | return m_commandBuffer.get(); 217 | } 218 | 219 | MTL4::CommandAllocator* Example::commandAllocator() const 220 | { 221 | return m_commandAllocator[m_currentFrameIndex].get(); 222 | } 223 | 224 | MTL::Texture* Example::msaaTexture() const 225 | { 226 | return m_msaaTexture.get(); 227 | } 228 | 229 | MTL::Texture* Example::depthStencilTexture() const 230 | { 231 | return m_depthStencilTexture.get(); 232 | } 233 | 234 | SDL_Window* Example::window() const 235 | { 236 | return m_window.get(); 237 | } 238 | 239 | int32_t Example::windowWidth() const 240 | { 241 | int32_t width = 0; 242 | SDL_GetWindowSizeInPixels(m_window.get(), &width, nullptr); 243 | return width; 244 | } 245 | 246 | int32_t Example::windowHeight() const 247 | { 248 | int32_t height = 0; 249 | SDL_GetWindowSizeInPixels(m_window.get(), nullptr, &height); 250 | return height; 251 | } 252 | 253 | uint32_t Example::frameIndex() const 254 | { 255 | return m_currentFrameIndex; 256 | } 257 | 258 | #ifdef SDL_PLATFORM_MACOS 259 | NS::Menu* Example::createMenuBar() 260 | { 261 | return nullptr; 262 | } 263 | #endif 264 | 265 | int Example::run([[maybe_unused]] int argc, [[maybe_unused]] char** argv) 266 | { 267 | #ifdef SDL_PLATFORM_MACOS 268 | if (const NS::Menu* menu = createMenuBar(); menu != nullptr) 269 | { 270 | NS::Application::sharedApplication()->setMainMenu(menu); 271 | } 272 | #endif 273 | if (!onLoad()) 274 | { 275 | return EXIT_FAILURE; 276 | } 277 | 278 | m_displayLink->addToRunLoop(NS::RunLoop::mainRunLoop(), NS::DefaultRunLoopMode); 279 | 280 | while (m_running) 281 | { 282 | SDL_Event e; 283 | if (SDL_WaitEvent(&e)) 284 | { 285 | ImGui_ImplSDL3_ProcessEvent(&e); 286 | 287 | if (e.type == SDL_EVENT_QUIT) 288 | { 289 | m_running = false; 290 | break; 291 | } 292 | 293 | if (e.type == SDL_EVENT_WINDOW_RESIZED) 294 | { 295 | const float density = SDL_GetWindowPixelDensity(m_window.get()); 296 | auto* layer = static_cast(SDL_Metal_GetLayer(m_view.get())); 297 | layer->setDrawableSize(CGSize { static_cast(e.window.data1) * density, 298 | static_cast(e.window.data2) * density }); 299 | 300 | // ImGuiIO& io = ImGui::GetIO(); 301 | // io.DisplaySize = 302 | // ImVec2((float)e.window.data1 * density, (float)e.window.data2 303 | // * density); 304 | 305 | onResize(e.window.data1, e.window.data2); 306 | } 307 | 308 | if (e.type == SDL_EVENT_JOYSTICK_ADDED) 309 | { 310 | if (SDL_IsGamepad(e.jdevice.which)) 311 | { 312 | m_gamepad = std::make_unique(e.jdevice.which); 313 | } 314 | } 315 | 316 | if (e.type == SDL_EVENT_JOYSTICK_REMOVED) 317 | { 318 | if (SDL_IsGamepad(e.jdevice.which)) 319 | { 320 | m_gamepad.reset(); 321 | } 322 | } 323 | 324 | if (e.type == SDL_EVENT_KEY_DOWN || e.type == SDL_EVENT_KEY_UP) 325 | { 326 | m_keyboard->registerKeyEvent(&e.key); 327 | } 328 | if (e.type == SDL_EVENT_MOUSE_BUTTON_UP || e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) 329 | { 330 | m_mouse->registerMouseButton(&e.button); 331 | } 332 | if (e.type == SDL_EVENT_MOUSE_MOTION) 333 | { 334 | m_mouse->registerMouseMotion(&e.motion); 335 | } 336 | if (e.type == SDL_EVENT_MOUSE_WHEEL) 337 | { 338 | m_mouse->registerMouseWheel(&e.wheel); 339 | } 340 | 341 | if (m_keyboard->isKeyClicked(SDL_SCANCODE_ESCAPE)) 342 | { 343 | quit(); 344 | } 345 | } 346 | } 347 | 348 | return 0; 349 | } 350 | 351 | void Example::onSetupUi(const GameTimer& timer) 352 | { 353 | ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0); 354 | ImGui::SetNextWindowPos(ImVec2(10, 10)); 355 | ImGui::SetNextWindowSize(ImVec2(125 * 2.0f, 0), ImGuiCond_FirstUseEver); 356 | ImGui::Begin("Metal Example", nullptr, 357 | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar); 358 | ImGui::Text("%s (%.1d fps)", SDL_GetWindowTitle(m_window.get()), timer.framesPerSecond()); 359 | ImGui::Text("Press Esc to quit"); 360 | ImGui::End(); 361 | ImGui::PopStyleVar(); 362 | } 363 | 364 | void Example::quit() 365 | { 366 | m_running = false; 367 | } 368 | 369 | void Example::metalDisplayLinkNeedsUpdate( 370 | [[maybe_unused]] CA::MetalDisplayLink* displayLink, CA::MetalDisplayLinkUpdate* update) 371 | { 372 | m_timer.tick([this] { onUpdate(m_timer); }); 373 | 374 | m_keyboard->update(); 375 | m_mouse->update(); 376 | 377 | CA::MetalDrawable* drawable = update->drawable(); 378 | if (!drawable) 379 | return; 380 | 381 | if (m_frameNumber >= s_bufferCount) 382 | { 383 | // Wait for the GPU to finish rendering the frame that's 384 | // `kMaxFramesInFlight` before this one, and then proceed to the next step. 385 | uint64_t previousValueToWaitFor = m_frameNumber - s_bufferCount; 386 | m_sharedEvent->waitUntilSignaledValue(previousValueToWaitFor, 10); 387 | } 388 | 389 | // Get the next allocator in the rotation. 390 | m_currentFrameIndex = m_frameNumber % s_bufferCount; 391 | MTL4::CommandAllocator* frameAllocator = m_commandAllocator[m_currentFrameIndex].get(); 392 | 393 | // Prepare to use or reuse the allocator by resetting it. 394 | frameAllocator->reset(); 395 | 396 | m_commandBuffer->beginCommandBuffer(frameAllocator); 397 | 398 | // TODO: This needs to be re-done with improved synchonization 399 | // Updating the frame buffer resources in the middle of rendering is not 400 | // good and should instead by done after all work as completed and before 401 | // any new work is scheduled. 402 | // Update depth stencil texture if necessary 403 | if (drawable->texture()->width() != m_depthStencilTexture->width() 404 | || drawable->texture()->height() != m_depthStencilTexture->height()) 405 | { 406 | const auto width = windowWidth(); 407 | const auto height = windowHeight(); 408 | createFrameResources(width, height); 409 | } 410 | 411 | onRender(drawable, m_commandBuffer.get(), m_timer); 412 | 413 | m_commandBuffer->endCommandBuffer(); 414 | 415 | m_commandQueue->wait(drawable); 416 | MTL4::CommandBuffer* buffers[] = { m_commandBuffer.get() }; 417 | 418 | m_commandQueue->commit(buffers, 1); 419 | m_commandQueue->signalDrawable(drawable); 420 | static_cast(drawable)->present(); 421 | 422 | // Signal when the GPU finishes rendering this frame with a shared event. 423 | uint64_t futureValueToWaitFor = m_frameNumber; 424 | m_commandQueue->signalEvent(m_sharedEvent.get(), futureValueToWaitFor); 425 | } 426 | -------------------------------------------------------------------------------- /source/textures/main.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) 2024. Matt Guerrette 3 | // SPDX-License-Identifier: MIT 4 | //////////////////////////////////////////////////////////////////////////////// 5 | 6 | #ifdef USE_KTX_LIBRARY 7 | #include 8 | #endif 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include "Camera.hpp" 20 | #include "Example.hpp" 21 | #include "File.hpp" 22 | 23 | #include 24 | 25 | XM_ALIGNED_STRUCT(16) Vertex 26 | { 27 | Vector4 position; 28 | Vector2 texCoord; 29 | }; 30 | 31 | XM_ALIGNED_STRUCT(16) Uniforms 32 | { 33 | [[maybe_unused]] Matrix modelViewProjection; 34 | }; 35 | 36 | static constexpr size_t g_textureCount = 5; 37 | 38 | XM_ALIGNED_STRUCT(16) FragmentArgumentBuffer 39 | { 40 | [[maybe_unused]] std::array textures; 41 | [[maybe_unused]] Matrix* transforms; 42 | }; 43 | 44 | static constexpr std::array g_comboItems 45 | = { "Texture 0", "Texture 1", "Texture 2", "Texture 3", "Texture 4" }; 46 | 47 | class Textures final : public Example 48 | { 49 | static constexpr int s_instanceCount = 3; 50 | 51 | public: 52 | Textures(); 53 | 54 | ~Textures() override; 55 | 56 | bool onLoad() override; 57 | 58 | void onUpdate(const GameTimer& timer) override; 59 | 60 | void onSetupUi(const GameTimer& timer) override; 61 | 62 | void onResize(uint32_t width, uint32_t height) override; 63 | 64 | void onRender(CA::MetalDrawable* drawable, 65 | MTL4::CommandBuffer* commandBuffer, 66 | const GameTimer& timer) override; 67 | 68 | private: 69 | void createArgumentTable(); 70 | 71 | void createResidencySet(); 72 | 73 | void createBuffers(); 74 | 75 | void createPipelineState(); 76 | 77 | void createTextureHeap(); 78 | 79 | void createArgumentBuffers(); 80 | 81 | void updateUniforms() const; 82 | 83 | [[nodiscard]] MTL::Texture* newTextureFromFileKTX(const std::string& fileName) const; 84 | 85 | NS::SharedPtr m_pipelineState; 86 | NS::SharedPtr m_vertexBuffer; 87 | NS::SharedPtr m_indexBuffer; 88 | std::array, s_bufferCount> m_instanceBuffer; 89 | NS::SharedPtr m_argumentTable; 90 | NS::SharedPtr m_residencySet; 91 | NS::SharedPtr m_computeEvent; 92 | std::unique_ptr m_mainCamera; 93 | NS::SharedPtr m_textureHeap; 94 | std::array, s_bufferCount> m_argumentBuffer; 95 | std::vector> m_heapTextures; 96 | float m_rotationX = 0.0F; 97 | float m_rotationY = 0.0F; 98 | }; 99 | 100 | Textures::Textures() 101 | : Example("Textures", 800, 600) 102 | { 103 | } 104 | 105 | Textures::~Textures() = default; 106 | 107 | bool Textures::onLoad() 108 | { 109 | const auto width = windowWidth(); 110 | const auto height = windowHeight(); 111 | const float aspect = static_cast(width) / static_cast(height); 112 | constexpr float fov = XMConvertToRadians(75.0F); 113 | constexpr float near = 0.01F; 114 | constexpr float far = 1000.0F; 115 | 116 | m_mainCamera = std::make_unique( 117 | Vector3::Zero, Vector3::Forward, Vector3::Up, fov, aspect, near, far); 118 | 119 | createArgumentTable(); 120 | 121 | createResidencySet(); 122 | 123 | createBuffers(); 124 | 125 | createPipelineState(); 126 | 127 | m_computeEvent = NS::TransferPtr(device()->newSharedEvent()); 128 | m_computeEvent->setSignaledValue(0); 129 | 130 | createTextureHeap(); 131 | 132 | createArgumentBuffers(); 133 | 134 | // populate residency set and bind to command queue 135 | m_residencySet->addAllocation(m_vertexBuffer.get()); 136 | m_residencySet->addAllocation(m_indexBuffer.get()); 137 | for (uint32_t i = 0; i < s_bufferCount; i++) 138 | { 139 | m_residencySet->addAllocation(m_instanceBuffer[i].get()); 140 | } 141 | 142 | commandQueue()->addResidencySet(m_residencySet.get()); 143 | commandQueue()->addResidencySet(metalLayer()->residencySet()); 144 | 145 | m_residencySet->commit(); 146 | 147 | return true; 148 | } 149 | 150 | void Textures::onSetupUi(const GameTimer& timer) 151 | { 152 | // TODO: Re-enable once Metal 4 support is added to ImGUI 153 | // ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0); 154 | // ImGui::SetNextWindowPos(ImVec2(10, 20)); 155 | // ImGui::SetNextWindowSize(ImVec2(250, 0), ImGuiCond_FirstUseEver); 156 | // ImGui::Begin("Metal Example", nullptr, 157 | // ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar); 158 | // ImGui::Text("%s (%.1d fps)", SDL_GetWindowTitle(window()), timer.framesPerSecond()); 159 | // if (ImGui::Combo(" ", &m_selectedTexture, g_comboItems.data(), g_comboItems.size())) 160 | // { 161 | // /// Update argument buffer index 162 | // for (const auto& buffer : m_argumentBuffer) 163 | // { 164 | // auto* contents = static_cast(buffer->contents()); 165 | // contents->textureIndex = m_selectedTexture; 166 | // } 167 | // } 168 | // #if defined(SDL_PLATFORM_MACOS) 169 | // ImGui::Text("Press Esc to quit"); 170 | // #endif 171 | // ImGui::End(); 172 | // ImGui::PopStyleVar(); 173 | } 174 | 175 | void Textures::onUpdate(const GameTimer& timer) 176 | { 177 | const auto elapsed = static_cast(timer.elapsedSeconds()); 178 | 179 | if (mouse().isLeftPressed()) 180 | { 181 | m_rotationY += static_cast(mouse().relativeX()) * elapsed; 182 | } 183 | 184 | updateUniforms(); 185 | } 186 | 187 | void Textures::onResize(const uint32_t width, const uint32_t height) 188 | { 189 | const float aspect = static_cast(width) / static_cast(height); 190 | constexpr float fov = XMConvertToRadians(75.0F); 191 | constexpr float near = 0.01F; 192 | constexpr float far = 1000.0F; 193 | m_mainCamera->setProjection(fov, aspect, near, far); 194 | } 195 | 196 | MTL::Texture* Textures::newTextureFromFileKTX(const std::string& fileName) const 197 | { 198 | MTL::Texture* texture = nullptr; 199 | 200 | try 201 | { 202 | const File file(fileName); 203 | 204 | const auto bytes = file.readAll(); 205 | 206 | constexpr uint32_t flags 207 | = KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT | KTX_TEXTURE_CREATE_SKIP_KVDATA_BIT; 208 | ktxTexture2* ktx2Texture = nullptr; 209 | KTX_error_code result = ktxTexture2_CreateFromMemory( 210 | reinterpret_cast(bytes.data()), bytes.size(), flags, &ktx2Texture); 211 | if (result != KTX_SUCCESS) 212 | { 213 | return nullptr; 214 | } 215 | 216 | constexpr MTL::TextureType type = MTL::TextureType2D; 217 | constexpr MTL::PixelFormat pixelFormat = MTL::PixelFormatASTC_8x8_sRGB; 218 | const bool genMipmaps = ktx2Texture->generateMipmaps; 219 | const NS::UInteger levelCount = ktx2Texture->numLevels; 220 | const NS::UInteger baseWidth = ktx2Texture->baseWidth; 221 | const NS::UInteger baseHeight = ktx2Texture->baseHeight; 222 | const NS::UInteger baseDepth = ktx2Texture->baseDepth; 223 | const auto maxMipLevelCount = static_cast( 224 | std::floor(std::log2(std::fmax(baseWidth, baseHeight))) + 1); 225 | const NS::UInteger storedMipLevelCount = genMipmaps ? maxMipLevelCount : levelCount; 226 | 227 | NS::SharedPtr textureDescriptor 228 | = NS::TransferPtr(MTL::TextureDescriptor::alloc()->init()); 229 | textureDescriptor->setTextureType(type); 230 | textureDescriptor->setPixelFormat(pixelFormat); 231 | textureDescriptor->setWidth(baseWidth); 232 | textureDescriptor->setHeight(ktx2Texture->numDimensions > 1 ? baseHeight : 1); 233 | textureDescriptor->setDepth(ktx2Texture->numDimensions > 2 ? baseDepth : 1); 234 | textureDescriptor->setUsage(MTL::TextureUsageShaderRead); 235 | textureDescriptor->setStorageMode(MTL::StorageModeShared); 236 | textureDescriptor->setArrayLength(1); 237 | textureDescriptor->setMipmapLevelCount(storedMipLevelCount); 238 | 239 | texture = device()->newTexture(textureDescriptor.get()); 240 | 241 | auto* ktx1Texture = reinterpret_cast(ktx2Texture); 242 | for (ktx_uint32_t level = 0; std::cmp_less(level, ktx2Texture->numLevels); ++level) 243 | { 244 | constexpr ktx_uint32_t faceSlice = 0; 245 | constexpr ktx_uint32_t layer = 0; 246 | ktx_size_t offset = 0; 247 | result = ktxTexture_GetImageOffset(ktx1Texture, level, layer, faceSlice, &offset); 248 | const ktx_uint8_t* imageBytes = ktxTexture_GetData(ktx1Texture) + offset; 249 | const ktx_uint32_t bytesPerRow = ktxTexture_GetRowPitch(ktx1Texture, level); 250 | const ktx_size_t bytesPerImage = ktxTexture_GetImageSize(ktx1Texture, level); 251 | const auto levelWidth 252 | = static_cast(std::fmax(1.0F, static_cast(baseWidth >> level))); 253 | const auto levelHeight 254 | = static_cast(std::fmax(1.0F, static_cast(baseHeight >> level))); 255 | 256 | texture->replaceRegion(MTL::Region(0, 0, levelWidth, levelHeight), level, faceSlice, 257 | imageBytes, bytesPerRow, bytesPerImage); 258 | } 259 | 260 | ktxTexture_Destroy((ktxTexture*)ktx1Texture); 261 | } 262 | catch (const std::runtime_error& error) 263 | { 264 | fmt::println("{}", error.what()); 265 | } 266 | 267 | return texture; 268 | } 269 | 270 | void Textures::onRender(CA::MetalDrawable* drawable, 271 | MTL4::CommandBuffer* commandBuffer, 272 | [[maybe_unused]] const GameTimer& timer) 273 | { 274 | 275 | const auto currentFrameIndex = frameIndex(); 276 | 277 | NS::SharedPtr renderPassDescriptor 278 | = NS::TransferPtr(defaultRenderPassDescriptor(drawable)); 279 | 280 | MTL4::RenderCommandEncoder* commandEncoder 281 | = commandBuffer->renderCommandEncoder(renderPassDescriptor.get()); 282 | 283 | commandEncoder->pushDebugGroup(MTLSTR("Texture Rendering")); 284 | 285 | commandEncoder->setRenderPipelineState(m_pipelineState.get()); 286 | commandEncoder->setDepthStencilState(depthStencilState()); 287 | commandEncoder->setFrontFacingWinding(MTL::WindingCounterClockwise); 288 | commandEncoder->setCullMode(MTL::CullModeNone); 289 | commandEncoder->setArgumentTable(m_argumentTable.get(), MTL::RenderStageVertex); 290 | 291 | m_argumentTable->setAddress(m_vertexBuffer->gpuAddress(), 0); 292 | m_argumentTable->setAddress(m_argumentBuffer[currentFrameIndex]->gpuAddress(), 1); 293 | 294 | commandEncoder->setArgumentTable(m_argumentTable.get(), MTL::RenderStageFragment); 295 | 296 | m_argumentTable->setAddress(m_argumentBuffer[currentFrameIndex]->gpuAddress(), 2); 297 | 298 | commandEncoder->drawIndexedPrimitives(MTL::PrimitiveTypeTriangle, 299 | m_indexBuffer->length() / sizeof(uint16_t), MTL::IndexTypeUInt16, 300 | m_indexBuffer->gpuAddress(), m_indexBuffer->length(), s_instanceCount); 301 | 302 | commandEncoder->popDebugGroup(); 303 | commandEncoder->endEncoding(); 304 | } 305 | 306 | void Textures::createResidencySet() 307 | { 308 | NS::Error* error = nullptr; 309 | 310 | NS::SharedPtr residencySetDescriptor 311 | = NS::TransferPtr(MTL::ResidencySetDescriptor::alloc()->init()); 312 | m_residencySet 313 | = NS::TransferPtr(device()->newResidencySet(residencySetDescriptor.get(), &error)); 314 | if (error != nullptr) 315 | { 316 | throw std::runtime_error(fmt::format( 317 | "Failed to create residence set: {}", error->localizedFailureReason()->utf8String())); 318 | } 319 | } 320 | 321 | void Textures::createArgumentTable() 322 | { 323 | NS::Error* error = nullptr; 324 | 325 | NS::SharedPtr argTableDescriptor 326 | = NS::TransferPtr(MTL4::ArgumentTableDescriptor::alloc()->init()); 327 | argTableDescriptor->setMaxBufferBindCount(3); 328 | 329 | m_argumentTable = NS::TransferPtr(device()->newArgumentTable(argTableDescriptor.get(), &error)); 330 | if (error != nullptr) 331 | { 332 | throw std::runtime_error(fmt::format( 333 | "Failed to create argument table: {}", error->localizedFailureReason()->utf8String())); 334 | } 335 | } 336 | 337 | void Textures::createPipelineState() 338 | { 339 | NS::SharedPtr vertexDescriptor 340 | = NS::TransferPtr(MTL::VertexDescriptor::alloc()->init()); 341 | 342 | // Position 343 | vertexDescriptor->attributes()->object(0)->setFormat(MTL::VertexFormatFloat4); 344 | vertexDescriptor->attributes()->object(0)->setOffset(0); 345 | vertexDescriptor->attributes()->object(0)->setBufferIndex(0); 346 | 347 | // Color 348 | vertexDescriptor->attributes()->object(1)->setFormat(MTL::VertexFormatFloat2); 349 | vertexDescriptor->attributes()->object(1)->setOffset(offsetof(Vertex, texCoord)); 350 | vertexDescriptor->attributes()->object(1)->setBufferIndex(0); 351 | 352 | vertexDescriptor->layouts()->object(0)->setStepFunction(MTL::VertexStepFunctionPerVertex); 353 | vertexDescriptor->layouts()->object(0)->setStride(sizeof(Vertex)); 354 | 355 | NS::SharedPtr pipelineDescriptor 356 | = NS::TransferPtr(MTL::RenderPipelineDescriptor::alloc()->init()); 357 | pipelineDescriptor->colorAttachments()->object(0)->setPixelFormat(s_defaultPixelFormat); 358 | pipelineDescriptor->colorAttachments()->object(0)->setBlendingEnabled(true); 359 | pipelineDescriptor->colorAttachments()->object(0)->setSourceRGBBlendFactor( 360 | MTL::BlendFactorSourceAlpha); 361 | pipelineDescriptor->colorAttachments()->object(0)->setDestinationRGBBlendFactor( 362 | MTL::BlendFactorOneMinusSourceAlpha); 363 | pipelineDescriptor->colorAttachments()->object(0)->setRgbBlendOperation(MTL::BlendOperationAdd); 364 | pipelineDescriptor->colorAttachments()->object(0)->setSourceAlphaBlendFactor( 365 | MTL::BlendFactorSourceAlpha); 366 | pipelineDescriptor->colorAttachments()->object(0)->setDestinationAlphaBlendFactor( 367 | MTL::BlendFactorOneMinusSourceAlpha); 368 | pipelineDescriptor->colorAttachments()->object(0)->setAlphaBlendOperation( 369 | MTL::BlendOperationAdd); 370 | pipelineDescriptor->setDepthAttachmentPixelFormat(MTL::PixelFormatDepth32Float_Stencil8); 371 | pipelineDescriptor->setStencilAttachmentPixelFormat(MTL::PixelFormatDepth32Float_Stencil8); 372 | pipelineDescriptor->setVertexFunction(shaderLibrary()->newFunction( 373 | NS::String::string("texture_vertex", NS::ASCIIStringEncoding))); 374 | pipelineDescriptor->setFragmentFunction(shaderLibrary()->newFunction( 375 | NS::String::string("texture_fragment", NS::ASCIIStringEncoding))); 376 | pipelineDescriptor->setVertexDescriptor(vertexDescriptor.get()); 377 | pipelineDescriptor->setSampleCount(s_multisampleCount); 378 | 379 | NS::Error* error = nullptr; 380 | m_pipelineState 381 | = NS::TransferPtr(device()->newRenderPipelineState(pipelineDescriptor.get(), &error)); 382 | if (error != nullptr) 383 | { 384 | throw std::runtime_error(fmt::format( 385 | "Failed to create pipeline state: {}", error->localizedFailureReason()->utf8String())); 386 | } 387 | } 388 | 389 | void Textures::createBuffers() 390 | { 391 | constexpr std::array vertices 392 | = std::to_array({ { .position = { -1, -1, 0, 1 }, .texCoord = { 0, 0 } }, 393 | { .position = { -1, 1, 0, 1 }, .texCoord = { 0, 1 } }, 394 | { .position = { 1, -1, 0, 1 }, .texCoord = { 1, 0 } }, 395 | { .position = { 1, 1, 0, 1 }, .texCoord = { 1, 1 } } }); 396 | constexpr size_t vertexBufferLength = vertices.size() * sizeof(Vertex); 397 | 398 | constexpr std::array indices = std::to_array({ 0, 1, 2, 2, 1, 3 }); 399 | constexpr size_t indexBufferLength = indices.size() * sizeof(uint16_t); 400 | 401 | m_vertexBuffer = NS::TransferPtr(device()->newBuffer( 402 | vertices.data(), vertexBufferLength, MTL::ResourceCPUCacheModeDefaultCache)); 403 | m_vertexBuffer->setLabel(NS::String::string("Vertices", NS::ASCIIStringEncoding)); 404 | 405 | m_indexBuffer = NS::TransferPtr(device()->newBuffer( 406 | indices.data(), indexBufferLength, MTL::ResourceOptionCPUCacheModeDefault)); 407 | m_indexBuffer->setLabel(NS::String::string("Indices", NS::ASCIIStringEncoding)); 408 | 409 | constexpr size_t instanceDataSize 410 | = static_cast(s_bufferCount * s_instanceCount) * sizeof(Matrix); 411 | for (auto index = 0; std::cmp_less(index, s_bufferCount); ++index) 412 | { 413 | const auto label = fmt::format("Instance Buffer: {}", index); 414 | const NS::SharedPtr nsLabel 415 | = NS::TransferPtr(NS::String::string(label.c_str(), NS::ASCIIStringEncoding)); 416 | 417 | m_instanceBuffer[index] = NS::TransferPtr( 418 | device()->newBuffer(instanceDataSize, MTL::ResourceOptionCPUCacheModeDefault)); 419 | m_instanceBuffer[index]->setLabel(nsLabel.get()); 420 | } 421 | } 422 | 423 | void Textures::updateUniforms() const 424 | { 425 | const auto currentFrameIndex = frameIndex(); 426 | 427 | MTL::Buffer* instanceBuffer = m_instanceBuffer[currentFrameIndex].get(); 428 | 429 | auto* instanceData = static_cast(instanceBuffer->contents()); 430 | for (auto index = 0; std::cmp_less(index, s_bufferCount); ++index) 431 | { 432 | auto position = Vector3(-5.0F + 5.0F * static_cast(index), 0.0F, -8.0F); 433 | auto rotationX = m_rotationX; 434 | auto rotationY = m_rotationY; 435 | auto scaleFactor = 1.0F; 436 | 437 | const Vector3 xAxis = Vector3::Right; 438 | const Vector3 yAxis = Vector3::Up; 439 | 440 | const Matrix xRot = Matrix::CreateFromAxisAngle(xAxis, rotationX); 441 | const Matrix yRot = Matrix::CreateFromAxisAngle(yAxis, rotationY); 442 | const Matrix rotation = xRot * yRot; 443 | const Matrix translation = Matrix::CreateTranslation(position); 444 | const Matrix scale = Matrix::CreateScale(scaleFactor); 445 | const Matrix model = scale * rotation * translation; 446 | const CameraUniforms cameraUniforms = m_mainCamera->uniforms(); 447 | 448 | instanceData[index] = model * cameraUniforms.viewProjection; 449 | } 450 | } 451 | 452 | void Textures::createTextureHeap() 453 | { 454 | std::vector> textures; 455 | textures.resize(g_textureCount); 456 | for (size_t i = 0; std::cmp_less(i, g_textureCount); ++i) 457 | { 458 | const auto fileName = fmt::format("00{}_basecolor.ktx", i + 1); 459 | 460 | // Load KTX textures using KTX lib directly 461 | textures[i] = NS::TransferPtr(newTextureFromFileKTX(fileName)); 462 | } 463 | 464 | MTL::HeapDescriptor* heapDescriptor = MTL::HeapDescriptor::alloc()->init(); 465 | heapDescriptor->setType(MTL::HeapTypeAutomatic); 466 | heapDescriptor->setStorageMode(MTL::StorageModePrivate); 467 | heapDescriptor->setSize(0); 468 | 469 | // Allocate space in the heap for each texture 470 | NS::UInteger heapSize = 0; 471 | for (size_t i = 0; std::cmp_less(i, g_textureCount); ++i) 472 | { 473 | const MTL::Texture* texture = textures[i].get(); 474 | if (texture == nullptr) 475 | { 476 | continue; 477 | } 478 | 479 | MTL::TextureDescriptor* textureDescriptor = MTL::TextureDescriptor::alloc()->init(); 480 | textureDescriptor->setTextureType(texture->textureType()); 481 | textureDescriptor->setPixelFormat(texture->pixelFormat()); 482 | textureDescriptor->setWidth(texture->width()); 483 | textureDescriptor->setHeight(texture->height()); 484 | textureDescriptor->setDepth(texture->depth()); 485 | textureDescriptor->setMipmapLevelCount(texture->mipmapLevelCount()); 486 | textureDescriptor->setSampleCount(texture->sampleCount()); 487 | textureDescriptor->setStorageMode(MTL::StorageModePrivate); 488 | 489 | auto [size, align] = device()->heapTextureSizeAndAlign(textureDescriptor); 490 | heapSize += size; 491 | 492 | textureDescriptor->release(); 493 | } 494 | heapDescriptor->setSize(heapSize); 495 | 496 | m_textureHeap = NS::TransferPtr(device()->newHeap(heapDescriptor)); 497 | heapDescriptor->release(); 498 | 499 | NS::SharedPtr _commandQueue = NS::TransferPtr(device()->newCommandQueue()); 500 | NS::SharedPtr _commandBuffer 501 | = NS::TransferPtr(_commandQueue->commandBuffer()); 502 | MTL::BlitCommandEncoder* blitCommandEncoder = _commandBuffer->blitCommandEncoder(); 503 | for (size_t i = 0; std::cmp_less(i, g_textureCount); ++i) 504 | { 505 | const MTL::Texture* texture = textures[i].get(); 506 | MTL::TextureDescriptor* textureDescriptor = MTL::TextureDescriptor::alloc()->init(); 507 | textureDescriptor->setTextureType(texture->textureType()); 508 | textureDescriptor->setPixelFormat(texture->pixelFormat()); 509 | textureDescriptor->setWidth(texture->width()); 510 | textureDescriptor->setHeight(texture->height()); 511 | textureDescriptor->setDepth(texture->depth()); 512 | textureDescriptor->setMipmapLevelCount(texture->mipmapLevelCount()); 513 | textureDescriptor->setSampleCount(texture->sampleCount()); 514 | textureDescriptor->setStorageMode(m_textureHeap->storageMode()); 515 | 516 | NS::SharedPtr heapTexture 517 | = NS::TransferPtr(m_textureHeap->newTexture(textureDescriptor)); 518 | textureDescriptor->release(); 519 | 520 | auto blitRegion = MTL::Region(0, 0, texture->width(), texture->height()); 521 | for (auto level = 0; std::cmp_less(level, texture->mipmapLevelCount()); ++level) 522 | { 523 | for (auto slice = 0; std::cmp_less(slice, texture->arrayLength()); ++slice) 524 | { 525 | blitCommandEncoder->copyFromTexture(texture, slice, level, blitRegion.origin, 526 | blitRegion.size, heapTexture.get(), slice, level, blitRegion.origin); 527 | } 528 | 529 | blitRegion.size.width /= 2; 530 | blitRegion.size.height /= 2; 531 | 532 | if (blitRegion.size.width == 0) 533 | { 534 | blitRegion.size.width = 1; 535 | } 536 | 537 | if (blitRegion.size.height == 0) 538 | { 539 | blitRegion.size.height = 1; 540 | } 541 | } 542 | 543 | m_heapTextures.push_back(heapTexture); 544 | } 545 | 546 | blitCommandEncoder->endEncoding(); 547 | _commandBuffer->commit(); 548 | _commandBuffer->waitUntilCompleted(); 549 | 550 | blitCommandEncoder->release(); 551 | m_residencySet->addAllocation(m_textureHeap.get()); 552 | } 553 | 554 | void Textures::createArgumentBuffers() 555 | { 556 | // Tier 2 argument buffers 557 | if (device()->argumentBuffersSupport() == MTL::ArgumentBuffersTier2) 558 | { 559 | for (auto i = 0; std::cmp_less(i, s_bufferCount); ++i) 560 | { 561 | constexpr auto size = sizeof(FragmentArgumentBuffer); 562 | m_argumentBuffer[i] = NS::TransferPtr( 563 | device()->newBuffer(size, MTL::ResourceOptionCPUCacheModeDefault)); 564 | 565 | NS::String* label = NS::String::string( 566 | fmt::format("Argument Buffer {}", i).c_str(), NS::UTF8StringEncoding); 567 | m_argumentBuffer[i]->setLabel(label); 568 | label->release(); 569 | 570 | auto* buffer = static_cast(m_argumentBuffer[i]->contents()); 571 | // Bind each texture's GPU id into argument buffer for access in fragment shader 572 | for (auto j = 0; std::cmp_less(j, m_heapTextures.size()); ++j) 573 | { 574 | const auto texture = m_heapTextures[j]; 575 | buffer->textures[j] = texture->gpuResourceID(); 576 | 577 | buffer->transforms = reinterpret_cast(m_instanceBuffer[i]->gpuAddress()); 578 | } 579 | 580 | m_residencySet->addAllocation(m_argumentBuffer[i].get()); 581 | m_argumentTable->setAddress(m_argumentBuffer[i]->gpuAddress(), 0); 582 | } 583 | } 584 | } 585 | 586 | int main(const int argc, char** argv) 587 | { 588 | int result = EXIT_FAILURE; 589 | try 590 | { 591 | const auto example = std::make_unique(); 592 | result = example->run(argc, argv); 593 | } 594 | catch (const std::runtime_error&) 595 | { 596 | fmt::println("Exiting..."); 597 | } 598 | 599 | return result; 600 | } 601 | -------------------------------------------------------------------------------- /source/base/SimpleMath.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------------- 2 | // SimpleMath.h -- Simplified C++ Math wrapper for DirectXMath 3 | // 4 | // Copyright (c) Microsoft Corporation. 5 | // Licensed under the MIT License. 6 | // 7 | // http://go.microsoft.com/fwlink/?LinkId=248929 8 | // http://go.microsoft.com/fwlink/?LinkID=615561 9 | //------------------------------------------------------------------------------------- 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | typedef uint32_t UINT; 16 | 17 | typedef int32_t LONG; 18 | 19 | typedef struct RECT 20 | { 21 | long left; 22 | long right; 23 | long top; 24 | long bottom; 25 | } RECT; 26 | 27 | #if (defined(_WIN32) || defined(WINAPI_FAMILY)) && !(defined(_XBOX_ONE) && defined(_TITLE)) && !defined(_GAMING_XBOX) 28 | #include 29 | #endif 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #if (__cplusplus >= 202002L) 37 | #include 38 | #endif 39 | 40 | #include 41 | #include 42 | #include 43 | 44 | #ifdef __clang__ 45 | #pragma clang diagnostic push 46 | #pragma clang diagnostic ignored "-Wfloat-equal" 47 | #endif 48 | 49 | 50 | namespace DirectX 51 | { 52 | namespace SimpleMath 53 | { 54 | struct Vector2; 55 | struct Vector4; 56 | struct Matrix; 57 | struct Quaternion; 58 | struct Plane; 59 | 60 | //------------------------------------------------------------------------------ 61 | // 2D rectangle 62 | struct Rectangle 63 | { 64 | long x; 65 | long y; 66 | long width; 67 | long height; 68 | 69 | // Creators 70 | Rectangle() noexcept 71 | : x(0), y(0), width(0), height(0) 72 | { 73 | } 74 | constexpr Rectangle(long ix, long iy, long iw, long ih) noexcept 75 | : x(ix), y(iy), width(iw), height(ih) 76 | { 77 | } 78 | explicit Rectangle(const RECT& rct) noexcept 79 | : x(rct.left), y(rct.top), width(rct.right - rct.left), height(rct.bottom - rct.top) 80 | { 81 | } 82 | 83 | Rectangle(const Rectangle&) = default; 84 | Rectangle& operator=(const Rectangle&) = default; 85 | 86 | Rectangle(Rectangle&&) = default; 87 | Rectangle& operator=(Rectangle&&) = default; 88 | 89 | operator RECT() noexcept 90 | { 91 | RECT rct; 92 | rct.left = x; 93 | rct.top = y; 94 | rct.right = (x + width); 95 | rct.bottom = (y + height); 96 | return rct; 97 | } 98 | #ifdef __cplusplus_winrt 99 | operator Windows::Foundation::Rect() noexcept { return Windows::Foundation::Rect(float(x), float(y), float(width), float(height)); } 100 | #endif 101 | 102 | // Comparison operators 103 | #if (__cplusplus >= 202002L) 104 | bool operator == (const Rectangle&) const = default; 105 | auto operator <=> (const Rectangle&) const = default; 106 | #else 107 | bool operator==(const Rectangle& r) const noexcept 108 | { 109 | return (x == r.x) && (y == r.y) && (width == r.width) && (height == r.height); 110 | } 111 | bool operator!=(const Rectangle& r) const noexcept 112 | { 113 | return (x != r.x) || (y != r.y) || (width != r.width) || (height != r.height); 114 | } 115 | #endif 116 | bool operator==(const RECT& rct) const noexcept 117 | { 118 | return (x == rct.left) && (y == rct.top) && (width == (rct.right - rct.left)) 119 | && (height == (rct.bottom - rct.top)); 120 | } 121 | bool operator!=(const RECT& rct) const noexcept 122 | { 123 | return (x != rct.left) || (y != rct.top) || (width != (rct.right - rct.left)) 124 | || (height != (rct.bottom - rct.top)); 125 | } 126 | 127 | // Assignment operators 128 | Rectangle& operator=(_In_ const RECT& rct) noexcept 129 | { 130 | x = rct.left; 131 | y = rct.top; 132 | width = (rct.right - rct.left); 133 | height = (rct.bottom - rct.top); 134 | return *this; 135 | } 136 | 137 | // Rectangle operations 138 | Vector2 Location() const noexcept; 139 | Vector2 Center() const noexcept; 140 | 141 | bool IsEmpty() const noexcept 142 | { 143 | return (width == 0 && height == 0 && x == 0 && y == 0); 144 | } 145 | 146 | bool Contains(long ix, long iy) const noexcept 147 | { 148 | return (x <= ix) && (ix < (x + width)) && (y <= iy) && (iy < (y + height)); 149 | } 150 | bool Contains(const Vector2& point) const noexcept; 151 | bool Contains(const Rectangle& r) const noexcept 152 | { 153 | return (x <= r.x) && ((r.x + r.width) <= (x + width)) && (y <= r.y) 154 | && ((r.y + r.height) <= (y + height)); 155 | } 156 | bool Contains(const RECT& rct) const noexcept 157 | { 158 | return (x <= rct.left) && (rct.right <= (x + width)) && (y <= rct.top) && (rct.bottom <= (y + height)); 159 | } 160 | 161 | void Inflate(long horizAmount, long vertAmount) noexcept; 162 | 163 | bool Intersects(const Rectangle& r) const noexcept 164 | { 165 | return (r.x < (x + width)) && (x < (r.x + r.width)) && (r.y < (y + height)) && (y < (r.y + r.height)); 166 | } 167 | bool Intersects(const RECT& rct) const noexcept 168 | { 169 | return (rct.left < (x + width)) && (x < rct.right) && (rct.top < (y + height)) && (y < rct.bottom); 170 | } 171 | 172 | void Offset(long ox, long oy) noexcept 173 | { 174 | x += ox; 175 | y += oy; 176 | } 177 | 178 | // Static functions 179 | static Rectangle Intersect(const Rectangle& ra, const Rectangle& rb) noexcept; 180 | static RECT Intersect(const RECT& rcta, const RECT& rctb) noexcept; 181 | 182 | static Rectangle Union(const Rectangle& ra, const Rectangle& rb) noexcept; 183 | static RECT Union(const RECT& rcta, const RECT& rctb) noexcept; 184 | }; 185 | 186 | //------------------------------------------------------------------------------ 187 | // 2D vector 188 | struct Vector2 : public XMFLOAT2 189 | { 190 | Vector2() noexcept 191 | : XMFLOAT2(0.f, 0.f) 192 | { 193 | } 194 | constexpr explicit Vector2(float ix) noexcept 195 | : XMFLOAT2(ix, ix) 196 | { 197 | } 198 | constexpr Vector2(float ix, float iy) noexcept 199 | : XMFLOAT2(ix, iy) 200 | { 201 | } 202 | explicit Vector2(_In_reads_(2) const float* pArray) noexcept 203 | : XMFLOAT2(pArray) 204 | { 205 | } 206 | Vector2(FXMVECTOR V) noexcept 207 | { 208 | XMStoreFloat2(this, V); 209 | } 210 | Vector2(const XMFLOAT2& V) noexcept 211 | { 212 | this->x = V.x; 213 | this->y = V.y; 214 | } 215 | explicit Vector2(const XMVECTORF32& F) noexcept 216 | { 217 | this->x = F.f[0]; 218 | this->y = F.f[1]; 219 | } 220 | 221 | Vector2(const Vector2&) = default; 222 | Vector2& operator=(const Vector2&) = default; 223 | 224 | Vector2(Vector2&&) = default; 225 | Vector2& operator=(Vector2&&) = default; 226 | 227 | operator XMVECTOR() const noexcept 228 | { 229 | return XMLoadFloat2(this); 230 | } 231 | 232 | // Comparison operators 233 | bool operator==(const Vector2& V) const noexcept; 234 | bool operator!=(const Vector2& V) const noexcept; 235 | 236 | // Assignment operators 237 | Vector2& operator=(const XMVECTORF32& F) noexcept 238 | { 239 | x = F.f[0]; 240 | y = F.f[1]; 241 | return *this; 242 | } 243 | Vector2& operator+=(const Vector2& V) noexcept; 244 | Vector2& operator-=(const Vector2& V) noexcept; 245 | Vector2& operator*=(const Vector2& V) noexcept; 246 | Vector2& operator*=(float S) noexcept; 247 | Vector2& operator/=(float S) noexcept; 248 | 249 | // Unary operators 250 | Vector2 operator+() const noexcept 251 | { 252 | return *this; 253 | } 254 | Vector2 operator-() const noexcept 255 | { 256 | return Vector2(-x, -y); 257 | } 258 | 259 | // Vector operations 260 | bool InBounds(const Vector2& Bounds) const noexcept; 261 | 262 | float Length() const noexcept; 263 | float LengthSquared() const noexcept; 264 | 265 | float Dot(const Vector2& V) const noexcept; 266 | void Cross(const Vector2& V, Vector2& result) const noexcept; 267 | Vector2 Cross(const Vector2& V) const noexcept; 268 | 269 | void Normalize() noexcept; 270 | void Normalize(Vector2& result) const noexcept; 271 | 272 | void Clamp(const Vector2& vmin, const Vector2& vmax) noexcept; 273 | void Clamp(const Vector2& vmin, const Vector2& vmax, Vector2& result) const noexcept; 274 | 275 | // Static functions 276 | static float Distance(const Vector2& v1, const Vector2& v2) noexcept; 277 | static float DistanceSquared(const Vector2& v1, const Vector2& v2) noexcept; 278 | 279 | static void Min(const Vector2& v1, const Vector2& v2, Vector2& result) noexcept; 280 | static Vector2 Min(const Vector2& v1, const Vector2& v2) noexcept; 281 | 282 | static void Max(const Vector2& v1, const Vector2& v2, Vector2& result) noexcept; 283 | static Vector2 Max(const Vector2& v1, const Vector2& v2) noexcept; 284 | 285 | static void Lerp(const Vector2& v1, const Vector2& v2, float t, Vector2& result) noexcept; 286 | static Vector2 Lerp(const Vector2& v1, const Vector2& v2, float t) noexcept; 287 | 288 | static void SmoothStep(const Vector2& v1, const Vector2& v2, float t, Vector2& result) noexcept; 289 | static Vector2 SmoothStep(const Vector2& v1, const Vector2& v2, float t) noexcept; 290 | 291 | static void Barycentric(const Vector2& v1, 292 | const Vector2& v2, 293 | const Vector2& v3, 294 | float f, 295 | float g, 296 | Vector2& result) noexcept; 297 | static Vector2 298 | Barycentric(const Vector2& v1, const Vector2& v2, const Vector2& v3, float f, float g) noexcept; 299 | 300 | static void CatmullRom(const Vector2& v1, 301 | const Vector2& v2, 302 | const Vector2& v3, 303 | const Vector2& v4, 304 | float t, 305 | Vector2& result) noexcept; 306 | static Vector2 307 | CatmullRom(const Vector2& v1, const Vector2& v2, const Vector2& v3, const Vector2& v4, float t) noexcept; 308 | 309 | static void Hermite(const Vector2& v1, 310 | const Vector2& t1, 311 | const Vector2& v2, 312 | const Vector2& t2, 313 | float t, 314 | Vector2& result) noexcept; 315 | static Vector2 316 | Hermite(const Vector2& v1, const Vector2& t1, const Vector2& v2, const Vector2& t2, float t) noexcept; 317 | 318 | static void Reflect(const Vector2& ivec, const Vector2& nvec, Vector2& result) noexcept; 319 | static Vector2 Reflect(const Vector2& ivec, const Vector2& nvec) noexcept; 320 | 321 | static void 322 | Refract(const Vector2& ivec, const Vector2& nvec, float refractionIndex, Vector2& result) noexcept; 323 | static Vector2 Refract(const Vector2& ivec, const Vector2& nvec, float refractionIndex) noexcept; 324 | 325 | static void Transform(const Vector2& v, const Quaternion& quat, Vector2& result) noexcept; 326 | static Vector2 Transform(const Vector2& v, const Quaternion& quat) noexcept; 327 | 328 | static void Transform(const Vector2& v, const Matrix& m, Vector2& result) noexcept; 329 | static Vector2 Transform(const Vector2& v, const Matrix& m) noexcept; 330 | static void Transform(_In_reads_(count) 331 | const Vector2* varray, 332 | size_t count, 333 | const Matrix& m, 334 | _Out_writes_(count) 335 | Vector2* resultArray) noexcept; 336 | 337 | static void Transform(const Vector2& v, const Matrix& m, Vector4& result) noexcept; 338 | static void Transform(_In_reads_(count) 339 | const Vector2* varray, 340 | size_t count, 341 | const Matrix& m, 342 | _Out_writes_(count) 343 | Vector4* resultArray) noexcept; 344 | 345 | static void TransformNormal(const Vector2& v, const Matrix& m, Vector2& result) noexcept; 346 | static Vector2 TransformNormal(const Vector2& v, const Matrix& m) noexcept; 347 | static void TransformNormal(_In_reads_(count) 348 | const Vector2* varray, 349 | size_t count, 350 | const Matrix& m, 351 | _Out_writes_(count) 352 | Vector2* resultArray) noexcept; 353 | 354 | // Constants 355 | static const Vector2 Zero; 356 | static const Vector2 One; 357 | static const Vector2 UnitX; 358 | static const Vector2 UnitY; 359 | }; 360 | 361 | // Binary operators 362 | Vector2 operator+(const Vector2& V1, const Vector2& V2) noexcept; 363 | Vector2 operator-(const Vector2& V1, const Vector2& V2) noexcept; 364 | Vector2 operator*(const Vector2& V1, const Vector2& V2) noexcept; 365 | Vector2 operator*(const Vector2& V, float S) noexcept; 366 | Vector2 operator/(const Vector2& V1, const Vector2& V2) noexcept; 367 | Vector2 operator/(const Vector2& V, float S) noexcept; 368 | Vector2 operator*(float S, const Vector2& V) noexcept; 369 | 370 | //------------------------------------------------------------------------------ 371 | // 3D vector 372 | struct Vector3 : public XMFLOAT3 373 | { 374 | Vector3() noexcept 375 | : XMFLOAT3(0.f, 0.f, 0.f) 376 | { 377 | } 378 | constexpr explicit Vector3(float ix) noexcept 379 | : XMFLOAT3(ix, ix, ix) 380 | { 381 | } 382 | constexpr Vector3(float ix, float iy, float iz) noexcept 383 | : XMFLOAT3(ix, iy, iz) 384 | { 385 | } 386 | explicit Vector3(_In_reads_(3) const float* pArray) noexcept 387 | : XMFLOAT3(pArray) 388 | { 389 | } 390 | Vector3(FXMVECTOR V) noexcept 391 | { 392 | XMStoreFloat3(this, V); 393 | } 394 | Vector3(const XMFLOAT3& V) noexcept 395 | { 396 | this->x = V.x; 397 | this->y = V.y; 398 | this->z = V.z; 399 | } 400 | explicit Vector3(const XMVECTORF32& F) noexcept 401 | { 402 | this->x = F.f[0]; 403 | this->y = F.f[1]; 404 | this->z = F.f[2]; 405 | } 406 | 407 | Vector3(const Vector3&) = default; 408 | Vector3& operator=(const Vector3&) = default; 409 | 410 | Vector3(Vector3&&) = default; 411 | Vector3& operator=(Vector3&&) = default; 412 | 413 | operator XMVECTOR() const noexcept 414 | { 415 | return XMLoadFloat3(this); 416 | } 417 | 418 | // Comparison operators 419 | bool operator==(const Vector3& V) const noexcept; 420 | bool operator!=(const Vector3& V) const noexcept; 421 | 422 | // Assignment operators 423 | Vector3& operator=(const XMVECTORF32& F) noexcept 424 | { 425 | x = F.f[0]; 426 | y = F.f[1]; 427 | z = F.f[2]; 428 | return *this; 429 | } 430 | Vector3& operator+=(const Vector3& V) noexcept; 431 | Vector3& operator-=(const Vector3& V) noexcept; 432 | Vector3& operator*=(const Vector3& V) noexcept; 433 | Vector3& operator*=(float S) noexcept; 434 | Vector3& operator/=(float S) noexcept; 435 | 436 | // Unary operators 437 | Vector3 operator+() const noexcept 438 | { 439 | return *this; 440 | } 441 | Vector3 operator-() const noexcept; 442 | 443 | // Vector operations 444 | bool InBounds(const Vector3& Bounds) const noexcept; 445 | 446 | float Length() const noexcept; 447 | float LengthSquared() const noexcept; 448 | 449 | float Dot(const Vector3& V) const noexcept; 450 | void Cross(const Vector3& V, Vector3& result) const noexcept; 451 | Vector3 Cross(const Vector3& V) const noexcept; 452 | 453 | void Normalize() noexcept; 454 | void Normalize(Vector3& result) const noexcept; 455 | 456 | void Clamp(const Vector3& vmin, const Vector3& vmax) noexcept; 457 | void Clamp(const Vector3& vmin, const Vector3& vmax, Vector3& result) const noexcept; 458 | 459 | // Static functions 460 | static float Distance(const Vector3& v1, const Vector3& v2) noexcept; 461 | static float DistanceSquared(const Vector3& v1, const Vector3& v2) noexcept; 462 | 463 | static void Min(const Vector3& v1, const Vector3& v2, Vector3& result) noexcept; 464 | static Vector3 Min(const Vector3& v1, const Vector3& v2) noexcept; 465 | 466 | static void Max(const Vector3& v1, const Vector3& v2, Vector3& result) noexcept; 467 | static Vector3 Max(const Vector3& v1, const Vector3& v2) noexcept; 468 | 469 | static void Lerp(const Vector3& v1, const Vector3& v2, float t, Vector3& result) noexcept; 470 | static Vector3 Lerp(const Vector3& v1, const Vector3& v2, float t) noexcept; 471 | 472 | static void SmoothStep(const Vector3& v1, const Vector3& v2, float t, Vector3& result) noexcept; 473 | static Vector3 SmoothStep(const Vector3& v1, const Vector3& v2, float t) noexcept; 474 | 475 | static void Barycentric(const Vector3& v1, 476 | const Vector3& v2, 477 | const Vector3& v3, 478 | float f, 479 | float g, 480 | Vector3& result) noexcept; 481 | static Vector3 482 | Barycentric(const Vector3& v1, const Vector3& v2, const Vector3& v3, float f, float g) noexcept; 483 | 484 | static void CatmullRom(const Vector3& v1, 485 | const Vector3& v2, 486 | const Vector3& v3, 487 | const Vector3& v4, 488 | float t, 489 | Vector3& result) noexcept; 490 | static Vector3 491 | CatmullRom(const Vector3& v1, const Vector3& v2, const Vector3& v3, const Vector3& v4, float t) noexcept; 492 | 493 | static void Hermite(const Vector3& v1, 494 | const Vector3& t1, 495 | const Vector3& v2, 496 | const Vector3& t2, 497 | float t, 498 | Vector3& result) noexcept; 499 | static Vector3 500 | Hermite(const Vector3& v1, const Vector3& t1, const Vector3& v2, const Vector3& t2, float t) noexcept; 501 | 502 | static void Reflect(const Vector3& ivec, const Vector3& nvec, Vector3& result) noexcept; 503 | static Vector3 Reflect(const Vector3& ivec, const Vector3& nvec) noexcept; 504 | 505 | static void 506 | Refract(const Vector3& ivec, const Vector3& nvec, float refractionIndex, Vector3& result) noexcept; 507 | static Vector3 Refract(const Vector3& ivec, const Vector3& nvec, float refractionIndex) noexcept; 508 | 509 | static void Transform(const Vector3& v, const Quaternion& quat, Vector3& result) noexcept; 510 | static Vector3 Transform(const Vector3& v, const Quaternion& quat) noexcept; 511 | 512 | static void Transform(const Vector3& v, const Matrix& m, Vector3& result) noexcept; 513 | static Vector3 Transform(const Vector3& v, const Matrix& m) noexcept; 514 | static void Transform(_In_reads_(count) 515 | const Vector3* varray, 516 | size_t count, 517 | const Matrix& m, 518 | _Out_writes_(count) 519 | Vector3* resultArray) noexcept; 520 | 521 | static void Transform(const Vector3& v, const Matrix& m, Vector4& result) noexcept; 522 | static void Transform(_In_reads_(count) 523 | const Vector3* varray, 524 | size_t count, 525 | const Matrix& m, 526 | _Out_writes_(count) 527 | Vector4* resultArray) noexcept; 528 | 529 | static void TransformNormal(const Vector3& v, const Matrix& m, Vector3& result) noexcept; 530 | static Vector3 TransformNormal(const Vector3& v, const Matrix& m) noexcept; 531 | static void TransformNormal(_In_reads_(count) 532 | const Vector3* varray, 533 | size_t count, 534 | const Matrix& m, 535 | _Out_writes_(count) 536 | Vector3* resultArray) noexcept; 537 | 538 | // Constants 539 | static const Vector3 Zero; 540 | static const Vector3 One; 541 | static const Vector3 UnitX; 542 | static const Vector3 UnitY; 543 | static const Vector3 UnitZ; 544 | static const Vector3 Up; 545 | static const Vector3 Down; 546 | static const Vector3 Right; 547 | static const Vector3 Left; 548 | static const Vector3 Forward; 549 | static const Vector3 Backward; 550 | }; 551 | 552 | // Binary operators 553 | Vector3 operator+(const Vector3& V1, const Vector3& V2) noexcept; 554 | Vector3 operator-(const Vector3& V1, const Vector3& V2) noexcept; 555 | Vector3 operator*(const Vector3& V1, const Vector3& V2) noexcept; 556 | Vector3 operator*(const Vector3& V, float S) noexcept; 557 | Vector3 operator/(const Vector3& V1, const Vector3& V2) noexcept; 558 | Vector3 operator/(const Vector3& V, float S) noexcept; 559 | Vector3 operator*(float S, const Vector3& V) noexcept; 560 | 561 | //------------------------------------------------------------------------------ 562 | // 4D vector 563 | struct Vector4 : public XMFLOAT4 564 | { 565 | Vector4() noexcept 566 | : XMFLOAT4(0.f, 0.f, 0.f, 0.f) 567 | { 568 | } 569 | constexpr explicit Vector4(float ix) noexcept 570 | : XMFLOAT4(ix, ix, ix, ix) 571 | { 572 | } 573 | constexpr Vector4(float ix, float iy, float iz, float iw) noexcept 574 | : XMFLOAT4(ix, iy, iz, iw) 575 | { 576 | } 577 | explicit Vector4(_In_reads_(4) const float* pArray) noexcept 578 | : XMFLOAT4(pArray) 579 | { 580 | } 581 | Vector4(FXMVECTOR V) noexcept 582 | { 583 | XMStoreFloat4(this, V); 584 | } 585 | Vector4(const XMFLOAT4& V) noexcept 586 | { 587 | this->x = V.x; 588 | this->y = V.y; 589 | this->z = V.z; 590 | this->w = V.w; 591 | } 592 | explicit Vector4(const XMVECTORF32& F) noexcept 593 | { 594 | this->x = F.f[0]; 595 | this->y = F.f[1]; 596 | this->z = F.f[2]; 597 | this->w = F.f[3]; 598 | } 599 | 600 | Vector4(const Vector4&) = default; 601 | Vector4& operator=(const Vector4&) = default; 602 | 603 | Vector4(Vector4&&) = default; 604 | Vector4& operator=(Vector4&&) = default; 605 | 606 | operator XMVECTOR() const noexcept 607 | { 608 | return XMLoadFloat4(this); 609 | } 610 | 611 | // Comparison operators 612 | bool operator==(const Vector4& V) const noexcept; 613 | bool operator!=(const Vector4& V) const noexcept; 614 | 615 | // Assignment operators 616 | Vector4& operator=(const XMVECTORF32& F) noexcept 617 | { 618 | x = F.f[0]; 619 | y = F.f[1]; 620 | z = F.f[2]; 621 | w = F.f[3]; 622 | return *this; 623 | } 624 | Vector4& operator+=(const Vector4& V) noexcept; 625 | Vector4& operator-=(const Vector4& V) noexcept; 626 | Vector4& operator*=(const Vector4& V) noexcept; 627 | Vector4& operator*=(float S) noexcept; 628 | Vector4& operator/=(float S) noexcept; 629 | 630 | // Unary operators 631 | Vector4 operator+() const noexcept 632 | { 633 | return *this; 634 | } 635 | Vector4 operator-() const noexcept; 636 | 637 | // Vector operations 638 | bool InBounds(const Vector4& Bounds) const noexcept; 639 | 640 | float Length() const noexcept; 641 | float LengthSquared() const noexcept; 642 | 643 | float Dot(const Vector4& V) const noexcept; 644 | void Cross(const Vector4& v1, const Vector4& v2, Vector4& result) const noexcept; 645 | Vector4 Cross(const Vector4& v1, const Vector4& v2) const noexcept; 646 | 647 | void Normalize() noexcept; 648 | void Normalize(Vector4& result) const noexcept; 649 | 650 | void Clamp(const Vector4& vmin, const Vector4& vmax) noexcept; 651 | void Clamp(const Vector4& vmin, const Vector4& vmax, Vector4& result) const noexcept; 652 | 653 | // Static functions 654 | static float Distance(const Vector4& v1, const Vector4& v2) noexcept; 655 | static float DistanceSquared(const Vector4& v1, const Vector4& v2) noexcept; 656 | 657 | static void Min(const Vector4& v1, const Vector4& v2, Vector4& result) noexcept; 658 | static Vector4 Min(const Vector4& v1, const Vector4& v2) noexcept; 659 | 660 | static void Max(const Vector4& v1, const Vector4& v2, Vector4& result) noexcept; 661 | static Vector4 Max(const Vector4& v1, const Vector4& v2) noexcept; 662 | 663 | static void Lerp(const Vector4& v1, const Vector4& v2, float t, Vector4& result) noexcept; 664 | static Vector4 Lerp(const Vector4& v1, const Vector4& v2, float t) noexcept; 665 | 666 | static void SmoothStep(const Vector4& v1, const Vector4& v2, float t, Vector4& result) noexcept; 667 | static Vector4 SmoothStep(const Vector4& v1, const Vector4& v2, float t) noexcept; 668 | 669 | static void Barycentric(const Vector4& v1, 670 | const Vector4& v2, 671 | const Vector4& v3, 672 | float f, 673 | float g, 674 | Vector4& result) noexcept; 675 | static Vector4 676 | Barycentric(const Vector4& v1, const Vector4& v2, const Vector4& v3, float f, float g) noexcept; 677 | 678 | static void CatmullRom(const Vector4& v1, 679 | const Vector4& v2, 680 | const Vector4& v3, 681 | const Vector4& v4, 682 | float t, 683 | Vector4& result) noexcept; 684 | static Vector4 685 | CatmullRom(const Vector4& v1, const Vector4& v2, const Vector4& v3, const Vector4& v4, float t) noexcept; 686 | 687 | static void Hermite(const Vector4& v1, 688 | const Vector4& t1, 689 | const Vector4& v2, 690 | const Vector4& t2, 691 | float t, 692 | Vector4& result) noexcept; 693 | static Vector4 694 | Hermite(const Vector4& v1, const Vector4& t1, const Vector4& v2, const Vector4& t2, float t) noexcept; 695 | 696 | static void Reflect(const Vector4& ivec, const Vector4& nvec, Vector4& result) noexcept; 697 | static Vector4 Reflect(const Vector4& ivec, const Vector4& nvec) noexcept; 698 | 699 | static void 700 | Refract(const Vector4& ivec, const Vector4& nvec, float refractionIndex, Vector4& result) noexcept; 701 | static Vector4 Refract(const Vector4& ivec, const Vector4& nvec, float refractionIndex) noexcept; 702 | 703 | static void Transform(const Vector2& v, const Quaternion& quat, Vector4& result) noexcept; 704 | static Vector4 Transform(const Vector2& v, const Quaternion& quat) noexcept; 705 | 706 | static void Transform(const Vector3& v, const Quaternion& quat, Vector4& result) noexcept; 707 | static Vector4 Transform(const Vector3& v, const Quaternion& quat) noexcept; 708 | 709 | static void Transform(const Vector4& v, const Quaternion& quat, Vector4& result) noexcept; 710 | static Vector4 Transform(const Vector4& v, const Quaternion& quat) noexcept; 711 | 712 | static void Transform(const Vector4& v, const Matrix& m, Vector4& result) noexcept; 713 | static Vector4 Transform(const Vector4& v, const Matrix& m) noexcept; 714 | static void Transform(_In_reads_(count) 715 | const Vector4* varray, 716 | size_t count, 717 | const Matrix& m, 718 | _Out_writes_(count) 719 | Vector4* resultArray) noexcept; 720 | 721 | // Constants 722 | static const Vector4 Zero; 723 | static const Vector4 One; 724 | static const Vector4 UnitX; 725 | static const Vector4 UnitY; 726 | static const Vector4 UnitZ; 727 | static const Vector4 UnitW; 728 | }; 729 | 730 | // Binary operators 731 | Vector4 operator+(const Vector4& V1, const Vector4& V2) noexcept; 732 | Vector4 operator-(const Vector4& V1, const Vector4& V2) noexcept; 733 | Vector4 operator*(const Vector4& V1, const Vector4& V2) noexcept; 734 | Vector4 operator*(const Vector4& V, float S) noexcept; 735 | Vector4 operator/(const Vector4& V1, const Vector4& V2) noexcept; 736 | Vector4 operator/(const Vector4& V, float S) noexcept; 737 | Vector4 operator*(float S, const Vector4& V) noexcept; 738 | 739 | //------------------------------------------------------------------------------ 740 | // 4x4 Matrix (assumes right-handed cooordinates) 741 | struct Matrix : public XMFLOAT4X4 742 | { 743 | Matrix() noexcept 744 | : XMFLOAT4X4(1.f, 0, 0, 0, 745 | 0, 1.f, 0, 0, 746 | 0, 0, 1.f, 0, 747 | 0, 0, 0, 1.f) 748 | { 749 | } 750 | constexpr Matrix(float m00, float m01, float m02, float m03, 751 | float m10, float m11, float m12, float m13, 752 | float m20, float m21, float m22, float m23, 753 | float m30, float m31, float m32, float m33) noexcept 754 | : XMFLOAT4X4(m00, m01, m02, m03, 755 | m10, m11, m12, m13, 756 | m20, m21, m22, m23, 757 | m30, m31, m32, m33) 758 | { 759 | } 760 | explicit Matrix(const Vector3& r0, const Vector3& r1, const Vector3& r2) noexcept 761 | : XMFLOAT4X4(r0.x, r0.y, r0.z, 0, 762 | r1.x, r1.y, r1.z, 0, 763 | r2.x, r2.y, r2.z, 0, 764 | 0, 0, 0, 1.f) 765 | { 766 | } 767 | explicit Matrix(const Vector4& r0, const Vector4& r1, const Vector4& r2, const Vector4& r3) noexcept 768 | : XMFLOAT4X4(r0.x, r0.y, r0.z, r0.w, 769 | r1.x, r1.y, r1.z, r1.w, 770 | r2.x, r2.y, r2.z, r2.w, 771 | r3.x, r3.y, r3.z, r3.w) 772 | { 773 | } 774 | Matrix(const XMFLOAT4X4& M) noexcept 775 | { 776 | memcpy(this, &M, sizeof(XMFLOAT4X4)); 777 | } 778 | Matrix(const XMFLOAT3X3& M) noexcept; 779 | Matrix(const XMFLOAT4X3& M) noexcept; 780 | 781 | explicit Matrix(_In_reads_(16) const float* pArray) noexcept 782 | : XMFLOAT4X4(pArray) 783 | { 784 | } 785 | Matrix(CXMMATRIX M) noexcept 786 | { 787 | XMStoreFloat4x4(this, M); 788 | } 789 | 790 | Matrix(const Matrix&) = default; 791 | Matrix& operator=(const Matrix&) = default; 792 | 793 | Matrix(Matrix&&) = default; 794 | Matrix& operator=(Matrix&&) = default; 795 | 796 | operator XMMATRIX() const noexcept 797 | { 798 | return XMLoadFloat4x4(this); 799 | } 800 | 801 | // Comparison operators 802 | bool operator==(const Matrix& M) const noexcept; 803 | bool operator!=(const Matrix& M) const noexcept; 804 | 805 | // Assignment operators 806 | Matrix& operator=(const XMFLOAT3X3& M) noexcept; 807 | Matrix& operator=(const XMFLOAT4X3& M) noexcept; 808 | Matrix& operator+=(const Matrix& M) noexcept; 809 | Matrix& operator-=(const Matrix& M) noexcept; 810 | Matrix& operator*=(const Matrix& M) noexcept; 811 | Matrix& operator*=(float S) noexcept; 812 | Matrix& operator/=(float S) noexcept; 813 | 814 | Matrix& operator/=(const Matrix& M) noexcept; 815 | // Element-wise divide 816 | 817 | // Unary operators 818 | Matrix operator+() const noexcept 819 | { 820 | return *this; 821 | } 822 | Matrix operator-() const noexcept; 823 | 824 | // Properties 825 | Vector3 Up() const noexcept 826 | { 827 | return Vector3(_21, _22, _23); 828 | } 829 | void Up(const Vector3& v) noexcept 830 | { 831 | _21 = v.x; 832 | _22 = v.y; 833 | _23 = v.z; 834 | } 835 | 836 | Vector3 Down() const noexcept 837 | { 838 | return Vector3(-_21, -_22, -_23); 839 | } 840 | void Down(const Vector3& v) noexcept 841 | { 842 | _21 = -v.x; 843 | _22 = -v.y; 844 | _23 = -v.z; 845 | } 846 | 847 | Vector3 Right() const noexcept 848 | { 849 | return Vector3(_11, _12, _13); 850 | } 851 | void Right(const Vector3& v) noexcept 852 | { 853 | _11 = v.x; 854 | _12 = v.y; 855 | _13 = v.z; 856 | } 857 | 858 | Vector3 Left() const noexcept 859 | { 860 | return Vector3(-_11, -_12, -_13); 861 | } 862 | void Left(const Vector3& v) noexcept 863 | { 864 | _11 = -v.x; 865 | _12 = -v.y; 866 | _13 = -v.z; 867 | } 868 | 869 | Vector3 Forward() const noexcept 870 | { 871 | return Vector3(-_31, -_32, -_33); 872 | } 873 | void Forward(const Vector3& v) noexcept 874 | { 875 | _31 = -v.x; 876 | _32 = -v.y; 877 | _33 = -v.z; 878 | } 879 | 880 | Vector3 Backward() const noexcept 881 | { 882 | return Vector3(_31, _32, _33); 883 | } 884 | void Backward(const Vector3& v) noexcept 885 | { 886 | _31 = v.x; 887 | _32 = v.y; 888 | _33 = v.z; 889 | } 890 | 891 | Vector3 Translation() const noexcept 892 | { 893 | return Vector3(_41, _42, _43); 894 | } 895 | void Translation(const Vector3& v) noexcept 896 | { 897 | _41 = v.x; 898 | _42 = v.y; 899 | _43 = v.z; 900 | } 901 | 902 | // Matrix operations 903 | bool Decompose(Vector3& scale, Quaternion& rotation, Vector3& translation) noexcept; 904 | 905 | Matrix Transpose() const noexcept; 906 | void Transpose(Matrix& result) const noexcept; 907 | 908 | Matrix Invert() const noexcept; 909 | void Invert(Matrix& result) const noexcept; 910 | 911 | float Determinant() const noexcept; 912 | 913 | // Computes rotation about y-axis (y), then x-axis (x), then z-axis (z) 914 | Vector3 ToEuler() const noexcept; 915 | 916 | // Static functions 917 | static Matrix CreateBillboard( 918 | const Vector3& object, 919 | const Vector3& cameraPosition, 920 | const Vector3& cameraUp, 921 | _In_opt_ 922 | const Vector3* cameraForward = nullptr) noexcept; 923 | 924 | static Matrix CreateConstrainedBillboard( 925 | const Vector3& object, 926 | const Vector3& cameraPosition, 927 | const Vector3& rotateAxis, 928 | _In_opt_ 929 | const Vector3* cameraForward = nullptr, 930 | _In_opt_ 931 | const Vector3* objectForward = nullptr) noexcept; 932 | 933 | static Matrix CreateTranslation(const Vector3& position) noexcept; 934 | static Matrix CreateTranslation(float x, float y, float z) noexcept; 935 | 936 | static Matrix CreateScale(const Vector3& scales) noexcept; 937 | static Matrix CreateScale(float xs, float ys, float zs) noexcept; 938 | static Matrix CreateScale(float scale) noexcept; 939 | 940 | static Matrix CreateRotationX(float radians) noexcept; 941 | static Matrix CreateRotationY(float radians) noexcept; 942 | static Matrix CreateRotationZ(float radians) noexcept; 943 | 944 | static Matrix CreateFromAxisAngle(const Vector3& axis, float angle) noexcept; 945 | 946 | static Matrix 947 | CreatePerspectiveFieldOfView(float fov, float aspectRatio, float nearPlane, float farPlane) noexcept; 948 | static Matrix CreatePerspective(float width, float height, float nearPlane, float farPlane) noexcept; 949 | static Matrix CreatePerspectiveOffCenter(float left, 950 | float right, 951 | float bottom, 952 | float top, 953 | float nearPlane, 954 | float farPlane) noexcept; 955 | static Matrix CreateOrthographic(float width, float height, float zNearPlane, float zFarPlane) noexcept; 956 | static Matrix CreateOrthographicOffCenter(float left, 957 | float right, 958 | float bottom, 959 | float top, 960 | float zNearPlane, 961 | float zFarPlane) noexcept; 962 | 963 | static Matrix CreateLookAt(const Vector3& position, const Vector3& target, const Vector3& up) noexcept; 964 | static Matrix CreateWorld(const Vector3& position, const Vector3& forward, const Vector3& up) noexcept; 965 | 966 | static Matrix CreateFromQuaternion(const Quaternion& quat) noexcept; 967 | 968 | // Rotates about y-axis (yaw), then x-axis (pitch), then z-axis (roll) 969 | static Matrix CreateFromYawPitchRoll(float yaw, float pitch, float roll) noexcept; 970 | 971 | // Rotates about y-axis (angles.y), then x-axis (angles.x), then z-axis (angles.z) 972 | static Matrix CreateFromYawPitchRoll(const Vector3& angles) noexcept; 973 | 974 | static Matrix CreateShadow(const Vector3& lightDir, const Plane& plane) noexcept; 975 | 976 | static Matrix CreateReflection(const Plane& plane) noexcept; 977 | 978 | static void Lerp(const Matrix& M1, const Matrix& M2, float t, Matrix& result) noexcept; 979 | static Matrix Lerp(const Matrix& M1, const Matrix& M2, float t) noexcept; 980 | 981 | static void Transform(const Matrix& M, const Quaternion& rotation, Matrix& result) noexcept; 982 | static Matrix Transform(const Matrix& M, const Quaternion& rotation) noexcept; 983 | 984 | // Constants 985 | static const Matrix Identity; 986 | }; 987 | 988 | // Binary operators 989 | Matrix operator+(const Matrix& M1, const Matrix& M2) noexcept; 990 | Matrix operator-(const Matrix& M1, const Matrix& M2) noexcept; 991 | Matrix operator*(const Matrix& M1, const Matrix& M2) noexcept; 992 | Matrix operator*(const Matrix& M, float S) noexcept; 993 | Matrix operator/(const Matrix& M, float S) noexcept; 994 | Matrix operator/(const Matrix& M1, const Matrix& M2) noexcept; 995 | // Element-wise divide 996 | Matrix operator*(float S, const Matrix& M) noexcept; 997 | 998 | //----------------------------------------------------------------------------- 999 | // Plane 1000 | struct Plane : public XMFLOAT4 1001 | { 1002 | Plane() noexcept 1003 | : XMFLOAT4(0.f, 1.f, 0.f, 0.f) 1004 | { 1005 | } 1006 | constexpr Plane(float ix, float iy, float iz, float iw) noexcept 1007 | : XMFLOAT4(ix, iy, iz, iw) 1008 | { 1009 | } 1010 | Plane(const Vector3& normal, float d) noexcept 1011 | : XMFLOAT4(normal.x, normal.y, normal.z, d) 1012 | { 1013 | } 1014 | Plane(const Vector3& point1, const Vector3& point2, const Vector3& point3) noexcept; 1015 | Plane(const Vector3& point, const Vector3& normal) noexcept; 1016 | explicit Plane(const Vector4& v) noexcept 1017 | : XMFLOAT4(v.x, v.y, v.z, v.w) 1018 | { 1019 | } 1020 | explicit Plane(_In_reads_(4) const float* pArray) noexcept 1021 | : XMFLOAT4(pArray) 1022 | { 1023 | } 1024 | Plane(FXMVECTOR V) noexcept 1025 | { 1026 | XMStoreFloat4(this, V); 1027 | } 1028 | Plane(const XMFLOAT4& p) noexcept 1029 | { 1030 | this->x = p.x; 1031 | this->y = p.y; 1032 | this->z = p.z; 1033 | this->w = p.w; 1034 | } 1035 | explicit Plane(const XMVECTORF32& F) noexcept 1036 | { 1037 | this->x = F.f[0]; 1038 | this->y = F.f[1]; 1039 | this->z = F.f[2]; 1040 | this->w = F.f[3]; 1041 | } 1042 | 1043 | Plane(const Plane&) = default; 1044 | Plane& operator=(const Plane&) = default; 1045 | 1046 | Plane(Plane&&) = default; 1047 | Plane& operator=(Plane&&) = default; 1048 | 1049 | operator XMVECTOR() const noexcept 1050 | { 1051 | return XMLoadFloat4(this); 1052 | } 1053 | 1054 | // Comparison operators 1055 | bool operator==(const Plane& p) const noexcept; 1056 | bool operator!=(const Plane& p) const noexcept; 1057 | 1058 | // Assignment operators 1059 | Plane& operator=(const XMVECTORF32& F) noexcept 1060 | { 1061 | x = F.f[0]; 1062 | y = F.f[1]; 1063 | z = F.f[2]; 1064 | w = F.f[3]; 1065 | return *this; 1066 | } 1067 | 1068 | // Properties 1069 | Vector3 Normal() const noexcept 1070 | { 1071 | return Vector3(x, y, z); 1072 | } 1073 | void Normal(const Vector3& normal) noexcept 1074 | { 1075 | x = normal.x; 1076 | y = normal.y; 1077 | z = normal.z; 1078 | } 1079 | 1080 | float D() const noexcept 1081 | { 1082 | return w; 1083 | } 1084 | void D(float d) noexcept 1085 | { 1086 | w = d; 1087 | } 1088 | 1089 | // Plane operations 1090 | void Normalize() noexcept; 1091 | void Normalize(Plane& result) const noexcept; 1092 | 1093 | float Dot(const Vector4& v) const noexcept; 1094 | float DotCoordinate(const Vector3& position) const noexcept; 1095 | float DotNormal(const Vector3& normal) const noexcept; 1096 | 1097 | // Static functions 1098 | static void Transform(const Plane& plane, const Matrix& M, Plane& result) noexcept; 1099 | static Plane Transform(const Plane& plane, const Matrix& M) noexcept; 1100 | 1101 | static void Transform(const Plane& plane, const Quaternion& rotation, Plane& result) noexcept; 1102 | static Plane Transform(const Plane& plane, const Quaternion& rotation) noexcept; 1103 | // Input quaternion must be the inverse transpose of the transformation 1104 | }; 1105 | 1106 | //------------------------------------------------------------------------------ 1107 | // Quaternion 1108 | struct Quaternion : public XMFLOAT4 1109 | { 1110 | Quaternion() noexcept 1111 | : XMFLOAT4(0, 0, 0, 1.f) 1112 | { 1113 | } 1114 | constexpr Quaternion(float ix, float iy, float iz, float iw) noexcept 1115 | : XMFLOAT4(ix, iy, iz, iw) 1116 | { 1117 | } 1118 | Quaternion(const Vector3& v, float scalar) noexcept 1119 | : XMFLOAT4(v.x, v.y, v.z, scalar) 1120 | { 1121 | } 1122 | explicit Quaternion(const Vector4& v) noexcept 1123 | : XMFLOAT4(v.x, v.y, v.z, v.w) 1124 | { 1125 | } 1126 | explicit Quaternion(_In_reads_(4) const float* pArray) noexcept 1127 | : XMFLOAT4(pArray) 1128 | { 1129 | } 1130 | Quaternion(FXMVECTOR V) noexcept 1131 | { 1132 | XMStoreFloat4(this, V); 1133 | } 1134 | Quaternion(const XMFLOAT4& q) noexcept 1135 | { 1136 | this->x = q.x; 1137 | this->y = q.y; 1138 | this->z = q.z; 1139 | this->w = q.w; 1140 | } 1141 | explicit Quaternion(const XMVECTORF32& F) noexcept 1142 | { 1143 | this->x = F.f[0]; 1144 | this->y = F.f[1]; 1145 | this->z = F.f[2]; 1146 | this->w = F.f[3]; 1147 | } 1148 | 1149 | Quaternion(const Quaternion&) = default; 1150 | Quaternion& operator=(const Quaternion&) = default; 1151 | 1152 | Quaternion(Quaternion&&) = default; 1153 | Quaternion& operator=(Quaternion&&) = default; 1154 | 1155 | operator XMVECTOR() const noexcept 1156 | { 1157 | return XMLoadFloat4(this); 1158 | } 1159 | 1160 | // Comparison operators 1161 | bool operator==(const Quaternion& q) const noexcept; 1162 | bool operator!=(const Quaternion& q) const noexcept; 1163 | 1164 | // Assignment operators 1165 | Quaternion& operator=(const XMVECTORF32& F) noexcept 1166 | { 1167 | x = F.f[0]; 1168 | y = F.f[1]; 1169 | z = F.f[2]; 1170 | w = F.f[3]; 1171 | return *this; 1172 | } 1173 | Quaternion& operator+=(const Quaternion& q) noexcept; 1174 | Quaternion& operator-=(const Quaternion& q) noexcept; 1175 | Quaternion& operator*=(const Quaternion& q) noexcept; 1176 | Quaternion& operator*=(float S) noexcept; 1177 | Quaternion& operator/=(const Quaternion& q) noexcept; 1178 | 1179 | // Unary operators 1180 | Quaternion operator+() const noexcept 1181 | { 1182 | return *this; 1183 | } 1184 | Quaternion operator-() const noexcept; 1185 | 1186 | // Quaternion operations 1187 | float Length() const noexcept; 1188 | float LengthSquared() const noexcept; 1189 | 1190 | void Normalize() noexcept; 1191 | void Normalize(Quaternion& result) const noexcept; 1192 | 1193 | void Conjugate() noexcept; 1194 | void Conjugate(Quaternion& result) const noexcept; 1195 | 1196 | void Inverse(Quaternion& result) const noexcept; 1197 | 1198 | float Dot(const Quaternion& Q) const noexcept; 1199 | 1200 | void RotateTowards(const Quaternion& target, float maxAngle) noexcept; 1201 | void __cdecl RotateTowards(const Quaternion& target, float maxAngle, Quaternion& result) const noexcept; 1202 | 1203 | // Computes rotation about y-axis (y), then x-axis (x), then z-axis (z) 1204 | Vector3 ToEuler() const noexcept; 1205 | 1206 | // Static functions 1207 | static Quaternion CreateFromAxisAngle(const Vector3& axis, float angle) noexcept; 1208 | 1209 | // Rotates about y-axis (yaw), then x-axis (pitch), then z-axis (roll) 1210 | static Quaternion CreateFromYawPitchRoll(float yaw, float pitch, float roll) noexcept; 1211 | 1212 | // Rotates about y-axis (angles.y), then x-axis (angles.x), then z-axis (angles.z) 1213 | static Quaternion CreateFromYawPitchRoll(const Vector3& angles) noexcept; 1214 | 1215 | static Quaternion CreateFromRotationMatrix(const Matrix& M) noexcept; 1216 | 1217 | static void Lerp(const Quaternion& q1, const Quaternion& q2, float t, Quaternion& result) noexcept; 1218 | static Quaternion Lerp(const Quaternion& q1, const Quaternion& q2, float t) noexcept; 1219 | 1220 | static void Slerp(const Quaternion& q1, const Quaternion& q2, float t, Quaternion& result) noexcept; 1221 | static Quaternion Slerp(const Quaternion& q1, const Quaternion& q2, float t) noexcept; 1222 | 1223 | static void Concatenate(const Quaternion& q1, const Quaternion& q2, Quaternion& result) noexcept; 1224 | static Quaternion Concatenate(const Quaternion& q1, const Quaternion& q2) noexcept; 1225 | 1226 | static void 1227 | __cdecl FromToRotation(const Vector3& fromDir, const Vector3& toDir, Quaternion& result) noexcept; 1228 | static Quaternion FromToRotation(const Vector3& fromDir, const Vector3& toDir) noexcept; 1229 | 1230 | static void __cdecl LookRotation(const Vector3& forward, const Vector3& up, Quaternion& result) noexcept; 1231 | static Quaternion LookRotation(const Vector3& forward, const Vector3& up) noexcept; 1232 | 1233 | static float Angle(const Quaternion& q1, const Quaternion& q2) noexcept; 1234 | 1235 | // Constants 1236 | static const Quaternion Identity; 1237 | }; 1238 | 1239 | // Binary operators 1240 | Quaternion operator+(const Quaternion& Q1, const Quaternion& Q2) noexcept; 1241 | Quaternion operator-(const Quaternion& Q1, const Quaternion& Q2) noexcept; 1242 | Quaternion operator*(const Quaternion& Q1, const Quaternion& Q2) noexcept; 1243 | Quaternion operator*(const Quaternion& Q, float S) noexcept; 1244 | Quaternion operator/(const Quaternion& Q1, const Quaternion& Q2) noexcept; 1245 | Quaternion operator*(float S, const Quaternion& Q) noexcept; 1246 | 1247 | //------------------------------------------------------------------------------ 1248 | // Color 1249 | struct Color : public XMFLOAT4 1250 | { 1251 | Color() noexcept 1252 | : XMFLOAT4(0, 0, 0, 1.f) 1253 | { 1254 | } 1255 | constexpr Color(float _r, float _g, float _b) noexcept 1256 | : XMFLOAT4(_r, _g, _b, 1.f) 1257 | { 1258 | } 1259 | constexpr Color(float _r, float _g, float _b, float _a) noexcept 1260 | : XMFLOAT4(_r, _g, _b, _a) 1261 | { 1262 | } 1263 | explicit Color(const Vector3& clr) noexcept 1264 | : XMFLOAT4(clr.x, clr.y, clr.z, 1.f) 1265 | { 1266 | } 1267 | explicit Color(const Vector4& clr) noexcept 1268 | : XMFLOAT4(clr.x, clr.y, clr.z, clr.w) 1269 | { 1270 | } 1271 | explicit Color(_In_reads_(4) const float* pArray) noexcept 1272 | : XMFLOAT4(pArray) 1273 | { 1274 | } 1275 | Color(FXMVECTOR V) noexcept 1276 | { 1277 | XMStoreFloat4(this, V); 1278 | } 1279 | Color(const XMFLOAT4& c) noexcept 1280 | { 1281 | this->x = c.x; 1282 | this->y = c.y; 1283 | this->z = c.z; 1284 | this->w = c.w; 1285 | } 1286 | explicit Color(const XMVECTORF32& F) noexcept 1287 | { 1288 | this->x = F.f[0]; 1289 | this->y = F.f[1]; 1290 | this->z = F.f[2]; 1291 | this->w = F.f[3]; 1292 | } 1293 | 1294 | // BGRA Direct3D 9 D3DCOLOR packed color 1295 | explicit Color(const DirectX::PackedVector::XMCOLOR& Packed) noexcept; 1296 | 1297 | // RGBA XNA Game Studio packed color 1298 | explicit Color(const DirectX::PackedVector::XMUBYTEN4& Packed) noexcept; 1299 | 1300 | Color(const Color&) = default; 1301 | Color& operator=(const Color&) = default; 1302 | 1303 | Color(Color&&) = default; 1304 | Color& operator=(Color&&) = default; 1305 | 1306 | operator XMVECTOR() const noexcept 1307 | { 1308 | return XMLoadFloat4(this); 1309 | } 1310 | operator const float*() const noexcept 1311 | { 1312 | return reinterpret_cast(this); 1313 | } 1314 | 1315 | // Comparison operators 1316 | bool operator==(const Color& c) const noexcept; 1317 | bool operator!=(const Color& c) const noexcept; 1318 | 1319 | // Assignment operators 1320 | Color& operator=(const XMVECTORF32& F) noexcept 1321 | { 1322 | x = F.f[0]; 1323 | y = F.f[1]; 1324 | z = F.f[2]; 1325 | w = F.f[3]; 1326 | return *this; 1327 | } 1328 | Color& operator=(const DirectX::PackedVector::XMCOLOR& Packed) noexcept; 1329 | Color& operator=(const DirectX::PackedVector::XMUBYTEN4& Packed) noexcept; 1330 | Color& operator+=(const Color& c) noexcept; 1331 | Color& operator-=(const Color& c) noexcept; 1332 | Color& operator*=(const Color& c) noexcept; 1333 | Color& operator*=(float S) noexcept; 1334 | Color& operator/=(const Color& c) noexcept; 1335 | 1336 | // Unary operators 1337 | Color operator+() const noexcept 1338 | { 1339 | return *this; 1340 | } 1341 | Color operator-() const noexcept; 1342 | 1343 | // Properties 1344 | float R() const noexcept 1345 | { 1346 | return x; 1347 | } 1348 | void R(float r) noexcept 1349 | { 1350 | x = r; 1351 | } 1352 | 1353 | float G() const noexcept 1354 | { 1355 | return y; 1356 | } 1357 | void G(float g) noexcept 1358 | { 1359 | y = g; 1360 | } 1361 | 1362 | float B() const noexcept 1363 | { 1364 | return z; 1365 | } 1366 | void B(float b) noexcept 1367 | { 1368 | z = b; 1369 | } 1370 | 1371 | float A() const noexcept 1372 | { 1373 | return w; 1374 | } 1375 | void A(float a) noexcept 1376 | { 1377 | w = a; 1378 | } 1379 | 1380 | // Color operations 1381 | DirectX::PackedVector::XMCOLOR BGRA() const noexcept; 1382 | DirectX::PackedVector::XMUBYTEN4 RGBA() const noexcept; 1383 | 1384 | Vector3 ToVector3() const noexcept; 1385 | Vector4 ToVector4() const noexcept; 1386 | 1387 | void Negate() noexcept; 1388 | void Negate(Color& result) const noexcept; 1389 | 1390 | void Saturate() noexcept; 1391 | void Saturate(Color& result) const noexcept; 1392 | 1393 | void Premultiply() noexcept; 1394 | void Premultiply(Color& result) const noexcept; 1395 | 1396 | void AdjustSaturation(float sat) noexcept; 1397 | void AdjustSaturation(float sat, Color& result) const noexcept; 1398 | 1399 | void AdjustContrast(float contrast) noexcept; 1400 | void AdjustContrast(float contrast, Color& result) const noexcept; 1401 | 1402 | // Static functions 1403 | static void Modulate(const Color& c1, const Color& c2, Color& result) noexcept; 1404 | static Color Modulate(const Color& c1, const Color& c2) noexcept; 1405 | 1406 | static void Lerp(const Color& c1, const Color& c2, float t, Color& result) noexcept; 1407 | static Color Lerp(const Color& c1, const Color& c2, float t) noexcept; 1408 | }; 1409 | 1410 | // Binary operators 1411 | Color operator+(const Color& C1, const Color& C2) noexcept; 1412 | Color operator-(const Color& C1, const Color& C2) noexcept; 1413 | Color operator*(const Color& C1, const Color& C2) noexcept; 1414 | Color operator*(const Color& C, float S) noexcept; 1415 | Color operator/(const Color& C1, const Color& C2) noexcept; 1416 | Color operator*(float S, const Color& C) noexcept; 1417 | 1418 | //------------------------------------------------------------------------------ 1419 | // Ray 1420 | class Ray 1421 | { 1422 | public: 1423 | Vector3 position; 1424 | Vector3 direction; 1425 | 1426 | Ray() noexcept 1427 | : position(0, 0, 0), direction(0, 0, 1) 1428 | { 1429 | } 1430 | Ray(const Vector3& pos, const Vector3& dir) noexcept 1431 | : position(pos), direction(dir) 1432 | { 1433 | } 1434 | 1435 | Ray(const Ray&) = default; 1436 | Ray& operator=(const Ray&) = default; 1437 | 1438 | Ray(Ray&&) = default; 1439 | Ray& operator=(Ray&&) = default; 1440 | 1441 | // Comparison operators 1442 | bool operator==(const Ray& r) const noexcept; 1443 | bool operator!=(const Ray& r) const noexcept; 1444 | 1445 | // Ray operations 1446 | bool Intersects(const BoundingSphere& sphere, _Out_ float& Dist) const noexcept; 1447 | bool Intersects(const BoundingBox& box, _Out_ float& Dist) const noexcept; 1448 | bool 1449 | Intersects(const Vector3& tri0, const Vector3& tri1, const Vector3& tri2, _Out_ float& Dist) const noexcept; 1450 | bool Intersects(const Plane& plane, _Out_ float& Dist) const noexcept; 1451 | }; 1452 | 1453 | //------------------------------------------------------------------------------ 1454 | // Viewport 1455 | class Viewport 1456 | { 1457 | public: 1458 | float x; 1459 | float y; 1460 | float width; 1461 | float height; 1462 | float minDepth; 1463 | float maxDepth; 1464 | 1465 | Viewport() noexcept 1466 | : 1467 | x(0.f), y(0.f), width(0.f), height(0.f), minDepth(0.f), maxDepth(1.f) 1468 | { 1469 | } 1470 | constexpr Viewport(float ix, float iy, float iw, float ih, float iminz = 0.f, float imaxz = 1.f) noexcept 1471 | : 1472 | x(ix), y(iy), width(iw), height(ih), minDepth(iminz), maxDepth(imaxz) 1473 | { 1474 | } 1475 | explicit Viewport(const RECT& rct) noexcept 1476 | : 1477 | x(float(rct.left)), y(float(rct.top)), 1478 | width(float(rct.right - rct.left)), 1479 | height(float(rct.bottom - rct.top)), 1480 | minDepth(0.f), maxDepth(1.f) 1481 | { 1482 | } 1483 | 1484 | #if defined(__d3d11_h__) || defined(__d3d11_x_h__) 1485 | // Direct3D 11 interop 1486 | explicit Viewport(const D3D11_VIEWPORT& vp) noexcept : 1487 | x(vp.TopLeftX), y(vp.TopLeftY), 1488 | width(vp.Width), height(vp.Height), 1489 | minDepth(vp.MinDepth), maxDepth(vp.MaxDepth) 1490 | { 1491 | } 1492 | 1493 | operator D3D11_VIEWPORT() noexcept { return *reinterpret_cast(this); } 1494 | const D3D11_VIEWPORT* Get11() const noexcept { return reinterpret_cast(this); } 1495 | Viewport& operator= (const D3D11_VIEWPORT& vp) noexcept; 1496 | #endif 1497 | 1498 | #if defined(__d3d12_h__) || defined(__d3d12_x_h__) || defined(__XBOX_D3D12_X__) 1499 | // Direct3D 12 interop 1500 | explicit Viewport(const D3D12_VIEWPORT& vp) noexcept : 1501 | x(vp.TopLeftX), y(vp.TopLeftY), 1502 | width(vp.Width), height(vp.Height), 1503 | minDepth(vp.MinDepth), maxDepth(vp.MaxDepth) 1504 | { 1505 | } 1506 | 1507 | operator D3D12_VIEWPORT() noexcept { return *reinterpret_cast(this); } 1508 | const D3D12_VIEWPORT* Get12() const noexcept { return reinterpret_cast(this); } 1509 | Viewport& operator= (const D3D12_VIEWPORT& vp) noexcept; 1510 | #endif 1511 | 1512 | Viewport(const Viewport&) = default; 1513 | Viewport& operator=(const Viewport&) = default; 1514 | 1515 | Viewport(Viewport&&) = default; 1516 | Viewport& operator=(Viewport&&) = default; 1517 | 1518 | // Comparison operators 1519 | #if (__cplusplus >= 202002L) 1520 | bool operator == (const Viewport&) const = default; 1521 | auto operator <=> (const Viewport&) const = default; 1522 | #else 1523 | bool operator==(const Viewport& vp) const noexcept; 1524 | bool operator!=(const Viewport& vp) const noexcept; 1525 | #endif 1526 | 1527 | // Assignment operators 1528 | Viewport& operator=(const RECT& rct) noexcept; 1529 | 1530 | // Viewport operations 1531 | float AspectRatio() const noexcept; 1532 | 1533 | Vector3 1534 | Project(const Vector3& p, const Matrix& proj, const Matrix& view, const Matrix& world) const noexcept; 1535 | void Project(const Vector3& p, 1536 | const Matrix& proj, 1537 | const Matrix& view, 1538 | const Matrix& world, 1539 | Vector3& result) const noexcept; 1540 | 1541 | Vector3 1542 | Unproject(const Vector3& p, const Matrix& proj, const Matrix& view, const Matrix& world) const noexcept; 1543 | void Unproject(const Vector3& p, 1544 | const Matrix& proj, 1545 | const Matrix& view, 1546 | const Matrix& world, 1547 | Vector3& result) const noexcept; 1548 | 1549 | // Static methods 1550 | #if defined(__dxgi1_2_h__) || defined(__d3d11_x_h__) || defined(__d3d12_x_h__) || defined(__XBOX_D3D12_X__) 1551 | static RECT __cdecl ComputeDisplayArea(DXGI_SCALING scaling, UINT backBufferWidth, UINT backBufferHeight, int outputWidth, int outputHeight) noexcept; 1552 | #endif 1553 | static RECT __cdecl ComputeTitleSafeArea(UINT backBufferWidth, UINT backBufferHeight) noexcept; 1554 | }; 1555 | 1556 | #include "SimpleMath.inl" 1557 | 1558 | } // namespace SimpleMath 1559 | 1560 | } // namespace DirectX 1561 | 1562 | //------------------------------------------------------------------------------ 1563 | // Support for SimpleMath and Standard C++ Library containers 1564 | namespace std 1565 | { 1566 | 1567 | template<> 1568 | struct less 1569 | { 1570 | bool 1571 | operator()(const DirectX::SimpleMath::Rectangle& r1, const DirectX::SimpleMath::Rectangle& r2) const noexcept 1572 | { 1573 | return ((r1.x < r2.x) 1574 | || ((r1.x == r2.x) && (r1.y < r2.y)) 1575 | || ((r1.x == r2.x) && (r1.y == r2.y) && (r1.width < r2.width)) 1576 | || ((r1.x == r2.x) && (r1.y == r2.y) && (r1.width == r2.width) && (r1.height < r2.height))); 1577 | } 1578 | }; 1579 | 1580 | template<> 1581 | struct less 1582 | { 1583 | bool operator()(const DirectX::SimpleMath::Vector2& V1, const DirectX::SimpleMath::Vector2& V2) const noexcept 1584 | { 1585 | return ((V1.x < V2.x) || ((V1.x == V2.x) && (V1.y < V2.y))); 1586 | } 1587 | }; 1588 | 1589 | template<> 1590 | struct less 1591 | { 1592 | bool operator()(const DirectX::SimpleMath::Vector3& V1, const DirectX::SimpleMath::Vector3& V2) const noexcept 1593 | { 1594 | return ((V1.x < V2.x) 1595 | || ((V1.x == V2.x) && (V1.y < V2.y)) 1596 | || ((V1.x == V2.x) && (V1.y == V2.y) && (V1.z < V2.z))); 1597 | } 1598 | }; 1599 | 1600 | template<> 1601 | struct less 1602 | { 1603 | bool operator()(const DirectX::SimpleMath::Vector4& V1, const DirectX::SimpleMath::Vector4& V2) const noexcept 1604 | { 1605 | return ((V1.x < V2.x) 1606 | || ((V1.x == V2.x) && (V1.y < V2.y)) 1607 | || ((V1.x == V2.x) && (V1.y == V2.y) && (V1.z < V2.z)) 1608 | || ((V1.x == V2.x) && (V1.y == V2.y) && (V1.z == V2.z) && (V1.w < V2.w))); 1609 | } 1610 | }; 1611 | 1612 | template<> 1613 | struct less 1614 | { 1615 | bool operator()(const DirectX::SimpleMath::Matrix& M1, const DirectX::SimpleMath::Matrix& M2) const noexcept 1616 | { 1617 | if (M1._11 != M2._11) return M1._11 < M2._11; 1618 | if (M1._12 != M2._12) return M1._12 < M2._12; 1619 | if (M1._13 != M2._13) return M1._13 < M2._13; 1620 | if (M1._14 != M2._14) return M1._14 < M2._14; 1621 | if (M1._21 != M2._21) return M1._21 < M2._21; 1622 | if (M1._22 != M2._22) return M1._22 < M2._22; 1623 | if (M1._23 != M2._23) return M1._23 < M2._23; 1624 | if (M1._24 != M2._24) return M1._24 < M2._24; 1625 | if (M1._31 != M2._31) return M1._31 < M2._31; 1626 | if (M1._32 != M2._32) return M1._32 < M2._32; 1627 | if (M1._33 != M2._33) return M1._33 < M2._33; 1628 | if (M1._34 != M2._34) return M1._34 < M2._34; 1629 | if (M1._41 != M2._41) return M1._41 < M2._41; 1630 | if (M1._42 != M2._42) return M1._42 < M2._42; 1631 | if (M1._43 != M2._43) return M1._43 < M2._43; 1632 | if (M1._44 != M2._44) return M1._44 < M2._44; 1633 | 1634 | return false; 1635 | } 1636 | }; 1637 | 1638 | template<> 1639 | struct less 1640 | { 1641 | bool operator()(const DirectX::SimpleMath::Plane& P1, const DirectX::SimpleMath::Plane& P2) const noexcept 1642 | { 1643 | return ((P1.x < P2.x) 1644 | || ((P1.x == P2.x) && (P1.y < P2.y)) 1645 | || ((P1.x == P2.x) && (P1.y == P2.y) && (P1.z < P2.z)) 1646 | || ((P1.x == P2.x) && (P1.y == P2.y) && (P1.z == P2.z) && (P1.w < P2.w))); 1647 | } 1648 | }; 1649 | 1650 | template<> 1651 | struct less 1652 | { 1653 | bool 1654 | operator()(const DirectX::SimpleMath::Quaternion& Q1, const DirectX::SimpleMath::Quaternion& Q2) const noexcept 1655 | { 1656 | return ((Q1.x < Q2.x) 1657 | || ((Q1.x == Q2.x) && (Q1.y < Q2.y)) 1658 | || ((Q1.x == Q2.x) && (Q1.y == Q2.y) && (Q1.z < Q2.z)) 1659 | || ((Q1.x == Q2.x) && (Q1.y == Q2.y) && (Q1.z == Q2.z) && (Q1.w < Q2.w))); 1660 | } 1661 | }; 1662 | 1663 | template<> 1664 | struct less 1665 | { 1666 | bool operator()(const DirectX::SimpleMath::Color& C1, const DirectX::SimpleMath::Color& C2) const noexcept 1667 | { 1668 | return ((C1.x < C2.x) 1669 | || ((C1.x == C2.x) && (C1.y < C2.y)) 1670 | || ((C1.x == C2.x) && (C1.y == C2.y) && (C1.z < C2.z)) 1671 | || ((C1.x == C2.x) && (C1.y == C2.y) && (C1.z == C2.z) && (C1.w < C2.w))); 1672 | } 1673 | }; 1674 | 1675 | template<> 1676 | struct less 1677 | { 1678 | bool operator()(const DirectX::SimpleMath::Ray& R1, const DirectX::SimpleMath::Ray& R2) const noexcept 1679 | { 1680 | if (R1.position.x != R2.position.x) return R1.position.x < R2.position.x; 1681 | if (R1.position.y != R2.position.y) return R1.position.y < R2.position.y; 1682 | if (R1.position.z != R2.position.z) return R1.position.z < R2.position.z; 1683 | 1684 | if (R1.direction.x != R2.direction.x) return R1.direction.x < R2.direction.x; 1685 | if (R1.direction.y != R2.direction.y) return R1.direction.y < R2.direction.y; 1686 | if (R1.direction.z != R2.direction.z) return R1.direction.z < R2.direction.z; 1687 | 1688 | return false; 1689 | } 1690 | }; 1691 | 1692 | template<> 1693 | struct less 1694 | { 1695 | bool 1696 | operator()(const DirectX::SimpleMath::Viewport& vp1, const DirectX::SimpleMath::Viewport& vp2) const noexcept 1697 | { 1698 | if (vp1.x != vp2.x) return (vp1.x < vp2.x); 1699 | if (vp1.y != vp2.y) return (vp1.y < vp2.y); 1700 | 1701 | if (vp1.width != vp2.width) return (vp1.width < vp2.width); 1702 | if (vp1.height != vp2.height) return (vp1.height < vp2.height); 1703 | 1704 | if (vp1.minDepth != vp2.minDepth) return (vp1.minDepth < vp2.minDepth); 1705 | if (vp1.maxDepth != vp2.maxDepth) return (vp1.maxDepth < vp2.maxDepth); 1706 | 1707 | return false; 1708 | } 1709 | }; 1710 | 1711 | } // namespace std 1712 | 1713 | #ifdef __clang__ 1714 | #pragma clang diagnostic pop 1715 | #endif 1716 | --------------------------------------------------------------------------------