Projects
home:darix:branches:Multimedia
obs-studio
Sign Up
Log In
Username
Password
We truncated the diff of some files because they were too big. If you want to see the full diff for every file,
click here
.
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 5
View file
obs-studio.changes
Changed
@@ -1,4 +1,46 @@ ------------------------------------------------------------------- +Fri Sep 05 21:38:34 UTC 2025 - darix <packman@nordisch.org> + +- Update to version 32.0.0~beta2: + * libobs-metal: Added README file for current state of implementation + * libobs: Update default draw effect to also provide D65P3 conversion + * frontend: Add Metal to available list of renderers in basic settings + * libobs-metal: Add Metal renderer + * CI: Update macOS runner for building to use Xcode 16.4 + * libobs: Add prerequisites for Metal and Swift support + * cmake: Enable DEBUG flag for Swift + * CI: Update swift-format configuration with more explicit rules + * libobs: Remove Qt5 module check + * Revert "libobs/util: Reject plugins linking Qt5 library for Linux" + * Revert "libobs: Assume Qt 6, always warn about Qt 5 plugins" + * Revert "libobs/util: Prevent locking mutex in child process when checking Qt5" + * cmake: Remove library compat symlink on Linux + * Revert "cmake: Avoid breaking ABI through major version bump on Linux" + * Revert "cmake: Use fixed SOVERSION everywhere" + * libobs: Re-include groups in obs_enum_scenes + * CI,build-aux: Use rebuilt CEF on Linux and macOS + * frontend/data: Remove unused context bar string + * shared/idian: Make checked status of collapsible rows public + * shared/idian: Make title and description common to all row types + * shared/idian: Pass "this" instead of "=" to lambda + * frontend: Include OBSIdianPlayground MOC + * frontend/themes: Remove Idian test rule + * rtmp-services: Remove defunct servers/services + * nv-filters: Guard function introduced in sdk >= 1.6.0 + * libobs: Use RTLD_NOW to load modules + * libobs: Fix comment typo + * frontend: Fix plugin manager module type loading + * libobs: Set module for outputs + * libobs: Remove unused obs_*_info module pointer + * libobs: Fix scene and group load state + * plugins: Ensure that graphics device type checks use graphics context + * frontend: Change crash sentinel location to separate subdirectory + * obs-ffmpeg: Null-check url query parameters + * frontend: Fix plugin manager config loading crash + * frontend: Do not enable crash log upload without log file + * frontend: Remove unneeded argument from log upload privacy notice + +------------------------------------------------------------------- Thu Aug 28 23:12:47 UTC 2025 - darix <packman@nordisch.org> - Update to version 32.0.0~beta1:
View file
obs-studio.spec
Changed
@@ -39,7 +39,7 @@ %endif Name: obs-studio -Version: 32.0.0~beta1 +Version: 32.0.0~beta2 Release: 0 Summary: A recording/broadcasting program Group: Productivity/Multimedia/Video/Editors and Convertors
View file
_service
Changed
@@ -1,7 +1,7 @@ <services> <service name="tar_scm" mode="manual"> <param name="versionformat">@PARENT_TAG@</param> - <param name="revision">32.0.0-beta1</param> + <param name="revision">32.0.0-beta2</param> <param name="url">https://github.com/obsproject/obs-studio.git</param> <param name="versionrewrite-pattern">(\.\d+)-(a-z.*)</param> <param name="versionrewrite-replacement">\1~\2</param>
View file
_servicedata
Changed
@@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/obsproject/obs-studio.git</param> - <param name="changesrevision">1239ef5f830455973af97a4cf87ed66f1ace818f</param> + <param name="changesrevision">ee212e863d6c7dcda13e039f8b710a388d1a81a5</param> </service> </servicedata> \ No newline at end of file
View file
obs-studio-32.0.0~beta1.tar.xz/.github/workflows/analyze-project.yaml -> obs-studio-32.0.0~beta2.tar.xz/.github/workflows/analyze-project.yaml
Changed
@@ -57,8 +57,8 @@ : Set Up Environment 🔧 if (( ${+RUNNER_DEBUG} )) setopt XTRACE - print '::group::Enable Xcode 16.1' - sudo xcode-select --switch /Applications/Xcode_16.1.0.app/Contents/Developer + print '::group::Enable Xcode 16.4' + sudo xcode-select --switch /Applications/Xcode_16.4.app/Contents/Developer print '::endgroup::' print '::group::Clean Homebrew Environment'
View file
obs-studio-32.0.0~beta1.tar.xz/.github/workflows/build-project.yaml -> obs-studio-32.0.0~beta2.tar.xz/.github/workflows/build-project.yaml
Changed
@@ -80,8 +80,8 @@ : Set Up Environment 🔧 if (( ${+RUNNER_DEBUG} )) setopt XTRACE - print '::group::Enable Xcode 16.1' - sudo xcode-select --switch /Applications/Xcode_16.1.0.app/Contents/Developer + print '::group::Enable Xcode 16.4' + sudo xcode-select --switch /Applications/Xcode_16.4.app/Contents/Developer print '::endgroup::' print '::group::Clean Homebrew Environment'
View file
obs-studio-32.0.0~beta1.tar.xz/.swift-format -> obs-studio-32.0.0~beta2.tar.xz/.swift-format
Changed
@@ -8,5 +8,16 @@ "maximumBlankLines": 1, "respectsExistingLineBreaks": true, "lineBreakBeforeControlFlowKeywords": false, - "lineBreakBeforeEachArgument": false + "lineBreakBeforeEachArgument": false, + "lineBreakBeforeEachGenericRequirement": false, + "lineBreakBetweenDeclarationAttributes": false, + "prioritizeKeepingFunctionOutputTogether": false, + "indentConditionalCompilationBlocks": true, + "lineBreakAroundMultilineExpressionChainComponents": false, + "fileScopedDeclarationPrivacy": {"accessLevel": "private"}, + "indentSwitchCaseLabels": false, + "spacesAroundRangeFormationOperators": false, + "noAssignmentInExpressions": { "allowedFunctions" : "XCTAssertNoThrow" }, + "multiElementCollectionTrailingCommas": true, + "indentBlankLines": false, }
View file
obs-studio-32.0.0~beta1.tar.xz/CMakeLists.txt -> obs-studio-32.0.0~beta2.tar.xz/CMakeLists.txt
Changed
@@ -25,6 +25,9 @@ add_subdirectory(libobs-winrt) endif() add_subdirectory(libobs-opengl) +if(OS_MACOS) + add_subdirectory(libobs-metal) +endif() add_subdirectory(plugins) add_subdirectory(test/test-input)
View file
obs-studio-32.0.0~beta1.tar.xz/build-aux/modules/99-cef.json -> obs-studio-32.0.0~beta2.tar.xz/build-aux/modules/99-cef.json
Changed
@@ -17,8 +17,8 @@ "sources": { "type": "archive", - "url": "https://cdn-fastly.obsproject.com/downloads/cef_binary_6533_linux_x86_64_v5.tar.xz", - "sha256": "df38ef6d8078895953d224a58dd811b83110b4f8644c5cd2b6246d04b0023ee6" + "url": "https://cdn-fastly.obsproject.com/downloads/cef_binary_6533_linux_x86_64_v6.tar.xz", + "sha256": "7963335519a19ccdc5233f7334c5ab023026e2f3e9a0cc417007c09d86608146" } }
View file
obs-studio-32.0.0~beta1.tar.xz/buildspec.json -> obs-studio-32.0.0~beta2.tar.xz/buildspec.json
Changed
@@ -30,18 +30,18 @@ "baseUrl": "https://cdn-fastly.obsproject.com/downloads", "label": "Chromium Embedded Framework", "hashes": { - "macos-x86_64": "94ff9dffd60a83a3cd30851eb5b55c1c8d00e7626bd2b8b22d332fc013643105", - "macos-arm64": "7c6c1c3706e08f470fb09c57bcfa49e760ba4a00dc59467e8c2c0d83bc99f0d5", - "ubuntu-x86_64": "df38ef6d8078895953d224a58dd811b83110b4f8644c5cd2b6246d04b0023ee6", - "ubuntu-aarch64": "b1ebcedbe63657c7f38a4d547398a4759544f75d955777eea386052abc9c9228", + "macos-x86_64": "37bf7571a48c5dfa8519817e4a90a3503a0eb30f9eadd68f4c3e783e363f272a", + "macos-arm64": "429b50e74f6c174dcfe2f14d8204b54add497eaafe117f7b69ce6bb2354d2626", + "ubuntu-x86_64": "7963335519a19ccdc5233f7334c5ab023026e2f3e9a0cc417007c09d86608146", + "ubuntu-aarch64": "642514469eaa29a5c887891084d2e73f7dc2d7405f7dfa7726b2dbc24b309999", "windows-x64": "922efbda1f2f8be9e5b2754d878a14d90afc81f04e94fc9101a7513e2b5cecc1", "windows-arm64": "df9df4bd85826b4c071c6db404fd59cf93efd9c58ec3ab64e204466ae19bb02a" }, "revision": { - "macos-x86_64": 4, - "macos-arm64": 4, - "ubuntu-x86_64": 5, - "ubuntu-aarch64": 5, + "macos-x86_64": 5, + "macos-arm64": 5, + "ubuntu-x86_64": 6, + "ubuntu-aarch64": 6, "windows-x64": 2 } }
View file
obs-studio-32.0.0~beta1.tar.xz/cmake/linux/helpers.cmake -> obs-studio-32.0.0~beta2.tar.xz/cmake/linux/helpers.cmake
Changed
@@ -19,7 +19,6 @@ endwhile() get_target_property(target_type ${target} TYPE) - set(OBS_SOVERSION 30) if(target_type STREQUAL EXECUTABLE) install(TARGETS ${target} RUNTIME DESTINATION "${OBS_EXECUTABLE_DESTINATION}" COMPONENT Runtime) @@ -60,8 +59,8 @@ set_target_properties( ${target} PROPERTIES - VERSION ${OBS_SOVERSION} - SOVERSION ${OBS_SOVERSION} + VERSION ${OBS_VERSION_CANONICAL} + SOVERSION ${OBS_VERSION_MAJOR} BUILD_RPATH "${OBS_OUTPUT_DIR}/$<CONFIG>/${OBS_LIBRARY_DESTINATION}" INSTALL_RPATH "${OBS_LIBRARY_RPATH}" ) @@ -85,36 +84,15 @@ COMMENT "Copy ${target} to library directory (${OBS_LIBRARY_DESTINATION})" VERBATIM ) - - if(target STREQUAL libobs OR target STREQUAL obs-frontend-api) - install( - FILES "$<TARGET_FILE_DIR:${target}>/$<TARGET_FILE_PREFIX:${target}>$<TARGET_FILE_BASE_NAME:${target}>.so.0" - DESTINATION "${OBS_LIBRARY_DESTINATION}" - ) - - add_custom_command( - TARGET ${target} - POST_BUILD - COMMAND - "${CMAKE_COMMAND}" -E create_symlink - "$<TARGET_FILE_PREFIX:${target}>$<TARGET_FILE_BASE_NAME:${target}>.so.${OBS_SOVERSION}" - "$<TARGET_FILE_PREFIX:${target}>$<TARGET_FILE_BASE_NAME:${target}>.so.0" - COMMAND - "${CMAKE_COMMAND}" -E copy_if_different - "$<TARGET_FILE_DIR:${target}>/$<TARGET_FILE_PREFIX:${target}>$<TARGET_FILE_BASE_NAME:${target}>.so.0" - "${OBS_OUTPUT_DIR}/$<CONFIG>/${OBS_LIBRARY_DESTINATION}" - COMMENT "Create symlink for legacy ${target}" - ) - endif() elseif(target_type STREQUAL MODULE_LIBRARY) if(target STREQUAL obs-browser) - set_target_properties(${target} PROPERTIES VERSION 0 SOVERSION ${OBS_SOVERSION}) + set_target_properties(${target} PROPERTIES VERSION 0 SOVERSION ${OBS_VERSION_MAJOR}) else() set_target_properties( ${target} PROPERTIES VERSION 0 - SOVERSION ${OBS_SOVERSION} + SOVERSION ${OBS_VERSION_MAJOR} BUILD_RPATH "${OBS_OUTPUT_DIR}/$<CONFIG>/${OBS_LIBRARY_DESTINATION}" INSTALL_RPATH "${OBS_MODULE_RPATH}" )
View file
obs-studio-32.0.0~beta1.tar.xz/cmake/macos/compilerconfig.cmake -> obs-studio-32.0.0~beta2.tar.xz/cmake/macos/compilerconfig.cmake
Changed
@@ -88,7 +88,7 @@ # * -Wno-non-virtual-dtor add_compile_definitions( - $<$<NOT:$<COMPILE_LANGUAGE:Swift>>:$<$<CONFIG:DEBUG>:DEBUG>> + $<$<CONFIG:DEBUG>:DEBUG> $<$<NOT:$<COMPILE_LANGUAGE:Swift>>:$<$<CONFIG:DEBUG>:_DEBUG>> $<$<NOT:$<COMPILE_LANGUAGE:Swift>>:SIMDE_ENABLE_OPENMP> )
View file
obs-studio-32.0.0~beta1.tar.xz/frontend/OBSApp.cpp -> obs-studio-32.0.0~beta2.tar.xz/frontend/OBSApp.cpp
Changed
@@ -292,8 +292,13 @@ #if _WIN32 config_set_default_string(appConfig, "Video", "Renderer", "Direct3D 11"); #else +#if defined(__APPLE__) && defined(__aarch64__) + // TODO: Change this value to "Metal" once the renderer has reached production quality + config_set_default_string(appConfig, "Video", "Renderer", "OpenGL"); +#else config_set_default_string(appConfig, "Video", "Renderer", "OpenGL"); #endif +#endif #ifdef _WIN32 config_set_default_bool(appConfig, "Audio", "DisableAudioDucking", true); @@ -1077,9 +1082,17 @@ const char *OBSApp::GetRenderModule() const { +#if defined(_WIN32) const char *renderer = config_get_string(appConfig, "Video", "Renderer"); return (astrcmpi(renderer, "Direct3D 11") == 0) ? DL_D3D11 : DL_OPENGL; +#elif defined(__APPLE__) && defined(__aarch64__) + const char *renderer = config_get_string(appConfig, "Video", "Renderer"); + + return (astrcmpi(renderer, "Metal (Experimental)") == 0) ? DL_METAL : DL_OPENGL; +#else + return DL_OPENGL; +#endif } static bool StartupOBS(const char *locale, profiler_name_store_t *store)
View file
obs-studio-32.0.0~beta1.tar.xz/frontend/data/locale/en-US.ini -> obs-studio-32.0.0~beta2.tar.xz/frontend/data/locale/en-US.ini
Changed
@@ -1490,7 +1490,6 @@ # Context Bar ContextBar.NoSelectedSource="No source selected" -ContextBar.ResetTransform="Reset Transform" # Context Bar Media Controls ContextBar.MediaControls.PlayMedia="Play Media"
View file
obs-studio-32.0.0~beta1.tar.xz/frontend/data/themes/Yami.obt -> obs-studio-32.0.0~beta2.tar.xz/frontend/data/themes/Yami.obt
Changed
@@ -2230,13 +2230,6 @@ border-bottom-right-radius: var(--border_radius); } -idian--Row > QWidget QLabel { - background: red; - margin: 4px; - font-weight: 500; - max-height: var(--input_height); -} - idian--Row > QLabel.description { font-size: var(--font_small); color: var(--text_muted);
View file
obs-studio-32.0.0~beta1.tar.xz/frontend/dialogs/LogUploadDialog.cpp -> obs-studio-32.0.0~beta2.tar.xz/frontend/dialogs/LogUploadDialog.cpp
Changed
@@ -45,8 +45,7 @@ ui->stackedWidget->setCurrentIndex(DialogPage::Start); - ui->privacyNotice->setText( - QTStr("LogUploadDialog.Labels.PrivacyNotice").arg(QTStr("LogUploadDialog.Buttons.ConfirmUpload"))); + ui->privacyNotice->setText(QTStr("LogUploadDialog.Labels.PrivacyNotice")); if (uploadType_ == LogFileType::CrashLog) { ui->analyzeURL->hide();
View file
obs-studio-32.0.0~beta1.tar.xz/frontend/dialogs/OBSIdianPlayground.cpp -> obs-studio-32.0.0~beta2.tar.xz/frontend/dialogs/OBSIdianPlayground.cpp
Changed
@@ -21,6 +21,8 @@ #include <QTimer> +#include "moc_OBSIdianPlayground.cpp" + using namespace idian; OBSIdianPlayground::OBSIdianPlayground(QWidget *parent) : QDialog(parent), ui(new Ui_OBSIdianPlayground) @@ -101,7 +103,8 @@ tmp->setSuffix(new ToggleSwitch); test->properties()->addRow(tmp); - CollapsibleRow *tmp2 = new CollapsibleRow("A Collapsible row!", this); + CollapsibleRow *tmp2 = new CollapsibleRow(this); + tmp2->setTitle("A Collapsible row!"); tmp2->setCheckable(true); test->addRow(tmp2);
View file
obs-studio-32.0.0~beta1.tar.xz/frontend/plugin-manager/PluginManager.cpp -> obs-studio-32.0.0~beta2.tar.xz/frontend/plugin-manager/PluginManager.cpp
Changed
@@ -94,7 +94,15 @@ auto modulesFile = getConfigFilePath_(); if (std::filesystem::exists(modulesFile)) { std::ifstream jsonFile(modulesFile); - nlohmann::json data = nlohmann::json::parse(jsonFile); + nlohmann::json data; + try { + data = nlohmann::json::parse(jsonFile); + } catch (const nlohmann::json::parse_error &error) { + modules_.clear(); + blog(LOG_ERROR, "Error loading modules config file: %s", error.what()); + blog(LOG_ERROR, "Generating new config file."); + return; + } modules_.clear(); for (auto it : data) { ModuleInfo obsModule; @@ -191,7 +199,7 @@ i = 0; while (obs_enum_output_types(i, &output_id)) { i += 1; - obs_module_t *obsModule = obs_source_get_module(output_id); + obs_module_t *obsModule = obs_output_get_module(output_id); if (!obsModule) { continue; } @@ -208,7 +216,7 @@ i = 0; while (obs_enum_encoder_types(i, &encoder_id)) { i += 1; - obs_module_t *obsModule = obs_source_get_module(encoder_id); + obs_module_t *obsModule = obs_encoder_get_module(encoder_id); if (!obsModule) { continue; } @@ -225,7 +233,7 @@ i = 0; while (obs_enum_service_types(i, &service_id)) { i += 1; - obs_module_t *obsModule = obs_source_get_module(service_id); + obs_module_t *obsModule = obs_service_get_module(service_id); if (!obsModule) { continue; }
View file
obs-studio-32.0.0~beta1.tar.xz/frontend/settings/OBSBasicSettings.cpp -> obs-studio-32.0.0~beta2.tar.xz/frontend/settings/OBSBasicSettings.cpp
Changed
@@ -609,10 +609,23 @@ ui->processPriority->addItem(QTStr(pri.name), pri.val); #else +#if defined(__APPLE__) && defined(__aarch64__) + delete ui->adapterLabel; + delete ui->adapter; + + ui->adapterLabel = nullptr; + ui->adapter = nullptr; +#else delete ui->rendererLabel; delete ui->renderer; delete ui->adapterLabel; delete ui->adapter; + + ui->rendererLabel = nullptr; + ui->renderer = nullptr; + ui->adapterLabel = nullptr; + ui->adapter = nullptr; +#endif delete ui->processPriorityLabel; delete ui->processPriority; delete ui->enableNewSocketLoop; @@ -624,10 +637,6 @@ #endif delete ui->disableAudioDucking; - ui->rendererLabel = nullptr; - ui->renderer = nullptr; - ui->adapterLabel = nullptr; - ui->adapter = nullptr; ui->processPriorityLabel = nullptr; ui->processPriority = nullptr; ui->enableNewSocketLoop = nullptr; @@ -1384,16 +1393,21 @@ void OBSBasicSettings::LoadRendererList() { -#ifdef _WIN32 +#if defined(_WIN32) || (defined(__APPLE__) && defined(__aarch64__)) const char *renderer = config_get_string(App()->GetAppConfig(), "Video", "Renderer"); - +#ifdef _WIN32 ui->renderer->addItem(QT_UTF8("Direct3D 11")); - if (opt_allow_opengl || strcmp(renderer, "OpenGL") == 0) + if (opt_allow_opengl || strcmp(renderer, "OpenGL") == 0) { ui->renderer->addItem(QT_UTF8("OpenGL")); - - int idx = ui->renderer->findText(QT_UTF8(renderer)); - if (idx == -1) - idx = 0; + } +#else + ui->renderer->addItem(QT_UTF8("OpenGL")); + ui->renderer->addItem(QT_UTF8("Metal (Experimental)")); +#endif + int index = ui->renderer->findText(QT_UTF8(renderer)); + if (index == -1) { + index = 0; + } // the video adapter selection is not currently implemented, hide for now // to avoid user confusion. was previously protected by @@ -1403,7 +1417,7 @@ ui->adapter = nullptr; ui->adapterLabel = nullptr; - ui->renderer->setCurrentIndex(idx); + ui->renderer->setCurrentIndex(index); #endif } @@ -3130,10 +3144,13 @@ { QString lastMonitoringDevice = config_get_string(main->Config(), "Audio", "MonitoringDeviceId"); -#ifdef _WIN32 - if (WidgetChanged(ui->renderer)) +#if defined(_WIN32) || (defined(__APPLE__) && defined(__aarch64__)) + if (WidgetChanged(ui->renderer)) { config_set_string(App()->GetAppConfig(), "Video", "Renderer", QT_TO_UTF8(ui->renderer->currentText())); + } +#endif +#ifdef _WIN32 std::string priority = QT_TO_UTF8(ui->processPriority->currentData().toString()); config_set_string(App()->GetAppConfig(), "General", "ProcessPriority", priority.c_str()); if (main->Active())
View file
obs-studio-32.0.0~beta1.tar.xz/frontend/utility/CrashHandler.cpp -> obs-studio-32.0.0~beta2.tar.xz/frontend/utility/CrashHandler.cpp
Changed
@@ -33,8 +33,8 @@ namespace { -constexpr std::string_view crashSentinelPath = "obs-studio"; -constexpr std::string_view crashSentinelPrefix = "crash_sentinel_"; +constexpr std::string_view crashSentinelPath = "obs-studio/.sentinel"; +constexpr std::string_view crashSentinelPrefix = "run_"; constexpr std::string_view crashUploadURL = "https://obsproject.com/logs/upload"; #ifndef NDEBUG @@ -141,6 +141,10 @@ { CrashLogUpdateResult result = updateLocalCrashLogState(); + if (result == CrashLogUpdateResult::NotAvailable) { + return false; + } + bool hasNewCrashLog = (result == CrashLogUpdateResult::Updated); bool hasNoLogUrl = lastCrashLogURL_.empty(); @@ -153,6 +157,10 @@ std::filesystem::path lastLocalCrashLogFile = findLastCrashLog(); + if (lastLocalCrashLogFile.empty() && lastCrashLogFile_.empty()) { + return CrashLogUpdateResult::NotAvailable; + } + if (lastLocalCrashLogFile != lastCrashLogFile_) { lastCrashLogFile_ = std::move(lastLocalCrashLogFile); lastCrashLogFileName_ = lastCrashLogFile_.filename().u8string(); @@ -182,8 +190,14 @@ std::filesystem::path crashSentinelPath = crashSentinelFile_.parent_path(); if (!std::filesystem::exists(crashSentinelPath)) { - blog(LOG_ERROR, "Crash sentinel location '%s' does not exist", crashSentinelPath.u8string().c_str()); - return; + try { + std::filesystem::create_directory(crashSentinelPath); + } catch (const std::filesystem::filesystem_error &error) { + blog(LOG_ERROR, + "Crash sentinel location '%s' does not exist and unable to create directory:\n%s.", + crashSentinelPath.u8string().c_str(), error.what()); + return; + } } for (const auto &entry : std::filesystem::directory_iterator(crashSentinelPath)) {
View file
obs-studio-32.0.0~beta1.tar.xz/frontend/utility/CrashHandler.hpp -> obs-studio-32.0.0~beta2.tar.xz/frontend/utility/CrashHandler.hpp
Changed
@@ -67,7 +67,7 @@ std::filesystem::path getCrashLogDirectory() const; void uploadLastCrashLog(); - enum class CrashLogUpdateResult { InvalidResult, NotUpdated, Updated }; + enum class CrashLogUpdateResult { InvalidResult, NotAvailable, NotUpdated, Updated }; private: void checkCrashState();
View file
obs-studio-32.0.0~beta1.tar.xz/frontend/widgets/OBSBasic_MainControls.cpp -> obs-studio-32.0.0~beta2.tar.xz/frontend/widgets/OBSBasic_MainControls.cpp
Changed
@@ -27,6 +27,9 @@ #include <dialogs/OBSBasicInteraction.hpp> #include <dialogs/OBSBasicProperties.hpp> #include <dialogs/OBSBasicTransform.hpp> +#ifdef ENABLE_IDIAN_PLAYGROUND +#include <dialogs/OBSIdianPlayground.hpp> +#endif #include <dialogs/OBSLogViewer.hpp> #ifdef __APPLE__ #include <dialogs/OBSPermissions.hpp> @@ -42,10 +45,6 @@ #endif #include <wizards/AutoConfig.hpp> -#ifdef ENABLE_IDIAN_PLAYGROUND -#include "dialogs/OBSIdianPlayground.hpp" -#endif - #include <qt-wrappers.hpp> #include <QDesktopServices>
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal
Added
+(directory)
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/CMakeLists.txt
Added
@@ -0,0 +1,70 @@ +cmake_minimum_required(VERSION 3.28...3.30) + +add_library(libobs-metal SHARED) +add_library(OBS::libobs-metal ALIAS libobs-metal) + +target_sources( + libobs-metal + PRIVATE + CVPixelFormat+Extensions.swift + MTLCullMode+Extensions.swift + MTLOrigin+Extensions.swift + MTLPixelFormat+Extensions.swift + MTLRegion+Extensions.swift + MTLSize+Extensions.swift + MTLTexture+Extensions.swift + MTLTextureDescriptor+Extensions.swift + MTLTextureType+Extensions.swift + MTLViewport+Extensions.swift + MetalBuffer.swift + MetalDevice.swift + MetalError.swift + MetalRenderState.swift + MetalShader+Extensions.swift + MetalShader.swift + MetalStageBuffer.swift + MetalTexture.swift + OBSShader.swift + OBSSwapChain.swift + Sequence+Hashable.swift + libobs+Extensions.swift + libobs+SignalHandlers.swift + libobs-metal-Bridging-Header.h + metal-indexbuffer.swift + metal-samplerstate.swift + metal-shader.swift + metal-stagesurf.swift + metal-subsystem.swift + metal-swapchain.swift + metal-texture2d.swift + metal-texture3d.swift + metal-unimplemented.swift + metal-vertexbuffer.swift + metal-zstencilbuffer.swift +) + +target_link_libraries(libobs-metal PRIVATE OBS::libobs) + +target_enable_feature(libobs "Metal renderer") + +set_property(SOURCE OBSMetalRenderer.swift APPEND PROPERTY COMPILE_FLAGS -emit-objc-header) + +set_target_properties_obs( + libobs-metal + PROPERTIES FOLDER core + VERSION 0 + PREFIX "" +) + +set_target_xcode_properties( + libobs-metal + PROPERTIES SWIFT_VERSION 6.0 + CLANG_ENABLE_OBJC_ARC YES + CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION YES + GCC_WARN_SHADOW YES + CLANG_ENABLE_MODULES YES + CLANG_MODULES_AUTOLINK YES + GCC_STRICT_ALIASING YES + DEFINES_MODULE YES + SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/libobs-metal-Bridging-Header.h" +)
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/CVPixelFormat+Extensions.swift
Added
@@ -0,0 +1,51 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import CoreVideo +import Metal + +extension OSType { + /// Conversion of CoreVideo pixel formats into corresponding Metal pixel formats + var mtlFormat: MTLPixelFormat? { + switch self { + case kCVPixelFormatType_OneComponent8: + return .r8Unorm + case kCVPixelFormatType_OneComponent16Half: + return .r16Float + case kCVPixelFormatType_OneComponent32Float: + return .r32Float + case kCVPixelFormatType_TwoComponent8: + return .rg8Unorm + case kCVPixelFormatType_TwoComponent16Half: + return .rg16Float + case kCVPixelFormatType_TwoComponent32Float: + return .rg32Float + case kCVPixelFormatType_32BGRA: + return .bgra8Unorm + case kCVPixelFormatType_32RGBA: + return .rgba8Unorm + case kCVPixelFormatType_64RGBAHalf: + return .rgba16Float + case kCVPixelFormatType_128RGBAFloat: + return .rgba32Float + case kCVPixelFormatType_ARGB2101010LEPacked: + return .bgr10a2Unorm + default: + return nil + } + } +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/MTLCullMode+Extensions.swift
Added
@@ -0,0 +1,33 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal + +extension MTLCullMode { + /// Conversion of the cull mode into its corresponding `libobs` type + var obsMode: gs_cull_mode { + switch self { + case .back: + return GS_BACK + case .front: + return GS_FRONT + default: + return GS_NEITHER + } + } +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/MTLOrigin+Extensions.swift
Added
@@ -0,0 +1,25 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal + +extension MTLOrigin: @retroactive Equatable { + public static func == (lhs: MTLOrigin, rhs: MTLOrigin) -> Bool { + lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z + } +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/MTLPixelFormat+Extensions.swift
Added
@@ -0,0 +1,406 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import CoreGraphics +import CoreVideo +import Foundation +import Metal + +extension MTLPixelFormat { + /// Property to check whether the pixel format is an 8-bit format + var is8Bit: Bool { + switch self { + case .a8Unorm, .r8Unorm, .r8Snorm, .r8Uint, .r8Sint: + return true + case .r8Unorm_srgb: + return true + default: + return false + } + } + + /// Property to check whether the pixel format is a 16-bit format + var is16Bit: Bool { + switch self { + case .r16Unorm, .r16Snorm, .r16Uint, .r16Sint: + return true + case .rg8Unorm, .rg8Snorm, .rg8Uint, .rg8Sint: + return true + case .rg16Float: + return true + case .rg8Unorm_srgb: + return true + default: + return false + } + } + + /// Property to check whether the pixel format is a packed 16-bit format + var isPacked16Bit: Bool { + switch self { + case .b5g6r5Unorm, .a1bgr5Unorm, .abgr4Unorm, .bgr5A1Unorm: + return true + default: + return false + } + } + + /// Property to check whether the pixel format is a 32-bit format + var is32Bit: Bool { + switch self { + case .r32Uint, .r32Sint: + return true + case .r32Float: + return true + case .rg16Unorm, .rg16Snorm, .rg16Uint, .rg16Sint: + return true + case .rg16Float: + return true + case .rgba8Unorm, .rgba8Snorm, .rgba8Uint, .rgba8Sint, .bgra8Unorm: + return true + case .rgba8Unorm_srgb, .bgra8Unorm_srgb: + return true + default: + return false + } + } + + /// Property to check whether the pixel format is a packed 32-bit format + var isPacked32Bit: Bool { + switch self { + case .rgb10a2Unorm, .rgb10a2Uint, .bgr10a2Unorm: + return true + case .rg11b10Float: + return true + case .rgb9e5Float: + return true + case .bgr10_xr, .bgr10_xr_srgb: + return true + default: + return false + } + } + + /// Property to check whether the pixel format is a 64-bit format + var is64Bit: Bool { + switch self { + case .rg32Uint, .rg32Sint: + return true + case .rg32Float: + return true + case .rgba16Unorm, .rgba16Snorm, .rgba16Uint, .rgba16Sint: + return true + case .rgba16Float: + return true + case .bgra10_xr, .bgra10_xr_srgb: + return true + default: + return false + } + } + + /// Property to check whether the pixel format is a 128-bit format + var is128Bit: Bool { + switch self { + case .rgba32Uint, .rgba32Sint: + return true + case .rgba32Float: + return true + default: + return false + } + } + + /// Property to check whether the pixel format will trigger automatic sRGB gamma encoding and decoding + var isSRGB: Bool { + switch self { + case .r8Unorm_srgb, .rg8Unorm_srgb, .bgra8Unorm_srgb, .rgba8Unorm_srgb: + return true + case .bgr10_xr_srgb, .bgra10_xr_srgb: + return true + case .astc_4x4_srgb, .astc_5x4_srgb, .astc_5x5_srgb, .astc_6x5_srgb, .astc_6x6_srgb, .astc_8x5_srgb, + .astc_8x6_srgb, .astc_8x8_srgb, .astc_10x5_srgb, .astc_10x6_srgb, .astc_10x8_srgb, .astc_10x10_srgb, + .astc_12x10_srgb, .astc_12x12_srgb: + return true + case .bc1_rgba_srgb, .bc2_rgba_srgb, .bc3_rgba_srgb, .bc7_rgbaUnorm_srgb: + return true + case .eac_rgba8_srgb, .etc2_rgb8, .etc2_rgb8a1_srgb: + return true + default: + return false + } + } + + /// Property to check whether the pixel format is an extended dynamic range (EDR) format + var isEDR: Bool { + switch self { + case .bgr10_xr, .bgra10_xr, .bgr10_xr_srgb, .bgra10_xr_srgb: + return true + default: + return false + } + } + + /// Property to check whether the pixel format uses a form of texture compression + var isCompressed: Bool { + switch self { + // S3TC + case .bc1_rgba, .bc1_rgba_srgb, .bc2_rgba, .bc2_rgba_srgb, .bc3_rgba, .bc3_rgba_srgb: + return true + // RGTC + case .bc4_rUnorm, .bc4_rSnorm, .bc5_rgUnorm, .bc5_rgSnorm: + return true + // BPTC + case .bc6H_rgbFloat, .bc6H_rgbuFloat, .bc7_rgbaUnorm, .bc7_rgbaUnorm_srgb: + return true + // EAC + case .eac_r11Unorm, .eac_r11Snorm, .eac_rg11Unorm, .eac_rg11Snorm, .eac_rgba8, .eac_rgba8_srgb: + return true + // ETC + case .etc2_rgb8, .etc2_rgb8_srgb, .etc2_rgb8a1, .etc2_rgb8a1_srgb: + return true + // ASTC + case .astc_4x4_srgb, .astc_5x4_srgb, .astc_5x5_srgb, .astc_6x5_srgb, .astc_6x6_srgb, .astc_8x5_srgb, + .astc_8x6_srgb, .astc_8x8_srgb, .astc_10x5_srgb, .astc_10x6_srgb, .astc_10x8_srgb, .astc_10x10_srgb, + .astc_12x10_srgb, .astc_12x12_srgb, .astc_4x4_ldr, .astc_5x4_ldr, .astc_5x5_ldr, .astc_6x5_ldr, + .astc_6x6_ldr, .astc_8x5_ldr, .astc_8x6_ldr, .astc_8x8_ldr, .astc_10x5_ldr, .astc_10x6_ldr, .astc_10x8_ldr, + .astc_10x10_ldr, .astc_12x10_ldr, .astc_12x12_ldr: + return true + // ASTC HDR + case .astc_4x4_hdr, .astc_5x4_hdr, .astc_5x5_hdr, .astc_6x5_hdr, .astc_6x6_hdr, .astc_8x5_hdr, .astc_8x6_hdr, + .astc_8x8_hdr, .astc_10x5_hdr, .astc_10x6_hdr, .astc_10x8_hdr, .astc_10x10_hdr, .astc_12x10_hdr, + .astc_12x12_hdr: + return true + default: + return false + } + } + + /// Property to check whether the pixel format is a depth buffer format + var isDepth: Bool { + switch self { + case .depth16Unorm, .depth32Float: + return true + default: + return false
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/MTLRegion+Extensions.swift
Added
@@ -0,0 +1,25 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal + +extension MTLRegion: @retroactive Equatable { + public static func == (lhs: MTLRegion, rhs: MTLRegion) -> Bool { + lhs.origin == rhs.origin && lhs.size == rhs.size + } +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/MTLSize+Extensions.swift
Added
@@ -0,0 +1,25 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal + +extension MTLSize: @retroactive Equatable { + public static func == (lhs: MTLSize, rhs: MTLSize) -> Bool { + lhs.width == rhs.width && lhs.height == rhs.height && lhs.depth == rhs.depth + } +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/MTLTexture+Extensions.swift
Added
@@ -0,0 +1,76 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal + +extension MTLTexture { + /// Creates an opaque pointer of a ``MTLTexture`` instance and increases the reference count. + /// - Returns: Opaque pointer for the ``MTLTexture`` + func getRetained() -> OpaquePointer { + let retained = Unmanaged.passRetained(self).toOpaque() + + return OpaquePointer(retained) + } + + /// Creates an opaque pointer of a ``MTLTexture`` instance without increasing the reference count. + /// - Returns: Opaque pointer for the ``MTLTexture`` + func getUnretained() -> OpaquePointer { + let unretained = Unmanaged.passUnretained(self).toOpaque() + + return OpaquePointer(unretained) + } +} + +extension MTLTexture { + /// Convenience property to get the texture's size as a ``MTLSize`` object + var size: MTLSize { + .init( + width: self.width, + height: self.height, + depth: self.depth + ) + } + + /// Convenience property to get the texture's region as a ``MTLRegion`` object + var region: MTLRegion { + .init( + origin: .init(x: 0, y: 0, z: 0), + size: self.size + ) + } + + /// Gets a new ``MTLTextureDescriptor`` instance with the properties of the texture + var descriptor: MTLTextureDescriptor { + let descriptor = MTLTextureDescriptor() + + descriptor.textureType = self.textureType + descriptor.pixelFormat = self.pixelFormat + descriptor.width = self.width + descriptor.height = self.height + descriptor.depth = self.depth + descriptor.mipmapLevelCount = self.mipmapLevelCount + descriptor.sampleCount = self.sampleCount + descriptor.arrayLength = self.arrayLength + descriptor.storageMode = self.storageMode + descriptor.cpuCacheMode = self.cpuCacheMode + descriptor.usage = self.usage + descriptor.allowGPUOptimizedContents = self.allowGPUOptimizedContents + + return descriptor + } +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/MTLTextureDescriptor+Extensions.swift
Added
@@ -0,0 +1,93 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Metal + +extension MTLTextureDescriptor { + + /// Convenience initializer for a texture descriptor with `libobs` data + /// - Parameters: + /// - type: Metal texture type + /// - width: Width of texture + /// - height: Height of texture + /// - depth: Depth of texture + /// - colorFormat: `libobs` color format for the texture + /// - levels: Mip map levels + /// - flags: Additional usage flags as `libobs` bitfield + convenience init?( + type: MTLTextureType, width: UInt32, height: UInt32, depth: UInt32, colorFormat: gs_color_format, + levels: UInt32, flags: UInt32 + ) { + let arrayLength: Int + switch type { + case .type2D: + arrayLength = 1 + case .type3D: + arrayLength = 1 + case .typeCube: + arrayLength = 6 + default: + assertionFailure("MTLTextureDescriptor: Unsupported texture type for libobs initializer") + return nil + } + + self.init() + + self.textureType = type + self.pixelFormat = colorFormat.mtlFormat + self.width = Int(width) + self.height = Int(height) + self.depth = Int(depth) + self.sampleCount = 1 + self.arrayLength = arrayLength + self.cpuCacheMode = .defaultCache + self.allowGPUOptimizedContents = true + self.hazardTrackingMode = .default + + if (Int32(flags) & GS_BUILD_MIPMAPS) != 0 { + self.mipmapLevelCount = Int(levels) + } else { + self.mipmapLevelCount = 1 + } + + if (Int32(flags) & GS_RENDER_TARGET) != 0 { + self.storageMode = .private + self.usage = .shaderRead, .renderTarget + } else { + self.storageMode = .shared + self.usage = .shaderRead + } + } + + convenience init?(width: UInt32, height: UInt32, colorFormat: gs_zstencil_format) { + self.init() + + self.textureType = .type2D + self.pixelFormat = colorFormat.mtlFormat + self.width = Int(width) + self.height = Int(height) + self.depth = 1 + self.sampleCount = 1 + self.arrayLength = 1 + self.cpuCacheMode = .defaultCache + self.allowGPUOptimizedContents = true + self.hazardTrackingMode = .default + self.mipmapLevelCount = 1 + self.storageMode = .private + self.usage = .shaderRead + } +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/MTLTextureType+Extensions.swift
Added
@@ -0,0 +1,36 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal + +extension MTLTextureType { + /// Converts the Metal texture type into a compatible `libobs` texture type or `nil` if no compatible mapping is + /// possible. + var gsTextureType: gs_texture_type? { + switch self { + case .type2D: + return GS_TEXTURE_2D + case .type3D: + return GS_TEXTURE_3D + case .typeCube: + return GS_TEXTURE_CUBE + default: + return nil + } + } +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/MTLViewport+Extensions.swift
Added
@@ -0,0 +1,31 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal + +extension MTLViewport: @retroactive Equatable { + /// Checks two ``MTLViewPort`` objects for equality + /// - Parameters: + /// - lhs: First ``MTLViewPort``object + /// - rhs: Second ``MTLViewPort`` object + /// - Returns: `true` if the dimensions and origins of both view ports match, `false` otherwise. + public static func == (lhs: MTLViewport, rhs: MTLViewport) -> Bool { + lhs.width == rhs.width && lhs.height == rhs.height && lhs.originX == rhs.originX + && lhs.originY == rhs.originY + } +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/MetalBuffer.swift
Added
@@ -0,0 +1,308 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal + +enum MetalBufferType { + case vertex + case index +} + +/// The MetalBuffer class serves as the super class for both vertex and index buffer objects. +/// +/// It provides convenience functions to pass buffer instances as retained and unretained opaque pointers and provides +/// a generic buffer factory method. +class MetalBuffer { + enum BufferDataType { + case vertex + case normal + case tangent + case color + case texcoord + } + + private let device: MTLDevice + fileprivate let isDynamic: Bool + + init(device: MetalDevice, isDynamic: Bool) { + self.device = device.device + self.isDynamic = isDynamic + } + + /// Creates a new buffer with the provided data or updates an existing buffer with the provided data + /// - Parameters: + /// - buffer: Reference to a buffer variable to either receive the new buffer or provide an existing buffer + /// - data: Pointer to raw data of provided type `T` + /// - count: Byte size of data to be written into the buffer + /// - dynamic: `true` if underlying buffer is dynamically updated for each frame, `false` otherwise. + /// + /// > Note: Some sources (like the `text-freetype2` source) generate "dynamic" buffers but don't update them at + /// every frame and instead treat them as "static" buffers. For this reason `MTLBuffer` objects have to be cached + /// and re-used per `MetalBuffer` instance and cannot be dynamically provided from a pool of buffers of a `MTLHeap`. + fileprivate func createOrUpdateBuffer<T>( + buffer: inout MTLBuffer?, data: UnsafeMutablePointer<T>, count: Int, dynamic: Bool + ) { + let size = MemoryLayout<T>.size * count + let alignedSize = (size + 15) & ~15 + + if buffer != nil { + if dynamic && buffer!.length == alignedSize { + buffer!.contents().copyMemory(from: data, byteCount: size) + return + } + } + + buffer = device.makeBuffer( + bytes: data, length: alignedSize, options: .cpuCacheModeWriteCombined, .storageModeShared) + } + + /// Gets an opaque pointer for the ``MetalBuffer`` instance and increases its reference count by one + /// - Returns: `OpaquePointer` to class instance + /// + /// > Note: Use this method when the instance is to be shared via an `OpaquePointer` and needs to be retained. Any + /// opaque pointer shared this way needs to be converted into a retained reference again to ensure automatic + /// deinitialization by the Swift runtime. + func getRetained() -> OpaquePointer { + let retained = Unmanaged.passRetained(self).toOpaque() + + return OpaquePointer(retained) + } + + /// Gets an opaque pointer for the ``MetalBuffer`` instance without increasing its reference count + /// - Returns: `OpaquePointer` to class instance + func getUnretained() -> OpaquePointer { + let unretained = Unmanaged.passUnretained(self).toOpaque() + + return OpaquePointer(unretained) + } +} + +final class MetalVertexBuffer: MetalBuffer { + public var vertexData: UnsafeMutablePointer<gs_vb_data>? + private var points: MTLBuffer? + private var normals: MTLBuffer? + private var tangents: MTLBuffer? + private var vertexColors: MTLBuffer? + private var uvCoordinates: MTLBuffer? + + init(device: MetalDevice, data: UnsafeMutablePointer<gs_vb_data>, dynamic: Bool) { + self.vertexData = data + self.uvCoordinates = Array(repeating: nil, count: data.pointee.num_tex) + + super.init(device: device, isDynamic: dynamic) + + if !dynamic { + setupBuffers() + } + } + + /// Sets up buffer objects for the data provided in the provided `gs_vb_data` structure + /// - Parameter data: Pointer to a `gs_vb_data` instance + /// + /// The provided `gs_vb_data` instance is expected to: + /// * Always contain vertex data + /// * Optionally contain normals data + /// * Optionally contain tangents data + /// * Optionally contain color data + /// * Optionally contain either 2 or 4 texture coordinates per vertex + /// + /// > Note: The color data needs to be converted from the packed UInt32 format used by `libobs` into a normalized + /// vector of Float32 values as Metal does not support implicit conversion of these types when vertex data is + /// provided in a single buffer to a vertex shader. + public func setupBuffers(data: UnsafeMutablePointer<gs_vb_data>? = nil) { + guard let data = data ?? self.vertexData else { + assertionFailure("MetalBuffer: Unable to create MTLBuffers without vertex data") + return + } + + let numVertices = data.pointee.num + + createOrUpdateBuffer(buffer: &points, data: data.pointee.points, count: numVertices, dynamic: isDynamic) + + #if DEBUG + points?.label = "Vertex buffer points data" + #endif + + if let normalsData = data.pointee.normals { + createOrUpdateBuffer(buffer: &normals, data: normalsData, count: numVertices, dynamic: isDynamic) + + #if DEBUG + normals?.label = "Vertex buffer normals data" + #endif + } + + if let tangentsData = data.pointee.tangents { + createOrUpdateBuffer(buffer: &tangents, data: tangentsData, count: numVertices, dynamic: isDynamic) + + #if DEBUG + tangents?.label = "Vertex buffer tangents data" + #endif + } + + if let colorsData = data.pointee.colors { + var unpackedColors = SIMD4<Float>() + unpackedColors.reserveCapacity(4) + + for i in 0..<numVertices { + let vertexColor = colorsData.advanced(by: i) + + vertexColor.withMemoryRebound(to: UInt8.self, capacity: 4) { + let colorValues = UnsafeBufferPointer<UInt8>(start: $0, count: 4) + + let color = SIMD4<Float>( + x: Float(colorValues0) / 255.0, + y: Float(colorValues1) / 255.0, + z: Float(colorValues2) / 255.0, + w: Float(colorValues3) / 255.0 + ) + + unpackedColors.append(color) + } + } + + unpackedColors.withUnsafeMutableBufferPointer { + createOrUpdateBuffer( + buffer: &vertexColors, data: $0.baseAddress!, count: numVertices, dynamic: isDynamic) + } + + #if DEBUG + vertexColors?.label = "Vertex buffer colors data" + #endif + } + + guard data.pointee.num_tex > 0 else { + return + } + + let textureVertices = UnsafeMutableBufferPointer<gs_tvertarray>( + start: data.pointee.tvarray, count: data.pointee.num_tex) + + for (textureSlot, textureVertex) in textureVertices.enumerated() { + textureVertex.array.withMemoryRebound(to: Float32.self, capacity: textureVertex.width * numVertices) { + createOrUpdateBuffer( + buffer: &uvCoordinatestextureSlot, data: $0, count: textureVertex.width * numVertices, + dynamic: isDynamic)
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/MetalDevice.swift
Added
@@ -0,0 +1,786 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import AppKit +import Foundation +import Metal +import simd + +/// Describes which clear actions to take when an explicit clear is requested +struct ClearState { + var colorAction: MTLLoadAction = .dontCare + var depthAction: MTLLoadAction = .dontCare + var stencilAction: MTLLoadAction = .dontCare + var clearColor: MTLClearColor = MTLClearColor() + var clearDepth: Double = 0.0 + var clearStencil: UInt32 = 0 + var clearTarget: MetalTexture? = nil +} + +/// Object wrapping an `MTLDevice` object and providing convenience functions for interaction with `libobs` +class MetalDevice { + private let identityMatrix = matrix_float4x4.init(diagonal: SIMD4(1.0, 1.0, 1.0, 1.0)) + private let fallbackVertexBuffer: MTLBuffer + private var nopVertexFunction: MTLFunction + private var pipelines = Int: MTLRenderPipelineState() + private var depthStencilStates = Int: MTLDepthStencilState() + private var obsSignalCallbacks = MetalSignalType: () -> Void() + private var displayLink: CVDisplayLink? + + let device: MTLDevice + let commandQueue: MTLCommandQueue + var renderState: MetalRenderState + var swapChains = OBSSwapChain() + let swapChainQueue = DispatchQueue(label: "swapchainUpdateQueue", qos: .userInteractive) + + init(device: MTLDevice) throws { + self.device = device + + guard let commandQueue = device.makeCommandQueue() else { + throw MetalError.MTLDeviceError.commandQueueCreationFailure + } + + guard let buffer = device.makeBuffer(length: 1, options: .storageModePrivate) else { + throw MetalError.MTLDeviceError.bufferCreationFailure("Fallback vertex buffer") + } + + let nopVertexSource = "vertex float4 vsNop() { return (float4)0; }" + + let compileOptions = MTLCompileOptions() + if #available(macOS 15, *) { + compileOptions.mathMode = .fast + } else { + compileOptions.fastMathEnabled = true + } + + guard let library = try? device.makeLibrary(source: nopVertexSource, options: compileOptions), + let function = library.makeFunction(name: "vsNop") + else { + throw MetalError.MTLDeviceError.shaderCompilationFailure("Vertex NOP shader") + } + + CVDisplayLinkCreateWithActiveCGDisplays(&displayLink) + if displayLink == nil { + throw MetalError.MTLDeviceError.displayLinkCreationFailure + } + + self.commandQueue = commandQueue + self.nopVertexFunction = function + self.fallbackVertexBuffer = buffer + + self.renderState = MetalRenderState( + viewMatrix: identityMatrix, + projectionMatrix: identityMatrix, + viewProjectionMatrix: identityMatrix, + scissorRectEnabled: false, + gsColorSpace: GS_CS_SRGB + ) + + let clearPipelineDescriptor = renderState.clearPipelineDescriptor + clearPipelineDescriptor.colorAttachments0.isBlendingEnabled = false + clearPipelineDescriptor.vertexFunction = nopVertexFunction + clearPipelineDescriptor.fragmentFunction = nil + clearPipelineDescriptor.inputPrimitiveTopology = .point + + setupSignalHandlers() + setupDisplayLink() + } + + func dispatchSignal(type: MetalSignalType) { + if let callback = obsSignalCallbackstype { + callback() + } + } + + /// Creates signal handlers for specific OBS signals and adds them to a collection of signal handlers using the signal name as their key + private func setupSignalHandlers() { + let videoResetCallback = { self in + guard let displayLink else { return } + + CVDisplayLinkStop(displayLink) + CVDisplayLinkStart(displayLink) + } + + obsSignalCallbacks.updateValue(videoResetCallback, forKey: MetalSignalType.videoReset) + } + + /// Sets up the `CVDisplayLink` used by the ``MetalDevice`` to synchronize projector output with the operating + /// system's screen refresh rate. + private func setupDisplayLink() { + func displayLinkCallback( + displayLink: CVDisplayLink, + _ now: UnsafePointer<CVTimeStamp>, + _ outputTime: UnsafePointer<CVTimeStamp>, + _ flagsIn: CVOptionFlags, + _ flagsOut: UnsafeMutablePointer<CVOptionFlags>, + _ displayLinkContext: UnsafeMutableRawPointer? + ) -> CVReturn { + guard let displayLinkContext else { return kCVReturnSuccess } + + let metalDevice = unsafeBitCast(displayLinkContext, to: MetalDevice.self) + + metalDevice.blitSwapChains() + + return kCVReturnSuccess + } + + let opaqueSelf = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()) + + CVDisplayLinkSetOutputCallback(displayLink!, displayLinkCallback, opaqueSelf) + } + + /// Iterates over all ``OBSSwapChain`` instances present on the ``MetalDevice`` instance and encodes a block + /// transfer command on the GPU to copy the contents of the projector rendered by `libobs`'s render loop into the + /// drawable provided by a `CAMetalLayer`. + func blitSwapChains() { + guard swapChains.count > 0 else { return } + + guard let commandBuffer = commandQueue.makeCommandBuffer(), + let encoder = commandBuffer.makeBlitCommandEncoder() + else { + return + } + + self.swapChainQueue.sync { + swapChains = swapChains.filter { $0.discard == false } + } + + for swapChain in swapChains { + guard let renderTarget = swapChain.renderTarget, let drawable = swapChain.layer.nextDrawable() else { + continue + } + + guard renderTarget.texture.width == drawable.texture.width, + renderTarget.texture.height == drawable.texture.height, + renderTarget.texture.pixelFormat == drawable.texture.pixelFormat + else { + continue + } + + autoreleasepool { + encoder.waitForFence(swapChain.fence) + encoder.copy(from: renderTarget.texture, to: drawable.texture) + + commandBuffer.addScheduledHandler { _ in + drawable.present() + } + } + } + + encoder.endEncoding() + commandBuffer.commit() + } + + /// Simulates an explicit "clear" command commonly used in OpenGL or Direct3D11 implementations. + /// - Parameter state: A ``ClearState`` object holding the requested clear actions + /// + /// Metal (like Direct3D12 and Vulkan) does not have an explicit clear command anymore. Devices with M- and + /// A-series SOCs have deferred tile-based GPUs which do not load render targets as single large textures, but + /// instead interact with textures via tiles. A load and store command is executed every time this occurs and a + /// clear is achieved via a load command. + /// + /// If no actual rendering occurs however, no load or store commands are executed, and a render target will be + /// "untouched". This would lead to issues in situations like switching to an empty scene, as the lack of any + /// sources would trigger no draw calls. + ///
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/MetalError.swift
Added
@@ -0,0 +1,126 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +enum MetalError { + enum MTLCommandQueueError: Error, CustomStringConvertible { + case commandBufferCreationFailure + + var description: String { + switch self { + case .commandBufferCreationFailure: + "MTLCommandQueue failed to create command buffer" + } + } + } + + enum MTLDeviceError: Error, CustomStringConvertible { + case commandQueueCreationFailure + case displayLinkCreationFailure + case bufferCreationFailure(String) + case shaderCompilationFailure(String) + case pipelineStateCreationFailure + case depthStencilStateCreationFailure + case samplerStateCreationFailure + + var description: String { + switch self { + case .commandQueueCreationFailure: + "MTLDevice failed to create command queue" + case .displayLinkCreationFailure: + "MTLDevice failed to create CVDisplayLink for projector output" + case .bufferCreationFailure(_): + "MTLDevice failed to create buffer" + case .shaderCompilationFailure(_): + "MTLDevice failed to create shader library and function" + case .pipelineStateCreationFailure: + "MTLDevice failed to create render pipeline state" + case .depthStencilStateCreationFailure: + "MTLDevice failed to create depth stencil state" + case .samplerStateCreationFailure: + "MTLDevice failed to create sampler state with provided descriptor" + } + } + } + + enum MTLCommandBufferError: Error, CustomStringConvertible { + case encoderCreationFailure + + var description: String { + switch self { + case .encoderCreationFailure: + "MTLCommandBuffer failed to create command encoder" + } + } + } + + enum MetalShaderError: Error, CustomStringConvertible { + case missingVertexDescriptor + case missingSamplerDescriptors + + var description: String { + switch self { + case .missingVertexDescriptor: + "MetalShader of type vertex requires a vertex descriptor" + case .missingSamplerDescriptors: + "MetalShader of type fragment requires at least a single sampler descriptor" + } + } + } + + enum OBSShaderParserError: Error, CustomStringConvertible { + case parseFail(String) + case unsupportedType + case missingNextToken + case unexpectedToken + case missingMainFunction + + var description: String { + switch self { + case .parseFail: + "Failed to parse provided shader string" + case .unsupportedType: + "Provided GS type is not convertible to a Metal type" + case .missingNextToken: + "Required next token not found in parser token collection" + case .unexpectedToken: + "Required next token had unexpected type in parser token collection" + case .missingMainFunction: + "Shader has no main function" + } + } + } + + enum OBSShaderError: Error, CustomStringConvertible { + case unsupportedType + case parseFail(String) + case parseError(String) + case transpileError(String) + + var description: String { + switch self { + case .unsupportedType: + "Unsupported Metal shader type" + case .parseFail(_): + "OBS shader parser failed to parse effect" + case .parseError(_): + "OBS shader parser encountered warnings and/or errors while parsing effect" + case .transpileError(_): + "Transpiling OBS effects file into MSL shader failed" + } + } + } +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/MetalRenderState.swift
Added
@@ -0,0 +1,79 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal +import simd + +/// The MetalRenderState struct emulates a state object like Direct3D's `ID3D11DeviceContext`, holding references to +/// elements of a render pipeline that would be considered the "current" variant of each. +/// +/// Typical "current" state elements include (but are not limited to): +/// +/// * Variant of the render target for linear color writes +/// * Variant of the render target for color writes with automatic sRGB gamma encoding +/// * View matrix and view projection matrix +/// * Vertex buffer and optional index buffer +/// * Depth stencil attachment +/// * Vertex shader +/// * Fragment shader +/// * View port size +/// * Cull mode +/// +/// These references are swapped out by OBS for each "scene" and "scene items" within it before issuing draw calls, +/// thus actual pipelines need to be created "on demand" based on the pipeline descriptor and stored in a cache to +/// avoid the cost of pipeline validation on consecutive render passes. +struct MetalRenderState { + var viewMatrix: matrix_float4x4 + var projectionMatrix: matrix_float4x4 + var viewProjectionMatrix: matrix_float4x4 + + var renderTarget: MetalTexture? + var sRGBrenderTarget: MetalTexture? + var depthStencilAttachment: MetalTexture? + var isRendertargetChanged = false + + var vertexBuffer: MetalVertexBuffer? + var indexBuffer: MetalIndexBuffer? + + var vertexShader: MetalShader? + var fragmentShader: MetalShader? + + var viewPort = MTLViewport() + var cullMode = MTLCullMode.none + + var scissorRectEnabled: Bool + var scissorRect: MTLScissorRect? + + var gsColorSpace: gs_color_space + var useSRGBGamma = false + + var swapChain: OBSSwapChain? + var isInDisplaysRenderStage = false + + var pipelineDescriptor = MTLRenderPipelineDescriptor() + var clearPipelineDescriptor = MTLRenderPipelineDescriptor() + var renderPassDescriptor = MTLRenderPassDescriptor() + var depthStencilDescriptor = MTLDepthStencilDescriptor() + var commandBuffer: MTLCommandBuffer? + + var textures = MTLTexture?(repeating: nil, count: Int(GS_MAX_TEXTURES)) + var samplers = MTLSamplerState?(repeating: nil, count: Int(GS_MAX_TEXTURES)) + + var projections = matrix_float4x4() + var inFlightRenderTargets = Set<MetalTexture>() +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/MetalShader+Extensions.swift
Added
@@ -0,0 +1,27 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal + +/// Adds the comparison operator to make ``MetalShader`` instances comparable. Comparison is based on the source string +/// and function type. +extension MetalShader: Equatable { + static func == (lhs: MetalShader, rhs: MetalShader) -> Bool { + return lhs.source == rhs.source && lhs.function.functionType == rhs.function.functionType + } +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/MetalShader.swift
Added
@@ -0,0 +1,287 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal + +class MetalShader { + /// This class wraps a single uniform shader variable, which will hold the data associated with the uniform updated + /// by `libobs` at each render loop, which is then converted and set as vertex or fragment bytes for a render pass + /// by the ``MetalDevice/draw`` function. + class ShaderUniform { + let name: String + let gsType: gs_shader_param_type + fileprivate let textureSlot: Int + var samplerState: MTLSamplerState? + fileprivate let byteOffset: Int + + var currentValues: UInt8? + var defaultValues: UInt8? + fileprivate var hasUpdates: Bool + + init( + name: String, gsType: gs_shader_param_type, textureSlot: Int, samplerState: MTLSamplerState?, + byteOffset: Int + ) { + self.name = name + self.gsType = gsType + + self.textureSlot = textureSlot + self.samplerState = samplerState + self.byteOffset = byteOffset + self.currentValues = nil + self.defaultValues = nil + self.hasUpdates = false + } + + /// Sets the data for the shader uniform + /// - Parameters: + /// - data: Pointer to data of type `T` + /// - size: Size of data available at the pointer provided by `data` + /// + /// This function will reinterpet the data provided by the pointer as raw bytes and store it as raw bytes on + /// the Uniform. + public func setParameter<T>(data: UnsafePointer<T>?, size: Int) { + guard let data else { + assertionFailure( + "MetalShader.ShaderUniform: Attempted to set a shader parameter with an empty data pointer") + return + } + + data.withMemoryRebound(to: UInt8.self, capacity: size) { + self.currentValues = Array(UnsafeBufferPointer<UInt8>(start: $0, count: size)) + } + + hasUpdates = true + } + } + + /// This struct serves as a data container to communicate shader meta data between the ``OBSShader`` shader + /// transpiler and the actual ``MetalShader`` instances created with them. + struct ShaderData { + let uniforms: ShaderUniform + let bufferOrder: MetalBuffer.BufferDataType + + let vertexDescriptor: MTLVertexDescriptor? + let samplerDescriptors: MTLSamplerDescriptor? + + let bufferSize: Int + let textureCount: Int + } + + private weak var device: MetalDevice? + let source: String + private var uniformData: UInt8 + private var uniformSize: Int + private var uniformBuffer: MTLBuffer? + + private let library: MTLLibrary + let function: MTLFunction + var uniforms: ShaderUniform + var vertexDescriptor: MTLVertexDescriptor? + var textureCount = 0 + var samplers: MTLSamplerState? + + let type: MTLFunctionType + let bufferOrder: MetalBuffer.BufferDataType + + var viewProjection: ShaderUniform? + + init(device: MetalDevice, source: String, type: MTLFunctionType, data: ShaderData) throws { + self.device = device + self.source = source + self.type = type + self.uniforms = data.uniforms + self.bufferOrder = data.bufferOrder + self.uniformSize = (data.bufferSize + 0x0F) & ~0x0F + self.uniformData = UInt8(repeating: 0, count: self.uniformSize) + self.textureCount = data.textureCount + + switch type { + case .vertex: + guard let descriptor = data.vertexDescriptor else { + throw MetalError.MetalShaderError.missingVertexDescriptor + } + + self.vertexDescriptor = descriptor + + self.viewProjection = self.uniforms.first(where: { $0.name == "ViewProj" }) + case .fragment: + guard let samplerDescriptors = data.samplerDescriptors else { + throw MetalError.MetalShaderError.missingSamplerDescriptors + } + + var samplers = MTLSamplerState() + samplers.reserveCapacity(samplerDescriptors.count) + + for descriptor in samplerDescriptors { + guard let samplerState = device.device.makeSamplerState(descriptor: descriptor) else { + throw MetalError.MTLDeviceError.samplerStateCreationFailure + } + + samplers.append(samplerState) + } + + self.samplers = samplers + default: + fatalError("MetalShader: Unsupported shader type \(type)") + } + + do { + library = try device.device.makeLibrary(source: source, options: nil) + } catch { + throw MetalError.MTLDeviceError.shaderCompilationFailure("Failed to create shader library") + } + + guard let function = library.makeFunction(name: "_main") else { + throw MetalError.MTLDeviceError.shaderCompilationFailure("Failed to create '_main' function") + } + + self.function = function + } + + /// Updates the Metal-specific data associated with a ``ShaderUniform`` with the raw bytes provided by `libobs` + /// - Parameter uniform: Inout reference to the ``ShaderUniform`` instance + /// + /// Uniform data is provided by `libobs` precisely in the format required by the shader (and interpreted by + /// `libobs`), which means that the raw bytes stored on the ``ShaderUniform`` are usually already in the correct + /// order and can be used without reinterpretation. + /// + /// The exception to this rule is data for textures, which represents a copy of a `gs_shader_texture` struct that + /// itself contains the pointer address of an `OpaquePointer` for a ``MetalTexture`` instance. + private func updateUniform(uniform: inout ShaderUniform) { + guard let device = self.device else { return } + guard let currentValues = uniform.currentValues else { return } + + if uniform.gsType == GS_SHADER_PARAM_TEXTURE { + var textureObject: OpaquePointer? + var isSrgb = false + + currentValues.withUnsafeBufferPointer { + $0.baseAddress?.withMemoryRebound(to: gs_shader_texture.self, capacity: 1) { + textureObject = $0.pointee.tex + isSrgb = $0.pointee.srgb + } + } + + if let textureObject { + let texture: MetalTexture = unretained(UnsafeRawPointer(textureObject)) + + if texture.sRGBtexture != nil, isSrgb { + device.renderState.texturesuniform.textureSlot = texture.sRGBtexture! + } else { + device.renderState.texturesuniform.textureSlot = texture.texture + } + } + + if let samplerState = uniform.samplerState { + device.renderState.samplersuniform.textureSlot = samplerState + uniform.samplerState = nil + } + } else { + if uniform.hasUpdates { + let startIndex = uniform.byteOffset + let endIndex = uniform.byteOffset + currentValues.count +
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/MetalStageBuffer.swift
Added
@@ -0,0 +1,65 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal + +class MetalStageBuffer { + let device: MetalDevice + let buffer: MTLBuffer + let format: MTLPixelFormat + let width: Int + let height: Int + + init?(device: MetalDevice, width: Int, height: Int, format: MTLPixelFormat) { + self.device = device + self.width = width + self.height = height + self.format = format + + guard let bytesPerPixel = format.bytesPerPixel, + let buffer = device.device.makeBuffer( + length: width * height * bytesPerPixel, + options: .storageModeShared + ) + else { + return nil + } + + self.buffer = buffer + } + + /// Gets an opaque pointer for the ``MetalStageBuffer`` instance and increases its reference count by one + /// - Returns: `OpaquePointer` to class instance + /// + /// > Note: Use this method when the instance is to be shared via an `OpaquePointer` and needs to be retained. Any + /// opaque pointer shared this way needs to be converted into a retained reference again to ensure automatic + /// deinitialization by the Swift runtime. + func getRetained() -> OpaquePointer { + let retained = Unmanaged.passRetained(self).toOpaque() + + return OpaquePointer(retained) + } + + /// Gets an opaque pointer for the ``MetalStageBuffer`` instance without increasing its reference count + /// - Returns: `OpaquePointer` to class instance + func getUnretained() -> OpaquePointer { + let unretained = Unmanaged.passUnretained(self).toOpaque() + + return OpaquePointer(unretained) + } +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/MetalTexture.swift
Added
@@ -0,0 +1,433 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import CoreVideo +import Foundation +import Metal + +private let bgraSurfaceFormat = kCVPixelFormatType_32BGRA // 0x42_47_52_41 +private let l10rSurfaceFormat = kCVPixelFormatType_ARGB2101010LEPacked // 0x6C_31_30_72 + +enum MetalTextureMapMode { + case unmapped + case read + case write +} + +/// Struct used for data exchange between ``MetalTexture`` and `libobs` API functions during mapping and unmapping of +/// textures. +struct MetalTextureMapping { + let mode: MetalTextureMapMode + let rowSize: Int + let data: UnsafeMutableRawPointer +} + +/// Convenience class for managing ``MTLTexture`` objects +class MetalTexture { + private let descriptor: MTLTextureDescriptor + private var mappingMode: MetalTextureMapMode + private let resourceID: UUID + + weak var device: MetalDevice? + var data: UnsafeMutableRawPointer? + var hasPendingWrites: Bool = false + var sRGBtexture: MTLTexture? + var texture: MTLTexture + var stageBuffer: MetalStageBuffer? + + /// Binds the provided `IOSurfaceRef` to a new `MTLTexture` instance + /// - Parameters: + /// - device: `MTLDevice` instance to use for texture object creation + /// - surface: `IOSurfaceRef` reference to an existing `IOSurface` + /// - Returns: `MTLTexture` instance if texture was created successfully, `nil` otherwise + private static func bindSurface(device: MetalDevice, surface: IOSurfaceRef) -> MTLTexture? { + guard let pixelFormat = MTLPixelFormat.init(osType: IOSurfaceGetPixelFormat(surface)) else { + assertionFailure("MetalDevice: IOSurface pixel format is not supported") + return nil + } + + let descriptor = MTLTextureDescriptor.texture2DDescriptor( + pixelFormat: pixelFormat, + width: IOSurfaceGetWidth(surface), + height: IOSurfaceGetHeight(surface), + mipmapped: false + ) + + descriptor.usage = .shaderRead + + let texture = device.device.makeTexture(descriptor: descriptor, iosurface: surface, plane: 0) + return texture + } + + /// Creates a new ``MetalDevice`` instance with the provided `MTLTextureDescriptor` + /// - Parameters: + /// - device: `MTLDevice` instance to use for texture object creation + /// - descriptor: `MTLTextureDescriptor` to use for texture object creation + init?(device: MetalDevice, descriptor: MTLTextureDescriptor) { + self.device = device + + let texture = device.device.makeTexture(descriptor: descriptor) + + guard let texture else { + assertionFailure( + "MetalTexture: Failed to create texture with size \(descriptor.width)x\(descriptor.height)") + return nil + } + + self.texture = texture + + self.resourceID = UUID() + self.mappingMode = .unmapped + self.descriptor = texture.descriptor + + updateSRGBView() + } + + /// Creates a new ``MetalDevice`` instance with the provided `IOSurfaceRef` + /// - Parameters: + /// - device: `MTLDevice` instance to use for texture object creation + /// - surface: `IOSurfaceRef` to use for texture object creation + init?(device: MetalDevice, surface: IOSurfaceRef) { + self.device = device + + let texture = MetalTexture.bindSurface(device: device, surface: surface) + + guard let texture else { + assertionFailure("MetalTexture: Failed to create texture with IOSurface") + return nil + } + + self.texture = texture + + self.resourceID = UUID() + self.mappingMode = .unmapped + self.descriptor = texture.descriptor + + updateSRGBView() + } + + /// Creates a new ``MetalDevice`` instance with the provided `MTLTexture` + /// - Parameters: + /// - device: `MTLDevice` instance to use for future texture operations + /// - surface: `MTLTexture` to wrap in the ``MetalDevice`` instance + init?(device: MetalDevice, texture: MTLTexture) { + self.device = device + self.texture = texture + + self.resourceID = UUID() + self.mappingMode = .unmapped + self.descriptor = texture.descriptor + + updateSRGBView() + } + + /// Creates a new ``MetalDevice`` instance with a placeholder texture + /// - Parameters: + /// - device: `MTLDevice` instance to use for future texture operations + /// + /// This constructor creates a "placeholder" object that can be shared with `libobs` or updated with an actual + /// `MTLTexture` later. + init?(device: MetalDevice) { + self.device = device + + let descriptor = MTLTextureDescriptor.texture2DDescriptor( + pixelFormat: .bgra8Unorm, width: 2, height: 2, mipmapped: false) + + guard let texture = device.device.makeTexture(descriptor: descriptor) else { + assertionFailure("MetalTexture: Failed to create placeholder texture object") + return nil + } + + self.texture = texture + self.sRGBtexture = nil + self.resourceID = UUID() + self.mappingMode = .unmapped + self.descriptor = texture.descriptor + } + + /// Updates the ``MetalTexture`` with a new `IOSurfaceRef` + /// - Parameter surface: Updated `IOSurfaceRef` to a new `IOSurface` + /// - Returns: `true` if update was successful, `false` otherwise + /// + /// "Rebinding" was used with the OpenGL backend, but is not available in Metal. Instead a new `MTLTexture` is + /// created with the provided `IOSurfaceRef` and the ``MetalTexture`` is updated accordingly. + /// + func rebind(surface: IOSurfaceRef) -> Bool { + guard let device = self.device, let texture = MetalTexture.bindSurface(device: device, surface: surface) else { + assertionFailure("MetalTexture: Failed to rebind IOSurface to texture") + return false + } + + self.texture = texture + + updateSRGBView() + + return true + } + + /// Creates a `MTLTextureView` for the texture wrapped by the ``MetalTexture`` instance with a corresponding sRGB + /// pixel format, if the texture's pixel format has an appropriate sRGB variant. + func updateSRGBView() { + guard !texture.isFramebufferOnly else { + self.sRGBtexture = nil + return + } + + let sRGBFormat: MTLPixelFormat? = + switch texture.pixelFormat { + case .bgra8Unorm: .bgra8Unorm_srgb + case .rgba8Unorm: .rgba8Unorm_srgb + case .r8Unorm: .r8Unorm_srgb + case .rg8Unorm: .rg8Unorm_srgb + case .bgra10_xr: .bgra10_xr_srgb + default: nil + } +
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/OBSShader.swift
Added
@@ -0,0 +1,1603 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal + +private enum SampleVariant { + case load + case sample + case sampleBias + case sampleGrad + case sampleLevel +} + +private struct VariableType: OptionSet { + var rawValue: UInt + + static let typeUniform = VariableType(rawValue: 1 << 0) + static let typeStruct = VariableType(rawValue: 1 << 1) + static let typeStructMember = VariableType(rawValue: 1 << 2) + static let typeInput = VariableType(rawValue: 1 << 3) + static let typeOutput = VariableType(rawValue: 1 << 4) + static let typeTexture = VariableType(rawValue: 1 << 5) + static let typeConstant = VariableType(rawValue: 1 << 6) + +} + +private struct OBSShaderFunction { + let name: String + + var returnType: String + var typeMap: String: String + + var requiresUniformBuffers: Bool + var textures: String + var samplers: String + + var arguments: OBSShaderVariable + + let gsFunction: UnsafeMutablePointer<shader_func> +} + +private struct OBSShaderVariable { + let name: String + + var type: String + var mapping: String? + var storageType: VariableType + + var requiredBy: Set<String> + var returnedBy: Set<String> + + var isStage: Bool + var attributeId: Int? + var isConstant: Bool + var isReference: Bool + + let gsVariable: UnsafeMutablePointer<shader_var> +} + +private struct OBSShaderStruct { + let name: String + + var storageType: VariableType + var members: OBSShaderVariable + + let gsVariable: UnsafeMutablePointer<shader_struct> +} + +private struct MSLTemplates { + static let header = """ + #include <metal_stdlib> + + using namespace metal; + """ + + static let variable = "qualifier type name mapping" + + static let shaderStruct = """ + typedef struct { + variable + } typename; + """ + + static let function = "decorator type name(parameters) {content}" +} + +private typealias ParserError = MetalError.OBSShaderParserError +private typealias ShaderError = MetalError.OBSShaderError + +class OBSShader { + private let type: MTLFunctionType + private let content: String + private let fileLocation: String + + private var parser: shader_parser + private var parsed: Bool + + private var uniformsOrder = String() + private var uniforms = String: OBSShaderVariable() + private var structs = String: OBSShaderStruct() + private var functionsOrder = String() + private var functions = String: OBSShaderFunction() + private var referenceVariables = String() + + var metaData: MetalShader.ShaderData? + + init(type: MTLFunctionType, content: String, fileLocation: String) throws { + guard type == .vertex || type == .fragment else { + throw ShaderError.unsupportedType + } + + self.type = type + self.content = content + self.fileLocation = fileLocation + + self.parsed = false + + self.parser = shader_parser() + + try withUnsafeMutablePointer(to: &parser) { + shader_parser_init($0) + + let result = shader_parse($0, content.cString(using: .utf8), content.cString(using: .utf8)) + let warnings = shader_parser_geterrors($0) + + if let warnings { + throw ShaderError.parseError(String(cString: warnings)) + } + + if !result { + throw ShaderError.parseFail("Shader failed to parse: \(fileLocation)") + } else { + self.parsed = true + } + } + } + + /// Transpiles a `libobs` effect string into a Metal Shader Language (MSL) string + /// - Returns: MSL string representing the transpiled shader + func transpiled() throws -> String { + try analyzeUniforms() + try analyzeParameters() + try analyzeFunctions() + + let uniforms = try transpileUniforms() + let structs = try transpileStructs() + let functions = try transpileFunctions() + + self.metaData = try buildMetadata() + + return MSLTemplates.header, uniforms, structs, functions.joined(separator: "\n\n") + } + + /// Builds a metadata object for the current shader + /// - Returns: ``ShaderData`` object with the shader metadata + /// + /// The effects used by `libobs` are written in HLSL with some customizations to allow multiple shaders within the + /// same effects file (which is supported natively by MSL). As MSL does not support "global" variables, uniforms + /// have to be provided explicitly via buffers and the data inside those buffers needs to be laid out in the correct + /// way. + /// + /// Uniforms are converted into `struct` objects in the shader files and as MSL is based on C++14, these structs + /// will have a size, stride, and alignment, set by the compiler. Thus the uniform data used by the shader needs to + /// be laid out in the buffer according to this alignment. + /// + /// The layout of vertex buffer data also needs to be communicated using `MTLVertexDescriptor` instances for vertex + /// shaders and `MTLSamplerState` instances for fragment shaders. Both will be created and set up in a + /// ``ShaderData`` which is used to create the actual ``MetalShader`` object. + private func buildMetadata() throws -> MetalShader.ShaderData { + var uniformInfo = MetalShader.ShaderUniform() + + var textureSlot = 0 + var uniformBufferSize = 0 + + /// The order of buffers and uniforms is "load-bearing" as the order (and thus alignment and offsets) of + /// uniforms in the corresponding uniforms struct are + /// influenced by it. + for uniformName in uniformsOrder { + guard let uniform = uniformsuniformName else { + throw ParserError.parseFail("No uniform data found for '\(uniformName)'") + } + + let gsType = get_shader_param_type(uniform.gsVariable.pointee.type) + let isTexture = uniform.storageType.contains(.typeTexture)
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/OBSSwapChain.swift
Added
@@ -0,0 +1,125 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import AppKit +import CoreVideo +import Foundation +import Metal + +class OBSSwapChain { + enum ColorRange { + case sdr + case hdrPQ + case hdrHLG + } + + private weak var device: MetalDevice? + private var view: NSView? + + var colorRange: ColorRange + var edrHeadroom: CGFloat = 0.0 + let layer: CAMetalLayer + var renderTarget: MetalTexture? + var viewSize: MTLSize + var fence: MTLFence + var discard: Bool = false + + init?(device: MetalDevice, size: MTLSize, colorSpace: gs_color_format) { + self.device = device + self.viewSize = size + self.layer = CAMetalLayer() + self.layer.framebufferOnly = false + self.layer.device = device.device + self.layer.drawableSize = CGSize(width: viewSize.width, height: viewSize.height) + self.layer.pixelFormat = .bgra8Unorm_srgb + self.layer.colorspace = CGColorSpace(name: CGColorSpace.sRGB) + self.layer.wantsExtendedDynamicRangeContent = false + self.layer.edrMetadata = nil + self.layer.displaySyncEnabled = false + self.colorRange = .sdr + + guard let fence = device.device.makeFence() else { return nil } + + self.fence = fence + } + + /// Updates the provided view to use the `CAMetalLayer` managed by the ``OBSSwapChain`` + /// - Parameter view: `NSView` instance to update + /// + /// > Important: This function has to be called from the main thread + @MainActor + func updateView(_ view: NSView) { + self.view = view + view.layer = self.layer + view.wantsLayer = true + + updateEdrHeadroom() + } + + /// Updates the EDR headroom value on the ``OBSSwapChain`` with the value from the screen the managed `NSView` is + /// associated with. + /// + /// This is necessary to ensure that the projector uses the appropriate SDR or EDR output depending on the screen + /// the view is on. + @MainActor + func updateEdrHeadroom() { + guard let view = self.view else { + return + } + + if let screen = view.window?.screen { + self.edrHeadroom = screen.maximumPotentialExtendedDynamicRangeColorComponentValue + } else { + self.edrHeadroom = CGFloat(1.0) + } + } + + /// Resizes the drawable of the managed `CAMetalLayer` to the provided size + /// - Parameter size: Desired new size of the drawable + /// + /// This is usually achieved via a delegate method directly on the associated `NSView` instance, but because the + /// view is managed by Qt, the resize event is routed manually into the ``OBSSwapChain`` instance by `libobs`. + func resize(_ size: MTLSize) { + guard viewSize.width != size.width || viewSize.height != size.height else { return } + + viewSize = size + layer.drawableSize = CGSize( + width: viewSize.width, + height: viewSize.height) + renderTarget = nil + } + + /// Gets an opaque pointer for the ``OBSSwapChain`` instance and increases its reference count by one + /// - Returns: `OpaquePointer` to class instance + /// + /// > Note: Use this method when the instance is to be shared via an `OpaquePointer` and needs to be retained. Any + /// opaque pointer shared this way needs to be converted into a retained reference again to ensure automatic + /// deinitialization by the Swift runtime. + func getRetained() -> OpaquePointer { + let retained = Unmanaged.passRetained(self).toOpaque() + + return OpaquePointer(retained) + } + + /// Gets an opaque pointer for the ``OBSSwapChain`` instance without increasing its reference count + /// - Returns: `OpaquePointer` to class instance + func getUnretained() -> OpaquePointer { + let unretained = Unmanaged.passUnretained(self).toOpaque() + + return OpaquePointer(unretained) + } +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/README.md
Added
@@ -0,0 +1,83 @@ +libobs-metal +============ + +This is an alpha quality implementation of a Metal renderer backend for OBS exclusive to Apple Silicon Macs. It supports all default source types, filters, and transitions provided by OBS Studio + +## Overview + +* The renderer backend is implemented entirely in Swift +* A C interface header is generated automatically via the `-emit-objc-header` compile flag and `@cdecl("<FUNCTION NAME>")` decorators are used to expose desired functions to `libobs` +* Only Metal Version 3 is supported (this is by design) +* Only Apple Silicon Macs are supported (this is by design) + +## Implemented functionality + +* Default source types are supported: + * Color Source + * Image Source + * Media Source + * SCK Capture Source + * Browser Source + * Capture Card and Video Capture Device Source + * Text (Freetype 2) +* Default transitions are supported: + * Cut + * Fade + * Stinger + * Fade To Color + * Luma Wipe +* Default filters are supported: + * Apply LUT + * Chroma Key + * Color Correction + * Crop/Pad + * Image Mask/Blend + * Luma Key + * Scaling/Aspect Ratio + * Scroll + * Sharpen +* sRGB-aware rendering is enabled by default +* HDR output in previews and projectors is supported on screens which have EDR support +* HDR output is not tonemapped by OBS - if the screen has EDR support, the previews will always output content in their actual format +* Recording and streaming with VideoToolbox encoders works +* Preview, separate projectors, and multi-view all work (with caveats, see below) + +## Known Issues + +* Previews can stutter or be stuck with low FPS - will not be fully fixed before alpha release (see below) +* Not all possible encoder configurations have been tested +* Performance is not optimized (see below) + +## The State Of Previews + +To manually render contents into a window using Metal one has to use a `CAMetalLayer` that is set to be a `NSView`'s backing layer. This layer can provide a `CAMetalDrawable` object which the compositor will use when it renders a new frame of the desktop. This drawable can provide a texture that OBS Studio can render into to generate output like the main preview. + +Because Metal is much more integrated with macOS than OpenGL and designed with energy efficiency in mind, a `CAMetalLayer` will never provide more drawables than necessary, which means that there can be at most 3 drawables "in flight". If all available drawables are in use (either by OBS Studio to render into or by the compositor to render the desktop output) a request for a new drawable will block until an old drawable expires and a new one has been generated. + +This means that if OBS renders at a higher framerate than the operating system's compositor, it will exhaust this budget and OBS Studio's renderer will be stalled and will have to wait until a new drawable is available. This effectively means that OBS Studio's maximum frame rate is limited to the operating system's screen refresh interval. + +The current implementation avoids the issue of stalling OBS Studio's video render framerate, at the cost of possible framerate issues with the preview itself. OBS will always render a preview at its own framerate (which can be higher but also lower than the operating system's refresh interval) and callback provided to macOS will be used instead to copy (or "blit") this preview texture into a drawable that is only kept around as short as necessary to finish this copy operation. + +This decouples the update of previews from the rendering of their contents, but obviously makes this blit operation dependent on a projector having finished rendering, as otherwise the callback might blit an incomplete preview or multi-view. It is this synchronization that can lead to slow and "choppy" frame rates if the refresh interval of the operating system and the interval at which OBS can finish rendering a preview are too misaligned. + +**Note:** This is a known issue and work on a fix or better implementation of preview rendering is in progress. As the way `CAMetalLayer` works is the opposite of the way `DXGISwapChain`s work, it requires a lot more resource management and housekeeping in the Metal backend to get right. + +## On Performance + +Compiled in Release configuration the Metal renderer already has about the same CPU impact and render times as the OpenGL renderer on an M1 Mac even though neither the Swift code nor the Metal code has been optimized in any way. The late generation (and switches) of pipeline states and buffers is a costly operation and the way OBS Studio's renderer operates puts a natural ceiling on the performance improvements the Metal renderer could achieve (as it does lots of small render operations but with a lot of context switching between CPU and GPU). + +In Debug mode the performance is a bit worse, but that's in part due to Xcode using the debug variant of the Metal framework, which allows inspection and reflection on all Metal types, including live previews of textures, buffers, debugging of shaders, and more. + +Usually one would prefer to upload all data in big batches (preferably into a big `MTLHeap` object) and then pick and choose elements for each render pass to limit the switch between CPU and GPU, but this is not compatible with how OBS Studio's renderer works at this moment. + +**Note:** All these observations are based on OBS Studio's own CPU and render time statistics which are flawed as the clock speeds of either CPU and GPU are not taken into account. + +## Required Fixes and Workarounds + +* Metal Shader Language is stricter than HLSL and GLSL and does not allow type punning or implicit casting - all type conversions have to be explicit - and commonly allows only a specific set of types for vector data, colour data, or UV coordinates + * The transpiler has to force conversions to unsigned integers and unsigned integer vectors for texture `Load` calls because `libobs` shaders depend on the implicit conversion of a 32-bit float vector to integer values when passed to the texture's load command (`read` in MSL) + * Metal has no support for BGRX/RGBX formats, color always has to be specified using a vector of 4 floats, some `libobs` shaders assume BGRX and only provide a `float3` value in their pixel shaders. Transpiled Metal shaders instead return a `float4` with a `1.0` alpha value + * This might not be exhaustive, as other - so far untested - shaders might depend on other implicit conversions of HLSL/GLSL and will require additional workarounds and wrapping of existing code to return the correct types expected by MSL +* Metal does not support unpacking `UInt32` values into a `float4` in vertex data provided via the `stage_in` attribute to benefit from vertex fetch (where the pipeline itself is made aware of the buffer layout via a vertex descriptor and thus fetches the data from the buffer as needed) vs. the classic "vertex push" method + * This is commonly used in `libobs` to provide color buffer data - to fix this, the values are unpacked and converted into a `float4` when the GPU buffers are created for a vertex buffer +* There is no explicit clear command in Metal, as clears are implemented as a command that is run when a render target (or more precisely a tile of the render target) is loaded into tile memory for a render pass. If no render pass occurs, no load command is executed and the render target is not cleared. OBS Studio depends on a "clear" call actually clearing the texture, thus an explicit (but lightweight) draw call is scheduled to ensure that the render target is loaded, cleared, and stored.
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/Sequence+Hashable.swift
Added
@@ -0,0 +1,25 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +extension Sequence where Iterator.Element: Hashable { + /// Filters a `Sequence` to only contain its unique elements, retaining order for unique elements. + /// - Returns: Filtered `Sequence` with unique elements of original `Sequence` + func unique() -> Iterator.Element { + var seen: Set<Iterator.Element> = + return filter { seen.insert($0).inserted } + } +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/libobs+Extensions.swift
Added
@@ -0,0 +1,486 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal +import simd + +public enum OBSLogLevel: Int32 { + case error = 100 + case warning = 200 + case info = 300 + case debug = 400 +} + +extension strref { + mutating func getString() -> String { + let buffer = UnsafeRawBufferPointer(start: self.array, count: self.len) + + let string = String(decoding: buffer, as: UTF8.self) + + return string + } + + mutating func isEqualTo(_ comparison: String) -> Bool { + return strref_cmp(&self, comparison.cString(using: .utf8)) == 0 + } + + mutating func isEqualToCString(_ comparison: UnsafeMutablePointer<CChar>?) -> Bool { + if let comparison { + let result = withUnsafeMutablePointer(to: &self) { + strref_cmp($0, comparison) == 0 + } + + return result + } + + return false + } +} + +extension cf_parser { + mutating func advanceToken() -> Bool { + let result = withUnsafeMutablePointer(to: &self) { + cf_next_token($0) + } + + return result + } + + mutating func hasNextToken() -> Bool { + let result = withUnsafeMutablePointer(to: &self) { + var nextToken: UnsafeMutablePointer<cf_token>? + + switch $0.pointee.cur_token.pointee.type { + case CFTOKEN_SPACETAB, CFTOKEN_NEWLINE, CFTOKEN_NONE: + nextToken = $0.pointee.cur_token + default: + nextToken = $0.pointee.cur_token.advanced(by: 1) + } + + if var nextToken { + while nextToken.pointee.type == CFTOKEN_SPACETAB || nextToken.pointee.type == CFTOKEN_NEWLINE { + nextToken = nextToken.successor() + } + + return nextToken.pointee.type != CFTOKEN_NONE + } else { + return false + } + } + + return result + } + + mutating func tokenIsEqualTo(_ comparison: String) -> Bool { + let result = withUnsafeMutablePointer(to: &self) { + cf_token_is($0, comparison.cString(using: .utf8)) + } + + return result + } +} + +extension gs_shader_param_type { + var size: Int { + switch self { + case GS_SHADER_PARAM_BOOL, GS_SHADER_PARAM_INT, GS_SHADER_PARAM_FLOAT: + return MemoryLayout<Float32>.size + case GS_SHADER_PARAM_INT2, GS_SHADER_PARAM_VEC2: + return MemoryLayout<Float32>.size * 2 + case GS_SHADER_PARAM_INT3, GS_SHADER_PARAM_VEC3: + return MemoryLayout<Float32>.size * 3 + case GS_SHADER_PARAM_INT4, GS_SHADER_PARAM_VEC4: + return MemoryLayout<Float32>.size * 4 + case GS_SHADER_PARAM_MATRIX4X4: + return MemoryLayout<Float32>.size * 4 * 4 + case GS_SHADER_PARAM_TEXTURE: + return MemoryLayout<gs_shader_texture>.size + case GS_SHADER_PARAM_STRING, GS_SHADER_PARAM_UNKNOWN: + return 0 + default: + return 0 + } + } + + var mtlSize: Int { + switch self { + case GS_SHADER_PARAM_BOOL, GS_SHADER_PARAM_INT, GS_SHADER_PARAM_FLOAT: + return MemoryLayout<simd_float1>.size + case GS_SHADER_PARAM_INT2, GS_SHADER_PARAM_VEC2: + return MemoryLayout<simd_float2>.size + case GS_SHADER_PARAM_INT3, GS_SHADER_PARAM_VEC3: + return MemoryLayout<simd_float3>.size + case GS_SHADER_PARAM_INT4, GS_SHADER_PARAM_VEC4: + return MemoryLayout<simd_float4>.size + case GS_SHADER_PARAM_MATRIX4X4: + return MemoryLayout<simd_float4x4>.size + case GS_SHADER_PARAM_TEXTURE: + return MemoryLayout<gs_shader_texture>.size + case GS_SHADER_PARAM_STRING, GS_SHADER_PARAM_UNKNOWN: + return 0 + default: + return 0 + } + } + + var mtlAlignment: Int { + switch self { + case GS_SHADER_PARAM_BOOL, GS_SHADER_PARAM_INT, GS_SHADER_PARAM_FLOAT: + return MemoryLayout<simd_float1>.alignment + case GS_SHADER_PARAM_INT2, GS_SHADER_PARAM_VEC2: + return MemoryLayout<simd_float2>.alignment + case GS_SHADER_PARAM_INT3, GS_SHADER_PARAM_VEC3: + return MemoryLayout<simd_float3>.alignment + case GS_SHADER_PARAM_INT4, GS_SHADER_PARAM_VEC4: + return MemoryLayout<simd_float4>.alignment + case GS_SHADER_PARAM_MATRIX4X4: + return MemoryLayout<simd_float4x4>.alignment + case GS_SHADER_PARAM_TEXTURE: + return 0 + case GS_SHADER_PARAM_STRING, GS_SHADER_PARAM_UNKNOWN: + return 0 + default: + return 0 + + } + } +} + +extension gs_color_format { + var sRGBVariant: MTLPixelFormat? { + switch self { + case GS_RGBA: + return .rgba8Unorm_srgb + case GS_BGRX, GS_BGRA: + return .bgra8Unorm_srgb + default: + return nil + } + } + + var mtlFormat: MTLPixelFormat { + switch self { + case GS_A8: + return .a8Unorm + case GS_R8: + return .r8Unorm + case GS_R8G8: + return .rg8Unorm + case GS_R16: + return .r16Unorm + case GS_R16F: + return .r16Float + case GS_RG16: + return .rg16Unorm + case GS_RG16F: + return .rg16Float + case GS_R32F: + return .r32Float + case GS_RG32F: + return .rg32Float + case GS_RGBA: + return .rgba8Unorm + case GS_BGRX, GS_BGRA: + return .bgra8Unorm
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/libobs+SignalHandlers.swift
Added
@@ -0,0 +1,34 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation + +enum MetalSignalType: String { + case videoReset = "video_reset" +} + +/// Dispatches the video reset event to the ``MetalDevice`` instance +/// - Parameters: +/// - param: Opaque pointer to a ``MetalDevice`` instance +/// - _: Unused pointer to signal callback data +public func metal_video_reset_handler(_ param: UnsafeMutableRawPointer?, _: UnsafeMutablePointer<calldata>?) { + guard let param else { return } + + let metalDevice = unsafeBitCast(param, to: MetalDevice.self) + + metalDevice.dispatchSignal(type: .videoReset) +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/libobs-metal-Bridging-Header.h
Added
@@ -0,0 +1,32 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +#import <util/base.h> +#import <util/cf-parser.h> +#import <util/cf-lexer.h> + +#import <obs.h> + +#import <graphics/graphics.h> +#import <graphics/device-exports.h> +#import <graphics/vec2.h> +#import <graphics/matrix3.h> +#import <graphics/matrix4.h> +#import <graphics/shader-parser.h> + +static const char *const device_name = "Metal"; +static const char *const preprocessor_name = "_Metal";
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/metal-indexbuffer.swift
Added
@@ -0,0 +1,158 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal + +/// Creates a ``MetalIndexBuffer`` object to share with `libobs` and hold the provided indices +/// +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - type: Size of each index value (16 bit or 32 bit) +/// - indices: Opaque pointer to index buffer data set up by `libobs` +/// - num: Count of vertices present at the memory address provided by the `indices` argument +/// - flags: Bit field of `libobs` buffer flags +/// - Returns: Opaque pointer to a retained ``MetalIndexBuffer`` instance if valid index type was provided, `nil` +/// otherwise +/// +/// > Note: The ownership of the memory pointed to by `indices` is implicitly transferred to the ``MetalIndexBuffer`` +/// instance, but is not managed by Swift. +@_cdecl("device_indexbuffer_create") +public func device_indexbuffer_create( + device: UnsafeRawPointer, type: gs_index_type, indices: UnsafeMutableRawPointer, num: UInt32, flags: UInt32 +) -> OpaquePointer? { + let device: MetalDevice = unretained(device) + + guard let indexType = type.mtlType else { + return nil + } + + let indexBuffer = MetalIndexBuffer( + device: device, + type: indexType, + data: indices, + count: Int(num), + dynamic: (Int32(flags) & GS_DYNAMIC) != 0 + ) + + return indexBuffer.getRetained() +} + +/// Sets up a ``MetalIndexBuffer`` as the index buffer for the current pipeline +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - indexbuffer: Opaque pointer to ``MetalIndexBuffer`` instance shared with `libobs` +/// +/// > Note: The reference count of the ``MetalIndexBuffer`` instance will not be increased by this call. +/// +/// > Important: If a `nil` pointer is provided as the index buffer, the index buffer will be _unset_. +@_cdecl("device_load_indexbuffer") +public func device_load_indexbuffer(device: UnsafeRawPointer, indexbuffer: UnsafeRawPointer?) { + let device: MetalDevice = unretained(device) + + if let indexbuffer { + device.renderState.indexBuffer = unretained(indexbuffer) + } else { + device.renderState.indexBuffer = nil + } +} + +/// Requests the deinitialization of a shared ``MetalIndexBuffer`` instance +/// - Parameter indexBuffer: Opaque pointer to ``MetalIndexBuffer`` instance shared with `libobs` +/// +/// The deinitialization is handled automatically by Swift after the ownership of the instance has been transferred +/// into the function and becomes the last strong reference to it. After the function leaves its scope, the object will +/// be deinitialized and deallocated automatically. +/// +/// > Note: The index buffer data memory is implicitly owned by the ``MetalIndexBuffer`` instance and will be manually +/// cleaned up and deallocated by the instance's `deinit` method. +@_cdecl("gs_indexbuffer_destroy") +public func gs_indexbuffer_destroy(indexBuffer: UnsafeRawPointer) { + let _ = retained(indexBuffer) as MetalIndexBuffer +} + +/// Requests the index buffer's current data to be transferred into GPU memory +/// - Parameter indexBuffer: Opaque pointer to ``MetalIndexBuffer`` instance shared with `libobs` +/// +/// This function will call `gs_indexbuffer_flush_direct` with `nil` data pointer. +@_cdecl("gs_indexbuffer_flush") +public func gs_indexbuffer_flush(indexBuffer: UnsafeRawPointer) { + gs_indexbuffer_flush_direct(indexBuffer: indexBuffer, data: nil) +} + +/// Requests the index buffer to be updated with the provided data and then transferred into GPU memory +/// - Parameters: +/// - indexBuffer: Opaque pointer to ``MetalIndexBuffer`` instance shared with `libobs` +/// - data: Opaque pointer to index buffer data set up by `libobs` +/// +/// This function is called to ensure that the index buffer data that is contained in the memory pointed at by the +/// `data` argument is uploaded into GPU memory. If a `nil` pointer is provided instead, the data provided to the +/// instance during creation will be used instead. +@_cdecl("gs_indexbuffer_flush_direct") +public func gs_indexbuffer_flush_direct(indexBuffer: UnsafeRawPointer, data: UnsafeMutableRawPointer?) { + let indexBuffer: MetalIndexBuffer = unretained(indexBuffer) + + indexBuffer.setupBuffers(data) +} + +/// Returns an opaque pointer to the index buffer data associated with the ``MetalIndexBuffer`` instance +/// - Parameter indexBuffer: Opaque pointer to ``MetalIndexBuffer`` instance shared with `libobs` +/// - Returns: Opaque pointer to index buffer data in memory +/// +/// The returned opaque pointer represents the unchanged memory address that was provided for the creation of the index +/// buffer object. +/// +/// > Warning: There is only limited memory safety associated with this pointer. It is implicitly owned and its +/// lifetime is managed by the ``MetalIndexBuffer`` instance, but it was originally created by `libobs`. +@_cdecl("gs_indexbuffer_get_data") +public func gs_indexbuffer_get_data(indexBuffer: UnsafeRawPointer) -> UnsafeMutableRawPointer? { + let indexBuffer: MetalIndexBuffer = unretained(indexBuffer) + + return indexBuffer.indexData +} + +/// Returns the number of indices associated with the ``MetalIndexBuffer`` instance +/// - Parameter indexBuffer: Opaque pointer to ``MetalIndexBuffer`` instance shared with `libobs` +/// - Returns: Number of index buffers +/// +/// > Note: This returns the same number that was provided for the creation of the index buffer object. +@_cdecl("gs_indexbuffer_get_num_indices") +public func gs_indexbuffer_get_num_indices(indexBuffer: UnsafeRawPointer) -> UInt32 { + let indexBuffer: MetalIndexBuffer = unretained(indexBuffer) + + return UInt32(indexBuffer.count) +} + +/// Gets the index buffer type as a `libobs` enum value +/// - Parameter indexBuffer: Opaque pointer to ``MetalIndexBuffer`` instance shared with `libobs` +/// - Returns: Index buffer type as identified by the `gs_index_type` enum +/// +/// > Warning: As the `gs_index_type` enumeration does not provide an "invalid" value (and thus `0` becomes a valied +/// value), this function has no way to communicate an incompatible index buffer type that might be introduced at a +/// later point. +@_cdecl("gs_indexbuffer_get_type") +public func gs_indexbuffer_get_type(indexBuffer: UnsafeRawPointer) -> gs_index_type { + let indexBuffer: MetalIndexBuffer = unretained(indexBuffer) + + switch indexBuffer.type { + case .uint16: return GS_UNSIGNED_SHORT + case .uint32: return GS_UNSIGNED_LONG + @unknown default: + assertionFailure("gs_indexbuffer_get_type: Unsupported index buffer type \(indexBuffer.type)") + return GS_UNSIGNED_SHORT + } +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/metal-samplerstate.swift
Added
@@ -0,0 +1,100 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal + +/// Creates a new ``MTLSamplerDescriptor`` to share as an opaque pointer with `libobs` +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - info: Sampler information encoded as a `gs_sampler_info` struct +/// - Returns: Opaque pointer to a new ``MTLSamplerDescriptor`` instance on success, `nil` otherwise +@_cdecl("device_samplerstate_create") +public func device_samplerstate_create(device: UnsafeRawPointer, info: gs_sampler_info) -> OpaquePointer? { + let device: MetalDevice = unretained(device) + + guard let sAddressMode = info.address_u.mtlMode, + let tAddressMode = info.address_v.mtlMode, + let rAddressMode = info.address_w.mtlMode + else { + assertionFailure("device_samplerstate_create: Invalid address modes provided") + return nil + } + + guard let minFilter = info.filter.minMagFilter, let magFilter = info.filter.minMagFilter, + let mipFilter = info.filter.mipFilter + else { + assertionFailure("device_samplerstate_create: Invalid filter modes provided") + return nil + } + + let descriptor = MTLSamplerDescriptor() + descriptor.sAddressMode = sAddressMode + descriptor.tAddressMode = tAddressMode + descriptor.rAddressMode = rAddressMode + + descriptor.minFilter = minFilter + descriptor.magFilter = magFilter + descriptor.mipFilter = mipFilter + + descriptor.maxAnisotropy = max(16, min(1, Int(info.max_anisotropy))) + + descriptor.compareFunction = .always + descriptor.borderColor = + if (info.border_color & 0x00_00_00_FF) == 0 { + .transparentBlack + } else if info.border_color == 0xFF_FF_FF_FF { + .opaqueWhite + } else { + .opaqueBlack + } + + guard let samplerState = device.device.makeSamplerState(descriptor: descriptor) else { + assertionFailure("device_samplerstate_create: Unable to create sampler state") + return nil + } + + let retained = Unmanaged.passRetained(samplerState).toOpaque() + + return OpaquePointer(retained) +} + +/// Requests the deinitialization of the ``MTLSamplerState`` instance shared with `libobs` +/// - Parameter samplerstate: Opaque pointer to ``MTLSamplerState`` instance shared with `libobs` +/// +/// Ownership of the ``MTLSamplerState`` instance will be transferred into the function and if this was the last +/// strong reference to it, the object will be automatically deinitialized and deallocated by Swift. +@_cdecl("gs_samplerstate_destroy") +public func gs_samplerstate_destroy(samplerstate: UnsafeRawPointer) { + let _ = retained(samplerstate) as MTLSamplerState +} + +/// Loads the provided ``MTLSamplerState`` into the current pipeline's sampler array at the requested texture unit +/// number +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - samplerstate: Opaque pointer to ``MTLSamplerState`` instance shared with `libobs` +/// - unit: Number identifying the "texture slot" used by OBS Studio's renderer. +/// +/// Texture slot numbers are equivalent to array index and represent a direct mapping between samplers and textures. +@_cdecl("device_load_samplerstate") +public func device_load_samplerstate(device: UnsafeRawPointer, samplerstate: UnsafeRawPointer, unit: UInt32) { + let device: MetalDevice = unretained(device) + let samplerState: MTLSamplerState = unretained(samplerstate) + + device.renderState.samplersInt(unit) = samplerState +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/metal-shader.swift
Added
@@ -0,0 +1,593 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal + +private typealias ParserError = MetalError.OBSShaderParserError +private typealias ShaderError = MetalError.OBSShaderError +private typealias MetalShaderError = MetalError.MetalShaderError + +/// Creates a ``MetalShader`` instance from the given shader string for use as a vertex shader. +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - shader: C character pointer with the contents of the `libobs` effect file +/// - file: C character pointer with the contents of the `libobs` effect file location +/// - error_string: Pointer for another C character pointer with the contents of an error description +/// - Returns: Opaque pointer to a new ``MetalShader`` instance on success or `nil` on error +/// +/// The string pointed to by the `data` argument is a re-compiled shader string created from the associated "effect" +/// file (which will contain multiple effects). Each effect is made up of several passes (though usually only a single +/// pass is defined), each of which contains a vertex and fragment shader. This function is then called with just the +/// vertex shader string. +/// +/// This vertex shader string needs to be parsed again and transpiled into a Metal shader string, which is handled by +/// the ``OBSShader`` class. The transpiled string is then used to create the actual ``MetalShader`` instance. +@_cdecl("device_vertexshader_create") +public func device_vertexshader_create( + device: UnsafeRawPointer, shader: UnsafePointer<CChar>, file: UnsafePointer<CChar>, + error_string: UnsafeMutablePointer<UnsafeMutablePointer<CChar>> +) -> OpaquePointer? { + let device: MetalDevice = unretained(device) + + let content = String(cString: shader) + let fileLocation = String(cString: file) + + do { + let obsShader = try OBSShader(type: .vertex, content: content, fileLocation: fileLocation) + let transpiled = try obsShader.transpiled() + + guard let metaData = obsShader.metaData else { + OBSLog(.error, "device_vertexshader_create: No required metadata found for transpiled shader") + return nil + } + + let metalShader = try MetalShader(device: device, source: transpiled, type: .vertex, data: metaData) + + return metalShader.getRetained() + } catch let error as ParserError { + switch error { + case .parseFail(let description): + OBSLog(.error, "device_vertexshader_create: Error parsing shader.\n\(description)") + default: + OBSLog(.error, "device_vertexshader_create: Error parsing shader.\n\(error.description)") + } + } catch let error as ShaderError { + switch error { + case .transpileError(let description): + OBSLog(.error, "device_vertexshader_create: Error transpiling shader.\n\(description)") + case .parseError(let description): + OBSLog(.error, "device_vertexshader_create: OBS parser error.\n\(description)") + case .parseFail(let description): + OBSLog(.error, "device_vertexshader_create: OBS parser failure.\n\(description)") + default: + OBSLog(.error, "device_vertexshader_create: OBS shader error.\n\(error.description)") + } + } catch { + switch error { + case let error as MetalShaderError: + OBSLog(.error, "device_vertexshader_create: Error compiling shader.\n\(error.description)") + case let error as MetalError.MTLDeviceError: + OBSLog(.error, "device_vertexshader_create: Device error compiling shader.\n\(error.description)") + default: + OBSLog(.error, "device_vertexshader_create: Unknown error occurred") + } + } + + return nil +} + +/// Creates a ``MetalShader`` instance from the given shader string for use as a fragment shader. +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - shader: C character pointer with the contents of the `libobs` effect file +/// - file: C character pointer with the contents of the `libobs` effect file location +/// - error_string: Pointer for another C character pointer with the contents of an error description +/// - Returns: Opaque pointer to a new ``MetalShader`` instance on success or `nil` on error +/// +/// The string pointed to by the `data` argument is a re-compiled shader string created from the associated "effect" +/// file (which will contain multiple effects). Each effect is made up of several passes (though usually only a single +/// pass is defined), each of which contains a vertex and fragment shader. This function is then called with just the +/// vertex shader string. +/// +/// This fragment shader string needs to be parsed again and transpiled into a Metal shader string, which is handled by +/// the ``OBSShader`` class. The transpiled string is then used to create the actual ``MetalShader`` instance. +@_cdecl("device_pixelshader_create") +public func device_pixelshader_create( + device: UnsafeRawPointer, shader: UnsafePointer<CChar>, file: UnsafePointer<CChar>, + error_string: UnsafeMutablePointer<UnsafeMutablePointer<CChar>> +) -> OpaquePointer? { + let device: MetalDevice = unretained(device) + + let content = String(cString: shader) + let fileLocation = String(cString: file) + + do { + let obsShader = try OBSShader(type: .fragment, content: content, fileLocation: fileLocation) + let transpiled = try obsShader.transpiled() + + guard let metaData = obsShader.metaData else { + OBSLog(.error, "device_pixelshader_create: No required metadata found for transpiled shader") + return nil + } + + let metalShader = try MetalShader(device: device, source: transpiled, type: .fragment, data: metaData) + + return metalShader.getRetained() + } catch let error as ParserError { + switch error { + case .parseFail(let description): + OBSLog(.error, "device_vertexshader_create: Error parsing shader.\n\(description)") + default: + OBSLog(.error, "device_vertexshader_create: Error parsing shader.\n\(error.description)") + } + } catch let error as ShaderError { + switch error { + case .transpileError(let description): + OBSLog(.error, "device_vertexshader_create: Error transpiling shader.\n\(description)") + case .parseError(let description): + OBSLog(.error, "device_vertexshader_create: OBS parser error.\n\(description)") + case .parseFail(let description): + OBSLog(.error, "device_vertexshader_create: OBS parser failure.\n\(description)") + default: + OBSLog(.error, "device_vertexshader_create: OBS shader error.\n\(error.description)") + } + } catch { + switch error { + case let error as MetalShaderError: + OBSLog(.error, "device_vertexshader_create: Error compiling shader.\n\(error.description)") + case let error as MetalError.MTLDeviceError: + OBSLog(.error, "device_vertexshader_create: Device error compiling shader.\n\(error.description)") + default: + OBSLog(.error, "device_vertexshader_create: Unknown error occurred") + } + } + + return nil +} + +/// Loads the ``MetalShader`` instance for use as the vertex shader for the current render pipeline descriptor. +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - vertShader: Opaque pointer to ``MetalShader`` instance shared with `libobs` +/// +/// This function will simply set up the ``MTLFunction`` wrapped by the ``MetalShader`` instance as the current +/// pipeline descriptor's `vertexFunction`. The Metal renderer will lazily create new render pipeline states for each +/// permutation of pipeline descriptors, which is a comparatively costly operation but will only occur once for any +/// such permutation. +/// +/// > Note: If a `NULL` pointer is passed for the `vertShader` argument, the vertex function on the current render +/// pipeline descriptor will be _unset_. +/// +@_cdecl("device_load_vertexshader") +public func device_load_vertexshader(device: UnsafeRawPointer, vertShader: UnsafeRawPointer?) { + let device: MetalDevice = unretained(device) + + if let vertShader { + let shader: MetalShader = unretained(vertShader) + + guard shader.type == .vertex else { + assertionFailure("device_load_vertexshader: Invalid shader type \(shader.type)") + return + } + + device.renderState.vertexShader = shader + device.renderState.pipelineDescriptor.vertexFunction = shader.function + device.renderState.pipelineDescriptor.vertexDescriptor = shader.vertexDescriptor + } else { + device.renderState.vertexShader = nil + device.renderState.pipelineDescriptor.vertexFunction = nil + device.renderState.pipelineDescriptor.vertexDescriptor = nil + } +} + +/// Loads the ``MetalShader`` instance for use as the fragment shader for the current render pipeline descriptor. +/// - Parameters:
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/metal-stagesurf.swift
Added
@@ -0,0 +1,130 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal + +/// Creates a ``MetalStageBuffer`` instance for use as a stage surface by `libobs` +/// - Parameters: +/// - device: Opaque pointer to ``MetalStageBuffer`` instance shared with `libobs` +/// - width: Number of data rows +/// - height: Number of data columns +/// - format: Color format of the stage surface texture as defined by `libobs`'s `gs_color_format` struct +/// - Returns: A ``MetalStageBuffer`` instance that wraps a `MTLBuffer` or a `nil` pointer otherwise +/// +/// Stage surfaces are used by `libobs` for transfer of image data from the GPU to the CPU. The most common use case is +/// to block transfer (blit) the video output texture into a staging texture and then downloading the texture data from +/// the staging texture into CPU memory. +@_cdecl("device_stagesurface_create") +public func device_stagesurface_create(device: UnsafeRawPointer, width: UInt32, height: UInt32, format: gs_color_format) + -> OpaquePointer? +{ + let device: MetalDevice = unretained(device) + + guard + let buffer = MetalStageBuffer( + device: device, + width: Int(width), + height: Int(height), + format: format.mtlFormat + ) + else { + OBSLog(.error, "device_stagesurface_create: Unable to create MetalStageBuffer with provided format \(format)") + return nil + } + + return buffer.getRetained() +} + +/// Requests the deinitialization of the ``MetalStageBuffer`` instance that was shared with `libobs` +/// - Parameter stagesurf: Opaque pointer to ``MetalStageBuffer`` instance shared with `libobs` +/// +/// The ownership of the shared pointer is transferred into this function and the instance is placed under Swift's +/// memory management again. +@_cdecl("gs_stagesurface_destroy") +public func gs_stagesurface_destroy(stagesurf: UnsafeRawPointer) { + let _ = retained(stagesurf) as MetalStageBuffer +} + +/// Gets the "width" of the staging texture +/// - Parameter stagesurf: Opaque pointer to ``MetalStageBuffer`` instance shared with `libobs` +/// - Returns: Amount of data rows in the buffer representing the width of an image +@_cdecl("gs_stagesurface_get_width") +public func gs_stagesurface_get_width(stagesurf: UnsafeRawPointer) -> UInt32 { + let stageSurface: MetalStageBuffer = unretained(stagesurf) + + return UInt32(stageSurface.width) +} + +/// Gets the "height" of the staging texture +/// - Parameter stagesurf: Opaque pointer to ``MetalStageBuffer`` instance shared with `libobs` +/// - Returns: Amount of data columns in the buffer representing the height of an image +@_cdecl("gs_stagesurface_get_height") +public func gs_stagesurface_get_height(stagesurf: UnsafeRawPointer) -> UInt32 { + let stageSurface: MetalStageBuffer = unretained(stagesurf) + + return UInt32(stageSurface.height) +} + +/// Gets the color format of the staged image data +/// - Parameter stagesurf: Opaque pointer to ``MetalStageBuffer`` instance shared with `libobs` +/// - Returns: Color format in `libobs`'s own color format struct +/// +/// The Metal color format is automatically converted into its corresponding `gs_color_format` variant. +@_cdecl("gs_stagesurface_get_color_format") +public func gs_stagesurface_get_height(stagesurf: UnsafeRawPointer) -> gs_color_format { + let stageSurface: MetalStageBuffer = unretained(stagesurf) + + return stageSurface.format.gsColorFormat +} + +/// Provides a pointer to memory that contains the buffer's raw data. +/// - Parameters: +/// - stagesurf: Opaque pointer to ``MetalStageBuffer`` instance shared with `libobs` +/// - ptr: Opaque pointer to memory which itself can hold a pointer to the actual image data +/// - linesize: Opaque pointer to memory which itself can hold the row size of the image data +/// - Returns: `true` if the data can be provided, `false` otherwise +/// +/// Metal does not provide "map" and "unmap" operations as they exist in Direct3D11, as resource management and +/// synchronization needs to be handled explicitly by the application. To reduce unnecessary copy operations, the +/// original texture's data was copied into a `MTLBuffer` (instead of another texture) using a block transfer on the +/// GPU. +/// +/// As the Metal renderer is only available on Apple Silicon machines, this means that the buffer itself is available +/// for direct access by the CPU and thus a pointer to the raw bytes of the buffer can be shared with `libobs`. +@_cdecl("gs_stagesurface_map") +public func gs_stagesurface_map( + stagesurf: UnsafeRawPointer, ptr: UnsafeMutablePointer<UnsafeMutableRawPointer>, + linesize: UnsafeMutablePointer<UInt32> +) -> Bool { + let stageSurface: MetalStageBuffer = unretained(stagesurf) + + ptr.pointee = stageSurface.buffer.contents() + linesize.pointee = UInt32(stageSurface.width * stageSurface.format.bytesPerPixel!) + + return true +} + +/// Signals that the downloaded image data of the stage texture is not needed anymore. +/// +/// - Parameter stagesurf: Opaque pointer to ``MetalStageBuffer`` instance shared with `libobs` +/// +/// This function has no effect as the `MTLBuffer` used by the ``MetalStageBuffer`` does not need to be "unmapped". +@_cdecl("gs_stagesurface_unmap") +public func gs_stagesurface_unmap(stagesurf: UnsafeRawPointer) { + return +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/metal-subsystem.swift
Added
@@ -0,0 +1,985 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal +import simd + +@inlinable +public func unretained<Instance>(_ pointer: UnsafeRawPointer) -> Instance where Instance: AnyObject { + Unmanaged<Instance>.fromOpaque(pointer).takeUnretainedValue() +} + +@inlinable +public func retained<Instance>(_ pointer: UnsafeRawPointer) -> Instance where Instance: AnyObject { + Unmanaged<Instance>.fromOpaque(pointer).takeRetainedValue() +} + +@inlinable +public func OBSLog(_ level: OBSLogLevel, _ format: String, _ args: CVarArg...) { + let logMessage = String.localizedStringWithFormat(format, args) + + logMessage.withCString { cMessage in + withVaList(cMessage) { arguments in + blogva(level.rawValue, "%s", arguments) + } + } +} + +/// Returns the graphics API name implemented by the "device". +/// - Returns: Constant pointer to a C string with the API name +/// +@_cdecl("device_get_name") +public func device_get_name() -> UnsafePointer<CChar> { + return device_name +} + +/// Gets the graphics API identifier number for the "device". +/// - Returns: Numerical identifier +/// +@_cdecl("device_get_type") +public func device_get_type() -> Int32 { + return GS_DEVICE_METAL +} + +/// Returns a string to be used as a suffix for libobs' shader preprocessor, which will be used as part of a shaders +/// identifying information. +/// - Returns: Constant pointer to a C string with the suffix text +@_cdecl("device_preprocessor_name") +public func device_preprocessor_name() -> UnsafePointer<CChar> { + return preprocessor_name +} + +/// Creates a new Metal device instance and stores an opaque pointer to a ``MetalDevice`` instance in the provided +/// pointer. +/// +/// - Parameters: +/// - devicePointer: Pointer to memory allocated by the caller to receive the pointer of the create device instance +/// - adapter: Numerical identifier of a graphics display adaptor to create the device on. +/// - Returns: Device creation result value defined as preprocessor macro in libobs' graphics API header +/// +/// This method will increment the reference count on the created ``MetalDevice`` instance to ensure it will not be +/// deallocated until `libobs` actively relinquishes ownership of it via a call of `device_destroy`. +/// +/// > Important: As the Metal API is only supported on Apple Silicon devices, the adapter argument is effectively +/// ignored (there is only ever one "adapter" in an Apple Silicon machine and thus only the "default" device is used. +@_cdecl("device_create") +public func device_create(devicePointer: UnsafeMutableRawPointer, adapter: UInt32) -> Int32 { + guard NSProtocolFromString("MTLDevice") != nil else { + OBSLog(.error, "This Mac does not support Metal.") + return GS_ERROR_NOT_SUPPORTED + } + + OBSLog(.info, "---------------------------------") + + guard let metalDevice = MTLCreateSystemDefaultDevice() else { + OBSLog(.error, "Unable to initialize Metal device.") + return GS_ERROR_FAIL + } + + var descriptions: String = + + descriptions.append("Initializing Metal...") + descriptions.append("\t- Name : \(metalDevice.name)") + descriptions.append("\t- Unified Memory : \(metalDevice.hasUnifiedMemory ? "Yes" : "No")") + descriptions.append("\t- Raytracing Support : \(metalDevice.supportsRaytracing ? "Yes" : "No")") + + if #available(macOS 14.0, *) { + descriptions.append("\t- Architecture : \(metalDevice.architecture.name)") + } + + OBSLog(.info, descriptions.joined(separator: "\n")) + + do { + let device = try MetalDevice(device: metalDevice) + + let retained = Unmanaged.passRetained(device).toOpaque() + + let signalName = MetalSignalType.videoReset.rawValue + let signalHandler = obs_get_signal_handler() + signalName.withCString { + signal_handler_connect(signalHandler, $0, metal_video_reset_handler, retained) + } + + devicePointer.storeBytes(of: OpaquePointer(retained), as: OpaquePointer.self) + } catch { + OBSLog(.error, "Unable to create MetalDevice wrapper instance") + return GS_ERROR_FAIL + } + + return GS_SUCCESS +} + +/// Uninitializes the Metal device instance created for libobs. +/// - Parameter device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// +/// This method will take ownership of the reference shared with `libobs` and thus return all strong references to the +/// shared ``MetalDevice`` instance to pure Swift code (and thus its own memory managed). The active call to +/// ``MetalDevice/shutdown()`` is necessary to ensure that internal clean up code runs _before_ `libobs` runs any of +/// its own clean up code (which is not memory safe). +@_cdecl("device_destroy") +public func device_destroy(device: UnsafeMutableRawPointer) { + let signalName = MetalSignalType.videoReset.rawValue + let signalHandler = obs_get_signal_handler() + + signalName.withCString { + signal_handler_disconnect(signalHandler, $0, metal_video_reset_handler, device) + } + + let device: MetalDevice = retained(device) + + device.shutdown() +} + +/// Returns opaque pointer to actual (wrapped) API-specific device object +/// - Parameter device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - Returns: Opaque pointer to ``MTLDevice`` object wrapped by ``MetalDevice`` instance +/// +/// The pointer shared by this function is unretained and is thus unsafe. It doesn't seem that anything in OBS Studio's +/// codebase actually uses this function, but it is part of the graphics API and thus has to be implemented. +@_cdecl("device_get_device_obj") +public func device_get_device_obj(device: UnsafeMutableRawPointer) -> OpaquePointer? { + let metalDevice: MetalDevice = unretained(device) + let mtlDevice = metalDevice.device + + return OpaquePointer(Unmanaged.passUnretained(mtlDevice).toOpaque()) +} + +/// Sets up the blend factor to be used by the current pipeline. +/// +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - src: `libobs` blend type for the source +/// - dest: `libobs` blend type for the destination +/// +/// This function uses the same blend factor for color and alpha channel. The enum values provided by `libobs` are +/// converted into their appropriate ``MTLBlendFactor``variants automatically (if possible). +/// +/// > Important: Calling this function can trigger the creation of an entirely new render pipeline state, which is a +/// costly operation. +@_cdecl("device_blend_function") +public func device_blend_function(device: UnsafeRawPointer, src: gs_blend_type, dest: gs_blend_type) { + device_blend_function_separate( + device: device, + src_c: src, + dest_c: dest, + src_a: src, + dest_a: dest + ) +} + +/// Sets up the color and alpha blend factors to be used by the current pipeline +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - src_c: `libobs` blend factor for the source color +/// - dest_c: `libobs` blend factor for the destination color +/// - src_a: `libobs` blend factor for the source alpha channel +/// - dest_a: `libobs` blend factor for the destination alpha channel +/// +/// This function uses different blend factors for color and alpha channel. The enum values provided by `libobs` are +/// converted into their appropriate ``MTLBlendFactor`` variants automatically (if possible). +/// +/// > Important: Calling this function can trigger the creation of an entirely new render pipeline state, which is a +/// costly operation. +@_cdecl("device_blend_function_separate") +public func device_blend_function_separate(
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/metal-swapchain.swift
Added
@@ -0,0 +1,269 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import AppKit +import Foundation + +/// Creates a ``OBSSwapChain`` instance for use as a pseudo swap chain implementation to be shared with `libobs` +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - data: Pointer to platform-specific `gs_init_data` struct +/// - Returns: Opaque pointer to a new ``OBSSwapChain`` on success or `nil` on error +/// +/// As interaction with UI elements needs to happen on the main thread of macOS, this function is marked with +/// `@MainActor`. This is also necessary because ``OBSSwapChain/updateView`` itself interacts with the ``NSView`` +/// instance passed via the `data` argument and also has to occur on the main thread. +/// +/// As applications cannot manage their own swap chain on macOS, the ``OBSSwapChain`` class merely wraps the +/// management of the ``CAMetalLayer`` that will be associated with the ``NSView`` and handles the drawables used to +/// render their contents. +/// +/// > Important: This function can only be called from the main thread. +@MainActor +@_cdecl("device_swapchain_create") +public func device_swapchain_create(device: UnsafeMutableRawPointer, data: UnsafePointer<gs_init_data>) + -> OpaquePointer? +{ + let device: MetalDevice = unretained(device) + + let view = data.pointee.window.view.takeUnretainedValue() as! NSView + let size = MTLSize( + width: Int(data.pointee.cx), + height: Int(data.pointee.cy), + depth: 0 + ) + + guard let swapChain = OBSSwapChain(device: device, size: size, colorSpace: data.pointee.format) else { return nil } + + swapChain.updateView(view) + + device.swapChainQueue.sync { + device.swapChains.append(swapChain) + } + + return swapChain.getRetained() +} + +/// Updates the internal size parameter and dimension of the ``CAMetalLayer`` managed by the ``OBSSwapChain`` instance +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - width: Width to update the layer's dimensions to +/// - height: Height to update the layer's dimensions to +/// +/// As the relationship between the ``CAMetalLayer`` and the ``NSView`` it is associated with is managed indirectly, +/// the metal layer cannot directly react to size changes (even though it would be possible to do so). Instead +/// ``AppKit`` will report a size change to the application, which will be picked up by Qt, who will emit a size +/// change event on the main loop, which will update internal state of the ``OBSQTDisplay`` class. These changes are +/// asynchronously picked up by `libobs` render loop, which will then call this function. +@_cdecl("device_resize") +public func device_resize(device: UnsafeMutableRawPointer, width: UInt32, height: UInt32) { + let device: MetalDevice = unretained(device) + + guard let swapChain = device.renderState.swapChain else { + return + } + + swapChain.resize(.init(width: Int(width), height: Int(height), depth: 0)) +} + +/// This function does nothing on Metal +/// - Parameter device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// +/// The intended purpose of this function is to update the render target in the "current" swap chain with the color +/// space of its "display" and thus pick up changes in color spaces between different screens. +/// +/// On macOS this just requires updating the EDR headroom for the screen the view might be associated with, as the +/// actual color space and EDR capabilities are evaluated on every render loop. +/// +/// > Important: This function can only be called from the main thread. +@_cdecl("device_update_color_space") +public func device_update_color_space(device: UnsafeRawPointer) { + let device: MetalDevice = unretained(device) + + guard device.renderState.swapChain != nil else { + return + } + + nonisolated(unsafe) let swapChain = device.renderState.swapChain! + + Task { @MainActor in + swapChain.updateEdrHeadroom() + } +} + +/// Gets the dimensions of the ``CAMetalLayer`` managed by the ``OBSSwapChain`` instance set up in the current pipeline +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - cx: Pointer to memory for the width of the layer +/// - cy: Pointer to memory for the height of the layer +@_cdecl("device_get_size") +public func device_get_size( + device: UnsafeMutableRawPointer, cx: UnsafeMutablePointer<UInt32>, cy: UnsafeMutablePointer<UInt32> +) { + let device: MetalDevice = unretained(device) + + guard let swapChain = device.renderState.swapChain else { + cx.pointee = 0 + cy.pointee = 0 + return + } + + cx.pointee = UInt32(swapChain.viewSize.width) + cy.pointee = UInt32(swapChain.viewSize.height) +} + +/// Gets the width of the ``CAMetalLayer`` managed by the ``OBSSwapChain`` instance set up in the current pipeline +/// - Parameter device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - Returns: Width of the layer +@_cdecl("device_get_width") +public func device_get_width(device: UnsafeRawPointer) -> UInt32 { + let device: MetalDevice = unretained(device) + + guard let swapChain = device.renderState.swapChain else { + return 0 + } + + return UInt32(swapChain.viewSize.width) +} + +/// Gets the height of the ``CAMetalLayer`` managed by the ``OBSSwapChain`` instance set up in the current pipeline +/// - Parameter device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - Returns: Height of the layer +@_cdecl("device_get_height") +public func device_get_height(device: UnsafeRawPointer) -> UInt32 { + let device: MetalDevice = unretained(device) + + guard let swapChain = device.renderState.swapChain else { + return 0 + } + + return UInt32(swapChain.viewSize.height) +} + +/// Sets up the ``OBSSwapChain`` for use in the current pipeline +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - swap: Opaque pointer to ``OBSSwapChain`` instance shared with `libobs` +/// +/// The first call of this function in any render loop marks the "begin" of OBS Studio's display render stage. There +/// will only ever be one "current" swap chain in use by `libobs` and there is no dedicated call to "reset" or unload +/// the current swap chain, instead a new swap chain is loaded or the "scene end" function is called. +@_cdecl("device_load_swapchain") +public func device_load_swapchain(device: UnsafeRawPointer, swap: UnsafeRawPointer) { + let device: MetalDevice = unretained(device) + let swapChain: OBSSwapChain = unretained(swap) + + if swapChain.edrHeadroom > 1.0 { + var videoInfo: obs_video_info = obs_video_info() + obs_get_video_info(&videoInfo) + + let videoColorSpace = videoInfo.colorspace + + switch videoColorSpace { + case VIDEO_CS_2100_PQ: + if swapChain.colorRange != .hdrPQ { + // TODO: Investigate whether it's viable to use PQ or HLG tone mapping for the preview + // Use the following code to enable it for either: + // 2100 PQ: + // let maxLuminance = obs_get_video_hdr_nominal_peak_level() + // swapChain.layer.edrMetadata = .hdr10( + // minLuminance: 0.0001, maxLuminance: maxLuminance, opticalOutputScale: 10000) + // HLG: + // swapChain.layer.edrMetadata = .hlg + swapChain.layer.pixelFormat = .rgba16Float + swapChain.layer.colorspace = CGColorSpace(name: CGColorSpace.extendedLinearSRGB) + swapChain.layer.wantsExtendedDynamicRangeContent = true + swapChain.layer.edrMetadata = nil + swapChain.colorRange = .hdrPQ + swapChain.renderTarget = nil + } + case VIDEO_CS_2100_HLG: + if swapChain.colorRange != .hdrHLG { + swapChain.layer.pixelFormat = .rgba16Float + swapChain.layer.colorspace = CGColorSpace(name: CGColorSpace.extendedLinearSRGB) + swapChain.layer.wantsExtendedDynamicRangeContent = true + swapChain.layer.edrMetadata = nil
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/metal-texture2d.swift
Added
@@ -0,0 +1,528 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal + +/// Creates a two-dimensional ``MetalTexture`` instance with the specified usage options and the raw image data (if +/// provided) +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - width: Desired width of the texture +/// - height: Desired height of the texture +/// - color_format: Desired color format of the texture as described by `gs_color_format` +/// - levels: Amount of mip map levels to generate for the texture +/// - data: Optional pointer to raw pixel data per mip map level +/// - flags: Texture resource use information encoded as `libobs` bitfield +/// - Returns: Opaque pointer to a created ``MetalTexture`` instance or a `nil` pointer on error +/// +/// This function will create a new ``MTLTexture`` wrapped within a ``MetalTexture`` class and also upload any pixel +/// data if non-`nil` pointers have been provided via the `data` argument. +/// +/// > Important: If mipmap generation is requested, execution will be blocked by waiting for the blit command encoder +/// to generate the mipmaps. +@_cdecl("device_texture_create") +public func device_texture_create( + device: UnsafeRawPointer, width: UInt32, height: UInt32, color_format: gs_color_format, levels: UInt32, + data: UnsafePointer<UnsafePointer<UInt8>?>?, flags: UInt32 +) -> OpaquePointer? { + let device: MetalDevice = unretained(device) + + let descriptor = MTLTextureDescriptor.init( + type: .type2D, + width: width, + height: height, + depth: 1, + colorFormat: color_format, + levels: levels, + flags: flags + ) + + guard let descriptor, let texture = MetalTexture(device: device, descriptor: descriptor) else { + return nil + } + + if let data { + texture.upload(data: data, mipmapLevels: descriptor.mipmapLevelCount) + } + + return texture.getRetained() +} + +/// Creates a ``MetalTexture`` instance for a cube texture with the specified usage options and the raw image data (if provided) +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - size: Desized edge length for the cube +/// - color_format: Desired color format of the texture as described by `gs_color_format` +/// - levels: Amount of mip map levels to generate for the texture +/// - data: Optional pointer to raw pixel data per mip map level +/// - flags: Texture resource use information encoded as `libobs` bitfield +/// - Returns: Opaque pointer to created ``MetalTexture`` instance or a `nil` pointer on error +/// +/// This function will create a new ``MTLTexture`` wrapped within a ``MetalTexture`` class and also upload any pixel +/// data if non-`nil` pointers have +/// been provided via the `data` argument. +/// +/// > Important: If mipmap generation is requested, execution will be blocked by waiting for the blit command encoder +/// to generate the mipmaps. +@_cdecl("device_cubetexture_create") +public func device_cubetexture_create( + device: UnsafeRawPointer, size: UInt32, color_format: gs_color_format, levels: UInt32, + data: UnsafePointer<UnsafePointer<UInt8>?>?, flags: UInt32 +) -> OpaquePointer? { + let device: MetalDevice = unretained(device) + + let descriptor = MTLTextureDescriptor.init( + type: .typeCube, + width: size, + height: size, + depth: 1, + colorFormat: color_format, + levels: levels, + flags: flags + ) + + guard let descriptor, let texture = MetalTexture(device: device, descriptor: descriptor) else { + return nil + } + + if let data { + texture.upload(data: data, mipmapLevels: descriptor.mipmapLevelCount) + } + + return texture.getRetained() +} + +/// Requests deinitialization of the ``MetalTexture`` instance shared with `libobs` +/// - Parameter texture: Opaque pointer to ``MetalTexture`` instance shared with `libobs` +/// +/// The ownership of the shared pointer is transferred into this function and the instance is placed under Swift's +/// memory management again. +@_cdecl("gs_texture_destroy") +public func gs_texture_destroy(texture: UnsafeRawPointer) { + let _ = retained(texture) as MetalTexture +} + +/// Gets the type of the texture wrapped by the ``MetalTexture`` instance +/// - Parameter texture: Opaque pointer to ``MetalTexture`` instance shared with `libobs` +/// - Returns: Texture type identified by `gs_texture_type` enum value +/// +/// > Warning: As `libobs` has no enum value for "invalid texture type", there is no way for this function to signal +/// that the wrapped texture has an incompatible ``MTLTextureType``. Instead of crashing the program (which would +/// avoid undefined behavior), this function will return the 2D texture type value instead, which is incorrect, but is +/// more in line with how OBS Studio handles undefined behavior. +@_cdecl("device_get_texture_type") +public func device_get_texture_type(texture: UnsafeRawPointer) -> gs_texture_type { + let texture: MetalTexture = unretained(texture) + + return texture.texture.textureType.gsTextureType ?? GS_TEXTURE_2D +} + +/// Requests the ``MetalTexture`` instance to be loaded as one of the current pipeline's fragment attachments in the +/// specified texture slot +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - tex: Opaque pointer to ``MetalTexture`` instance shared with `libobs` +/// - unit: Texture slot for fragment attachment +/// +/// OBS Studio expects pipelines to support fragment attachments for textures and samplers up to the amount defined in +/// the `GS_MAX_TEXTURES` preprocessor directive. The order of this calls can be arbitrary, so at any point in time a +/// request to load a texture into slot "5" can take place, even if slots 0 to 4 are empty. +@_cdecl("device_load_texture") +public func device_load_texture(device: UnsafeRawPointer, tex: UnsafeRawPointer, unit: UInt32) { + let device: MetalDevice = unretained(device) + let texture: MetalTexture = unretained(tex) + + device.renderState.texturesInt(unit) = texture.texture +} + +/// Requests an sRGB variant of a ``MetalTexture`` instance to be set as one of the current pipeline's fragment +/// attachments in the specified texture slot. +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - tex: Opaque pointer to ``MetalTexture`` instance shared with `libobs` +/// - unit: Texture slot for fragment attachment +/// OBS Studio expects pipelines to support fragment attachments for textures and samplers up to the amount defined in +/// the `GS_MAX_TEXTURES` preprocessor directive. The order of this calls can be arbitrary, so at any point in time a +/// request to load a texture into slot "5" can take place, even if slots 0 to 4 are empty. +/// +/// > Important: This variant of the texture load functions expects a texture whose color values are already sRGB gamma +/// encoded and thus also expects that the color values used in the fragment shader will have been automatically +/// decoded into linear gamma. If the ``MetalTexture`` instance has no dedicated ``MetalTexture/sRGBtexture`` instance, +/// it will use the normal ``MetalTexture/texture`` instance instead. +@_cdecl("device_load_texture_srgb") +public func device_load_texture_srgb(device: UnsafeRawPointer, tex: UnsafeRawPointer, unit: UInt32) { + let device: MetalDevice = unretained(device) + let texture: MetalTexture = unretained(tex) + + if texture.sRGBtexture != nil { + device.renderState.texturesInt(unit) = texture.sRGBtexture! + } else { + device.renderState.texturesInt(unit) = texture.texture + } +} + +/// Copies image data from a region in the source ``MetalTexture`` into a destination ``MetalTexture`` at the provided +/// origin +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - dst: Opaque pointer to ``MetalTexture`` instance shared with `libobs`, used as destination for the copy operation +/// - dst_x: X coordinate of the origin in the destination texture +/// - dst_y: Y coordinate of the origin in the destination texture +/// - src: Opaque pointer to ``MetalTexture`` instance shared with `libobs`, used as source for the copy operation +/// - src_x: X coordinate of the origin in the source texture +/// - src_y: Y coordinate of the origin in the source texture +/// - src_w: Width of the region in the source texture +/// - src_h: Height of the region in the source texture +/// +/// This function will fail if the destination texture's dimensions aren't large enough to hold the region copied from +/// the source texture. This check will use the desired origin within the destination texture and the region's size +/// into account and checks whether the total dimensions of the destination are large enough (starting at the +/// destination origin) to hold the source's region. +/// +/// > Important: Execution will **not** be blocked, the copy operation will be committed to the command queue and +/// executed at some point after this function returns. +@_cdecl("device_copy_texture_region")
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/metal-texture3d.swift
Added
@@ -0,0 +1,113 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal + +/// Creates a three-dimensional ``MetalTexture`` instance with the specified usage options and the raw image data +/// (if provided) +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - size: Desired size of the texture +/// - color_format: Desired color format of the texture as described by `gs_color_format` +/// - levels: Amount of mip map levels to generate for the texture +/// - data: Optional pointer to raw pixel data per mip map level +/// - flags: Texture resource use information encoded as `libobs` bitfield +/// - Returns: Opaque pointer to a created ``MetalTexture`` instance or a `NULL` pointer on error +/// +/// This function will create a new ``MTLTexture`` wrapped within a ``MetalTexture`` class and also upload any pixel +/// data if non-`NULL` pointers have been provided via the `data` argument. +/// +/// > Important: If mipmap generation is requested, execution will be blocked by waiting for the blit command encoder +/// to generate the mipmaps. +@_cdecl("device_voltexture_create") +public func device_voltexture_create( + device: UnsafeRawPointer, width: UInt32, height: UInt32, depth: UInt32, color_format: gs_color_format, + levels: UInt32, data: UnsafePointer<UnsafePointer<UInt8>?>?, flags: UInt32 +) -> OpaquePointer? { + let device = Unmanaged<MetalDevice>.fromOpaque(device).takeUnretainedValue() + + let descriptor = MTLTextureDescriptor.init( + type: .type3D, + width: width, + height: height, + depth: depth, + colorFormat: color_format, + levels: levels, + flags: flags + ) + + guard let descriptor, let texture = MetalTexture(device: device, descriptor: descriptor) else { + return nil + } + + if let data { + texture.upload(data: data, mipmapLevels: descriptor.mipmapLevelCount) + } + + return texture.getRetained() +} + +/// Requests deinitialization of the ``MetalTexture`` instance shared with `libobs` +/// - Parameter texture: Opaque pointer to ``MetalTexture`` instance shared with `libobs` +/// +/// The ownership of the shared pointer is transferred into this function and the instance is placed under +/// Swift's memory management again. +@_cdecl("gs_voltexture_destroy") +public func gs_voltexture_destroy(voltex: UnsafeRawPointer) { + let _ = retained(voltex) as MetalTexture +} + +/// Gets the width of the texture wrapped by the ``MetalTexture`` instance +/// - Parameter voltex: Opaque pointer to ``MetalTexture`` instance shared with `libobs` +/// - Returns: Width of the texture +@_cdecl("gs_voltexture_get_width") +public func gs_voltexture_get_width(voltex: UnsafeRawPointer) -> UInt32 { + let texture: MetalTexture = unretained(voltex) + + return UInt32(texture.texture.width) +} + +/// Gets the height of the texture wrapped by the ``MetalTexture`` instance +/// - Parameter voltex: Opaque pointer to ``MetalTexture`` instance shared with `libobs` +/// - Returns: Height of the texture +@_cdecl("gs_voltexture_get_height") +public func gs_voltexture_get_height(voltex: UnsafeRawPointer) -> UInt32 { + let texture: MetalTexture = unretained(voltex) + + return UInt32(texture.texture.height) +} + +/// Gets the depth of the texture wrapped by the ``Metaltexture`` instance +/// - Parameter voltex: Opaque pointer to ``MetalTexture`` instance shared with `libobs` +/// - Returns: Depth of the texture +@_cdecl("gs_voltexture_get_depth") +public func gs_voltexture_get_depth(voltex: UnsafeRawPointer) -> UInt32 { + let texture: MetalTexture = unretained(voltex) + + return UInt32(texture.texture.depth) +} + +/// Gets the color format of the texture wrapped by the ``MetalTexture`` instance +/// - Parameter voltex: Opaque pointer to ``MetalTexture`` instance shared with `libobs` +/// - Returns: Color format as defined by the `gs_color_format` enumeration +@_cdecl("gs_voltexture_get_color_format") +public func gs_voltexture_get_color_format(voltex: UnsafeRawPointer) -> gs_color_format { + let texture: MetalTexture = unretained(voltex) + + return texture.texture.pixelFormat.gsColorFormat +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/metal-unimplemented.swift
Added
@@ -0,0 +1,97 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +@_cdecl("device_load_default_samplerstate") +public func device_load_default_samplerstate(device: UnsafeRawPointer, b_3d: Bool, unit: Int) { + return +} + +@_cdecl("device_enter_context") +public func device_enter_context(device: UnsafeMutableRawPointer) { + return +} + +@_cdecl("device_leave_context") +public func device_leave_context(device: UnsafeMutableRawPointer) { + return +} + +@_cdecl("device_timer_create") +public func device_timer_create(device: UnsafeRawPointer) { + return +} + +@_cdecl("device_timer_range_create") +public func device_timer_range_create(device: UnsafeRawPointer) { +} + +@_cdecl("gs_timer_destroy") +public func gs_timer_destroy(timer: UnsafeRawPointer) { + return +} + +@_cdecl("gs_timer_begin") +public func gs_timer_begin(timer: UnsafeRawPointer) { + return +} + +@_cdecl("gs_timer_end") +public func gs_timer_end(timer: UnsafeRawPointer) { + return +} + +@_cdecl("gs_timer_get_data") +public func gs_timer_get_data(timer: UnsafeRawPointer) -> Bool { + return false +} + +@_cdecl("gs_timer_range_destroy") +public func gs_timer_range_destroy(range: UnsafeRawPointer) { + return +} + +@_cdecl("gs_timer_range_begin") +public func gs_timer_range_begin(range: UnsafeRawPointer) { + return +} + +@_cdecl("gs_timer_range_end") +public func gs_timer_range_end(range: UnsafeRawPointer) { + return +} + +@_cdecl("gs_timer_range_get_data") +public func gs_timer_range_get_data(range: UnsafeRawPointer, disjoint: Bool, frequency: UInt64) -> Bool { + return false +} + +@_cdecl("device_debug_marker_begin") +public func device_debug_marker_begin(device: UnsafeRawPointer, monitor: UnsafeMutableRawPointer) { + return +} + +@_cdecl("device_debug_marker_end") +public func device_debug_marker_end(device: UnsafeRawPointer) { + return +} + +@_cdecl("device_set_cube_render_target") +public func device_set_cube_render_target( + device: UnsafeRawPointer, cubetex: UnsafeRawPointer, side: Int, zstencil: UnsafeRawPointer +) { + return +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/metal-vertexbuffer.swift
Added
@@ -0,0 +1,115 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +/// Creates a new ``MetalVertexBuffer`` instance with the given vertex buffer data and usage flags +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - data: Pointer to `gs_vb_data` vertex buffer data created by `libobs` +/// - flags: Usage flags encoded as `libobs` bitmask +/// - Returns: Opaque pointer to a new ``MetalVertexBuffer`` instance if successful, `nil` otherwise +/// +/// > Note: The ownership of the memory pointed to by `data` is implicitly transferred to the ``MetalVertexBuffer`` +/// instance, but is not managed by Swift. +@_cdecl("device_vertexbuffer_create") +public func device_vertexbuffer_create(device: UnsafeRawPointer, data: UnsafeMutablePointer<gs_vb_data>, flags: UInt32) + -> OpaquePointer +{ + let device: MetalDevice = unretained(device) + + let vertexBuffer = MetalVertexBuffer( + device: device, + data: data, + dynamic: (Int32(flags) & GS_DYNAMIC) != 0 + ) + + return vertexBuffer.getRetained() +} + +/// Requests the deinitialization of a shared ``MetalVertexBuffer`` instance +/// - Parameter indexBuffer: Opaque pointer to ``MetalVertexBuffer`` instance shared with `libobs` +/// +/// The deinitialization is handled automatically by Swift after the ownership of the instance has been transferred +/// into the function and becomes the last strong reference to it. After the function leaves its scope, the object will +/// be deinitialized and deallocated automatically. +/// +/// > Note: The vertex buffer data memory is implicitly owned by the ``MetalVertexBuffer`` instance and will be +/// manually cleaned up and deallocated by the instance's ``deinit`` method. +@_cdecl("gs_vertexbuffer_destroy") +public func gs_vertexbuffer_destroy(vertBuffer: UnsafeRawPointer) { + let _ = retained(vertBuffer) as MetalVertexBuffer +} + +/// Sets up a ``MetalVertexBuffer`` as the vertex buffer for the current pipeline +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - vertbuffer: Opaque pointer to ``MetalVertexBuffer`` instance shared with `libobs` +/// +/// > Note: The reference count of the ``MetalVertexBuffer`` instance will not be increased by this call. +/// +/// > Important: If a `nil` pointer is provided as the vertex buffer, the index buffer will be _unset_. +@_cdecl("device_load_vertexbuffer") +public func device_load_vertexbuffer(device: UnsafeRawPointer, vertBuffer: UnsafeMutableRawPointer?) { + let device: MetalDevice = unretained(device) + + if let vertBuffer { + device.renderState.vertexBuffer = unretained(vertBuffer) + } else { + device.renderState.vertexBuffer = nil + } +} + +/// Requests the vertex buffer's current data to be transferred into GPU memory +/// - Parameter vertBuffer: Opaque pointer to ``MetalVertexBuffer`` instance shared with `libobs` +/// +/// This function will call `gs_vertexbuffer_flush_direct` with a `nil` pointer as the data pointer. +@_cdecl("gs_vertexbuffer_flush") +public func gs_vertexbuffer_flush(vertbuffer: UnsafeRawPointer) { + gs_vertexbuffer_flush_direct(vertbuffer: vertbuffer, data: nil) +} + +/// Requests the vertex buffer to be updated with the provided data and then transferred into GPU memory +/// - Parameters: +/// - vertBuffer: Opaque pointer to ``MetalVertexBuffer`` instance shared with `libobs` +/// - data: Opaque pointer to vertex buffer data set up by `libobs` +/// +/// This function is called to ensure that the vertex buffer data that is contained in the memory pointed at by the +/// `data` argument is uploaded into GPU memory. +/// +/// If a `nil` pointer is provided instead, the data provided to the instance during creation will be used instead. +@_cdecl("gs_vertexbuffer_flush_direct") +public func gs_vertexbuffer_flush_direct(vertbuffer: UnsafeRawPointer, data: UnsafeMutablePointer<gs_vb_data>?) { + let vertexBuffer: MetalVertexBuffer = unretained(vertbuffer) + + vertexBuffer.setupBuffers(data: data) +} + +/// Returns an opaque pointer to the vertex buffer data associated with the ``MetalVertexBuffer`` instance +/// - Parameter vertBuffer: Opaque pointer to ``MetalVertexBuffer`` instance shared with `libobs` +/// - Returns: Opaque pointer to index buffer data in memory +/// +/// The returned opaque pointer represents the unchanged memory address that was provided for the creation of the index +/// buffer object. +/// +/// > Warning: There is only limited memory safety associated with this pointer. It is implicitly owned and its +/// lifetime is managed by the ``MetalVertexBuffer`` +/// instance, but it was originally created by `libobs`. +@_cdecl("gs_vertexbuffer_get_data") +public func gs_vertexbuffer_get_data(vertBuffer: UnsafeRawPointer) -> UnsafeMutablePointer<gs_vb_data>? { + let vertexBuffer: MetalVertexBuffer = unretained(vertBuffer) + + return vertexBuffer.vertexData +}
View file
obs-studio-32.0.0~beta2.tar.xz/libobs-metal/metal-zstencilbuffer.swift
Added
@@ -0,0 +1,69 @@ +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer <PatTheMav@users.noreply.github.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import Foundation +import Metal + +/// Creates ``MetalTexture`` for use as a depth stencil attachment +/// - Parameters: +/// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - width: Desired width of the texture +/// - height: Desired height of the texture +/// - color_format: Desired color format of the depth stencil attachment as described by `gs_zstencil_format` +/// - Returns: Opaque pointer to a created ``MetalTexture`` instance or a `NULL` pointer on error +@_cdecl("device_zstencil_create") +public func device_zstencil_create(device: UnsafeRawPointer, width: UInt32, height: UInt32, format: gs_zstencil_format) + -> OpaquePointer? +{ + let device: MetalDevice = unretained(device) + + let descriptor = MTLTextureDescriptor.init( + width: width, + height: height, + colorFormat: format + ) + + guard let descriptor, let texture = MetalTexture(device: device, descriptor: descriptor) else { + return nil + } + + return texture.getRetained() +} + +/// Gets the ``MetalTexture`` instance used as the depth stencil attachment for the current pipeline +/// - Parameter device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` +/// - Returns: Opaque pointer to ``MetalTexture`` instance if any is set, `nil` otherwise +@_cdecl("device_get_zstencil_target") +public func device_get_zstencil_target(device: UnsafeRawPointer) -> OpaquePointer? { + let device: MetalDevice = unretained(device) + + guard let stencilAttachment = device.renderState.depthStencilAttachment else { + return nil + } + + return stencilAttachment.getUnretained() +} + +/// Requests deinitialization of the ``MetalTexture`` instance shared with `libobs` +/// - Parameter zstencil: Opaque pointer to ``MetalTexture`` instance shared with `libobs` +/// +/// The ownership of the shared pointer is transferred into this function and the instance is placed under Swift's +/// memory management again. +@_cdecl("gs_zstencil_destroy") +public func gs_zstencil_destroy(zstencil: UnsafeRawPointer) { + let _ = retained(zstencil) as MetalTexture +}
View file
obs-studio-32.0.0~beta1.tar.xz/libobs/CMakeLists.txt -> obs-studio-32.0.0~beta2.tar.xz/libobs/CMakeLists.txt
Changed
@@ -15,10 +15,6 @@ find_package(ZLIB REQUIRED) find_package(Uthash REQUIRED) -if(ENABLE_UI) - find_package(Qt6 REQUIRED Core) -endif() - find_package(jansson REQUIRED) if(NOT TARGET OBS::caption) add_subdirectory("${CMAKE_SOURCE_DIR}/deps/libcaption" "${CMAKE_BINARY_DIR}/deps/libcaption")
View file
obs-studio-32.0.0~beta1.tar.xz/libobs/cmake/os-macos.cmake -> obs-studio-32.0.0~beta2.tar.xz/libobs/cmake/os-macos.cmake
Changed
@@ -26,7 +26,7 @@ util/threading-posix.h ) -target_compile_options(libobs PUBLIC -Wno-strict-prototypes -Wno-shorten-64-to-32) +target_compile_options(libobs PUBLIC "$<$<NOT:$<COMPILE_LANGUAGE:Swift>>:-Wno-strict-prototypes;-Wno-shorten-64-to-32>") set_property(SOURCE obs-cocoa.m util/platform-cocoa.m PROPERTY COMPILE_OPTIONS -fobjc-arc) set_property(TARGET libobs PROPERTY FRAMEWORK TRUE)
View file
obs-studio-32.0.0~beta1.tar.xz/libobs/data/default.effect -> obs-studio-32.0.0~beta2.tar.xz/libobs/data/default.effect
Changed
@@ -136,6 +136,14 @@ return rgba; } +float4 PSDrawD65P3(VertInOut vert_in) : TARGET +{ + float4 rgba = image.Sample(def_sampler, vert_in.uv); + rgba.rgb = srgb_nonlinear_to_linear(rgba.rgb); + rgba.rgb = d65p3_to_rec709(rgba.rgb); + return rgba; +} + technique Draw { pass @@ -252,3 +260,12 @@ pixel_shader = PSDrawTonemapPQ(vert_in); } } + +technique DrawD65P3 +{ + pass + { + vertex_shader = VSDefault(vert_in); + pixel_shader = PSDrawD65P3(vert_in); + } +}
View file
obs-studio-32.0.0~beta1.tar.xz/libobs/graphics/graphics.h -> obs-studio-32.0.0~beta2.tar.xz/libobs/graphics/graphics.h
Changed
@@ -500,6 +500,7 @@ #define GS_DEVICE_OPENGL 1 #define GS_DEVICE_DIRECT3D_11 2 +#define GS_DEVICE_METAL 3 EXPORT const char *gs_get_device_name(void); EXPORT const char *gs_get_driver_version(void);
View file
obs-studio-32.0.0~beta1.tar.xz/libobs/obs-encoder.h -> obs-studio-32.0.0~beta2.tar.xz/libobs/obs-encoder.h
Changed
@@ -344,9 +344,6 @@ bool (*encode_texture2)(void *data, struct encoder_texture *texture, int64_t pts, uint64_t lock_key, uint64_t *next_key, struct encoder_packet *packet, bool *received_packet); - - /** Pointer to module that generated this encoder **/ - obs_module_t *module; }; EXPORT void obs_register_encoder_s(const struct obs_encoder_info *info, size_t size);
View file
obs-studio-32.0.0~beta1.tar.xz/libobs/obs-module.c -> obs-studio-32.0.0~beta2.tar.xz/libobs/obs-module.c
Changed
@@ -442,7 +442,7 @@ da_push_back(obs->disabled_modules, &item); } -extern void get_plugin_info(const char *path, bool *is_obs_plugin, bool *can_load); +extern void get_plugin_info(const char *path, bool *is_obs_plugin); struct fail_info { struct dstr fail_modules; @@ -497,9 +497,8 @@ obs_module_t *disabled_module; bool is_obs_plugin; - bool can_load_obs_plugin; - get_plugin_info(info->bin_path, &is_obs_plugin, &can_load_obs_plugin); + get_plugin_info(info->bin_path, &is_obs_plugin); if (!is_obs_plugin) { blog(LOG_WARNING, "Skipping module '%s', not an OBS plugin", info->bin_path); @@ -518,14 +517,6 @@ return; } - if (!can_load_obs_plugin) { - blog(LOG_WARNING, - "Skipping module '%s' due to possible " - "import conflicts", - info->bin_path); - goto load_failure; - } - int code = obs_open_module(&module, info->bin_path, info->data_path); switch (code) { case MODULE_MISSING_EXPORTS: @@ -990,7 +981,6 @@ /* NOTE: The assignment of data.module must occur before memcpy! */ if (loadingModule) { - data.module = loadingModule; char *source_id = bstrdup(info->id); da_push_back(loadingModule->sources, &source_id); } @@ -1117,6 +1107,11 @@ strlist_free(protocols); } + if (loadingModule) { + char *output_id = bstrdup(info->id); + da_push_back(loadingModule->outputs, &output_id); + } + return; error:
View file
obs-studio-32.0.0~beta1.tar.xz/libobs/obs-module.h -> obs-studio-32.0.0~beta2.tar.xz/libobs/obs-module.h
Changed
@@ -180,8 +180,8 @@ /** Optional: Returns a description of the module */ MODULE_EXPORT const char *obs_module_description(void); -/** Returns the module's unique ID, or null if it doesn't have one */ +/** Returns the module's unique ID, or NULL if it doesn't have one */ MODULE_EXPORT const char *obs_get_module_id(obs_module_t *module); -/** Returns the module's semver verison number or null if it doesn't have one */ +/** Returns the module's semver version number or NULL if it doesn't have one */ MODULE_EXPORT const char *obs_get_module_version(obs_module_t *module);
View file
obs-studio-32.0.0~beta1.tar.xz/libobs/obs-output.h -> obs-studio-32.0.0~beta2.tar.xz/libobs/obs-output.h
Changed
@@ -85,9 +85,6 @@ /* required if OBS_OUTPUT_SERVICE */ const char *protocols; - - /* Pointer to module that generated this output */ - obs_module_t *module; }; EXPORT void obs_register_output_s(const struct obs_output_info *info, size_t size);
View file
obs-studio-32.0.0~beta1.tar.xz/libobs/obs-scene.c -> obs-studio-32.0.0~beta2.tar.xz/libobs/obs-scene.c
Changed
@@ -3963,11 +3963,21 @@ return source && strcmp(source->info.id, group_info.id) == 0; } +bool obs_source_type_is_group(const char *id) +{ + return id && strcmp(id, group_info.id) == 0; +} + bool obs_source_is_scene(const obs_source_t *source) { return source && strcmp(source->info.id, scene_info.id) == 0; } +bool obs_source_type_is_scene(const char *id) +{ + return id && strcmp(id, scene_info.id) == 0; +} + bool obs_scene_is_group(const obs_scene_t *scene) { return scene ? scene->is_group : false;
View file
obs-studio-32.0.0~beta1.tar.xz/libobs/obs-service.h -> obs-studio-32.0.0~beta2.tar.xz/libobs/obs-service.h
Changed
@@ -104,9 +104,6 @@ const char *(*get_connect_info)(void *data, uint32_t type); bool (*can_try_to_connect)(void *data); - - /* Pointer to module that generated this service */ - obs_module_t *module; }; EXPORT void obs_register_service_s(const struct obs_service_info *info, size_t size);
View file
obs-studio-32.0.0~beta1.tar.xz/libobs/obs-source.c -> obs-studio-32.0.0~beta2.tar.xz/libobs/obs-source.c
Changed
@@ -157,6 +157,12 @@ enum obs_module_load_state obs_source_load_state(const char *id) { + if (!id) + return OBS_MODULE_INVALID; + + if (obs_source_type_is_scene(id) || obs_source_type_is_group(id)) + return OBS_MODULE_ENABLED; + obs_module_t *module = obs_source_get_module(id); if (!module) { return OBS_MODULE_MISSING;
View file
obs-studio-32.0.0~beta1.tar.xz/libobs/obs-source.h -> obs-studio-32.0.0~beta2.tar.xz/libobs/obs-source.h
Changed
@@ -551,9 +551,6 @@ * @param source Source that the filter is being added to */ void (*filter_add)(void *data, obs_source_t *source); - - /** Pointer to module that generated this source **/ - obs_module_t *module; }; EXPORT void obs_register_source_s(const struct obs_source_info *info, size_t size);
View file
obs-studio-32.0.0~beta1.tar.xz/libobs/obs.c -> obs-studio-32.0.0~beta2.tar.xz/libobs/obs.c
Changed
@@ -1852,7 +1852,7 @@ while (source) { obs_source_t *s = obs_source_get_ref(source); if (s) { - if (obs_source_is_scene(source) && !enum_proc(param, s)) { + if (source->info.type == OBS_SOURCE_TYPE_SCENE && !enum_proc(param, s)) { obs_source_release(s); break; } @@ -2258,7 +2258,7 @@ if (!*v_id) v_id = id; - if (strcmp(id, scene_info.id) == 0 || strcmp(id, group_info.id) == 0) { + if (obs_source_type_is_scene(id) || obs_source_type_is_group(id)) { const char *canvas_uuid = obs_data_get_string(source_data, "canvas_uuid"); canvas = obs_get_canvas_by_uuid(canvas_uuid); /* Fall back to main canvas if canvas cannot be found. */
View file
obs-studio-32.0.0~beta1.tar.xz/libobs/obs.h -> obs-studio-32.0.0~beta2.tar.xz/libobs/obs.h
Changed
@@ -1710,6 +1710,7 @@ size_t item_order_size); EXPORT bool obs_source_is_scene(const obs_source_t *source); +EXPORT bool obs_source_type_is_scene(const char *id); /** Adds/creates a new scene item for a source */ EXPORT obs_sceneitem_t *obs_scene_add(obs_scene_t *scene, obs_source_t *source); @@ -1839,6 +1840,7 @@ EXPORT obs_sceneitem_t *obs_sceneitem_get_group(obs_scene_t *scene, obs_sceneitem_t *item); EXPORT bool obs_source_is_group(const obs_source_t *source); +EXPORT bool obs_source_type_is_group(const char *id); EXPORT bool obs_scene_is_group(const obs_scene_t *scene); EXPORT void obs_sceneitem_group_enum_items(obs_sceneitem_t *group,
View file
obs-studio-32.0.0~beta1.tar.xz/libobs/util/platform-nix.c -> obs-studio-32.0.0~beta2.tar.xz/libobs/util/platform-nix.c
Changed
@@ -14,14 +14,6 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include "obsconfig.h" - -#if !defined(__APPLE__) -#define _GNU_SOURCE -#include <link.h> -#include <stdlib.h> -#endif - #include <stdio.h> #include <errno.h> #include <sys/types.h> @@ -37,6 +29,8 @@ #include <signal.h> #include <uuid/uuid.h> +#include "obsconfig.h" + #if !defined(__APPLE__) #include <sys/times.h> #include <sys/wait.h> @@ -82,7 +76,7 @@ dstr_cat(&dylib_name, ".so"); #ifdef __APPLE__ - int dlopen_flags = RTLD_LAZY | RTLD_FIRST; + int dlopen_flags = RTLD_NOW | RTLD_FIRST; if (dstr_find(&dylib_name, "Python")) { dlopen_flags = dlopen_flags | RTLD_GLOBAL; } else { @@ -90,7 +84,7 @@ } void *res = dlopen(dylib_name.array, dlopen_flags); #else - void *res = dlopen(dylib_name.array, RTLD_LAZY); + void *res = dlopen(dylib_name.array, RTLD_NOW); #endif if (!res) blog(LOG_ERROR, "os_dlopen(%s->%s): %s\n", path, dylib_name.array, dlerror()); @@ -110,51 +104,9 @@ dlclose(module); } -#if !defined(__APPLE__) -int module_has_qt5_check(const char *path) -{ - void *mod = os_dlopen(path); - if (mod == NULL) { - return 1; - } - - struct link_map *list = NULL; - if (dlinfo(mod, RTLD_DI_LINKMAP, &list) == 0) { - for (struct link_map *ptr = list; ptr; ptr = ptr->l_next) { - if (strstr(ptr->l_name, "libQt5") != NULL) { - return 0; - } - } - } - - return 1; -} - -bool has_qt5_dependency(const char *path) -{ - pid_t pid = fork(); - if (pid == 0) { - base_set_log_handler(NULL, NULL); - _exit(module_has_qt5_check(path)); - } - if (pid < 0) { - return false; - } - int status; - if (waitpid(pid, &status, 0) < 0) { - return false; - } - return WIFEXITED(status) && WEXITSTATUS(status) == 0; -} -#endif - -void get_plugin_info(const char *path, bool *is_obs_plugin, bool *can_load) +void get_plugin_info(const char *path, bool *is_obs_plugin) { *is_obs_plugin = true; - *can_load = true; -#if !defined(__APPLE__) - *can_load = !has_qt5_dependency(path); -#endif UNUSED_PARAMETER(path); }
View file
obs-studio-32.0.0~beta1.tar.xz/libobs/util/platform-windows.c -> obs-studio-32.0.0~beta2.tar.xz/libobs/util/platform-windows.c
Changed
@@ -27,7 +27,6 @@ #include "platform.h" #include "darray.h" #include "dstr.h" -#include "obsconfig.h" #include "util_uint64.h" #include "windows/win-registry.h" #include "windows/win-version.h" @@ -134,63 +133,6 @@ FreeLibrary(module); } -static bool has_qt5_import(VOID *base, PIMAGE_NT_HEADERS nt_headers) -{ - __try { - PIMAGE_DATA_DIRECTORY data_dir; - data_dir = &nt_headers->OptionalHeader.DataDirectoryIMAGE_DIRECTORY_ENTRY_IMPORT; - - if (data_dir->Size == 0) - return false; - - PIMAGE_SECTION_HEADER section, last_section; - section = IMAGE_FIRST_SECTION(nt_headers); - last_section = section; - - /* find the section that contains the export directory */ - int i; - for (i = 0; i < nt_headers->FileHeader.NumberOfSections; i++) { - if (section->VirtualAddress <= data_dir->VirtualAddress) { - last_section = section; - section++; - continue; - } else { - break; - } - } - - /* double check in case we exited early */ - if (last_section->VirtualAddress > data_dir->VirtualAddress || - section->VirtualAddress <= data_dir->VirtualAddress) - return false; - - section = last_section; - - /* get a pointer to the import directory */ - PIMAGE_IMPORT_DESCRIPTOR import; - import = (PIMAGE_IMPORT_DESCRIPTOR)((byte *)base + data_dir->VirtualAddress - section->VirtualAddress + - section->PointerToRawData); - - while (import->Name != 0) { - char *name = (char *)((byte *)base + import->Name - section->VirtualAddress + - section->PointerToRawData); - - /* qt5? bingo, reject this library */ - if (astrcmpi_n(name, "qt5", 3) == 0) { - return true; - } - - import++; - } - - } __except (EXCEPTION_EXECUTE_HANDLER) { - /* we failed somehow, for compatibility assume no qt5 import */ - return false; - } - - return false; -} - static bool has_obs_export(VOID *base, PIMAGE_NT_HEADERS nt_headers) { __try { @@ -256,7 +198,7 @@ return false; } -void get_plugin_info(const char *path, bool *is_obs_plugin, bool *can_load) +void get_plugin_info(const char *path, bool *is_obs_plugin) { struct dstr dll_name; wchar_t *wpath; @@ -269,7 +211,6 @@ PIMAGE_NT_HEADERS nt_headers; *is_obs_plugin = false; - *can_load = false; if (!path) return; @@ -312,15 +253,10 @@ *is_obs_plugin = has_obs_export(base, nt_headers); - if (*is_obs_plugin) { - *can_load = !has_qt5_import(base, nt_headers); - } - } __except (EXCEPTION_EXECUTE_HANDLER) { /* we failed somehow, for compatibility let's assume it * was a valid plugin and let the loader deal with it */ *is_obs_plugin = true; - *can_load = true; goto cleanup; } @@ -338,11 +274,10 @@ bool os_is_obs_plugin(const char *path) { bool is_obs_plugin; - bool can_load; - get_plugin_info(path, &is_obs_plugin, &can_load); + get_plugin_info(path, &is_obs_plugin); - return is_obs_plugin && can_load; + return is_obs_plugin; } union time_data {
View file
obs-studio-32.0.0~beta1.tar.xz/plugins/mac-avcapture/plugin-main.m -> obs-studio-32.0.0~beta2.tar.xz/plugins/mac-avcapture/plugin-main.m
Changed
@@ -35,11 +35,14 @@ capture_info->settings = settings; capture_info->source = source; + obs_enter_graphics(); if (gs_get_device_type() == GS_DEVICE_OPENGL) { capture_info->effect = obs_get_base_effect(OBS_EFFECT_DEFAULT_RECT); } else { capture_info->effect = obs_get_base_effect(OBS_EFFECT_DEFAULT); } + obs_leave_graphics(); + capture_info->frameSize = CGRectZero; if (!capture_info->effect) {
View file
obs-studio-32.0.0~beta1.tar.xz/plugins/mac-capture/mac-display-capture.m -> obs-studio-32.0.0~beta2.tar.xz/plugins/mac-capture/mac-display-capture.m
Changed
@@ -255,6 +255,8 @@ dc->source = source; dc->hide_cursor = !obs_data_get_bool(settings, "show_cursor"); + obs_enter_graphics(); + if (gs_get_device_type() == GS_DEVICE_OPENGL) { dc->effect = obs_get_base_effect(OBS_EFFECT_DEFAULT_RECT); } else { @@ -264,8 +266,6 @@ if (!dc->effect) goto fail; - obs_enter_graphics(); - struct gs_sampler_info info = { .filter = GS_FILTER_LINEAR, .address_u = GS_ADDRESS_CLAMP,
View file
obs-studio-32.0.0~beta1.tar.xz/plugins/mac-capture/mac-sck-video-capture.m -> obs-studio-32.0.0~beta2.tar.xz/plugins/mac-capture/mac-sck-video-capture.m
Changed
@@ -291,11 +291,13 @@ sc->capture_delegate = ScreenCaptureDelegate alloc init; sc->capture_delegate.sc = sc; + obs_enter_graphics(); if (gs_get_device_type() == GS_DEVICE_OPENGL) { sc->effect = obs_get_base_effect(OBS_EFFECT_DEFAULT_RECT); } else { sc->effect = obs_get_base_effect(OBS_EFFECT_DEFAULT); } + obs_leave_graphics(); if (!sc->effect) goto fail; @@ -311,7 +313,6 @@ return sc; fail: - obs_leave_graphics(); sck_video_capture_destroy(sc); return NULL; }
View file
obs-studio-32.0.0~beta1.tar.xz/plugins/mac-syphon/syphon.m -> obs-studio-32.0.0~beta2.tar.xz/plugins/mac-syphon/syphon.m
Changed
@@ -312,13 +312,13 @@ obs_enter_graphics(); s->sampler = gs_samplerstate_create(&info); s->vertbuffer = create_vertbuffer(); - obs_leave_graphics(); if (gs_get_device_type() == GS_DEVICE_OPENGL) { s->effect = obs_get_base_effect(OBS_EFFECT_DEFAULT_RECT); } else { s->effect = obs_get_base_effect(OBS_EFFECT_DEFAULT); } + obs_leave_graphics(); return s->sampler != NULL && s->vertbuffer != NULL && s->effect != NULL; }
View file
obs-studio-32.0.0~beta1.tar.xz/plugins/nv-filters/nvidia-audiofx-filter.c -> obs-studio-32.0.0~beta2.tar.xz/plugins/nv-filters/nvidia-audiofx-filter.c
Changed
@@ -130,10 +130,15 @@ { struct nvidia_audio_data *ng = data; + if (!ng) + return; + if (ng->nvidia_sdk_dir_found) pthread_mutex_lock(&ng->nvafx_mutex); - NvAFX_UninitializeLogger(); + if (nvafx_new_sdk) + NvAFX_UninitializeLogger(); + for (size_t i = 0; i < ng->channels; i++) { if (ng->handle0) { if (NvAFX_DestroyEffect) {
View file
obs-studio-32.0.0~beta1.tar.xz/plugins/obs-ffmpeg/obs-ffmpeg-mpegts.c -> obs-studio-32.0.0~beta2.tar.xz/plugins/obs-ffmpeg/obs-ffmpeg-mpegts.c
Changed
@@ -863,8 +863,8 @@ const char *p; char buf1024; p = strchr(config->url, '?'); - if (av_find_info_tag(buf, sizeof(buf), "payload_size", p) || - av_find_info_tag(buf, sizeof(buf), "pkt_size", p)) { + if (p && (av_find_info_tag(buf, sizeof(buf), "payload_size", p) || + av_find_info_tag(buf, sizeof(buf), "pkt_size", p))) { config->srt_pkt_size = strtol(buf, NULL, 10); } return true;
View file
obs-studio-32.0.0~beta1.tar.xz/plugins/rtmp-services/data/package.json -> obs-studio-32.0.0~beta2.tar.xz/plugins/rtmp-services/data/package.json
Changed
@@ -1,11 +1,11 @@ { "$schema": "schema/package-schema.json", "url": "https://obsproject.com/obs2_update/rtmp-services/v5", - "version": 273, + "version": 274, "files": { "name": "services.json", - "version": 273 + "version": 274 } }
View file
obs-studio-32.0.0~beta1.tar.xz/plugins/rtmp-services/data/services.json -> obs-studio-32.0.0~beta2.tar.xz/plugins/rtmp-services/data/services.json
Changed
@@ -2409,24 +2409,6 @@ }, { - "name": "Live Streamer Cafe", - "more_info_link": "https://livestreamercafe.com/help.php", - "stream_key_link": "https://livestreamercafe.com/profile.php", - "servers": - { - "name": "Live Streamer Cafe Server", - "url": "rtmp://tophicles.com/live" - } - , - "recommended": { - "keyint": 2, - "max video bitrate": 6000 - }, - "supported video codecs": - "h264" - - }, - { "name": "Enchant.events", "more_info_link": "https://docs.enchant.events/knowledge-base-y4pOb", "servers":
View file
obs-studio-32.0.0~beta1.tar.xz/shared/qt/idian/components/ComboBox.cpp -> obs-studio-32.0.0~beta2.tar.xz/shared/qt/idian/components/ComboBox.cpp
Changed
@@ -46,7 +46,7 @@ // // All my efforts have failed so we get this instead. allowOpeningPopup = false; - QTimer::singleShot(120, this, =() { allowOpeningPopup = true; }); + QTimer::singleShot(120, this, this() { allowOpeningPopup = true; }); QComboBox::hidePopup(); }
View file
obs-studio-32.0.0~beta1.tar.xz/shared/qt/idian/include/Idian/Row.hpp -> obs-studio-32.0.0~beta2.tar.xz/shared/qt/idian/include/Idian/Row.hpp
Changed
@@ -40,6 +40,9 @@ public: GenericRow(QWidget *parent = nullptr) : QFrame(parent), Utils(this) { setAccessibleName(""); }; + + virtual void setTitle(const QString &title) = 0; + virtual void setDescription(const QString &description) = 0; }; // Row widget containing one or more controls @@ -61,8 +64,8 @@ void setPrefixEnabled(bool enabled); void setSuffixEnabled(bool enabled); - void setTitle(QString name); - void setDescription(QString description); + virtual void setTitle(const QString &title) override; + virtual void setDescription(const QString &description) override; void showTitle(bool visible); void showDescription(bool visible); @@ -171,14 +174,22 @@ Q_OBJECT public: - CollapsibleRow(const QString &name, QWidget *parent = nullptr); - CollapsibleRow(const QString &name, const QString &desc = nullptr, QWidget *parent = nullptr); + CollapsibleRow(QWidget *parent = nullptr); void setCheckable(bool check); bool isCheckable() { return checkable; } + void setChecked(bool checked); + bool isChecked() { return toggleSwitch->isChecked(); }; + + virtual void setTitle(const QString &title) override; + virtual void setDescription(const QString &description) override; + void addRow(GenericRow *actionRow); +signals: + void toggled(bool checked); + private: void toggleVisibility();
View file
obs-studio-32.0.0~beta1.tar.xz/shared/qt/idian/widgets/Group.cpp -> obs-studio-32.0.0~beta2.tar.xz/shared/qt/idian/widgets/Group.cpp
Changed
@@ -116,7 +116,7 @@ toggleSwitch = new ToggleSwitch(true); controlLayout->addWidget(toggleSwitch); connect(toggleSwitch, &ToggleSwitch::toggled, this, - =(bool checked) { propertyList->setEnabled(checked); }); + this(bool checked) { propertyList->setEnabled(checked); }); } if (!checkable && toggleSwitch) {
View file
obs-studio-32.0.0~beta1.tar.xz/shared/qt/idian/widgets/Row.cpp -> obs-studio-32.0.0~beta2.tar.xz/shared/qt/idian/widgets/Row.cpp
Changed
@@ -112,14 +112,14 @@ suffix_->setVisible(enabled); } -void Row::setTitle(QString name) +void Row::setTitle(const QString &name) { nameLabel->setText(name); setAccessibleName(name); showTitle(true); } -void Row::setDescription(QString description) +void Row::setDescription(const QString &description) { descriptionLabel->setText(description); setAccessibleDescription(description); @@ -260,7 +260,7 @@ } // Row variant that can be expanded to show another properties list -CollapsibleRow::CollapsibleRow(const QString &name, QWidget *parent) : GenericRow(parent) +CollapsibleRow::CollapsibleRow(QWidget *parent) : GenericRow(parent) { layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 0); @@ -274,7 +274,6 @@ rowWidget->setLayout(rowLayout); actionRow = new Row(); - actionRow->setTitle(name); actionRow->setChangeCursor(false); rowLayout->addWidget(actionRow); @@ -301,15 +300,9 @@ actionRow->setFocusProxy(expandButton); connect(expandButton, &QAbstractButton::clicked, this, &CollapsibleRow::toggleVisibility); - connect(actionRow, &Row::clicked, expandButton, &QAbstractButton::click); } -CollapsibleRow::CollapsibleRow(const QString &name, const QString &desc, QWidget *parent) : CollapsibleRow(name, parent) -{ - actionRow->setDescription(desc); -} - void CollapsibleRow::setCheckable(bool check) { checkable = check; @@ -322,6 +315,7 @@ actionRow->setSuffix(toggleSwitch, false); connect(toggleSwitch, &ToggleSwitch::toggled, propertyList, &PropertiesList::setEnabled); + connect(toggleSwitch, &ToggleSwitch::toggled, this, &CollapsibleRow::toggled); } if (!checkable && toggleSwitch) { @@ -332,6 +326,25 @@ } } +void CollapsibleRow::setChecked(bool checked) +{ + if (!isCheckable()) { + throw std::logic_error("Called setChecked on a non-checkable row."); + } + + toggleSwitch->setChecked(checked); +} + +void CollapsibleRow::setTitle(const QString &name) +{ + actionRow->setTitle(name); +} + +void CollapsibleRow::setDescription(const QString &description) +{ + actionRow->setDescription(description); +} + void CollapsibleRow::toggleVisibility() { bool visible = !propertyList->isVisible();
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.