diff --git a/.gitignore b/.gitignore index 3721f8b4..c076e5e1 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ build_xcode.nosync/ .github/copilot-instructions.md scop !examples/scop +**/*.entitlements +__cmake_systeminformation/ diff --git a/CMakeLists.txt b/CMakeLists.txt index b987324d..9f330b66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,6 @@ cmake_minimum_required(VERSION 3.22) -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") - include(FetchContent) set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) @@ -67,14 +65,21 @@ if (APPLE AND GFX_BUILD_METAL) endif() if (GFX_BUILD_VULKAN) + set(VULKAN_FIND_PACKAGE_ARGS 1.4.321.0) + if(APPLE) + list(APPEND VULKAN_FIND_PACKAGE_ARGS COMPONENTS MoltenVK) + endif() FetchContent_Declare(Vulkan GIT_REPOSITORY https://github.com/KhronosGroup/Vulkan-Headers.git GIT_TAG vulkan-sdk-1.4.321.0 GIT_SHALLOW 1 GIT_PROGRESS TRUE - FIND_PACKAGE_ARGS + FIND_PACKAGE_ARGS ${VULKAN_FIND_PACKAGE_ARGS} ) FetchContent_MakeAvailable(Vulkan) + if (NOT Vulkan_FOUND) + message(WARNING "Vulkan was not found. Only the build dependencies were fetched; the program may fail at runtime.") + endif() endif() if (NOT GFX_BUILD_METAL AND NOT GFX_BUILD_VULKAN) @@ -155,6 +160,7 @@ endif() if (GFX_BUILD_METAL) target_compile_definitions(Graphics PUBLIC "GFX_BUILD_METAL") + target_compile_options(Graphics PRIVATE "-fobjc-arc") endif() if (GFX_BUILD_VULKAN) @@ -186,23 +192,6 @@ endif() add_subdirectory("tools") -if (GFX_BUILD_METAL) - set(GFX_SHADER_SLIB "${CMAKE_CURRENT_BINARY_DIR}/shaders/gfx.slib") - file(GLOB GFX_SHADER_SRCS "shaders/*.slang") - add_custom_command( - OUTPUT ${GFX_SHADER_SLIB} - COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/shaders" - COMMAND $ -t metal -o ${GFX_SHADER_SLIB} ${GFX_SHADER_SRCS} - DEPENDS gfxsc ${GFX_SHADER_SRCS} - COMMENT "Building gfx shaders" - VERBATIM - ) - add_custom_target(gfx_shaders ALL DEPENDS ${GFX_SHADER_SLIB}) - set_target_properties(gfx_shaders PROPERTIES FOLDER "shaders") - target_compile_definitions(Graphics PRIVATE "GFX_SHADER_SLIB=\"${GFX_SHADER_SLIB}\"") - add_dependencies(Graphics gfx_shaders) -endif() - if(GFX_BUILD_EXAMPLES) add_subdirectory("examples") endif() diff --git a/cmake/find_slang.cmake b/cmake/find_slang.cmake deleted file mode 100644 index b3ec08c1..00000000 --- a/cmake/find_slang.cmake +++ /dev/null @@ -1,77 +0,0 @@ -# --------------------------------------------------- -# find_slang.cmake -# -# Find or download prebuilt Slang shader compiler -# -# This module creates a `slang` target that can be used -# for shader compilation. -# -# Author: Thomas Choquet -# Date: 2025/10/22 -# --------------------------------------------------- - -include(FetchContent) - -function(find_slang) - set(SLANG_SEARCH_INCLUDE_PATHS /usr/include /usr/local/include /opt/include /opt/local/include $ENV{VULKAN_SDK}/include $ENV{PROGRAMFILES}/slang/include $ENV{HOME}/.local/include) - set(SLANG_SEARCH_LIB_PATHS /usr/lib /usr/local/lib /opt/lib /opt/local/lib $ENV{VULKAN_SDK}/lib $ENV{PROGRAMFILES}/slang/lib $ENV{HOME}/.local/lib) - set(SLANG_SEARCH_BIN_PATHS $ENV{PATH} $ENV{VULKAN_SDK}/bin $ENV{PROGRAMFILES}/slang/bin $ENV{HOME}/.local/bin) - - if(NOT GFX_SLANG_FORCE_DOWNLOAD) - find_path(SLANG_INCLUDE_DIR NAMES slang.h PATHS ${SLANG_SEARCH_INCLUDE_PATHS} PATH_SUFFIXES slang) - if(SLANG_INCLUDE_DIR) - find_file(SLANG_COM_PTR NAMES slang-com-ptr.h PATHS ${SLANG_INCLUDE_DIR}) - if (NOT SLANG_COM_PTR) - unset(SLANG_INCLUDE_DIR CACHE) - endif() - endif() - find_library(SLANG_LIB NAMES slang PATHS ${SLANG_SEARCH_LIB_PATHS}) - if (WIN32) - find_file(SLANG_DLL NAMES slang.dll PATHS ${SLANG_SEARCH_BIN_PATHS}) - find_file(SLANG_GLSLANG_DLL NAMES slang-glslang.dll PATHS ${SLANG_SEARCH_BIN_PATHS}) - endif() - endif() - - if(NOT SLANG_INCLUDE_DIR OR NOT SLANG_LIB) - if(APPLE) - set(SLANG_PLATFORM "macos") - elseif(WIN32) - set(SLANG_PLATFORM "windows") - elseif(UNIX) - set(SLANG_PLATFORM "linux") - else() - message(FATAL_ERROR "Unsupported platform for Slang prebuilt binaries") - endif() - - if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64") - string(APPEND SLANG_PLATFORM "-aarch64") - else() - string(APPEND SLANG_PLATFORM "-x86_64") - endif() - - FetchContent_Declare(slang URL "https://github.com/shader-slang/slang/releases/download/v2025.19.1/slang-2025.19.1-${SLANG_PLATFORM}.zip" DOWNLOAD_EXTRACT_TIMESTAMP TRUE) - FetchContent_MakeAvailable(slang) - - find_path(SLANG_INCLUDE_DIR NAMES slang.h PATHS ${slang_SOURCE_DIR}/include PATH_SUFFIXES slang REQUIRED) - find_file(SLANG_COM_PTR NAMES slang-com-ptr.h PATHS ${SLANG_INCLUDE_DIR} REQUIRED) - find_library(SLANG_LIB NAMES slang PATHS ${slang_SOURCE_DIR}/lib REQUIRED) - if (WIN32) - find_file(SLANG_DLL NAMES slang.dll PATHS ${slang_SOURCE_DIR}/bin REQUIRED) - find_file(SLANG_GLSLANG_DLL NAMES slang-glslang.dll PATHS ${slang_SOURCE_DIR}/bin REQUIRED) - endif() - endif() - - message(STATUS "slang headers found: ${SLANG_INCLUDE_DIR}") - message(STATUS "slang library found: ${SLANG_LIB}") - - add_library(slang::slang SHARED IMPORTED GLOBAL) - target_include_directories(slang::slang INTERFACE ${SLANG_INCLUDE_DIR}) - target_link_libraries(slang::slang INTERFACE ${SLANG_LIB}) - set_target_properties(slang::slang PROPERTIES IMPORTED_IMPLIB ${SLANG_LIB}) - if (WIN32) - set_target_properties(slang::slang PROPERTIES IMPORTED_LOCATION ${SLANG_DLL}) - if(SLANG_GLSLANG_DLL) - set_target_properties(slang::slang PROPERTIES SLANG_GLSLANG_DLL_PATH ${SLANG_GLSLANG_DLL}) - endif() - endif() -endfunction() diff --git a/examples/imgui_usage/CMakeLists.txt b/examples/imgui_usage/CMakeLists.txt index 62cc09a0..1edb4241 100644 --- a/examples/imgui_usage/CMakeLists.txt +++ b/examples/imgui_usage/CMakeLists.txt @@ -89,3 +89,22 @@ endif() target_compile_definitions(imgui_usage PRIVATE "GLFW_INCLUDE_NONE") target_link_libraries(imgui_usage PRIVATE Graphics imgui) + +if(APPLE AND NOT CMAKE_GENERATOR STREQUAL "Xcode") + set(CODESIGN_IDENTITY "-" CACHE STRING "Codesigning identity for imgui_usage") + + set(IMGUI_USAGE_ENTITLEMENTS_FILE "${CMAKE_CURRENT_SOURCE_DIR}/imgui_usage.entitlements") + if(EXISTS "${IMGUI_USAGE_ENTITLEMENTS_FILE}") + add_custom_command(TARGET imgui_usage POST_BUILD + COMMAND /bin/sh -c "codesign --sign \"${CODESIGN_IDENTITY}\" --force --entitlements \"${IMGUI_USAGE_ENTITLEMENTS_FILE}\" \"$\" >/dev/null 2>&1" + COMMENT "Codesigning imgui_usage (with entitlements) [identity: ${CODESIGN_IDENTITY}]" + VERBATIM + ) + else() + add_custom_command(TARGET imgui_usage POST_BUILD + COMMAND /bin/sh -c "codesign --sign \"${CODESIGN_IDENTITY}\" --force \"$\" >/dev/null 2>&1" + COMMENT "Codesigning imgui_usage [identity: ${CODESIGN_IDENTITY}]" + VERBATIM + ) + endif() +endif() diff --git a/examples/mc_cube/CMakeLists.txt b/examples/mc_cube/CMakeLists.txt index 31ac82ff..9a86d8bb 100644 --- a/examples/mc_cube/CMakeLists.txt +++ b/examples/mc_cube/CMakeLists.txt @@ -142,3 +142,22 @@ target_compile_definitions(mc_cube PRIVATE "GLFW_INCLUDE_NONE") target_link_libraries(mc_cube PRIVATE Graphics glm::glm imgui stb_image) add_dependencies(mc_cube mc_cube_shader) + +if(APPLE AND NOT CMAKE_GENERATOR STREQUAL "Xcode") + set(CODESIGN_IDENTITY "-" CACHE STRING "Codesigning identity for mc_cube") + + set(MC_CUBE_ENTITLEMENTS_FILE "${CMAKE_CURRENT_SOURCE_DIR}/mc_cube.entitlements") + if(EXISTS "${MC_CUBE_ENTITLEMENTS_FILE}") + add_custom_command(TARGET mc_cube POST_BUILD + COMMAND /bin/sh -c "codesign --sign \"${CODESIGN_IDENTITY}\" --force --entitlements \"${MC_CUBE_ENTITLEMENTS_FILE}\" \"$\" >/dev/null 2>&1" + COMMENT "Codesigning mc_cube (with entitlements) [identity: ${CODESIGN_IDENTITY}]" + VERBATIM + ) + else() + add_custom_command(TARGET mc_cube POST_BUILD + COMMAND /bin/sh -c "codesign --sign \"${CODESIGN_IDENTITY}\" --force \"$\" >/dev/null 2>&1" + COMMENT "Codesigning mc_cube [identity: ${CODESIGN_IDENTITY}]" + VERBATIM + ) + endif() +endif() diff --git a/examples/multiBuffer/CMakeLists.txt b/examples/multiBuffer/CMakeLists.txt index 6b284d73..fa84c2e3 100644 --- a/examples/multiBuffer/CMakeLists.txt +++ b/examples/multiBuffer/CMakeLists.txt @@ -110,3 +110,22 @@ target_compile_definitions(multiBuffer PRIVATE "GLFW_INCLUDE_NONE") target_link_libraries(multiBuffer PRIVATE Graphics glfw glm::glm) add_dependencies(multiBuffer multiBuffer_shader) + +if(APPLE AND NOT CMAKE_GENERATOR STREQUAL "Xcode") + set(CODESIGN_IDENTITY "-" CACHE STRING "Codesigning identity for multiBuffer") + + set(multiBuffer_ENTITLEMENTS_FILE "${CMAKE_CURRENT_SOURCE_DIR}/multiBuffer.entitlements") + if(EXISTS "${MULTIBUFFER_ENTITLEMENTS_FILE}") + add_custom_command(TARGET multiBuffer POST_BUILD + COMMAND /bin/sh -c "codesign --sign \"${CODESIGN_IDENTITY}\" --force --entitlements \"${MULTIBUFFER_ENTITLEMENTS_FILE}\" \"$\" >/dev/null 2>&1" + COMMENT "Codesigning multiBuffer (with entitlements) [identity: ${CODESIGN_IDENTITY}]" + VERBATIM + ) + else() + add_custom_command(TARGET multiBuffer POST_BUILD + COMMAND /bin/sh -c "codesign --sign \"${CODESIGN_IDENTITY}\" --force \"$\" >/dev/null 2>&1" + COMMENT "Codesigning multiBuffer [identity: ${CODESIGN_IDENTITY}]" + VERBATIM + ) + endif() +endif() diff --git a/examples/scop/CMakeLists.txt b/examples/scop/CMakeLists.txt index c7c8255f..ab879f90 100644 --- a/examples/scop/CMakeLists.txt +++ b/examples/scop/CMakeLists.txt @@ -174,3 +174,22 @@ else() target_link_libraries(scop PRIVATE Graphics glm::glm imgui stb_image assimp::assimp) endif() add_dependencies(scop flat_color_shader textured_shader scop_shader) + +if(APPLE AND NOT CMAKE_GENERATOR STREQUAL "Xcode") + set(CODESIGN_IDENTITY "-" CACHE STRING "Codesigning identity") + + set(SCOP_ENTITLEMENTS_FILE "${CMAKE_CURRENT_SOURCE_DIR}/scop.entitlements") + if(EXISTS "${SCOP_ENTITLEMENTS_FILE}") + add_custom_command(TARGET scop POST_BUILD + COMMAND /bin/sh -c "codesign --sign \"${CODESIGN_IDENTITY}\" --force --entitlements \"${SCOP_ENTITLEMENTS_FILE}\" \"$\" >/dev/null 2>&1" + COMMENT "Codesigning scop (with entitlements) [identity: ${CODESIGN_IDENTITY}]" + VERBATIM + ) + else() + add_custom_command(TARGET scop POST_BUILD + COMMAND /bin/sh -c "codesign --sign \"${CODESIGN_IDENTITY}\" --force \"$\" >/dev/null 2>&1" + COMMENT "Codesigning scop [identity: ${CODESIGN_IDENTITY}]" + VERBATIM + ) + endif() +endif() diff --git a/examples/scop/scop.cpp b/examples/scop/scop.cpp index 9a2c32c5..9decfa7c 100644 --- a/examples/scop/scop.cpp +++ b/examples/scop/scop.cpp @@ -51,7 +51,7 @@ #include #include #include -#include +#include // IWYU pragma: keep #if __XCODE__ #include @@ -256,7 +256,7 @@ int main(int argc, char** argv) scopMaterial->setTextureStrength(textureStrengthTarget); } #else - static scop::Light* lightAttachedToCamera = nullptr; + static scop::Light* lightAttachedToCamera = light.get(); if (lightAttachedToCamera != nullptr) lightAttachedToCamera->setPosition(camera->position()); #endif diff --git a/examples/triangle/CMakeLists.txt b/examples/triangle/CMakeLists.txt index 0aaef37f..eb535a24 100644 --- a/examples/triangle/CMakeLists.txt +++ b/examples/triangle/CMakeLists.txt @@ -110,3 +110,22 @@ target_compile_definitions(triangle PRIVATE "GLFW_INCLUDE_NONE") target_link_libraries(triangle PRIVATE Graphics glfw glm::glm) add_dependencies(triangle triangle_shader) + +if(APPLE AND NOT CMAKE_GENERATOR STREQUAL "Xcode") + set(CODESIGN_IDENTITY "-" CACHE STRING "Codesigning identity for triangle") + + set(triangle_ENTITLEMENTS_FILE "${CMAKE_CURRENT_SOURCE_DIR}/triangle.entitlements") + if(EXISTS "${TRIANGLE_ENTITLEMENTS_FILE}") + add_custom_command(TARGET triangle POST_BUILD + COMMAND /bin/sh -c "codesign --sign \"${CODESIGN_IDENTITY}\" --force --entitlements \"${TRIANGLE_ENTITLEMENTS_FILE}\" \"$\" >/dev/null 2>&1" + COMMENT "Codesigning triangle (with entitlements) [identity: ${CODESIGN_IDENTITY}]" + VERBATIM + ) + else() + add_custom_command(TARGET triangle POST_BUILD + COMMAND /bin/sh -c "codesign --sign \"${CODESIGN_IDENTITY}\" --force \"$\" >/dev/null 2>&1" + COMMENT "Codesigning triangle [identity: ${CODESIGN_IDENTITY}]" + VERBATIM + ) + endif() +endif() diff --git a/examples/triangle/triangle.cpp b/examples/triangle/triangle.cpp index e6635c4e..b0c1c8d5 100644 --- a/examples/triangle/triangle.cpp +++ b/examples/triangle/triangle.cpp @@ -168,7 +168,6 @@ class Application if (m_lastCommandBuffers.at(m_frameIdx) != nullptr) { m_device->waitCommandBuffer(*m_lastCommandBuffers.at(m_frameIdx)); - m_lastCommandBuffers.at(m_frameIdx).reset(); m_commandBufferPools.at(m_frameIdx)->reset(); } @@ -199,7 +198,7 @@ class Application commandBuffer->endRenderPass(); commandBuffer->presentDrawable(drawable); - m_lastCommandBuffers.at(m_frameIdx) = commandBuffer; + m_lastCommandBuffers.at(m_frameIdx) = commandBuffer.get(); m_device->submitCommandBuffers(commandBuffer); m_frameIdx = (m_frameIdx + 1) % maxFrameInFlight; @@ -222,7 +221,7 @@ class Application std::shared_ptr m_vertexBuffer; uint8_t m_frameIdx = 0; std::array, maxFrameInFlight> m_commandBufferPools; - std::array, maxFrameInFlight> m_lastCommandBuffers = {}; + std::array m_lastCommandBuffers = {}; }; int main() diff --git a/shaders/imgui_metal.slang b/shaders/imgui_metal.slang deleted file mode 100644 index 6cbdb68c..00000000 --- a/shaders/imgui_metal.slang +++ /dev/null @@ -1,50 +0,0 @@ -/* - * --------------------------------------------------- - * imgui_metal.slang - * - * Author: Thomas Choquet - * Date: 2025/09/02 07:25:19 - * --------------------------------------------------- - */ - -struct VertexIn { - float2 position; - float2 texCoords; - uint color; -}; - -struct VertexOut { - float4 position : SV_Position; - float2 texCoords; - float4 color; -}; - -struct Uniforms -{ - float4x4 projectionMatrix; -}; - -ParameterBlock uniforms; - -[shader("vertex")] -VertexOut vertex_main(VertexIn in) -{ - VertexOut out; - out.position = mul(float4(in.position, 0, 1), uniforms.projectionMatrix); - out.texCoords = in.texCoords; - out.color = float4((in.color >> 0) & 0xFF, - (in.color >> 8) & 0xFF, - (in.color >> 16) & 0xFF, - (in.color >> 24) & 0xFF) / 255.0; - return out; -} - -Texture2D texture; -SamplerState linearSampler; - -[shader("fragment")] -half4 fragment_main(VertexOut in) -{ - half4 texColor = texture.Sample(linearSampler, in.texCoords); - return half4(in.color) * texColor; -} diff --git a/src/Metal/MetalBuffer.hpp b/src/Metal/MetalBuffer.hpp index be53c5ae..099a87b1 100644 --- a/src/Metal/MetalBuffer.hpp +++ b/src/Metal/MetalBuffer.hpp @@ -13,6 +13,10 @@ #include "Graphics/Buffer.hpp" #include "Graphics/Enums.hpp" +#if !defined(__OBJC__) +#error this file can only by used in objective c +#endif + namespace gfx { @@ -33,9 +37,9 @@ class MetalBuffer : public Buffer void setContent(const void* data, size_t size) override; - const id& mtlBuffer() const { return m_mtlBuffer; } + inline id mtlBuffer() const { return m_mtlBuffer; } - ~MetalBuffer() override; + ~MetalBuffer() override = default; protected: void* contentVoid() override; diff --git a/src/Metal/MetalBuffer.mm b/src/Metal/MetalBuffer.mm index c18da4b7..72a09a76 100644 --- a/src/Metal/MetalBuffer.mm +++ b/src/Metal/MetalBuffer.mm @@ -49,31 +49,23 @@ [m_mtlBuffer didModifyRange:NSMakeRange(0, size)]; }} -MetalBuffer::~MetalBuffer() { @autoreleasepool -{ - if (m_mtlBuffer != nil) - [m_mtlBuffer release]; -}} - void* MetalBuffer::contentVoid() { @autoreleasepool { assert(mtlBuffer().storageMode == MTLStorageModeShared); return mtlBuffer().contents; }} -MetalBuffer& MetalBuffer::operator = (MetalBuffer&& other) noexcept { @autoreleasepool +MetalBuffer& MetalBuffer::operator = (MetalBuffer&& other) noexcept { Buffer::operator=(std::move(other)); if (this != &other) { - if (m_mtlBuffer != nil) - [m_mtlBuffer release]; m_usages = std::exchange(other.m_usages, BufferUsage::uniformBuffer); m_storageMode = std::exchange(other.m_storageMode, ResourceStorageMode::hostVisible); m_mtlBuffer = std::exchange(other.m_mtlBuffer, nil); } return *this; -}} +} MetalBuffer::operator bool () const { diff --git a/src/Metal/MetalCommandBuffer.hpp b/src/Metal/MetalCommandBuffer.hpp index 4ff2644a..1940eb45 100644 --- a/src/Metal/MetalCommandBuffer.hpp +++ b/src/Metal/MetalCommandBuffer.hpp @@ -22,6 +22,10 @@ #include "Metal/MetalTexture.hpp" #include "Metal/MetalSampler.hpp" +#if !defined(__OBJC__) +#error this file can only by used in objective c +#endif + namespace gfx { @@ -65,10 +69,10 @@ class MetalCommandBuffer : public CommandBuffer void presentDrawable(const std::shared_ptr&) override; - inline const id& mtlCommandBuffer() const { return m_mtlCommandBuffer; } - inline const id& commandEncoder() const { return m_commandEncoder; } + inline id mtlCommandBuffer() const { return m_mtlCommandBuffer; } + inline id commandEncoder() const { return m_commandEncoder; } - ~MetalCommandBuffer() override; + ~MetalCommandBuffer() override = default; private: id m_mtlCommandBuffer = nil; diff --git a/src/Metal/MetalCommandBuffer.mm b/src/Metal/MetalCommandBuffer.mm index cb7828c0..5f4be7bf 100644 --- a/src/Metal/MetalCommandBuffer.mm +++ b/src/Metal/MetalCommandBuffer.mm @@ -19,14 +19,14 @@ #include "Metal/MetalSampler.hpp" #include "Metal/MetalTexture.hpp" #if defined(GFX_IMGUI_ENABLED) -# include "Metal/imgui_impl_metal.hpp" +# include "Metal/imgui_impl_metal.h" #endif #include "Metal/MetalGraphicsPipeline.hpp" #include "Metal/MetalParameterBlock.hpp" #include "Metal/MetalDrawable.hpp" #include "Metal/MetalCommandBufferPool.hpp" -#import "Metal/MetalEnums.h" +#import "Metal/MetalEnums.hpp" namespace gfx { @@ -45,14 +45,14 @@ MetalCommandBuffer::MetalCommandBuffer(const id& queue) { @autoreleasepool { - m_mtlCommandBuffer = [[queue commandBuffer] retain]; + m_mtlCommandBuffer = [queue commandBuffer]; }} void MetalCommandBuffer::beginRenderPass(const Framebuffer& framebuffer) { @autoreleasepool { assert(m_commandEncoder == nil); - MTLRenderPassDescriptor* renderPassDescriptor = [[[MTLRenderPassDescriptor alloc] init] autorelease]; + MTLRenderPassDescriptor* renderPassDescriptor = [[MTLRenderPassDescriptor alloc] init]; for (int i = 0; auto& colorAttachment : framebuffer.colorAttachments) { @@ -77,7 +77,7 @@ m_usedTextures.insert(texture); } - m_commandEncoder = [[m_mtlCommandBuffer renderCommandEncoderWithDescriptor: renderPassDescriptor] retain]; + m_commandEncoder = [m_mtlCommandBuffer renderCommandEncoderWithDescriptor: renderPassDescriptor]; }} void MetalCommandBuffer::usePipeline(const std::shared_ptr& _graphicsPipeline) { @autoreleasepool @@ -191,14 +191,13 @@ { assert(m_commandEncoder); [m_commandEncoder endEncoding]; - [m_commandEncoder release]; m_commandEncoder = nil; }} void MetalCommandBuffer::beginBlitPass() { @autoreleasepool { assert(m_commandEncoder == nil); - m_commandEncoder = [[m_mtlCommandBuffer blitCommandEncoder] retain]; + m_commandEncoder = [m_mtlCommandBuffer blitCommandEncoder]; }} void MetalCommandBuffer::copyBufferToBuffer(const std::shared_ptr& aSrc, const std::shared_ptr& aDst, size_t size) { @autoreleasepool @@ -254,7 +253,6 @@ { assert(m_commandEncoder); [m_commandEncoder endEncoding]; - [m_commandEncoder release]; m_commandEncoder = nil; }} @@ -266,23 +264,10 @@ [m_mtlCommandBuffer presentDrawable:drawable->mtlDrawable()]; }} -MetalCommandBuffer::~MetalCommandBuffer() { @autoreleasepool -{ - if (m_commandEncoder != nil) - [m_commandEncoder release]; - if (m_mtlCommandBuffer != nil) - [m_mtlCommandBuffer release]; -}} - MetalCommandBuffer& MetalCommandBuffer::operator = (MetalCommandBuffer&& other) noexcept { @autoreleasepool { if (this != &other) { - if (m_commandEncoder != nil) - [m_commandEncoder release]; - if (m_mtlCommandBuffer != nil) - [m_mtlCommandBuffer release]; - m_mtlCommandBuffer = std::exchange(other.m_mtlCommandBuffer, nil); m_commandEncoder = std::exchange(other.m_commandEncoder, nil); m_usedPipelines = std::move(other.m_usedPipelines); diff --git a/src/Metal/MetalCommandBufferPool.hpp b/src/Metal/MetalCommandBufferPool.hpp index 5b8f7c2c..7f81f7a0 100644 --- a/src/Metal/MetalCommandBufferPool.hpp +++ b/src/Metal/MetalCommandBufferPool.hpp @@ -15,6 +15,10 @@ #include "Metal/MetalCommandBuffer.hpp" +#if !defined(__OBJC__) +#error this file can only by used in objective c +#endif + namespace gfx { @@ -33,7 +37,7 @@ class MetalCommandBufferPool : public CommandBufferPool ~MetalCommandBufferPool() override = default; private: - const id* m_queue = nullptr; + __weak id m_queue = nullptr; std::deque> m_availableCommandBuffers; std::deque> m_usedCommandBuffers; diff --git a/src/Metal/MetalCommandBufferPool.mm b/src/Metal/MetalCommandBufferPool.mm index 170a3408..39c3cc77 100644 --- a/src/Metal/MetalCommandBufferPool.mm +++ b/src/Metal/MetalCommandBufferPool.mm @@ -16,7 +16,7 @@ { MetalCommandBufferPool::MetalCommandBufferPool(const id* queue) - : m_queue(queue) + : m_queue(*queue) { } @@ -26,10 +26,12 @@ if (m_availableCommandBuffers.empty() == false) { commandBuffer = std::move(m_availableCommandBuffers.front()); m_availableCommandBuffers.pop_front(); - *commandBuffer = MetalCommandBuffer(*m_queue); // in metal; the command buffer need to be destoyed and recreated every frame, so availabe command buffer are empty + assert(m_queue); + *commandBuffer = MetalCommandBuffer(m_queue); // in metal; the command buffer need to be destoyed and recreated every frame, so availabe command buffer are empty } else { - commandBuffer = std::make_shared(*m_queue); + assert(m_queue); + commandBuffer = std::make_shared(m_queue); } m_usedCommandBuffers.push_back(commandBuffer); return commandBuffer; diff --git a/src/Metal/MetalDevice.hpp b/src/Metal/MetalDevice.hpp index 9348f754..4f6c63b3 100644 --- a/src/Metal/MetalDevice.hpp +++ b/src/Metal/MetalDevice.hpp @@ -23,6 +23,10 @@ #include "Metal/MetalCommandBuffer.hpp" +#if !defined(__OBJC__) +#error this file can only by used in objective c +#endif + namespace gfx { @@ -33,7 +37,7 @@ class MetalDevice : public Device MetalDevice(const MetalDevice&) = delete; MetalDevice(MetalDevice&&) = delete; - MetalDevice(id&, const Device::Descriptor&); + MetalDevice(id, const Device::Descriptor&); inline Backend backend() const override { return Backend::metal; } @@ -59,7 +63,7 @@ class MetalDevice : public Device void waitCommandBuffer(const CommandBuffer&) override; void waitIdle() override; - inline const id& mtlDevice() const { return m_mtlDevice; } + inline id mtlDevice() const { return m_mtlDevice; } ~MetalDevice() override; diff --git a/src/Metal/MetalDevice.mm b/src/Metal/MetalDevice.mm index fd9935a8..d491d9b5 100644 --- a/src/Metal/MetalDevice.mm +++ b/src/Metal/MetalDevice.mm @@ -24,21 +24,23 @@ #include "Metal/MetalDrawable.hpp" #include "Metal/MetalShaderLib.hpp" #include "MetalParameterBlockLayout.hpp" +#include +#include #if defined(GFX_IMGUI_ENABLED) -# include "Metal/imgui_impl_metal.hpp" +# include "Metal/imgui_impl_metal.h" #endif #include "Metal/MetalTexture.hpp" #include "Metal/MetalSampler.hpp" -#import "Metal/MetalEnums.h" +#import "Metal/MetalEnums.hpp" namespace gfx { -MetalDevice::MetalDevice(id& device, const Device::Descriptor&) { @autoreleasepool +MetalDevice::MetalDevice(id device, const Device::Descriptor&) + : m_mtlDevice(device) { @autoreleasepool { - m_mtlDevice = [device retain]; - m_queue = [device newCommandQueue]; + m_queue = [m_mtlDevice newCommandQueue]; }} std::unique_ptr MetalDevice::newSwapchain(const Swapchain::Descriptor& desc) const @@ -87,20 +89,26 @@ } #if defined (GFX_IMGUI_ENABLED) -void MetalDevice::imguiInit(std::vector colorPixelFomats, std::optional depthPixelFormat) const -{ - ImGui_ImplMetal_Init(this, colorPixelFomats, depthPixelFormat); -} +void MetalDevice::imguiInit(std::vector colorPixelFomats, std::optional depthPixelFormat) const { @autoreleasepool +{ + ImGui_ImplMetal_Init( + m_mtlDevice, + 1, + toMTLPixelFormat(colorPixelFomats.front()), + depthPixelFormat.has_value() ? toMTLPixelFormat(*depthPixelFormat) : MTLPixelFormatInvalid, + MTLPixelFormatInvalid + ); +}} -void MetalDevice::imguiNewFrame() const +void MetalDevice::imguiNewFrame() const { @autoreleasepool { ImGui_ImplMetal_NewFrame(); -} +}} -void MetalDevice::imguiShutdown() +void MetalDevice::imguiShutdown() { @autoreleasepool { ImGui_ImplMetal_Shutdown(); -} +}} #endif void MetalDevice::submitCommandBuffers(const std::shared_ptr& aCommandBuffer) { @autoreleasepool // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved) @@ -136,11 +144,9 @@ waitCommandBuffer(*m_submittedCommandBuffers.back()); } -MetalDevice::~MetalDevice() { @autoreleasepool +MetalDevice::~MetalDevice() { waitIdle(); - [m_queue release]; - [m_mtlDevice release]; -}} +} } diff --git a/src/Metal/MetalDrawable.hpp b/src/Metal/MetalDrawable.hpp index 37a8ea39..4ff2b772 100644 --- a/src/Metal/MetalDrawable.hpp +++ b/src/Metal/MetalDrawable.hpp @@ -12,6 +12,10 @@ #include "Graphics/Drawable.hpp" +#if !defined(__OBJC__) +#error this file can only by used in objective c +#endif + namespace gfx { @@ -28,9 +32,9 @@ class MetalDrawable : public Drawable std::shared_ptr texture() const override; - const id& mtlDrawable() const { return m_mtlDrawable; } + id mtlDrawable() const { return m_mtlDrawable; } - ~MetalDrawable() override; + ~MetalDrawable() override = default; private: id m_mtlDrawable; diff --git a/src/Metal/MetalDrawable.mm b/src/Metal/MetalDrawable.mm index 2fc8b273..ef622e4d 100644 --- a/src/Metal/MetalDrawable.mm +++ b/src/Metal/MetalDrawable.mm @@ -11,16 +11,16 @@ #include "Graphics/Texture.hpp" #include "Metal/MetalDrawable.hpp" -#include "Metal/MetalEnums.h" +#include "Metal/MetalEnums.hpp" #include "Metal/MetalTexture.hpp" namespace gfx { -MetalDrawable::MetalDrawable(id mtlDrawable) { @autoreleasepool +MetalDrawable::MetalDrawable(id mtlDrawable) + : m_mtlDrawable(mtlDrawable) { - m_mtlDrawable = [mtlDrawable retain]; -}} +} std::shared_ptr MetalDrawable::texture() const { @autoreleasepool { @@ -36,9 +36,4 @@ return std::make_shared(m_mtlDrawable.texture, desc); }} -MetalDrawable::~MetalDrawable() -{ - [m_mtlDrawable release]; -} - } diff --git a/src/Metal/MetalEnums.h b/src/Metal/MetalEnums.hpp similarity index 97% rename from src/Metal/MetalEnums.h rename to src/Metal/MetalEnums.hpp index 4f03d152..cb62f68e 100644 --- a/src/Metal/MetalEnums.h +++ b/src/Metal/MetalEnums.hpp @@ -1,15 +1,17 @@ /* * --------------------------------------------------- - * MetalEnums.h + * MetalEnums.hpp * * Author: Thomas Choquet * Date: 2025/06/02 07:17:50 * --------------------------------------------------- */ -#include -#ifndef __OBJC__ -#error "this file can only by used in objective c" +#ifndef METALENUMS_HPP +#define METALENUMS_HPP + +#if !defined(__OBJC__) +#error this file can only by used in objective c #endif #include "Graphics/Enums.hpp" @@ -195,3 +197,5 @@ constexpr MTLCullMode toMTLCullMode(CullMode cullMode) } } + +#endif diff --git a/src/Metal/MetalGraphicsPipeline.hpp b/src/Metal/MetalGraphicsPipeline.hpp index 0cd02c81..80749d2f 100644 --- a/src/Metal/MetalGraphicsPipeline.hpp +++ b/src/Metal/MetalGraphicsPipeline.hpp @@ -12,6 +12,10 @@ #include "Graphics/GraphicsPipeline.hpp" +#if !defined(__OBJC__) +#error this file can only by used in objective c +#endif + namespace gfx { @@ -26,11 +30,11 @@ class MetalGraphicsPipeline : public GraphicsPipeline MetalGraphicsPipeline(const MetalDevice&, const GraphicsPipeline::Descriptor&); - inline const id& renderPipelineState() const { return m_renderPipelineState; } - inline const id& depthStencilState() const { return m_depthStencilState; } + inline id renderPipelineState() const { return m_renderPipelineState; } + inline id depthStencilState() const { return m_depthStencilState; } inline CullMode cullMode() const { return m_cullMode; } - ~MetalGraphicsPipeline() override; + ~MetalGraphicsPipeline() override = default; private: id m_renderPipelineState = nil; diff --git a/src/Metal/MetalGraphicsPipeline.mm b/src/Metal/MetalGraphicsPipeline.mm index cb3400df..9df99322 100644 --- a/src/Metal/MetalGraphicsPipeline.mm +++ b/src/Metal/MetalGraphicsPipeline.mm @@ -14,9 +14,7 @@ #include "Metal/MetalDevice.hpp" #include "Metal/MetalShaderFunction.hpp" -#import "Metal/MetalEnums.h" -#include <__ostream/print.h> -#include +#import "Metal/MetalEnums.hpp" namespace gfx { @@ -33,7 +31,7 @@ assert(desc.vertexShader != nullptr); assert(desc.fragmentShader != nullptr); - MTLRenderPipelineDescriptor* renderPipelineDescriptor = [[[MTLRenderPipelineDescriptor alloc] init] autorelease]; + MTLRenderPipelineDescriptor* renderPipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; MTLDepthStencilDescriptor* depthStencilDescriptor = nil; if (auto& vertexLayout = desc.vertexLayout) @@ -95,7 +93,7 @@ if (auto& depthPxFmt = desc.depthAttachmentPxFormat) { renderPipelineDescriptor.depthAttachmentPixelFormat = (MTLPixelFormat)toMTLPixelFormat(*depthPxFmt); - depthStencilDescriptor = [[[MTLDepthStencilDescriptor alloc] init] autorelease]; + depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init]; depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionLessEqual; depthStencilDescriptor.depthWriteEnabled = YES; } @@ -111,24 +109,12 @@ if (m_renderPipelineState == nil) throw std::runtime_error("failed to create DepthStencilState"); } - - m_cullMode = desc.cullMode; -}} - -MetalGraphicsPipeline::~MetalGraphicsPipeline() { @autoreleasepool -{ - if (m_depthStencilState) - [m_depthStencilState release]; - [m_renderPipelineState release]; }} MetalGraphicsPipeline& MetalGraphicsPipeline::operator=(MetalGraphicsPipeline&& other) noexcept { if (this != &other) { - if (m_depthStencilState) - [m_depthStencilState release]; - [m_renderPipelineState release]; m_renderPipelineState = std::exchange(other.m_renderPipelineState, nil); m_depthStencilState = std::exchange(other.m_depthStencilState, nil); m_cullMode = std::exchange(other.m_cullMode, CullMode::none); diff --git a/src/Metal/MetalInstance.hpp b/src/Metal/MetalInstance.hpp index a526db90..c8fa177f 100644 --- a/src/Metal/MetalInstance.hpp +++ b/src/Metal/MetalInstance.hpp @@ -32,11 +32,7 @@ class MetalInstance : public Instance std::unique_ptr newDevice(const Device::Descriptor&) override; - ~MetalInstance() override; - -private: - NSAutoreleasePool* m_autoReleasePool; - std::vector> m_devices; + ~MetalInstance() override = default; public: MetalInstance& operator=(const MetalInstance&) = delete; diff --git a/src/Metal/MetalInstance.mm b/src/Metal/MetalInstance.mm index 0ba39b36..843a12f6 100644 --- a/src/Metal/MetalInstance.mm +++ b/src/Metal/MetalInstance.mm @@ -17,7 +17,6 @@ { MetalInstance::MetalInstance(const Instance::Descriptor&) - : m_autoReleasePool([[NSAutoreleasePool alloc] init]) { } @@ -28,31 +27,15 @@ } #endif -std::unique_ptr MetalInstance::newDevice(const Device::Descriptor& desc) +std::unique_ptr MetalInstance::newDevice(const Device::Descriptor& desc) { @autoreleasepool { - @autoreleasepool - { - for (auto& device : m_devices) - [device release]; // refresh the list when newDevice is called - #if defined(TARGET_OS_OSX) - NSArray>* mtlDevices = [MTLCopyAllDevices() autorelease]; + NSArray>* mtlDevices = MTLCopyAllDevices(); #else - NSArray>* mtlDevices = @[ MTLCreateSystemDefaultDevice() ]; + NSArray>* mtlDevices = @[ MTLCreateSystemDefaultDevice() ]; #endif - for (NSUInteger i = 0; i < mtlDevices.count; i++) - m_devices.push_back([[mtlDevices objectAtIndex:i] retain]); - - return std::make_unique(m_devices.front(), desc); - } -} - -MetalInstance::~MetalInstance() -{ - for (auto& device : m_devices) - [device release]; - [m_autoReleasePool drain]; -} + return std::make_unique(mtlDevices.firstObject, desc); +}} } // namespace gfx diff --git a/src/Metal/MetalParameterBlockPool.cpp b/src/Metal/MetalParameterBlockPool.mm similarity index 100% rename from src/Metal/MetalParameterBlockPool.cpp rename to src/Metal/MetalParameterBlockPool.mm diff --git a/src/Metal/MetalSampler.hpp b/src/Metal/MetalSampler.hpp index c51df919..4fb5d9b8 100644 --- a/src/Metal/MetalSampler.hpp +++ b/src/Metal/MetalSampler.hpp @@ -12,6 +12,10 @@ #include "Graphics/Sampler.hpp" +#if !defined(__OBJC__) +#error this file can only by used in objective c +#endif + namespace gfx { @@ -26,9 +30,9 @@ class MetalSampler : public Sampler MetalSampler(const MetalDevice&, const Sampler::Descriptor&); - inline const id& mtlSamplerState() const { return m_mtlSamplerState; } + inline id mtlSamplerState() const { return m_mtlSamplerState; } - ~MetalSampler() override; + ~MetalSampler() override = default; private: id m_mtlSamplerState = nil; diff --git a/src/Metal/MetalSampler.mm b/src/Metal/MetalSampler.mm index 50ba7392..0a3b6282 100644 --- a/src/Metal/MetalSampler.mm +++ b/src/Metal/MetalSampler.mm @@ -12,7 +12,7 @@ #include "Metal/MetalSampler.hpp" #include "Metal/MetalDevice.hpp" -#import "Metal/MetalEnums.h" +#import "Metal/MetalEnums.hpp" namespace gfx { @@ -24,7 +24,7 @@ MetalSampler::MetalSampler(const MetalDevice& device, const Sampler::Descriptor& descriptor) { @autoreleasepool { - MTLSamplerDescriptor* mtlSamplerDescriptor = [[[MTLSamplerDescriptor alloc] init] autorelease]; + MTLSamplerDescriptor* mtlSamplerDescriptor = [[MTLSamplerDescriptor alloc] init]; mtlSamplerDescriptor.sAddressMode = toMTLSamplerAddressMode(descriptor.sAddressMode); mtlSamplerDescriptor.tAddressMode = toMTLSamplerAddressMode(descriptor.tAddressMode); @@ -38,18 +38,10 @@ throw std::runtime_error("sampler state creation failed"); }} -MetalSampler::~MetalSampler() -{ - if(m_mtlSamplerState != nil) - [m_mtlSamplerState release]; -} - MetalSampler& MetalSampler::operator=(MetalSampler&& other) noexcept { if (this != &other) { - if(m_mtlSamplerState != nil) - [m_mtlSamplerState release]; m_mtlSamplerState = std::exchange(other.m_mtlSamplerState, nil); } return *this; diff --git a/src/Metal/MetalShaderFunction.hpp b/src/Metal/MetalShaderFunction.hpp index a7ea5385..5b89d8d1 100644 --- a/src/Metal/MetalShaderFunction.hpp +++ b/src/Metal/MetalShaderFunction.hpp @@ -12,6 +12,10 @@ #include "Graphics/ShaderFunction.hpp" +#if !defined(__OBJC__) +#error this file can only by used in objective c +#endif + namespace gfx { @@ -26,7 +30,7 @@ class MetalShaderFunction : public ShaderFunction inline id mtlFunction() { return m_mtlFunction; } - ~MetalShaderFunction() override; + ~MetalShaderFunction() override = default; private: id m_mtlFunction; diff --git a/src/Metal/MetalShaderFunction.mm b/src/Metal/MetalShaderFunction.mm index 2f812af0..3646b0b5 100644 --- a/src/Metal/MetalShaderFunction.mm +++ b/src/Metal/MetalShaderFunction.mm @@ -13,30 +13,25 @@ namespace gfx { -MetalShaderFunction::MetalShaderFunction(MetalShaderFunction&& other) noexcept { @autoreleasepool +MetalShaderFunction::MetalShaderFunction(MetalShaderFunction&& other) noexcept + : m_mtlFunction(other.m_mtlFunction) { - m_mtlFunction = [other.m_mtlFunction retain]; -}} +} MetalShaderFunction::MetalShaderFunction(const id& mtlLibrary, const std::string& name) { @autoreleasepool { - NSString* functionNameNSString = [[[NSString alloc] initWithCString:name.c_str() encoding:NSUTF8StringEncoding] autorelease]; + NSString* functionNameNSString = [[NSString alloc] initWithCString:name.c_str() encoding:NSUTF8StringEncoding]; m_mtlFunction = [mtlLibrary newFunctionWithName:functionNameNSString]; if (m_mtlFunction == nil) throw std::runtime_error("failed to create the MTLFunction"); }} -MetalShaderFunction::~MetalShaderFunction() { @autoreleasepool -{ - [m_mtlFunction release]; -}} - MetalShaderFunction& MetalShaderFunction::operator=(MetalShaderFunction&& other) noexcept { @autoreleasepool { if (&other != this) { ShaderFunction::operator=(std::move(other)); - m_mtlFunction = [other.m_mtlFunction retain]; + m_mtlFunction = other.m_mtlFunction; } return *this; }} diff --git a/src/Metal/MetalShaderLib.hpp b/src/Metal/MetalShaderLib.hpp index b66b50e1..3f6ee9c6 100644 --- a/src/Metal/MetalShaderLib.hpp +++ b/src/Metal/MetalShaderLib.hpp @@ -14,6 +14,10 @@ #include "Metal/MetalShaderFunction.hpp" +#if !defined(__OBJC__) +#error this file can only by used in objective c +#endif + namespace gfx { @@ -30,7 +34,7 @@ class MetalShaderLib : public ShaderLib MetalShaderFunction& getFunction(const std::string&) override; - ~MetalShaderLib() override; + ~MetalShaderLib() override = default; private: id m_mtlLibrary; diff --git a/src/Metal/MetalShaderLib.mm b/src/Metal/MetalShaderLib.mm index a3948269..787fa592 100644 --- a/src/Metal/MetalShaderLib.mm +++ b/src/Metal/MetalShaderLib.mm @@ -41,10 +41,4 @@ return it->second; }} -MetalShaderLib::~MetalShaderLib() { @autoreleasepool -{ - m_shaderFunctions.clear(); - [m_mtlLibrary release]; -}} - } // namespace gfx diff --git a/src/Metal/MetalSurface.hpp b/src/Metal/MetalSurface.hpp index 441800aa..66755a62 100644 --- a/src/Metal/MetalSurface.hpp +++ b/src/Metal/MetalSurface.hpp @@ -13,6 +13,10 @@ #include "Graphics/Surface.hpp" #include "Graphics/Enums.hpp" +#if !defined(__OBJC__) +#error this file can only by used in objective c +#endif + namespace gfx { @@ -32,7 +36,7 @@ class MetalSurface : public Surface CAMetalLayer* mtlLayer() const { return m_mtlLayer; } - ~MetalSurface() override; + ~MetalSurface() override = default; private: CAMetalLayer* m_mtlLayer; diff --git a/src/Metal/MetalSurface.mm b/src/Metal/MetalSurface.mm index c5e2a956..d28bd0bc 100644 --- a/src/Metal/MetalSurface.mm +++ b/src/Metal/MetalSurface.mm @@ -17,7 +17,7 @@ #if defined(GFX_GLFW_ENABLED) MetalSurface::MetalSurface(GLFWwindow* glfwWindow) { @autoreleasepool { - m_mtlLayer = [[CAMetalLayer layer] retain]; + m_mtlLayer = [CAMetalLayer layer]; NSWindow* nswindow = glfwGetCocoaWindow(glfwWindow); // NOLINT nswindow.contentView.layer = m_mtlLayer; @@ -40,9 +40,4 @@ }; } -MetalSurface::~MetalSurface() { @autoreleasepool -{ - [m_mtlLayer release]; -}} - } diff --git a/src/Metal/MetalSwapchain.hpp b/src/Metal/MetalSwapchain.hpp index dd511688..096ee569 100644 --- a/src/Metal/MetalSwapchain.hpp +++ b/src/Metal/MetalSwapchain.hpp @@ -13,6 +13,10 @@ #include "Graphics/Swapchain.hpp" #include "Graphics/Drawable.hpp" +#if !defined(__OBJC__) +#error this file can only by used in objective c +#endif + namespace gfx { @@ -28,7 +32,7 @@ class MetalSwapchain : public Swapchain std::shared_ptr nextDrawable() override; - ~MetalSwapchain() override; + ~MetalSwapchain() override = default; private: CAMetalLayer* m_mtlLayer; diff --git a/src/Metal/MetalSwapchain.mm b/src/Metal/MetalSwapchain.mm index 0f21e115..841419c9 100644 --- a/src/Metal/MetalSwapchain.mm +++ b/src/Metal/MetalSwapchain.mm @@ -15,7 +15,7 @@ #include "Metal/MetalDrawable.hpp" #include "Metal/MetalSurface.hpp" -#import "Metal/MetalEnums.h" +#import "Metal/MetalEnums.hpp" namespace gfx { @@ -26,7 +26,7 @@ auto* mtlSurface = dynamic_cast(desc.surface); assert(mtlSurface); - m_mtlLayer = [mtlSurface->mtlLayer() retain]; + m_mtlLayer = mtlSurface->mtlLayer(); m_mtlLayer.device = device.mtlDevice(); m_mtlLayer.drawableSize = CGSize{CGFloat(desc.width), CGFloat(desc.height)}; m_mtlLayer.pixelFormat = toMTLPixelFormat(desc.pixelFormat); @@ -37,9 +37,4 @@ return std::make_shared([m_mtlLayer nextDrawable]); }} -MetalSwapchain::~MetalSwapchain() { @autoreleasepool -{ - [m_mtlLayer release]; -}} - } diff --git a/src/Metal/MetalTexture.hpp b/src/Metal/MetalTexture.hpp index cea1afa0..7e4b4a0f 100644 --- a/src/Metal/MetalTexture.hpp +++ b/src/Metal/MetalTexture.hpp @@ -15,6 +15,10 @@ #include "Metal/MetalBuffer.hpp" +#if !defined(__OBJC__) +#error this file can only by used in objective c +#endif + namespace gfx { @@ -37,9 +41,9 @@ class MetalTexture : public Texture inline TextureUsages usages() const override { return m_usages; }; inline ResourceStorageMode storageMode() const override { return m_storageMode; }; - inline const id& mtltexture() const { return m_mtlTexture; } + inline id mtltexture() const { return m_mtlTexture; } - ~MetalTexture() override; + ~MetalTexture() override = default; private: TextureUsages m_usages; diff --git a/src/Metal/MetalTexture.mm b/src/Metal/MetalTexture.mm index 5426abca..e082005e 100644 --- a/src/Metal/MetalTexture.mm +++ b/src/Metal/MetalTexture.mm @@ -13,21 +13,22 @@ #include "Metal/MetalDevice.hpp" #include "Metal/MetalTexture.hpp" -#import "Metal/MetalEnums.h" +#import "Metal/MetalEnums.hpp" namespace gfx { MetalTexture::MetalTexture(const id& mtltexture, const Texture::Descriptor& desc) - : m_usages(desc.usages), m_storageMode(desc.storageMode) { @autoreleasepool + : m_usages(desc.usages) + , m_storageMode(desc.storageMode) + , m_mtlTexture(mtltexture) { - m_mtlTexture = [mtltexture retain]; -}} +} MetalTexture::MetalTexture(const MetalDevice& device, const Texture::Descriptor& desc) : m_usages(desc.usages), m_storageMode(desc.storageMode) { @autoreleasepool { - MTLTextureDescriptor* mtlTextureDescriptor = [[[MTLTextureDescriptor alloc] init] autorelease]; + MTLTextureDescriptor* mtlTextureDescriptor = [[MTLTextureDescriptor alloc] init]; mtlTextureDescriptor.textureType = toMTLTextureType(desc.type); mtlTextureDescriptor.pixelFormat = toMTLPixelFormat(desc.pixelFormat); mtlTextureDescriptor.width = desc.width; @@ -61,9 +62,4 @@ return toPixelFormat([mtltexture() pixelFormat]); }} -MetalTexture::~MetalTexture() { @autoreleasepool -{ - [m_mtlTexture release]; -}} - } diff --git a/src/Metal/imgui_impl_metal.h b/src/Metal/imgui_impl_metal.h new file mode 100644 index 00000000..66965c9b --- /dev/null +++ b/src/Metal/imgui_impl_metal.h @@ -0,0 +1,81 @@ +// dear imgui: Renderer Backend for Metal +// This needs to be used along with a Platform Backend (e.g. OSX) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'MTLTexture' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! +// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +#pragma once +#include "imgui.h" // IMGUI_IMPL_API +#ifndef IMGUI_DISABLE + +//----------------------------------------------------------------------------- +// ObjC API +//----------------------------------------------------------------------------- + +#ifdef __OBJC__ + +@class MTLRenderPassDescriptor; +@protocol MTLDevice, MTLCommandBuffer, MTLRenderCommandEncoder; + +// Follow "Getting Started" link and check examples/ folder to learn about using backends! +IMGUI_IMPL_API bool ImGui_ImplMetal_Init(id device); +IMGUI_IMPL_API bool ImGui_ImplMetal_Init(id device, NSUInteger sampleCount, MTLPixelFormat colorPixelFormat, MTLPixelFormat depthPixelFormat, MTLPixelFormat stencilPixelFormat); +IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor); +IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(); +IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData, + id commandBuffer, + id commandEncoder); + +// Called by Init/NewFrame/Shutdown +IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(id device); +IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects(); + +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually. +IMGUI_IMPL_API void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex); + +#endif + +//----------------------------------------------------------------------------- +// C++ API +//----------------------------------------------------------------------------- + +// Enable Metal C++ binding support with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file +// More info about using Metal from C++: https://developer.apple.com/metal/cpp/ + +#ifdef IMGUI_IMPL_METAL_CPP +#include +#ifndef __OBJC__ + +// Follow "Getting Started" link and check examples/ folder to learn about using backends! +IMGUI_IMPL_API bool ImGui_ImplMetal_Init(MTL::Device* device); +IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor); +IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, + MTL::CommandBuffer* commandBuffer, + MTL::RenderCommandEncoder* commandEncoder); + +// Called by Init/NewFrame/Shutdown +IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device); +IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects(); + +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually. +IMGUI_IMPL_API void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex); + +#endif +#endif + +//----------------------------------------------------------------------------- + +#endif // #ifndef IMGUI_DISABLE diff --git a/src/Metal/imgui_impl_metal.hpp b/src/Metal/imgui_impl_metal.hpp deleted file mode 100644 index f2daf74e..00000000 --- a/src/Metal/imgui_impl_metal.hpp +++ /dev/null @@ -1,27 +0,0 @@ -/* - * --------------------------------------------------- - * imgui_impl_metal.hpp - * - * Author: Thomas Choquet - * Date: 2025/08/30 14:23:22 - * --------------------------------------------------- - */ - -#ifndef IMGUI_IMPL_METAL_HPP -#define IMGUI_IMPL_METAL_HPP - -#include "Graphics/Enums.hpp" - -#include "Metal/MetalDevice.hpp" - -namespace gfx -{ - -void ImGui_ImplMetal_Init(const MetalDevice*, const std::vector&, const std::optional&); -void ImGui_ImplMetal_Shutdown(); -void ImGui_ImplMetal_NewFrame(); -void ImGui_ImplMetal_RenderDrawData(ImDrawData*, id, id); - -} - -#endif // IMGUI_IMPL_METAL_HPP diff --git a/src/Metal/imgui_impl_metal.mm b/src/Metal/imgui_impl_metal.mm index 969f06ba..5ef12b5e 100644 --- a/src/Metal/imgui_impl_metal.mm +++ b/src/Metal/imgui_impl_metal.mm @@ -1,24 +1,48 @@ -/* - * --------------------------------------------------- - * imgui_impl_metal.mm - * - * Author: Thomas Choquet - * Date: 2025/08/30 14:21:30 - * --------------------------------------------------- - */ - -#include "Graphics/Enums.hpp" -#include "Graphics/ShaderLib.hpp" -#include "Graphics/Texture.hpp" -#include "Graphics/VertexLayout.hpp" -#include "Metal/MetalBuffer.hpp" -#include "Metal/MetalCommandBuffer.hpp" -#include "Metal/MetalDevice.hpp" -#include "Metal/MetalEnums.h" -#include "Metal/imgui_impl_metal.hpp" -#include "Metal/MetalSampler.hpp" -#include "Metal/MetalTexture.hpp" -#include "Metal/MetalGraphicsPipeline.hpp" +// dear imgui: Renderer Backend for Metal +// This needs to be used along with a Platform Backend (e.g. OSX) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'MTLTexture' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! +// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2025-XX-XX: Metal: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplMetal_CreateFontsTexture() and ImGui_ImplMetal_DestroyFontsTexture(). +// 2025-02-03: Metal: Crash fix. (#8367) +// 2025-01-08: Metal: Fixed memory leaks when using metal-cpp (#8276, #8166) or when using multiple contexts (#7419). +// 2022-08-23: Metal: Update deprecated property 'sampleCount'->'rasterSampleCount'. +// 2022-07-05: Metal: Add dispatch synchronization. +// 2022-06-30: Metal: Use __bridge for ARC based systems. +// 2022-06-01: Metal: Fixed null dereference on exit inside command buffer completion handler. +// 2022-04-27: Misc: Store backend data in a per-context struct, allowing to use this backend with multiple contexts. +// 2022-01-03: Metal: Ignore ImDrawCmd where ElemCount == 0 (very rare but can technically be manufactured by user code). +// 2021-12-30: Metal: Added Metal C++ support. Enable with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file. +// 2021-08-24: Metal: Fixed a crash when clipping rect larger than framebuffer is submitted. (#4464) +// 2021-05-19: Metal: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) +// 2021-02-18: Metal: Change blending equation to preserve alpha in output buffer. +// 2021-01-25: Metal: Fixed texture storage mode when building on Mac Catalyst. +// 2019-05-29: Metal: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. +// 2019-04-30: Metal: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. +// 2019-02-11: Metal: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display. +// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. +// 2018-07-05: Metal: Added new Metal backend implementation. + +#include "imgui.h" +#ifndef IMGUI_DISABLE +#include "imgui_impl_metal.h" +#import +#import // NOLINTBEGIN #define GetCurrentContext() (reinterpret_cast(getSym(DL_DEFAULT, "GetCurrentContext"))()) @@ -46,256 +70,232 @@ #define IM_DELETE GFX_IM_DELETE // NOLINTEND -namespace gfx -{ +// Forward Declarations +static void ImGui_ImplMetal_InitMultiViewportSupport(); +static void ImGui_ImplMetal_ShutdownMultiViewportSupport(); +static void ImGui_ImplMetal_CreateDeviceObjectsForPlatformWindows(); +static void ImGui_ImplMetal_InvalidateDeviceObjectsForPlatformWindows(); + +#pragma mark - Support classes + +// A wrapper around a MTLBuffer object that knows the last time it was reused +@interface MetalBuffer : NSObject +@property (nonatomic, strong) id buffer; +@property (nonatomic, assign) double lastReuseTime; +- (instancetype)initWithBuffer:(id)buffer; +@end + +// An object that encapsulates the data necessary to uniquely identify a +// render pipeline state. These are used as cache keys. +@interface FramebufferDescriptor : NSObject +@property (nonatomic, assign) unsigned long sampleCount; +@property (nonatomic, assign) MTLPixelFormat colorPixelFormat; +@property (nonatomic, assign) MTLPixelFormat depthPixelFormat; +@property (nonatomic, assign) MTLPixelFormat stencilPixelFormat; +- (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor; +- (instancetype)initWithSampleCount:(NSUInteger)sampleCount colorPixelFormat:(MTLPixelFormat)colorPixelFormat depthPixelFormat:(MTLPixelFormat)depthPixelFormat stencilPixelFormat:(MTLPixelFormat)stencilPixelFormat; +@end + +@interface MetalTexture : NSObject +@property (nonatomic, strong) id metalTexture; +- (instancetype)initWithTexture:(id)metalTexture; +@end + +// A singleton that stores long-lived objects that are needed by the Metal +// renderer backend. Stores the render pipeline state cache and the default +// font texture, and manages the reusable buffer cache. +@interface MetalContext : NSObject +@property (nonatomic, strong) id device; +@property (nonatomic, strong) id depthStencilState; +@property (nonatomic, strong) FramebufferDescriptor* framebufferDescriptor; // framebuffer descriptor for current frame; transient +@property (nonatomic, strong) NSMutableDictionary* renderPipelineStateCache; // pipeline cache; keyed on framebuffer descriptors +@property (nonatomic, strong) NSMutableArray* bufferCache; +@property (nonatomic, assign) double lastBufferCachePurge; +- (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device; +- (id)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id)device; +@end -namespace +struct ImGui_ImplMetal_Data { - inline CGSize MakeScaledSize(CGSize size, CGFloat scale) { return CGSizeMake(size.width * scale, size.height * scale); } - inline double GetMachAbsoluteTimeInSeconds() { return static_cast(clock_gettime_nsec_np(CLOCK_UPTIME_RAW)) / 1e9; } -} + MetalContext* SharedMetalContext; -struct FramebufferDescriptor -{ - uint32_t sampleCount = 1; - std::vector colorPixelFormats; - std::optional depthPixelFormat; - std::optional stencilPixelFormat; + ImGui_ImplMetal_Data() { memset((void*)this, 0, sizeof(*this)); } +}; - // Comparison operators to allow use as a key in ordered containers (e.g., std::map) - friend bool operator==(const FramebufferDescriptor& a, const FramebufferDescriptor& b) - { - if (a.sampleCount != b.sampleCount) - return false; - - auto na = a.colorPixelFormats.size(); - auto nb = b.colorPixelFormats.size(); - if (na != nb) - return false; - for (decltype(na) i = 0; i < na; ++i) - { - if (a.colorPixelFormats[i] != b.colorPixelFormats[i]) - return false; - } +static ImGui_ImplMetal_Data* ImGui_ImplMetal_GetBackendData() { return GetCurrentContext() ? (ImGui_ImplMetal_Data*)GetIO().BackendRendererUserData : nullptr; } +static void ImGui_ImplMetal_DestroyBackendData(){ IM_DELETE(ImGui_ImplMetal_GetBackendData()); } - const bool aHasDepth = a.depthPixelFormat.has_value(); - const bool bHasDepth = b.depthPixelFormat.has_value(); - if (aHasDepth != bHasDepth) - return false; - if (aHasDepth && (*a.depthPixelFormat != *b.depthPixelFormat)) - return false; - - const bool aHasStencil = a.stencilPixelFormat.has_value(); - const bool bHasStencil = b.stencilPixelFormat.has_value(); - if (aHasStencil != bHasStencil) - return false; - if (aHasStencil && (*a.stencilPixelFormat != *b.stencilPixelFormat)) - return false; - - return true; - } +static inline CFTimeInterval GetMachAbsoluteTimeInSeconds() { return (CFTimeInterval)(double)(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1e9); } - friend bool operator<(const FramebufferDescriptor& a, const FramebufferDescriptor& b) - { - if (a.sampleCount != b.sampleCount) - return a.sampleCount < b.sampleCount; - - auto na = a.colorPixelFormats.size(); - auto nb = b.colorPixelFormats.size(); - if (na != nb) - return na < nb; - for (decltype(na) i = 0; i < na; ++i) - { - if (a.colorPixelFormats[i] != b.colorPixelFormats[i]) - return a.colorPixelFormats[i] < b.colorPixelFormats[i]; - } +#ifdef IMGUI_IMPL_METAL_CPP - const bool aHasDepth = a.depthPixelFormat.has_value(); - const bool bHasDepth = b.depthPixelFormat.has_value(); - if (aHasDepth != bHasDepth) - return !aHasDepth && bHasDepth; // no depth < has depth - if (aHasDepth && (*a.depthPixelFormat != *b.depthPixelFormat)) - return *a.depthPixelFormat < *b.depthPixelFormat; - - const bool aHasStencil = a.stencilPixelFormat.has_value(); - const bool bHasStencil = b.stencilPixelFormat.has_value(); - if (aHasStencil != bHasStencil) - return !aHasStencil && bHasStencil; // no stencil < has stencil - if (aHasStencil && (*a.stencilPixelFormat != *b.stencilPixelFormat)) - return *a.stencilPixelFormat < *b.stencilPixelFormat; - - return false; // equal - } -}; +#pragma mark - Dear ImGui Metal C++ Backend API -struct ImGui_ImplMetal_Data +bool ImGui_ImplMetal_Init(MTL::Device* device) { - const MetalDevice* device = nullptr; - FramebufferDescriptor framebufferDescriptor; - - std::map graphicsPipelineCache; - std::vector> bufferCache; - std::map bufferLastReuseTimes; - double lastBufferCachePurge = GetMachAbsoluteTimeInSeconds(); - std::mutex bufferCacheMtx; - MetalSampler linearSampler; - - std::unique_ptr bufferOfLenght(uint32_t len) - { - double now = GetMachAbsoluteTimeInSeconds(); + return ImGui_ImplMetal_Init((__bridge id)(device)); +} - { - std::scoped_lock lock(this->bufferCacheMtx); +void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor) +{ + ImGui_ImplMetal_NewFrame((__bridge MTLRenderPassDescriptor*)(renderPassDescriptor)); +} - // Purge old buffers that haven't been useful for a while - if (now - this->lastBufferCachePurge > 1.0) - { - auto res = this->bufferCache | std::views::filter([&](auto& buff) -> bool { - return this->bufferLastReuseTimes[buff.get()] <= this->lastBufferCachePurge; - }); - std::vector> survivors; - std::map survivorsLastReuseTimes; - for (auto& buff : res) { - survivors.push_back(std::move(buff)); - survivorsLastReuseTimes[buff.get()] = this->bufferLastReuseTimes[buff.get()]; - } - this->bufferCache = std::move(survivors); - this->bufferLastReuseTimes = std::move(survivorsLastReuseTimes); - this->lastBufferCachePurge = now; - } +void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, + MTL::CommandBuffer* commandBuffer, + MTL::RenderCommandEncoder* commandEncoder) +{ + ImGui_ImplMetal_RenderDrawData(draw_data, + (__bridge id)(commandBuffer), + (__bridge id)(commandEncoder)); - // See if we have a buffer we can reuse - auto bestCandidate = this->bufferCache.end(); - for (auto candidate = this->bufferCache.begin(); candidate != this->bufferCache.end(); ++candidate) { - if ((*candidate)->size() >= len && (bestCandidate == this->bufferCache.end() || this->bufferLastReuseTimes[bestCandidate->get()] > this->bufferLastReuseTimes[candidate->get()])) - bestCandidate = candidate; - } +} - if (bestCandidate != this->bufferCache.end()) - { - std::unique_ptr buff = std::move(*bestCandidate); - this->bufferCache.erase(bestCandidate); - this->bufferLastReuseTimes[buff.get()] = now; - return buff; - } - } +bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device) +{ + return ImGui_ImplMetal_CreateDeviceObjects((__bridge id)(device)); +} - // No luck; make a new buffer - Buffer::Descriptor bufferDescriptor = { - .size = len, - .usages = BufferUsage::vertexBuffer, - .storageMode = ResourceStorageMode::hostVisible - }; - return std::make_unique(*device, bufferDescriptor); - } -}; +#endif // #ifdef IMGUI_IMPL_METAL_CPP -struct ImGuiViewportDataMetal -{ - CAMetalLayer* MetalLayer = nullptr; - id CommandQueue = nil; - MTLRenderPassDescriptor* RenderPassDescriptor = nullptr; - NSWindow* Handle = nullptr; - bool FirstFrame = true; -}; +#pragma mark - Dear ImGui Metal Backend API -namespace +bool ImGui_ImplMetal_Init(id device) { + ImGuiIO& io = GetIO(); + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); -inline ImGui_ImplMetal_Data* ImGui_ImplMetal_GetBackendData() { return GetCurrentContext() ? static_cast(GetIO().BackendRendererUserData) : nullptr; } + ImGui_ImplMetal_Data* bd = IM_NEW(ImGui_ImplMetal_Data)(); + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_metal"; + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) -void ImGui_ImplMetal_CreateWindow(ImGuiViewport*); -void ImGui_ImplMetal_DestroyWindow(ImGuiViewport*); -void ImGui_ImplMetal_SetWindowSize(ImGuiViewport*, ImVec2 size); -void ImGui_ImplMetal_RenderWindow(ImGuiViewport*, void*); + bd->SharedMetalContext = [[MetalContext alloc] init]; + bd->SharedMetalContext.device = device; -void ImGui_ImplMetal_DestroyTexture(ImTextureData* tex); -void ImGui_ImplMetal_CreateDeviceObjects(); -MetalGraphicsPipeline graphicPipelineStateForFramebufferDescriptor(const MetalDevice&, const FramebufferDescriptor&); -void ImGui_ImplMetal_UpdateTexture(ImTextureData*); -void ImGui_ImplMetal_SetupRenderState(ImDrawData* draw_data, id, id, const MetalGraphicsPipeline&, const MetalBuffer& vertexBuffer, size_t vertexBufferOffset); + ImGui_ImplMetal_InitMultiViewportSupport(); + return true; } -void ImGui_ImplMetal_Init(const MetalDevice* device, const std::vector& colorPixelFomats, const std::optional& depthPixelFormat) { @autoreleasepool +bool ImGui_ImplMetal_Init(id device, NSUInteger sampleCount, MTLPixelFormat colorPixelFormat, MTLPixelFormat depthPixelFormat, MTLPixelFormat stencilPixelFormat) { ImGuiIO& io = GetIO(); IMGUI_CHECKVERSION(); IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); - auto* bd = new ImGui_ImplMetal_Data; // NOLINT(cppcoreguidelines-owning-memory) - bd->device = device; - bd->framebufferDescriptor = { - .sampleCount = 1, - .colorPixelFormats = colorPixelFomats, - .depthPixelFormat = depthPixelFormat, - .stencilPixelFormat = std::nullopt - }; - bd->linearSampler = MetalSampler(*device, Sampler::Descriptor{}); - + ImGui_ImplMetal_Data* bd = IM_NEW(ImGui_ImplMetal_Data)(); io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_metal"; - io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. - io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. - io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) - ImGuiPlatformIO& platform_io = GetPlatformIO(); - platform_io.Renderer_CreateWindow = ImGui_ImplMetal_CreateWindow; - platform_io.Renderer_DestroyWindow = ImGui_ImplMetal_DestroyWindow; - platform_io.Renderer_SetWindowSize = ImGui_ImplMetal_SetWindowSize; - platform_io.Renderer_RenderWindow = ImGui_ImplMetal_RenderWindow; -}} + bd->SharedMetalContext = [[MetalContext alloc] init]; + bd->SharedMetalContext.device = device; + + ImGui_ImplMetal_InitMultiViewportSupport(); -void ImGui_ImplMetal_Shutdown() { @autoreleasepool + #ifdef IMGUI_IMPL_METAL_CPP + #error "not implemented" + #else + bd->SharedMetalContext.framebufferDescriptor = [[FramebufferDescriptor alloc] initWithSampleCount:sampleCount + colorPixelFormat:colorPixelFormat + depthPixelFormat:depthPixelFormat + stencilPixelFormat:stencilPixelFormat]; + #endif + + return true; +} + +void ImGui_ImplMetal_Shutdown() { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); IM_UNUSED(bd); IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); - - // ImGui_ImplMetal_ShutdownMultiViewportSupport - DestroyPlatformWindows(); - // - - // ImGui_ImplMetal_DestroyDeviceObjects - // { - // ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); - // - // Destroy all textures - for (ImTextureData* tex : GetPlatformIO().Textures) { - if (tex->RefCount == 1) - ImGui_ImplMetal_DestroyTexture(tex); - } - - // ImGui_ImplMetal_InvalidateDeviceObjectsForPlatformWindows - // { - ImGuiPlatformIO& platform_io = GetPlatformIO(); - for (int i = 1; i < platform_io.Viewports.Size; i++) - if (platform_io.Viewports[i]->RendererUserData) - ImGui_ImplMetal_DestroyWindow(platform_io.Viewports[i]); - // } ImGui_ImplMetal_InvalidateDeviceObjectsForPlatformWindows - bd->graphicsPipelineCache.clear(); - // } // ImGui_ImplMetal_DestroyDeviceObjects - - //ImGui_ImplMetal_DestroyBackendData(); - delete bd; // NOLINT(cppcoreguidelines-owning-memory) + ImGui_ImplMetal_ShutdownMultiViewportSupport(); + ImGui_ImplMetal_DestroyDeviceObjects(); + ImGui_ImplMetal_DestroyBackendData(); ImGuiIO& io = GetIO(); io.BackendRendererName = nullptr; io.BackendRendererUserData = nullptr; io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures | ImGuiBackendFlags_RendererHasViewports); -}} +} + +void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor) +{ + ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + IM_ASSERT(bd != nil && "Context or backend not initialized! Did you call ImGui_ImplMetal_Init()?"); +#ifdef IMGUI_IMPL_METAL_CPP + bd->SharedMetalContext.framebufferDescriptor = [[[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor]autorelease]; +#else + bd->SharedMetalContext.framebufferDescriptor = [[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor]; +#endif + if (bd->SharedMetalContext.depthStencilState == nil) + ImGui_ImplMetal_CreateDeviceObjects(bd->SharedMetalContext.device); +} -void ImGui_ImplMetal_NewFrame() { @autoreleasepool +void ImGui_ImplMetal_NewFrame() { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); IM_ASSERT(bd != nil && "Context or backend not initialized! Did you call ImGui_ImplMetal_Init()?"); - if (bd->graphicsPipelineCache.find(bd->framebufferDescriptor) == bd->graphicsPipelineCache.end()) - ImGui_ImplMetal_CreateDeviceObjects(); -}} + if (bd->SharedMetalContext.depthStencilState == nil) + ImGui_ImplMetal_CreateDeviceObjects(bd->SharedMetalContext.device); +} + +static void ImGui_ImplMetal_SetupRenderState(ImDrawData* draw_data, id commandBuffer, + id commandEncoder, id renderPipelineState, + MetalBuffer* vertexBuffer, size_t vertexBufferOffset) +{ + IM_UNUSED(commandBuffer); + ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + [commandEncoder setCullMode:MTLCullModeNone]; + [commandEncoder setDepthStencilState:bd->SharedMetalContext.depthStencilState]; + + // Setup viewport, orthographic projection matrix + // Our visible imgui space lies from draw_data->DisplayPos (top left) to + // draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps. + MTLViewport viewport = + { + .originX = 0.0, + .originY = 0.0, + .width = (double)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x), + .height = (double)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y), + .znear = 0.0, + .zfar = 1.0 + }; + [commandEncoder setViewport:viewport]; + + float L = draw_data->DisplayPos.x; + float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; + float T = draw_data->DisplayPos.y; + float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; + float N = (float)viewport.znear; + float F = (float)viewport.zfar; + const float ortho_projection[4][4] = + { + { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, + { 0.0f, 0.0f, 1/(F-N), 0.0f }, + { (R+L)/(L-R), (T+B)/(B-T), N/(F-N), 1.0f }, + }; + [commandEncoder setVertexBytes:&ortho_projection length:sizeof(ortho_projection) atIndex:1]; + + [commandEncoder setRenderPipelineState:renderPipelineState]; + + [commandEncoder setVertexBuffer:vertexBuffer.buffer offset:0 atIndex:0]; + [commandEncoder setVertexBufferOffset:vertexBufferOffset atIndex:0]; +} -void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id commandBuffer, id commandEncoder) { @autoreleasepool +// Metal Render function. +void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id commandBuffer, id commandEncoder) { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + MetalContext* ctx = bd->SharedMetalContext; // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); @@ -312,21 +312,22 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id // Try to retrieve a render pipeline state that is compatible with the framebuffer config for this frame // The hit rate for this cache should be very near 100%. - auto graphicsPipeline = bd->graphicsPipelineCache.find(bd->framebufferDescriptor); - if (graphicsPipeline == bd->graphicsPipelineCache.end()) + id renderPipelineState = ctx.renderPipelineStateCache[ctx.framebufferDescriptor]; + if (renderPipelineState == nil) { // No luck; make a new render pipeline state + renderPipelineState = [ctx renderPipelineStateForFramebufferDescriptor:ctx.framebufferDescriptor device:commandBuffer.device]; + // Cache render pipeline state for later reuse - auto [it, res] = bd->graphicsPipelineCache.insert(std::make_pair(bd->framebufferDescriptor, graphicPipelineStateForFramebufferDescriptor(*bd->device, bd->framebufferDescriptor))); - graphicsPipeline = it; + ctx.renderPipelineStateCache[ctx.framebufferDescriptor] = renderPipelineState; } size_t vertexBufferLength = (size_t)draw_data->TotalVtxCount * sizeof(ImDrawVert); size_t indexBufferLength = (size_t)draw_data->TotalIdxCount * sizeof(ImDrawIdx); - std::unique_ptr vertexBuffer = bd->bufferOfLenght(vertexBufferLength); - std::unique_ptr indexBuffer = bd->bufferOfLenght(indexBufferLength); + MetalBuffer* vertexBuffer = [ctx dequeueReusableBufferOfLength:vertexBufferLength device:commandBuffer.device]; + MetalBuffer* indexBuffer = [ctx dequeueReusableBufferOfLength:indexBufferLength device:commandBuffer.device]; - ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, graphicsPipeline->second, *vertexBuffer, 0); + ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, 0); // Will project scissor/clipping rectangles into framebuffer space ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports @@ -337,8 +338,8 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id size_t indexBufferOffset = 0; for (const ImDrawList* draw_list : draw_data->CmdLists) { - memcpy(vertexBuffer->content() + vertexBufferOffset, draw_list->VtxBuffer.Data, (size_t)draw_list->VtxBuffer.Size * sizeof(ImDrawVert)); - memcpy(indexBuffer->content() + indexBufferOffset, draw_list->IdxBuffer.Data, (size_t)draw_list->IdxBuffer.Size * sizeof(ImDrawIdx)); + memcpy((char*)vertexBuffer.buffer.contents + vertexBufferOffset, draw_list->VtxBuffer.Data, (size_t)draw_list->VtxBuffer.Size * sizeof(ImDrawVert)); + memcpy((char*)indexBuffer.buffer.contents + indexBufferOffset, draw_list->IdxBuffer.Data, (size_t)draw_list->IdxBuffer.Size * sizeof(ImDrawIdx)); for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++) { @@ -348,7 +349,7 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id // User callback, registered via ImDrawList::AddCallback() // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) - ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, graphicsPipeline->second, *vertexBuffer, vertexBufferOffset); + ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, vertexBufferOffset); else pcmd->UserCallback(draw_list, pcmd); } @@ -359,22 +360,10 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y); // Clamp to viewport as setScissorRect() won't accept values that are off bounds - if (clip_min.x < 0.0f) - { - clip_min.x = 0.0f; - } - if (clip_min.y < 0.0f) - { - clip_min.y = 0.0f; - } - if (clip_max.x > (float)fb_width) - { - clip_max.x = (float)fb_width; - } - if (clip_max.y > (float)fb_height) - { - clip_max.y = (float)fb_height; - } + if (clip_min.x < 0.0f) { clip_min.x = 0.0f; } + if (clip_min.y < 0.0f) { clip_min.y = 0.0f; } + if (clip_max.x > fb_width) { clip_max.x = (float)fb_width; } + if (clip_max.y > fb_height) { clip_max.y = (float)fb_height; } if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) continue; if (pcmd->ElemCount == 0) // drawIndexedPrimitives() validation doesn't accept this @@ -382,23 +371,24 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id // Apply scissor/clipping rectangle MTLScissorRect scissorRect = - { - .x = NSUInteger(clip_min.x), - .y = NSUInteger(clip_min.y), - .width = NSUInteger(clip_max.x - clip_min.x), - .height = NSUInteger(clip_max.y - clip_min.y)}; + { + .x = NSUInteger(clip_min.x), + .y = NSUInteger(clip_min.y), + .width = NSUInteger(clip_max.x - clip_min.x), + .height = NSUInteger(clip_max.y - clip_min.y) + }; [commandEncoder setScissorRect:scissorRect]; // Bind texture, Draw if (ImTextureID tex_id = pcmd->GetTexID()) - [commandEncoder setFragmentTexture:std::bit_cast>(tex_id) atIndex:0]; + [commandEncoder setFragmentTexture:(__bridge id)(void*)(intptr_t)(tex_id) atIndex:0]; - [commandEncoder setVertexBufferOffset:(vertexBufferOffset + pcmd->VtxOffset * sizeof(ImDrawVert)) atIndex:5]; + [commandEncoder setVertexBufferOffset:(vertexBufferOffset + pcmd->VtxOffset * sizeof(ImDrawVert)) atIndex:0]; [commandEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle - indexCount:pcmd->ElemCount - indexType:sizeof(ImDrawIdx) == 2 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32 - indexBuffer:indexBuffer->mtlBuffer() - indexBufferOffset:indexBufferOffset + pcmd->IdxOffset * sizeof(ImDrawIdx)]; + indexCount:pcmd->ElemCount + indexType:sizeof(ImDrawIdx) == 2 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32 + indexBuffer:indexBuffer.buffer + indexBufferOffset:indexBufferOffset + pcmd->IdxOffset * sizeof(ImDrawIdx)]; } } @@ -406,74 +396,191 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id indexBufferOffset += (size_t)draw_list->IdxBuffer.Size * sizeof(ImDrawIdx); } - auto* vertexBufferPtr = vertexBuffer.release(); - auto* indexBufferPtr = indexBuffer.release(); + MetalContext* sharedMetalContext = bd->SharedMetalContext; + [commandBuffer addCompletedHandler:^(id) + { + dispatch_async(dispatch_get_main_queue(), ^{ + @synchronized(sharedMetalContext.bufferCache) + { + [sharedMetalContext.bufferCache addObject:vertexBuffer]; + [sharedMetalContext.bufferCache addObject:indexBuffer]; + } + }); + }]; +} + +static void ImGui_ImplMetal_DestroyTexture(ImTextureData* tex) +{ + MetalTexture* backend_tex = (__bridge_transfer MetalTexture*)(tex->BackendUserData); + if (backend_tex == nullptr) + return; + IM_ASSERT(backend_tex.metalTexture == (__bridge id)(void*)(intptr_t)tex->TexID); + backend_tex.metalTexture = nil; + + // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) + tex->SetTexID(ImTextureID_Invalid); + tex->SetStatus(ImTextureStatus_Destroyed); + tex->BackendUserData = nullptr; +} + +void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex) +{ + ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + if (tex->Status == ImTextureStatus_WantCreate) + { + // Create and upload new texture to graphics system + //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); + + // We are retrieving and uploading the font atlas as a 4-channels RGBA texture here. + // In theory we could call GetTexDataAsAlpha8() and upload a 1-channel texture to save on memory access bandwidth. + // However, using a shader designed for 1-channel texture would make it less obvious to use the ImTextureID facility to render users own textures. + // You can make that change in your implementation. + MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm + width:(NSUInteger)tex->Width + height:(NSUInteger)tex->Height + mipmapped:NO]; + textureDescriptor.usage = MTLTextureUsageShaderRead; + #if TARGET_OS_OSX || TARGET_OS_MACCATALYST + textureDescriptor.storageMode = MTLStorageModeManaged; + #else + textureDescriptor.storageMode = MTLStorageModeShared; + #endif + id texture = [bd->SharedMetalContext.device newTextureWithDescriptor:textureDescriptor]; + [texture replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)tex->Width, (NSUInteger)tex->Height) mipmapLevel:0 withBytes:tex->Pixels bytesPerRow:(NSUInteger)tex->Width * 4]; + MetalTexture* backend_tex = [[MetalTexture alloc] initWithTexture:texture]; - [commandBuffer addCompletedHandler:^(id) { - dispatch_async(dispatch_get_main_queue(), ^{ + // Store identifiers + tex->SetTexID((ImTextureID)(intptr_t)texture); + tex->SetStatus(ImTextureStatus_OK); + tex->BackendUserData = (__bridge_retained void*)(backend_tex); + } + else if (tex->Status == ImTextureStatus_WantUpdates) + { + // Update selected blocks. We only ever write to textures regions which have never been used before! + // This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region. + MetalTexture* backend_tex = (__bridge MetalTexture*)(tex->BackendUserData); + for (ImTextureRect& r : tex->Updates) { - std::scoped_lock lock(bd->bufferCacheMtx); - bd->bufferCache.push_back(std::unique_ptr(vertexBufferPtr)); - bd->bufferCache.push_back(std::unique_ptr(indexBufferPtr)); + [backend_tex.metalTexture replaceRegion:MTLRegionMake2D((NSUInteger)r.x, (NSUInteger)r.y, (NSUInteger)r.w, (NSUInteger)r.h) + mipmapLevel:0 + withBytes:tex->GetPixelsAt(r.x, r.y) + bytesPerRow:(NSUInteger)tex->Width * 4]; } - }); - }]; -}} + tex->SetStatus(ImTextureStatus_OK); + } + else if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0) + { + ImGui_ImplMetal_DestroyTexture(tex); + } +} + +bool ImGui_ImplMetal_CreateDeviceObjects(id device) +{ + ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + MTLDepthStencilDescriptor* depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init]; + depthStencilDescriptor.depthWriteEnabled = NO; + depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways; + bd->SharedMetalContext.depthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDescriptor]; + ImGui_ImplMetal_CreateDeviceObjectsForPlatformWindows(); +#ifdef IMGUI_IMPL_METAL_CPP + [depthStencilDescriptor release]; +#endif + + return true; +} + +void ImGui_ImplMetal_DestroyDeviceObjects() +{ + ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + + // Destroy all textures + for (ImTextureData* tex : GetPlatformIO().Textures) + if (tex->RefCount == 1) + ImGui_ImplMetal_DestroyTexture(tex); + + ImGui_ImplMetal_InvalidateDeviceObjectsForPlatformWindows(); + [bd->SharedMetalContext.renderPipelineStateCache removeAllObjects]; +} + +#pragma mark - Multi-viewport support -namespace +#import + +#if TARGET_OS_OSX +#import +#endif + +//-------------------------------------------------------------------------------------------------------- +// MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT +// This is an _advanced_ and _optional_ feature, allowing the back-end to create and handle multiple viewports simultaneously. +// If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first.. +//-------------------------------------------------------------------------------------------------------- + +struct ImGuiViewportDataMetal { + CAMetalLayer* MetalLayer; + id CommandQueue; + MTLRenderPassDescriptor* RenderPassDescriptor; + void* Handle = nullptr; + bool FirstFrame = true; +}; -void ImGui_ImplMetal_CreateWindow(ImGuiViewport* viewport) { @autoreleasepool +static void ImGui_ImplMetal_CreateWindow(ImGuiViewport* viewport) { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); - auto* data = new ImGuiViewportDataMetal; // NOLINT(cppcoreguidelines-owning-memory) + ImGuiViewportDataMetal* data = IM_NEW(ImGuiViewportDataMetal)(); + viewport->RendererUserData = data; // PlatformHandleRaw should always be a NSWindow*, whereas PlatformHandle might be a higher-level handle (e.g. GLFWWindow*, SDL_Window*). // Some back-ends will leave PlatformHandleRaw == 0, in which case we assume PlatformHandle will contain the NSWindow*. - auto* window = static_cast(viewport->PlatformHandleRaw ? viewport->PlatformHandleRaw : viewport->PlatformHandle); - IM_ASSERT(window != nullptr); + void* handle = viewport->PlatformHandleRaw ? viewport->PlatformHandleRaw : viewport->PlatformHandle; + IM_ASSERT(handle != nullptr); + id device = bd->SharedMetalContext.device; CAMetalLayer* layer = [CAMetalLayer layer]; - layer.device = bd->device->mtlDevice(); + layer.device = device; layer.framebufferOnly = YES; - layer.pixelFormat = toMTLPixelFormat(bd->framebufferDescriptor.colorPixelFormats.front()); - + layer.pixelFormat = bd->SharedMetalContext.framebufferDescriptor.colorPixelFormat; +#if TARGET_OS_OSX + NSWindow* window = (__bridge NSWindow*)handle; NSView* view = window.contentView; view.layer = layer; view.wantsLayer = YES; +#endif data->MetalLayer = layer; - data->CommandQueue = [bd->device->mtlDevice() newCommandQueue]; + data->CommandQueue = [device newCommandQueue]; data->RenderPassDescriptor = [[MTLRenderPassDescriptor alloc] init]; - data->Handle = [window retain]; - - viewport->RendererUserData = data; -}} + data->Handle = handle; +} -void ImGui_ImplMetal_DestroyWindow(ImGuiViewport* viewport) { @autoreleasepool +static void ImGui_ImplMetal_DestroyWindow(ImGuiViewport* viewport) { // The main viewport (owned by the application) will always have RendererUserData == 0 since we didn't create the data for it. - if (auto* data = static_cast(viewport->RendererUserData)) { - [data->Handle release]; - [data->RenderPassDescriptor release]; - [data->CommandQueue release]; - delete data; // NOLINT(cppcoreguidelines-owning-memory) - } - + if (ImGuiViewportDataMetal* data = (ImGuiViewportDataMetal*)viewport->RendererUserData) + IM_DELETE(data); viewport->RendererUserData = nullptr; -}} +} -void ImGui_ImplMetal_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) { @autoreleasepool +inline static CGSize MakeScaledSize(CGSize size, CGFloat scale) { - auto* data = static_cast(viewport->RendererUserData); + return CGSizeMake(size.width * scale, size.height * scale); +} + +static void ImGui_ImplMetal_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) +{ + ImGuiViewportDataMetal* data = (ImGuiViewportDataMetal*)viewport->RendererUserData; data->MetalLayer.drawableSize = MakeScaledSize(CGSizeMake(size.x, size.y), viewport->DpiScale); -}} +} -void ImGui_ImplMetal_RenderWindow(ImGuiViewport* viewport, void*) { @autoreleasepool +static void ImGui_ImplMetal_RenderWindow(ImGuiViewport* viewport, void*) { - auto* data = static_cast(viewport->RendererUserData); + ImGuiViewportDataMetal* data = (ImGuiViewportDataMetal*)viewport->RendererUserData; - auto* window = static_cast(viewport->PlatformHandleRaw ? viewport->PlatformHandleRaw : viewport->PlatformHandle); - IM_ASSERT(window != nullptr); +#if TARGET_OS_OSX + void* handle = viewport->PlatformHandleRaw ? viewport->PlatformHandleRaw : viewport->PlatformHandle; + NSWindow* window = (__bridge NSWindow*)handle; // Always render the first frame, regardless of occlusionState, to avoid an initial flicker if ((window.occlusionState & NSWindowOcclusionStateVisible) == 0 && !data->FirstFrame) @@ -484,16 +591,15 @@ void ImGui_ImplMetal_RenderWindow(ImGuiViewport* viewport, void*) { @autorelease } data->FirstFrame = false; - auto fb_scale = (float)window.backingScaleFactor; - - // https://github.com/ocornut/imgui/issues/8856 - // if (data->MetalLayer.contentsScale != fb_scale) - // { - data->MetalLayer.contentsScale = fb_scale; - data->MetalLayer.drawableSize = MakeScaledSize(window.frame.size, fb_scale); - // } + float fb_scale = (float)window.backingScaleFactor; + if (data->MetalLayer.contentsScale != fb_scale) + { + data->MetalLayer.contentsScale = fb_scale; + data->MetalLayer.drawableSize = MakeScaledSize(window.frame.size, fb_scale); + } +#endif - id drawable = [data->MetalLayer nextDrawable]; + id drawable = [data->MetalLayer nextDrawable]; if (drawable == nil) return; @@ -503,174 +609,280 @@ void ImGui_ImplMetal_RenderWindow(ImGuiViewport* viewport, void*) { @autorelease if ((viewport->Flags & ImGuiViewportFlags_NoRendererClear) == 0) renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; - ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); - - FramebufferDescriptor oldFramebufferDescriptor = bd->framebufferDescriptor; - - bd->framebufferDescriptor = FramebufferDescriptor{ - .colorPixelFormats = { toPixelFormat(drawable.texture.pixelFormat) }, - .depthPixelFormat = std::nullopt, - .stencilPixelFormat = std::nullopt - }; - - id commandBuffer = [data->CommandQueue commandBuffer]; - id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; + id commandBuffer = [data->CommandQueue commandBuffer]; + id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; ImGui_ImplMetal_RenderDrawData(viewport->DrawData, commandBuffer, renderEncoder); [renderEncoder endEncoding]; - bd->framebufferDescriptor = oldFramebufferDescriptor; - [commandBuffer presentDrawable:drawable]; [commandBuffer commit]; -}} +} -void ImGui_ImplMetal_DestroyTexture(ImTextureData* tex) { @autoreleasepool +static void ImGui_ImplMetal_InitMultiViewportSupport() { - auto* backend_tex = static_cast(tex->BackendUserData); - if (backend_tex == nullptr) - return; - delete backend_tex; // NOLINT(cppcoreguidelines-owning-memory) - - // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) - tex->SetTexID(ImTextureID_Invalid); - tex->SetStatus(ImTextureStatus_Destroyed); - tex->BackendUserData = nullptr; -}} + ImGuiPlatformIO& platform_io = GetPlatformIO(); + platform_io.Renderer_CreateWindow = ImGui_ImplMetal_CreateWindow; + platform_io.Renderer_DestroyWindow = ImGui_ImplMetal_DestroyWindow; + platform_io.Renderer_SetWindowSize = ImGui_ImplMetal_SetWindowSize; + platform_io.Renderer_RenderWindow = ImGui_ImplMetal_RenderWindow; +} -void ImGui_ImplMetal_CreateDeviceObjects() { @autoreleasepool +static void ImGui_ImplMetal_ShutdownMultiViewportSupport() { - ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + DestroyPlatformWindows(); +} - bd->graphicsPipelineCache.insert(std::make_pair(bd->framebufferDescriptor, graphicPipelineStateForFramebufferDescriptor(*bd->device, bd->framebufferDescriptor))); - // ImGui_ImplMetal_CreateDeviceObjectsForPlatformWindows - // { +static void ImGui_ImplMetal_CreateDeviceObjectsForPlatformWindows() +{ ImGuiPlatformIO& platform_io = GetPlatformIO(); for (int i = 1; i < platform_io.Viewports.Size; i++) if (!platform_io.Viewports[i]->RendererUserData) ImGui_ImplMetal_CreateWindow(platform_io.Viewports[i]); - // } -}} +} -MetalGraphicsPipeline graphicPipelineStateForFramebufferDescriptor(const MetalDevice& device, const FramebufferDescriptor& framebufferDescriptor) +static void ImGui_ImplMetal_InvalidateDeviceObjectsForPlatformWindows() { - std::unique_ptr shaderLib = device.newShaderLib(GFX_SHADER_SLIB); - - GraphicsPipeline::Descriptor graphicsPipelineDescriptor = { - .vertexLayout = VertexLayout{ - .stride = sizeof(ImDrawVert), - .attributes = { - VertexAttribute{.format=VertexAttributeFormat::float2, .offset=offsetof(ImDrawVert, pos) }, - VertexAttribute{.format=VertexAttributeFormat::float2, .offset=offsetof(ImDrawVert, uv) }, - VertexAttribute{.format=VertexAttributeFormat::uint, .offset=offsetof(ImDrawVert, col) }, - } - }, - .vertexShader = &shaderLib->getFunction("vertex_main"), - .fragmentShader = &shaderLib->getFunction("fragment_main"), - .colorAttachmentPxFormats = framebufferDescriptor.colorPixelFormats, - .depthAttachmentPxFormat = framebufferDescriptor.depthPixelFormat, - .blendOperation = BlendOperation::srcA_plus_1_minus_srcA - }; - - return { device, graphicsPipelineDescriptor }; + ImGuiPlatformIO& platform_io = GetPlatformIO(); + for (int i = 1; i < platform_io.Viewports.Size; i++) + if (platform_io.Viewports[i]->RendererUserData) + ImGui_ImplMetal_DestroyWindow(platform_io.Viewports[i]); } -void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex) { @autoreleasepool +#pragma mark - MetalBuffer implementation + +@implementation MetalBuffer +- (instancetype)initWithBuffer:(id)buffer { - ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); - if (tex->Status == ImTextureStatus_WantCreate) + if ((self = [super init])) { - // Create and upload new texture to graphics system - // IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); - IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); - IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); + _buffer = buffer; + _lastReuseTime = GetMachAbsoluteTimeInSeconds(); + } + return self; +} +@end - // We are retrieving and uploading the font atlas as a 4-channels RGBA texture here. - // In theory we could call GetTexDataAsAlpha8() and upload a 1-channel texture to save on memory access bandwidth. - // However, using a shader designed for 1-channel texture would make it less obvious to use the ImTextureID facility to render users own textures. - // You can make that change in your implementation. - MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm - width:(NSUInteger)tex->Width - height:(NSUInteger)tex->Height - mipmapped:NO]; - textureDescriptor.usage = MTLTextureUsageShaderRead; -#if TARGET_OS_OSX || TARGET_OS_MACCATALYST - textureDescriptor.storageMode = MTLStorageModeManaged; -#else - textureDescriptor.storageMode = MTLStorageModeShared; -#endif - id texture = [bd->device->mtlDevice() newTextureWithDescriptor:textureDescriptor]; - [texture replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)tex->Width, (NSUInteger)tex->Height) mipmapLevel:0 withBytes:tex->Pixels bytesPerRow:(NSUInteger)tex->Width * 4]; - auto* backend_tex = new MetalTexture(texture, Texture::Descriptor{ - .width=static_cast(texture.width), .height=static_cast(texture.height)}); +#pragma mark - FramebufferDescriptor implementation - // Store identifiers - tex->SetTexID(std::bit_cast(texture)); - tex->SetStatus(ImTextureStatus_OK); - tex->BackendUserData = static_cast(backend_tex); - } - else if (tex->Status == ImTextureStatus_WantUpdates) +@implementation FramebufferDescriptor +- (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor +{ + if ((self = [super init])) { - // Update selected blocks. We only ever write to textures regions which have never been used before! - // This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region. - auto* backend_tex = static_cast(tex->BackendUserData); - for (ImTextureRect& r : tex->Updates) - { - [backend_tex->mtltexture() replaceRegion:MTLRegionMake2D((NSUInteger)r.x, (NSUInteger)r.y, (NSUInteger)r.w, (NSUInteger)r.h) - mipmapLevel:0 - withBytes:tex->GetPixelsAt(r.x, r.y) - bytesPerRow:(NSUInteger)tex->Width * 4]; - } - tex->SetStatus(ImTextureStatus_OK); + _sampleCount = renderPassDescriptor.colorAttachments[0].texture.sampleCount; + _colorPixelFormat = renderPassDescriptor.colorAttachments[0].texture.pixelFormat; + _depthPixelFormat = renderPassDescriptor.depthAttachment.texture.pixelFormat; + _stencilPixelFormat = renderPassDescriptor.stencilAttachment.texture.pixelFormat; } - else if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0) + return self; +} + +- (instancetype)initWithSampleCount:(NSUInteger)sampleCount + colorPixelFormat:(MTLPixelFormat)colorPixelFormat + depthPixelFormat:(MTLPixelFormat)depthPixelFormat + stencilPixelFormat:(MTLPixelFormat)stencilPixelFormat +{ + if ((self = [super init])) { - ImGui_ImplMetal_DestroyTexture(tex); + _sampleCount = sampleCount; + _colorPixelFormat = colorPixelFormat; + _depthPixelFormat = depthPixelFormat; + _stencilPixelFormat = stencilPixelFormat; } -}} + return self; +} -void ImGui_ImplMetal_SetupRenderState(ImDrawData* draw_data, id, id commandEncoder, const MetalGraphicsPipeline& graphicsPipeline, const MetalBuffer& vertexBuffer, size_t vertexBufferOffset) +- (nonnull id)copyWithZone:(nullable NSZone*)zone { - [commandEncoder setCullMode:MTLCullModeNone]; - if (graphicsPipeline.depthStencilState()) - [commandEncoder setDepthStencilState:graphicsPipeline.depthStencilState()]; + FramebufferDescriptor* copy = [[FramebufferDescriptor allocWithZone:zone] init]; + copy.sampleCount = self.sampleCount; + copy.colorPixelFormat = self.colorPixelFormat; + copy.depthPixelFormat = self.depthPixelFormat; + copy.stencilPixelFormat = self.stencilPixelFormat; + return copy; +} - ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); +- (NSUInteger)hash +{ + NSUInteger sc = _sampleCount & 0x3; + NSUInteger cf = _colorPixelFormat & 0x3FF; + NSUInteger df = _depthPixelFormat & 0x3FF; + NSUInteger sf = _stencilPixelFormat & 0x3FF; + NSUInteger hash = (sf << 22) | (df << 12) | (cf << 2) | sc; + return hash; +} - [commandEncoder setFragmentSamplerState:bd->linearSampler.mtlSamplerState() atIndex:0]; +- (BOOL)isEqual:(id)object +{ + FramebufferDescriptor* other = object; + if (![other isKindOfClass:[FramebufferDescriptor class]]) + return NO; + return other.sampleCount == self.sampleCount && + other.colorPixelFormat == self.colorPixelFormat && + other.depthPixelFormat == self.depthPixelFormat && + other.stencilPixelFormat == self.stencilPixelFormat; +} - // Setup viewport, orthographic projection matrix - // Our visible imgui space lies from draw_data->DisplayPos (top left) to - // draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps. - MTLViewport viewport = { - .originX = 0.0, - .originY = 0.0, - .width = (double)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x), - .height = (double)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y), - .znear = 0.0, - .zfar = 1.0}; - [commandEncoder setViewport:viewport]; +@end - float L = draw_data->DisplayPos.x; - float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; - float T = draw_data->DisplayPos.y; - float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; - auto N = static_cast(viewport.znear); - auto F = static_cast(viewport.zfar); - const float ortho_projection[4][4] = // NOLINT(cppcoreguidelines-avoid-c-arrays) - { - { 2.0f / (R - L), 0.0f, 0.0f, 0.0f }, - { 0.0f, 2.0f / (T - B), 0.0f, 0.0f }, - { 0.0f, 0.0f, 1 / (F - N), 0.0f }, - { (R + L) / (L - R), (T + B) / (B - T), N / (F - N), 1.0f }, - }; - [commandEncoder setVertexBytes:&ortho_projection length:sizeof(ortho_projection) atIndex:0]; +#pragma mark - MetalTexture implementation + +@implementation MetalTexture +- (instancetype)initWithTexture:(id)metalTexture +{ + if ((self = [super init])) + self.metalTexture = metalTexture; + return self; +} + +@end - [commandEncoder setRenderPipelineState:graphicsPipeline.renderPipelineState()]; +#pragma mark - MetalContext implementation - [commandEncoder setVertexBuffer:vertexBuffer.mtlBuffer() offset:0 atIndex:5]; - [commandEncoder setVertexBufferOffset:vertexBufferOffset atIndex:5]; +@implementation MetalContext +- (instancetype)init +{ + if ((self = [super init])) + { + self.renderPipelineStateCache = [NSMutableDictionary dictionary]; + self.bufferCache = [NSMutableArray array]; + _lastBufferCachePurge = GetMachAbsoluteTimeInSeconds(); + } + return self; } +- (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device +{ + uint64_t now = GetMachAbsoluteTimeInSeconds(); + + @synchronized(self.bufferCache) + { + // Purge old buffers that haven't been useful for a while + if (now - self.lastBufferCachePurge > 1.0) + { + NSMutableArray* survivors = [NSMutableArray array]; + for (MetalBuffer* candidate in self.bufferCache) + if (candidate.lastReuseTime > self.lastBufferCachePurge) + [survivors addObject:candidate]; + self.bufferCache = [survivors mutableCopy]; + self.lastBufferCachePurge = now; + } + + // See if we have a buffer we can reuse + MetalBuffer* bestCandidate = nil; + for (MetalBuffer* candidate in self.bufferCache) + if (candidate.buffer.length >= length && (bestCandidate == nil || bestCandidate.lastReuseTime > candidate.lastReuseTime)) + bestCandidate = candidate; + + if (bestCandidate != nil) + { + [self.bufferCache removeObject:bestCandidate]; + bestCandidate.lastReuseTime = now; + return bestCandidate; + } + } + + // No luck; make a new buffer + id backing = [device newBufferWithLength:length options:MTLResourceStorageModeShared]; + return [[MetalBuffer alloc] initWithBuffer:backing]; } +// Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling. +- (id)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id)device +{ + NSError* error = nil; + + NSString* shaderSource = @"" + "#include \n" + "using namespace metal;\n" + "\n" + "struct Uniforms {\n" + " float4x4 projectionMatrix;\n" + "};\n" + "\n" + "struct VertexIn {\n" + " float2 position [[attribute(0)]];\n" + " float2 texCoords [[attribute(1)]];\n" + " uchar4 color [[attribute(2)]];\n" + "};\n" + "\n" + "struct VertexOut {\n" + " float4 position [[position]];\n" + " float2 texCoords;\n" + " float4 color;\n" + "};\n" + "\n" + "vertex VertexOut vertex_main(VertexIn in [[stage_in]],\n" + " constant Uniforms &uniforms [[buffer(1)]]) {\n" + " VertexOut out;\n" + " out.position = uniforms.projectionMatrix * float4(in.position, 0, 1);\n" + " out.texCoords = in.texCoords;\n" + " out.color = float4(in.color) / float4(255.0);\n" + " return out;\n" + "}\n" + "\n" + "fragment half4 fragment_main(VertexOut in [[stage_in]],\n" + " texture2d texture [[texture(0)]]) {\n" + " constexpr sampler linearSampler(coord::normalized, min_filter::linear, mag_filter::linear, mip_filter::linear);\n" + " half4 texColor = texture.sample(linearSampler, in.texCoords);\n" + " return half4(in.color) * texColor;\n" + "}\n"; + + id library = [device newLibraryWithSource:shaderSource options:nil error:&error]; + if (library == nil) + { + NSLog(@"Error: failed to create Metal library: %@", error); + return nil; + } + + id vertexFunction = [library newFunctionWithName:@"vertex_main"]; + id fragmentFunction = [library newFunctionWithName:@"fragment_main"]; + + if (vertexFunction == nil || fragmentFunction == nil) + { + NSLog(@"Error: failed to find Metal shader functions in library: %@", error); + return nil; + } + + MTLVertexDescriptor* vertexDescriptor = [MTLVertexDescriptor vertexDescriptor]; + vertexDescriptor.attributes[0].offset = offsetof(ImDrawVert, pos); + vertexDescriptor.attributes[0].format = MTLVertexFormatFloat2; // position + vertexDescriptor.attributes[0].bufferIndex = 0; + vertexDescriptor.attributes[1].offset = offsetof(ImDrawVert, uv); + vertexDescriptor.attributes[1].format = MTLVertexFormatFloat2; // texCoords + vertexDescriptor.attributes[1].bufferIndex = 0; + vertexDescriptor.attributes[2].offset = offsetof(ImDrawVert, col); + vertexDescriptor.attributes[2].format = MTLVertexFormatUChar4; // color + vertexDescriptor.attributes[2].bufferIndex = 0; + vertexDescriptor.layouts[0].stepRate = 1; + vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex; + vertexDescriptor.layouts[0].stride = sizeof(ImDrawVert); + + MTLRenderPipelineDescriptor* pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; + pipelineDescriptor.vertexFunction = vertexFunction; + pipelineDescriptor.fragmentFunction = fragmentFunction; + pipelineDescriptor.vertexDescriptor = vertexDescriptor; + pipelineDescriptor.rasterSampleCount = self.framebufferDescriptor.sampleCount; + pipelineDescriptor.colorAttachments[0].pixelFormat = self.framebufferDescriptor.colorPixelFormat; + pipelineDescriptor.colorAttachments[0].blendingEnabled = YES; + pipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd; + pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; + pipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + pipelineDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd; + pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne; + pipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + pipelineDescriptor.depthAttachmentPixelFormat = self.framebufferDescriptor.depthPixelFormat; + pipelineDescriptor.stencilAttachmentPixelFormat = self.framebufferDescriptor.stencilPixelFormat; + + id renderPipelineState = [device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error]; + if (error != nil) + NSLog(@"Error: failed to create Metal pipeline state: %@", error); + + return renderPipelineState; } + +@end + +//----------------------------------------------------------------------------- + +#endif // #ifndef IMGUI_DISABLE diff --git a/src/pch.hpp b/src/pch.hpp index 1062571d..4339c464 100644 --- a/src/pch.hpp +++ b/src/pch.hpp @@ -41,25 +41,6 @@ #import #import -#else // __OBJC__ - -template using id = T*; -#define nil nullptr -class MTLBuffer; -class MTLCommandQueue; -class MTLCommandBuffer; -class MTLCommandEncoder; -class MTLDevice; -class CAMetalDrawable; -class MTLRenderPipelineState; -class MTLDepthStencilState; -class NSAutoreleasePool; -class MTLLibrary; -class MTLFunction; -class CAMetalLayer; -class MTLTexture; -class MTLSamplerState; - #endif // __OBJC__ #endif // GFX_BUILD_METAL diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 0226367e..e90b7113 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -6,9 +6,38 @@ # --------------------------------------------------- include(FetchContent) -include(find_slang) -find_slang() +if(NOT GFX_SLANG_FORCE_DOWNLOAD) + find_package(slang PATHS $ENV{HOME}/.local QUIET) +endif() + +if(NOT slang_FOUND) + set(SLANG_URL "https://github.com/shader-slang/slang/releases/download/v2025.24.2/slang-2025.24.2") + if(APPLE) + string(APPEND SLANG_URL "-macos") + elseif(WIN32) + string(APPEND SLANG_URL "-windows") + elseif(UNIX) + string(APPEND SLANG_URL "-linux") + else() + message(FATAL_ERROR "Unsupported platform for Slang prebuilt binaries") + endif() + if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64") + string(APPEND SLANG_URL "-aarch64") + else() + string(APPEND SLANG_URL "-x86_64") + endif() + string(APPEND SLANG_URL ".zip") + FetchContent_Declare(slang URL "${SLANG_URL}" DOWNLOAD_EXTRACT_TIMESTAMP ON) + FetchContent_MakeAvailable(slang) + if(WIN32) + find_file(SLANG_DLL NAMES slang.dll PATHS ${slang_SOURCE_DIR}/bin REQUIRED) + find_file(SLANG_GLSLANG_DLL NAMES slang-glslang.dll PATHS ${slang_SOURCE_DIR}/bin REQUIRED) + find_file(SLANG_COMPILER_DLL NAMES slang-compiler.dll PATHS ${slang_SOURCE_DIR}/bin REQUIRED) + set(REQUIRED_COPY_DLL "${SLANG_DLL}" "${SLANG_GLSLANG_DLL}" "${SLANG_COMPILER_DLL}") + endif() + find_package(slang REQUIRED PATHS "${slang_SOURCE_DIR}") +endif() FetchContent_Declare(argparse GIT_REPOSITORY https://github.com/p-ranav/argparse.git @@ -50,14 +79,8 @@ endif() target_link_libraries(gfxsc PRIVATE slang::slang argparse::argparse) -if (WIN32) +if (REQUIRED_COPY_DLL) add_custom_command(TARGET gfxsc POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${REQUIRED_COPY_DLL} $ ) - get_target_property(SLANG_GLSLANG_DLL slang::slang SLANG_GLSLANG_DLL_PATH) - if(SLANG_GLSLANG_DLL) - add_custom_command(TARGET gfxsc POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SLANG_GLSLANG_DLL} $ - ) - endif() endif()