Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 22 additions & 23 deletions cmake/FindSlang.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# You can use a custom installation instead by setting Slang_ROOT and Slang_VERSION.
#
# Sets the following variables:
# Slang_VERSION: The downloaded version of Slang. This script is compatible with >= 2025.20.
# Slang_VERSION: The downloaded version of Slang.
# Slang_ROOT: Path to the Slang SDK root directory.
# Slang_INCLUDE_DIR: Directory that includes slang.h.
# Slang_SLANGC_EXECUTABLE: Path to the Slang compiler.
Expand All @@ -23,7 +23,7 @@
# ...
# )

set(Slang_VERSION "2026.1.1" CACHE STRING "Slang version. If you change this and ran CMake before, you will need to delete the other Slang_* cache variables")
set(Slang_VERSION "2025.13.1")

if(NOT Slang_ROOT)
string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" ARCH_PROC)
Expand All @@ -47,6 +47,8 @@ if(NOT Slang_ROOT)

if(WIN32)
set(SLANG_OS "windows")
elseif(APPLE)
set(SLANG_OS "macos")
else()
set(SLANG_OS "linux")
endif()
Expand Down Expand Up @@ -105,12 +107,8 @@ find_program(Slang_SLANGD_EXECUTABLE
)
mark_as_advanced(Slang_SLANGD_EXECUTABLE)

# Slang versions since v2025.20 renamed their libraries. Linux .so files
# also now include version numbers. This allows us to remove
# LD_LIBRARY_PATH workarounds we had in the past.
find_library(Slang_LIBRARY
NAMES libslang-compiler.so.0.${Slang_VERSION} # Linux
slang-compiler.lib # Windows
NAMES slang
HINTS ${Slang_ROOT}/lib
NO_DEFAULT_PATH
DOC "Slang linker library"
Expand All @@ -119,7 +117,7 @@ mark_as_advanced(Slang_LIBRARY)

if(WIN32)
find_file(Slang_DLL
NAMES slang-compiler.dll
NAMES slang.dll
HINTS ${Slang_ROOT}/bin
NO_DEFAULT_PATH
DOC "Slang shared library (.dll)"
Expand All @@ -143,6 +141,13 @@ if(NOT TARGET Slang)
)
if(WIN32)
set_property(TARGET Slang PROPERTY IMPORTED_IMPLIB ${Slang_LIBRARY})
elseif(NOT APPLE)
# Vulkan SDK includes 'libslang.so' and sets LD_LIBRARY_PATH, which conflict
# with the downloaded slang. This uses the deprecated RPATH instead of
# RUNPATH to take priority over LD_LIBRARY_PATH.
set_target_properties(Slang PROPERTIES
INTERFACE_LINK_OPTIONS "-Wl,--disable-new-dtags"
)
endif()
endif()

Expand All @@ -153,22 +158,19 @@ endif()
# To make this work, we make the GLSL module an IMPORTED library, with the same
# IMPLIB as core Slang.
find_file(Slang_GLSL_MODULE
NAMES libslang-glsl-module-${Slang_VERSION}.so # Linuz
slang-glsl-module.dll # Windows
HINTS ${Slang_ROOT}/lib
${Slang_ROOT}/bin
NAMES ${CMAKE_SHARED_LIBRARY_PREFIX}slang-glsl-module${CMAKE_SHARED_LIBRARY_SUFFIX}
HINTS ${Slang_ROOT}/bin
${Slang_ROOT}/lib
NO_DEFAULT_PATH
DOC "Slang embedded GLSL module"
)
mark_as_advanced(Slang_GLSL_MODULE)

if(NOT TARGET SlangGlslModule)
add_library(SlangGlslModule SHARED IMPORTED)
set_target_properties(SlangGlslModule PROPERTIES
set_target_properties(SlangGlslModule PROPERTIES
IMPORTED_NO_SONAME ON # See https://github.com/shader-slang/slang/issues/7722
IMPORTED_LOCATION ${Slang_GLSL_MODULE}
# Samples shouldn't link with this; this is for safety in case they do.
# See https://github.com/shader-slang/slang/issues/7722
IMPORTED_NO_SONAME ON
)
if(WIN32)
set_property(TARGET SlangGlslModule PROPERTY IMPORTED_IMPLIB ${Slang_LIBRARY})
Expand All @@ -178,10 +180,9 @@ endif()
# Additionally, SLANG_OPTIMIZATION_LEVEL_HIGH requires slang-glslang.dll.
# Find it:
find_file(Slang_GLSLANG
NAMES libslang-glslang-${Slang_VERSION}.so # Linux
slang-glslang.dll # Windows
HINTS ${Slang_ROOT}/lib
${Slang_ROOT}/bin
NAMES ${CMAKE_SHARED_LIBRARY_PREFIX}slang-glslang${CMAKE_SHARED_LIBRARY_SUFFIX}
HINTS ${Slang_ROOT}/bin
${Slang_ROOT}/lib
NO_DEFAULT_PATH
DOC "slang-glslang shared library"
)
Expand All @@ -190,10 +191,8 @@ mark_as_advanced(Slang_GLSLANG)
if(NOT TARGET SlangGlslang)
add_library(SlangGlslang SHARED IMPORTED)
set_target_properties(SlangGlslang PROPERTIES
IMPORTED_NO_SONAME ON # See https://github.com/shader-slang/slang/issues/7722
IMPORTED_LOCATION ${Slang_GLSLANG}
# Samples shouldn't link with this; this is for safety in case they do.
# See https://github.com/shader-slang/slang/issues/7722
IMPORTED_NO_SONAME ON
)
if(WIN32)
set_property(TARGET SlangGlslang PROPERTY IMPORTED_IMPLIB ${Slang_LIBRARY})
Expand Down
13 changes: 1 addition & 12 deletions cmake/Setup.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,12 @@ if(MSVC)
# Enable parallel builds, and set warning level 3 for MSVC.
add_compile_options($<$<NOT:$<COMPILE_LANGUAGE:CUDA>>:/MP>)
add_compile_options($<$<NOT:$<COMPILE_LANGUAGE:CUDA>>:/W3>)
else()
elseif(NOT APPLE)
# Remove unused sections to save space (and remove usage_* doc functions).
# You can add -Wl,--print-gc-sections to see what was removed.
add_link_options(-Wl,--gc-sections)
endif()

# We might compile with Clang on Windows, but while '_WIN32' is defined by the compiler,
# we mostly use 'WIN32', which apparently will not be defined by default under Clang
if(WIN32 AND NOT MSVC)
add_compile_definitions(WIN32)
endif()

if((CMAKE_CXX_COMPILER_ID MATCHES "Clang") AND MSVC)
# VMA throws tons of warnings in the vk_mem_alloc.h header
add_compile_options(-Wno-nullability-completeness)
endif()

# This saves some time scanning source files for imported C++ modules.
# If your sample uses modules, you must explicitly turn on this property on your
# sample, or set this variable to ON before including Setup.cmake.
Expand Down
20 changes: 17 additions & 3 deletions nvgui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ set(LIB_NAME nvgui)

# Collect all source files in the utils directory
file(GLOB LIB_SOURCES "*.cpp" "*.h" "*.hpp")

# On macOS, also include Objective-C++ files
if(APPLE)
file(GLOB LIB_SOURCES_MM "*.mm")
list(APPEND LIB_SOURCES ${LIB_SOURCES_MM})
endif()

source_group("Source Files" FILES ${LIB_SOURCES})

# Define the utils library
Expand All @@ -13,14 +20,21 @@ cmake_path(GET CMAKE_CURRENT_LIST_DIR PARENT_PATH PARENT_DIR)
target_include_directories(${LIB_NAME} PUBLIC ${PARENT_DIR})

target_link_libraries(
${LIB_NAME}
PUBLIC
${LIB_NAME}
PUBLIC
imgui
glfw # for file_dialog
fmt
fmt
glm
)

# On macOS, link against AppKit and UniformTypeIdentifiers for file dialogs
if(APPLE)
target_link_libraries(${LIB_NAME} PRIVATE "-framework AppKit" "-framework UniformTypeIdentifiers")
# Disable precompiled headers for Objective-C++ files (incompatible with C++ PCH)
set_source_files_properties(${LIB_SOURCES_MM} PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
endif()

target_precompile_headers(${LIB_NAME} PRIVATE
<filesystem>
)
Expand Down
184 changes: 184 additions & 0 deletions nvgui/file_dialog_macos.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
* Copyright (c) 2019-2025, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2019-2025, NVIDIA CORPORATION.
* SPDX-License-Identifier: Apache-2.0
*/
//--------------------------------------------------------------------

#ifdef __APPLE__

#import <Cocoa/Cocoa.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>

#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>

#include <string>
#include <vector>
#include <sstream>

#include "file_dialog.hpp"

// Parse the extension filter string into a list of file extensions
// Format: "Description|*.ext1;*.ext2|Description2|*.ext3"
static std::vector<std::string> parseExtensions(const char* exts)
{
std::vector<std::string> extensions;
if (!exts || *exts == '\0') {
return extensions;
}

std::string extStr(exts);
std::istringstream stream(extStr);
std::string token;
int index = 0;

while (std::getline(stream, token, '|')) {
// Only process filter strings (odd indices), skip descriptions (even indices)
if (index % 2 == 1) {
// Split by semicolon for multiple extensions
std::istringstream extStream(token);
std::string ext;
while (std::getline(extStream, ext, ';')) {
// Remove leading *. if present
if (ext.size() > 2 && ext[0] == '*' && ext[1] == '.') {
ext = ext.substr(2);
} else if (ext.size() > 1 && ext[0] == '.') {
ext = ext.substr(1);
}
// Skip wildcard
if (ext != "*" && !ext.empty()) {
extensions.push_back(ext);
}
}
}
index++;
}

return extensions;
}

// Convert file extensions to UTTypes for macOS file dialogs
static NSArray<UTType*>* extensionsToUTTypes(const std::vector<std::string>& extensions)
{
if (extensions.empty()) {
return nil;
}

NSMutableArray<UTType*>* types = [NSMutableArray array];

for (const auto& ext : extensions) {
NSString* nsExt = [NSString stringWithUTF8String:ext.c_str()];
UTType* type = [UTType typeWithFilenameExtension:nsExt];
if (type) {
[types addObject:type];
}
}

return types.count > 0 ? types : nil;
}

std::filesystem::path nvgui::windowOpenFileDialog(struct GLFWwindow* glfwin, const char* title, const char* exts)
{
std::filesystem::path initialDir;
return windowOpenFileDialog(glfwin, title, exts, initialDir);
}

std::filesystem::path nvgui::windowOpenFileDialog(struct GLFWwindow* glfwin, const char* title, const char* exts, std::filesystem::path& initialDir)
{
@autoreleasepool {
NSOpenPanel* panel = [NSOpenPanel openPanel];

[panel setTitle:[NSString stringWithUTF8String:title]];
[panel setCanChooseFiles:YES];
[panel setCanChooseDirectories:NO];
[panel setAllowsMultipleSelection:NO];

// Set initial directory if provided
if (!initialDir.empty()) {
NSString* dirPath = [NSString stringWithUTF8String:initialDir.c_str()];
[panel setDirectoryURL:[NSURL fileURLWithPath:dirPath]];
}

// Set allowed file types
std::vector<std::string> extensions = parseExtensions(exts);
NSArray<UTType*>* allowedTypes = extensionsToUTTypes(extensions);
if (allowedTypes) {
[panel setAllowedContentTypes:allowedTypes];
}

if ([panel runModal] == NSModalResponseOK) {
NSURL* url = [[panel URLs] firstObject];
if (url) {
std::filesystem::path result([[url path] UTF8String]);
// Update initial directory
initialDir = result.parent_path();
return result;
}
}

return std::filesystem::path();
}
}

std::filesystem::path nvgui::windowSaveFileDialog(struct GLFWwindow* glfwin, const char* title, const char* exts)
{
@autoreleasepool {
NSSavePanel* panel = [NSSavePanel savePanel];

[panel setTitle:[NSString stringWithUTF8String:title]];
[panel setCanCreateDirectories:YES];

// Set allowed file types
std::vector<std::string> extensions = parseExtensions(exts);
NSArray<UTType*>* allowedTypes = extensionsToUTTypes(extensions);
if (allowedTypes) {
[panel setAllowedContentTypes:allowedTypes];
}

if ([panel runModal] == NSModalResponseOK) {
NSURL* url = [panel URL];
if (url) {
return std::filesystem::path([[url path] UTF8String]);
}
}

return std::filesystem::path();
}
}

std::filesystem::path nvgui::windowOpenFolderDialog(struct GLFWwindow* glfwin, const char* title)
{
@autoreleasepool {
NSOpenPanel* panel = [NSOpenPanel openPanel];

[panel setTitle:[NSString stringWithUTF8String:title]];
[panel setCanChooseFiles:NO];
[panel setCanChooseDirectories:YES];
[panel setAllowsMultipleSelection:NO];

if ([panel runModal] == NSModalResponseOK) {
NSURL* url = [[panel URLs] firstObject];
if (url) {
return std::filesystem::path([[url path] UTF8String]);
}
}

return std::filesystem::path();
}
}

#endif // __APPLE__
Loading