[vlc-commits] [Git][videolan/vlc][master] 14 commits: macosx: Add rule to build metal files

Felix Paul Kühne (@fkuehne) gitlab at videolan.org
Wed Jun 4 18:23:34 UTC 2025



Felix Paul Kühne pushed to branch master at VideoLAN / VLC


Commits:
f2a84fdb by Claudio Cambra at 2025-06-04T17:55:44+00:00
macosx: Add rule to build metal files

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
768e0a55 by Claudio Cambra at 2025-06-04T17:55:44+00:00
macosx: Add starter VLCSnowEffectView

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
011922e9 by Claudio Cambra at 2025-06-04T17:55:44+00:00
macosx: Add shader types

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
f508d0c1 by Claudio Cambra at 2025-06-04T17:55:44+00:00
macosx: Incorporate CoreGraphics, Metal, MetalKit

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
92d6a322 by Claudio Cambra at 2025-06-04T17:55:44+00:00
macosx: Add snow metal shader file

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
cf39653b by Claudio Cambra at 2025-06-04T17:55:44+00:00
macosx: Configure metal device and library

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
b9af8c7f by Claudio Cambra at 2025-06-04T17:55:44+00:00
macosx: Set up metal components in snow effect view

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
3517e34d by Claudio Cambra at 2025-06-04T17:55:44+00:00
macosx: Make the snow effect view see-through

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
40dce246 by Claudio Cambra at 2025-06-04T17:55:44+00:00
macosx: Do not attempt to load metal library if bundle path is nil

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
ec3ba46f by Claudio Cambra at 2025-06-04T17:55:44+00:00
macosx: Define winterHolidaysTheming property in VLCApplication

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
336d583a by Claudio Cambra at 2025-06-04T17:55:44+00:00
macosx: Instantiate and present snow view when relevant

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
1c9ed3b6 by Claudio Cambra at 2025-06-04T17:55:44+00:00
macosx: Check for Metal tool availability

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

macosx: Check for metallib availability

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
9a01b3e4 by Claudio Cambra at 2025-06-04T17:55:44+00:00
macosx: Make building snow shader toggleable

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
b6abafe5 by Claudio Cambra at 2025-06-04T17:55:44+00:00
macosx: Only instantiate metal library during winter holidays period

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -


13 changed files:

- configure.ac
- extras/package/macosx/VLC.xcodeproj/project.pbxproj
- extras/package/macosx/package.mak
- modules/gui/macosx/Makefile.am
- modules/gui/macosx/library/VLCLibraryWindow.m
- modules/gui/macosx/main/VLCApplication.h
- modules/gui/macosx/main/VLCApplication.m
- modules/gui/macosx/main/VLCMain.h
- modules/gui/macosx/main/VLCMain.m
- + modules/gui/macosx/shaders/VLCShaderTypes.h
- + modules/gui/macosx/shaders/VLCSnowShader.metal
- + modules/gui/macosx/views/VLCSnowEffectView.h
- + modules/gui/macosx/views/VLCSnowEffectView.m


Changes:

=====================================
configure.ac
=====================================
@@ -4416,6 +4416,44 @@ then
     ])
   ])
 
+  dnl
+  dnl If possible, use xcrun to find the right metal tool
+  dnl
+
+  AS_IF([test ! "${XCRUN}" = "no"], [
+    AC_MSG_CHECKING([for metal (using xcrun)])
+    METAL="$(eval $XCRUN -f metal 2>/dev/null || echo no)"
+    AC_MSG_RESULT([${METAL}])
+  ], [
+    AC_MSG_WARN([Looking for tools without using xcrun])
+  ])
+
+  AS_IF([test "${METAL}" = "no"], [
+    AC_PATH_PROG(METAL, [metal], [no])
+    AS_IF([test "${METAL}" = "no"], [
+      AC_MSG_ERROR([metal was not found, but is required for --enable-macosx])
+    ])
+  ])
+
+  dnl
+  dnl If possible, use xcrun to find the right metallib tool
+  dnl
+
+  AS_IF([test ! "${XCRUN}" = "no"], [
+    AC_MSG_CHECKING([for metallib (using xcrun)])
+    METALLIB="$(eval $XCRUN -f metallib 2>/dev/null || echo no)"
+    AC_MSG_RESULT([${METALLIB}])
+  ], [
+    AC_MSG_WARN([Looking for tools without using xcrun])
+  ])
+
+  AS_IF([test "${METALLIB}" = "no"], [
+    AC_PATH_PROG(METALLIB, [metallib], [no])
+    AS_IF([test "${METALLIB}" = "no"], [
+      AC_MSG_ERROR([metallib was not found, but is required for --enable-macosx])
+    ])
+  ])
+
   dnl
   dnl If possible, use xcrun to find the right actool
   dnl
@@ -4434,7 +4472,25 @@ then
       AC_MSG_ERROR([actool was not found, but is required for --enable-macosx])
     ])
   ])
+
+  dnl
+  dnl  MacOS X gui module metal shaders
+  dnl
+  _enable_macosx_ui_shaders_default="yes"
+
+  AC_ARG_ENABLE([macosx-ui-shaders],
+    AS_HELP_STRING([--disable-macosx-ui-shaders], [Disable macOS UI Metal shaders (default: enabled if macOS UI is active)]),
+    [], [enable_macosx_ui_shaders="${_enable_macosx_ui_shaders_default}"]
+  )
+
+  if test "${enable_macosx_ui_shaders}" != "no"
+  then
+    dnl Tool checks for metal and metallib are already performed above if enable_macosx is true.
+    dnl We assume they are found if we reach this point and shaders are enabled.
+    AC_DEFINE([ENABLE_MACOSX_UI_SHADERS], [1], [Define to 1 if macOS UI Metal shaders are enabled.])
+  fi
 fi
+AM_CONDITIONAL([ENABLE_MACOSX_UI_SHADERS], [test "${enable_macosx_ui_shaders}" != "no" -a "${enable_macosx}" != "no" -a "${SYS}" = "darwin"])
 AM_CONDITIONAL([ENABLE_MACOSX_UI], [test "$enable_macosx" != "no" -a "${SYS}" = "darwin"])
 
 dnl


=====================================
extras/package/macosx/VLC.xcodeproj/project.pbxproj
=====================================
@@ -86,6 +86,7 @@
 		531343E72A8E7B94007AEDFA /* VLCLibraryWindowNavigationSidebarViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 531343E62A8E7B94007AEDFA /* VLCLibraryWindowNavigationSidebarViewController.m */; };
 		531343EA2A8E8965007AEDFA /* VLCLibrarySegment.m in Sources */ = {isa = PBXBuildFile; fileRef = 531343E92A8E8965007AEDFA /* VLCLibrarySegment.m */; };
 		5317FE04294E3DD3001702F0 /* VLCLibraryCollectionViewDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5317FE03294E3DD3001702F0 /* VLCLibraryCollectionViewDelegate.m */; };
+		5318F9332DBBB42D0008D9C8 /* VLCSnowShader.metal in Sources */ = {isa = PBXBuildFile; fileRef = 5318F9322DBBB42D0008D9C8 /* VLCSnowShader.metal */; };
 		532572032C3D79D80068DEC3 /* VLCLibrarySegmentBookmarkedLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 532572022C3D79D80068DEC3 /* VLCLibrarySegmentBookmarkedLocation.m */; };
 		532572062C3EF3710068DEC3 /* VLCLibraryWindowNavigationSidebarOutlineView.m in Sources */ = {isa = PBXBuildFile; fileRef = 532572052C3EF3710068DEC3 /* VLCLibraryWindowNavigationSidebarOutlineView.m */; };
 		5325720F2C4966630068DEC3 /* VLCLibraryShowsDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 5325720E2C4966630068DEC3 /* VLCLibraryShowsDataSource.m */; };
@@ -123,6 +124,7 @@
 		536EFBF5295BCB8300F4CB13 /* VLCLibraryUIUnits.m in Sources */ = {isa = PBXBuildFile; fileRef = 536EFBF4295BCB8300F4CB13 /* VLCLibraryUIUnits.m */; };
 		536EFC39295E521600F4CB13 /* VLCLibraryVideoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 536EFC38295E521600F4CB13 /* VLCLibraryVideoViewController.m */; };
 		537040DF2DD848440030ABF5 /* VLCPlaybackEndViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 537040DE2DD848440030ABF5 /* VLCPlaybackEndViewController.m */; };
+		53741A5A2DCA7B4C00E112CD /* VLCSnowEffectView.m in Sources */ = {isa = PBXBuildFile; fileRef = 53741A592DCA7B4B00E112CD /* VLCSnowEffectView.m */; };
 		5377B15C2BD12EEE00D660B8 /* QuickLookThumbnailing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5377B15B2BD12EEE00D660B8 /* QuickLookThumbnailing.framework */; };
 		5377B15E2BD12F2900D660B8 /* QuickLook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5377B15D2BD12F2900D660B8 /* QuickLook.framework */; };
 		537976BA2A4319330036827E /* VLCSettingTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 537976B82A4319330036827E /* VLCSettingTextField.m */; };
@@ -326,6 +328,7 @@
 		5317FE02294E3DD3001702F0 /* VLCLibraryCollectionViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VLCLibraryCollectionViewDelegate.h; sourceTree = "<group>"; };
 		5317FE03294E3DD3001702F0 /* VLCLibraryCollectionViewDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VLCLibraryCollectionViewDelegate.m; sourceTree = "<group>"; };
 		5317FE05294E8D1A001702F0 /* VLCLibraryCollectionViewDataSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VLCLibraryCollectionViewDataSource.h; sourceTree = "<group>"; };
+		5318F9322DBBB42D0008D9C8 /* VLCSnowShader.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = VLCSnowShader.metal; sourceTree = "<group>"; };
 		53222C002CBBDBEE00CB9FA2 /* VLCLibraryWindowSidebarChildViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VLCLibraryWindowSidebarChildViewController.h; sourceTree = "<group>"; };
 		532572012C3D79D80068DEC3 /* VLCLibrarySegmentBookmarkedLocation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VLCLibrarySegmentBookmarkedLocation.h; sourceTree = "<group>"; };
 		532572022C3D79D80068DEC3 /* VLCLibrarySegmentBookmarkedLocation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VLCLibrarySegmentBookmarkedLocation.m; sourceTree = "<group>"; };
@@ -410,6 +413,9 @@
 		536EFC3A295F828000F4CB13 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		537040DD2DD848440030ABF5 /* VLCPlaybackEndViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VLCPlaybackEndViewController.h; sourceTree = "<group>"; };
 		537040DE2DD848440030ABF5 /* VLCPlaybackEndViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VLCPlaybackEndViewController.m; sourceTree = "<group>"; };
+		53741A582DCA7B4B00E112CD /* VLCSnowEffectView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VLCSnowEffectView.h; sourceTree = "<group>"; };
+		53741A592DCA7B4B00E112CD /* VLCSnowEffectView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VLCSnowEffectView.m; sourceTree = "<group>"; };
+		53741A5B2DCD141D00E112CD /* VLCShaderTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VLCShaderTypes.h; sourceTree = "<group>"; };
 		5377B15B2BD12EEE00D660B8 /* QuickLookThumbnailing.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLookThumbnailing.framework; path = System/Library/PrivateFrameworks/QuickLookThumbnailing.framework; sourceTree = SDKROOT; };
 		5377B15D2BD12F2900D660B8 /* QuickLook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLook.framework; path = System/Library/Frameworks/QuickLook.framework; sourceTree = SDKROOT; };
 		537976B82A4319330036827E /* VLCSettingTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VLCSettingTextField.m; sourceTree = "<group>"; };
@@ -1048,6 +1054,7 @@
 				1C1ED5142205A96600811EC0 /* playqueue */,
 				1C1ED5102204B06700811EC0 /* preferences */,
 				6CEA55632C6BA14300CCC2E7 /* private */,
+				5318F9312DBBB3B10008D9C8 /* shaders */,
 				1C1ED5062204AB7C00811EC0 /* views */,
 				1C1ED5072204AC5900811EC0 /* windows */,
 			);
@@ -1173,6 +1180,8 @@
 				6B13E2A71BC67678001AD24A /* VLCScrollingClipView.m */,
 				537976B92A4319330036827E /* VLCSettingTextField.h */,
 				537976B82A4319330036827E /* VLCSettingTextField.m */,
+				53741A582DCA7B4B00E112CD /* VLCSnowEffectView.h */,
+				53741A592DCA7B4B00E112CD /* VLCSnowEffectView.m */,
 				531062172CE4AEFB0087F863 /* VLCStatusNotifierView.h */,
 				531062182CE4AEFB0087F863 /* VLCStatusNotifierView.m */,
 				53628401291147C500640C15 /* VLCSubScrollView.h */,
@@ -1600,6 +1609,15 @@
 			path = iCarousel;
 			sourceTree = "<group>";
 		};
+		5318F9312DBBB3B10008D9C8 /* shaders */ = {
+			isa = PBXGroup;
+			children = (
+				53741A5B2DCD141D00E112CD /* VLCShaderTypes.h */,
+				5318F9322DBBB42D0008D9C8 /* VLCSnowShader.metal */,
+			);
+			path = shaders;
+			sourceTree = "<group>";
+		};
 		5325C5742930026600B2B63A /* audio-library */ = {
 			isa = PBXGroup;
 			children = (
@@ -2339,12 +2357,14 @@
 				5325C57D29302E6800B2B63A /* VLCLibraryAudioViewController.m in Sources */,
 				1C3113981E508C6900D4DD76 /* VLCAudioEffectsWindowController.m in Sources */,
 				53ED472629C78FE700795DB1 /* VLCLibraryAudioGroupTableViewDelegate.m in Sources */,
+				5318F9332DBBB42D0008D9C8 /* VLCSnowShader.metal in Sources */,
 				536283F9291146BC00640C15 /* VLCLibraryCollectionViewFlowLayout.m in Sources */,
 				6BBBF9851F7B257100B404CD /* VLCLogMessage.m in Sources */,
 				7D445D8E2203375100263D34 /* VLCPlayQueueMenuController.m in Sources */,
 				53903D3A29576ED500D0B308 /* VLCLibraryAudioGroupDataSource.m in Sources */,
 				1C31139A1E508C6900D4DD76 /* VLCBookmarksWindowController.m in Sources */,
 				6B0AB0F01F1AC8B3003A1B4E /* VLCPlaybackProgressSlider.m in Sources */,
+				53741A5A2DCA7B4C00E112CD /* VLCSnowEffectView.m in Sources */,
 				7D28E6362275B4820098D30E /* NSColor+VLCAdditions.m in Sources */,
 				534D28B42DB2B3C0006869CD /* NSTableCellView+VLCAdditions.m in Sources */,
 				535F1BBA2B47ACCE00C78D98 /* VLCLibraryHomeViewVideoContainerViewDataSource.m in Sources */,


=====================================
extras/package/macosx/package.mak
=====================================
@@ -31,6 +31,8 @@ VLC.app: macos-install
 	cp -R "$(top_builddir)/modules/gui/macosx/UI/." $@/Contents/Resources/Base.lproj/
 	## Copy Asset catalog
 	cp "$(top_builddir)/modules/gui/macosx/Resources/Assets.car" $@/Contents/Resources/Assets.car
+	## Copy Shaders metal library
+	cp "$(top_builddir)/modules/gui/macosx/Resources/Shaders.metallib" $@/Contents/Resources/Shaders.metallib
 	## Copy Info.plist and convert to binary
 	cp -R "$(top_builddir)/share/macosx/Info.plist" $@/Contents/
 	xcrun plutil -convert binary1 $@/Contents/Info.plist


=====================================
modules/gui/macosx/Makefile.am
=====================================
@@ -36,6 +36,37 @@ xib_verbose__0 = $(xib_verbose_0)
 	  --minimum-deployment-target 10.11 --output-format human-readable-text \
 	  --compile $@ $<
 
+if ENABLE_MACOSX_UI_SHADERS
+SUFFIXES += .air .metal
+
+metal_verbose = $(metal_verbose_$(V))
+metal_verbose_ = $(metal_verbose__$(AM_DEFAULT_VERBOSITY))
+metal_verbose_0 = @echo "  METAL     " $@;
+metal_verbose__0 = $(metal_verbose_0)
+
+# Rule to compile .metal to .air
+.metal.air:
+	$(AM_V_at)$(MKDIR_P) "gui/macosx/shaders"
+	$(metal_verbose)$(METAL) -c $< -o $@
+
+metallib_verbose = $(metallib_verbose_$(V))
+metallib_verbose_ = $(metallib_verbose__$(AM_DEFAULT_VERBOSITY))
+metallib_verbose_0 = @echo "  METALLIB " $@;
+metallib_verbose__0 = $(metallib_verbose_0)
+
+libmacosx_plugin_la_METAL_SOURCES = gui/macosx/shaders/VLCSnowShader.metal
+libmacosx_plugin_la_METAL_AIR_FILES = $(libmacosx_plugin_la_METAL_SOURCES:.metal=.air)
+libmacosx_plugin_la_METAL_LIBRARY = gui/macosx/resources/Shaders.metallib
+
+$(libmacosx_plugin_la_METAL_LIBRARY): $(libmacosx_plugin_la_METAL_AIR_FILES)
+	$(AM_V_at)$(MKDIR_P) "gui/macosx/shaders"
+	$(metallib_verbose)$(METALLIB) $(libmacosx_plugin_la_METAL_AIR_FILES) -o $@
+
+EXTRA_DIST += $(libmacosx_plugin_la_METAL_SOURCES)
+BUILT_SOURCES += $(libmacosx_plugin_la_METAL_LIBRARY)
+CLEANFILES += $(libmacosx_plugin_la_METAL_AIR_FILES) $(libmacosx_plugin_la_METAL_LIBRARY)
+
+endif
 
 libmacosx_plugin_la_OBJCFLAGS = $(AM_OBJCFLAGS) -fobjc-exceptions -fobjc-arc -I$(srcdir)/gui/macosx
 libmacosx_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(guidir)' \
@@ -43,6 +74,7 @@ libmacosx_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(guidir)' \
 	-Wl,-framework,AVFoundation \
 	-Wl,-framework,Cocoa \
 	-Wl,-framework,CoreAudio \
+	-Wl,-framework,CoreGraphics \
 	-Wl,-framework,CoreVideo \
 	-Wl,-framework,IOKit \
 	-Wl,-framework,QuartzCore \
@@ -52,6 +84,11 @@ libmacosx_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(guidir)' \
 	-Wl,-framework,QuickLookThumbnailing \
 	-Wl,-weak_framework,MediaPlayer
 
+if ENABLE_MACOSX_UI_SHADERS
+libmacosx_plugin_la_LDFLAGS += -Wl,-framework,Metal \
+	-Wl,-framework,MetalKit
+endif
+
 if HAVE_SPARKLE
 libmacosx_plugin_la_LDFLAGS += -Wl,-framework,Sparkle
 endif
@@ -369,6 +406,7 @@ libmacosx_plugin_la_SOURCES = \
 	gui/macosx/preferences/prefs.m \
 	gui/macosx/preferences/prefs_widgets.h \
 	gui/macosx/preferences/prefs_widgets.m \
+	gui/macosx/shaders/VLCShaderTypes.h \
 	gui/macosx/views/iCarousel/iCarousel.h \
 	gui/macosx/views/iCarousel/iCarousel.m \
 	gui/macosx/views/VLCBasicView.h \
@@ -407,6 +445,8 @@ libmacosx_plugin_la_SOURCES = \
 	gui/macosx/views/VLCScrollingClipView.m \
 	gui/macosx/views/VLCSettingTextField.h \
 	gui/macosx/views/VLCSettingTextField.m \
+	gui/macosx/views/VLCSnowEffectView.h \
+	gui/macosx/views/VLCSnowEffectView.m \
 	gui/macosx/views/VLCStatusNotifierView.h \
 	gui/macosx/views/VLCStatusNotifierView.m \
 	gui/macosx/views/VLCSubScrollView.h \
@@ -536,7 +576,6 @@ gui_LTLIBRARIES += libmacosx_plugin.la
 BUILT_SOURCES += $(libmacosx_plugin_la_XIB_SOURCES:.xib=.nib)
 endif
 
-
 libmacosx_plugin_la_RES = \
 	gui/macosx/Resources/App-Icons/VLC-Xmas.icns \
 	gui/macosx/Resources/App-Icons/VLC.icns \


=====================================
modules/gui/macosx/library/VLCLibraryWindow.m
=====================================
@@ -31,6 +31,7 @@
 #import "extensions/NSView+VLCAdditions.h"
 #import "extensions/NSWindow+VLCAdditions.h"
 
+#import "main/VLCApplication.h"
 #import "main/VLCMain.h"
 #import "menus/VLCMainMenu.h"
 
@@ -78,6 +79,7 @@
 #import "views/VLCNoResultsLabel.h"
 #import "views/VLCPlaybackEndViewController.h"
 #import "views/VLCRoundedCornerTextField.h"
+#import "views/VLCSnowEffectView.h"
 #import "views/VLCTrackingView.h"
 
 #import "windows/controlsbar/VLCMainWindowControlsBar.h"
@@ -289,6 +291,14 @@ static void addShadow(NSImageView *__unsafe_unretained imageView)
         [view.leftAnchor constraintEqualToAnchor:self.libraryTargetView.leftAnchor],
         [view.rightAnchor constraintEqualToAnchor:self.libraryTargetView.rightAnchor]
     ]];
+
+    if (VLCMain.sharedInstance.metalLibrary && ((VLCApplication *)NSApplication.sharedApplication).winterHolidaysTheming) {
+        VLCSnowEffectView * const snowView =
+            [[VLCSnowEffectView alloc] initWithFrame:self.contentView.bounds];
+        [self.libraryTargetView addSubview:snowView];
+        snowView.translatesAutoresizingMaskIntoConstraints = NO;
+        [snowView applyConstraintsToFillSuperview];
+    }
 }
 
 - (void)displayLibraryPlaceholderViewWithImage:(NSImage *)image


=====================================
modules/gui/macosx/main/VLCApplication.h
=====================================
@@ -43,5 +43,6 @@
  * Must be called from the main thread only.
  */
 @property(strong, readonly) NSImage *vlcAppIconImage;
+ at property(assign, readonly) BOOL winterHolidaysTheming;
 
 @end


=====================================
modules/gui/macosx/main/VLCApplication.m
=====================================
@@ -58,6 +58,18 @@
          * it ends-up after being relocated or rename */
         _appLocationURL = [[[NSBundle mainBundle] bundleURL] fileReferenceURL];
 
+        if (config_GetInt("macosx-icon-change")) {
+            NSCalendar *const gregorian =
+                [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
+            const NSUInteger dayOfYear = [gregorian ordinalityOfUnit:NSCalendarUnitDay
+                                                              inUnit:NSCalendarUnitYear
+                                                             forDate:[NSDate date]];
+
+            if (dayOfYear >= 354) {
+                _winterHolidaysTheming = YES;
+            }
+        }
+
     }
     return self;
 }
@@ -75,16 +87,12 @@
     if (_vlcAppIconImage != nil)
         return _vlcAppIconImage;
 
-    if (config_GetInt("macosx-icon-change")) {
+    if (self.winterHolidaysTheming) {
         /* After day 354 of the year, the usual VLC cone is replaced by another cone
          * wearing a Father Xmas hat.
          * Note: this icon doesn't represent an endorsement of The Coca-Cola Company.
          */
-        NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
-        NSUInteger dayOfYear = [gregorian ordinalityOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitYear forDate:[NSDate date]];
-
-        if (dayOfYear >= 354)
-            _vlcAppIconImage = [NSImage imageNamed:@"VLC-Xmas"];
+        _vlcAppIconImage = [NSImage imageNamed:@"VLC-Xmas"];
     }
 
     if (_vlcAppIconImage == nil)


=====================================
modules/gui/macosx/main/VLCMain.h
=====================================
@@ -28,6 +28,7 @@
 #endif
 
 #import <Cocoa/Cocoa.h>
+#import <MetalKit/MetalKit.h>
 
 #import <vlc_common.h>
 #import <vlc_interface.h>
@@ -91,5 +92,7 @@ extern NSString * const kVLCPreferencesVersion;
 @property (readonly) VLCVideoEffectsWindowController *videoEffectsPanel;
 @property (readonly) VLCVideoOutputProvider *voutProvider;
 @property (readonly) VLCDetachedAudioWindow *detachedAudioWindow;
+ at property (readonly) id<MTLDevice> metalDevice;
+ at property (readonly) id<MTLLibrary> metalLibrary;
 
 @end


=====================================
modules/gui/macosx/main/VLCMain.m
=====================================
@@ -44,6 +44,8 @@
 #include <vlc_variables.h>
 #include <vlc_preparser.h>
 
+#include <Metal/Metal.h>
+
 #import "extensions/NSString+Helpers.h"
 
 #import "library/VLCLibraryController.h"
@@ -286,6 +288,23 @@ static VLCMain *sharedInstance = nil;
 
 - (void)applicationWillFinishLaunching:(NSNotification *)aNotification
 {
+    // Only Metal shader in use at the moment is for holiday theming, so only load during this time.
+    // Change this if you are going to add new shaders!
+    if (((VLCApplication *)NSApplication.sharedApplication).winterHolidaysTheming) {
+        _metalDevice = MTLCreateSystemDefaultDevice();
+        NSString * const libraryPath =
+            [NSBundle.mainBundle pathForResource:@"Shaders" ofType:@"metallib"];
+        if (libraryPath) {
+            NSError *error = nil;
+            _metalLibrary = [_metalDevice newLibraryWithFile:libraryPath error:&error];
+            if (!_metalLibrary) {
+                NSLog(@"Error creating Metal library: %@", error);
+            }
+        } else {
+            NSLog(@"Error: Could not find Shaders.metallib in the bundle.");
+        }
+    }
+
     _clickerManager = [[VLCClickerManager alloc] init];
 
     [[NSBundle mainBundle] loadNibNamed:@"MainMenu" owner:_mainmenu topLevelObjects:nil];


=====================================
modules/gui/macosx/shaders/VLCShaderTypes.h
=====================================
@@ -0,0 +1,54 @@
+/*****************************************************************************
+ * VLCShaderTypes.h: MacOS X interface module
+ *****************************************************************************
+ * Copyright (C) 2025 VLC authors and VideoLAN
+ *
+ * Authors: Claudio Cambra <developer at claudiocambra.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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifndef VLCShaderTypes_h
+#define VLCShaderTypes_h
+
+#include <simd/simd.h>
+
+// These structs are shared between C/Objective-C and Metal.
+
+typedef struct {
+    vector_float3 initialPosition; // x, y (initial screen position), z (depth/parallax factor 0.0-1.0)
+    float randomSeed;              // (0.0-1.0)
+} Snowflake;
+
+typedef struct {
+    float time;                    // Current animation time
+    vector_float2 resolution;      // Viewport width and height
+} Uniforms;
+
+typedef struct {
+    vector_float2 position;        // Local offset for the quad vertex (e.g., -0.5 to 0.5)
+} VertexIn;
+
+
+#ifdef __METAL_VERSION__
+
+typedef struct {
+    float4 position       [[position]]; // Clip-space position (required output for vertex shader)
+    float2 texCoord;                    // Texture coordinate (0-1) across the snowflake quad
+} VertexOut;
+
+#endif // __METAL_VERSION__
+
+#endif


=====================================
modules/gui/macosx/shaders/VLCSnowShader.metal
=====================================
@@ -0,0 +1,81 @@
+/*****************************************************************************
+ * VLCSnowShader.metal: MacOS X interface module
+ *****************************************************************************
+ * Copyright (C) 2025 VLC authors and VideoLAN
+ *
+ * Authors: Claudio Cambra <developer at claudiocambra.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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#include <metal_stdlib>
+using namespace metal;
+
+#include "VLCShaderTypes.h"
+
+// It calculates the final screen position for that vertex of that snowflake.
+vertex VertexOut snowVertexShader(
+    uint vid [[vertex_id]],     // Index of the current vertex in the input buffer (0-5 for a quad)
+    uint iid [[instance_id]],   // Index of the current instance (snowflake)
+    constant VertexIn *vertices [[buffer(0)]],             // Per-vertex data (quad corners)
+    constant Snowflake *snowflakes [[buffer(1)]],          // Per-instance snowflake data
+    constant Uniforms &uniforms [[buffer(2)]]              // Global uniforms
+) {
+    VertexOut out;
+
+    Snowflake currentSnowflake = snowflakes[iid];
+    const float fallSpeed = 0.15 + currentSnowflake.randomSeed * 0.1;
+    // Closer flakes (larger z) fall faster & are bigger
+    const float parallaxFactor = 0.5 + currentSnowflake.initialPosition.z * 1.5;
+
+    // Calculate current Y position (falling down, wrapping around)
+    float currentYNormalized = currentSnowflake.initialPosition.y - (uniforms.time * fallSpeed * parallaxFactor);
+    currentYNormalized = fmod(currentYNormalized, 2.0f); // Wrap around a range of 2.0 (e.g. +1 to -1)
+    if (currentYNormalized < -1.0f) { // Ensure it wraps from bottom to top correctly
+        currentYNormalized += 2.0f;
+    }
+
+    // Horizontal sway using sine wave based on time and random seed
+    const float swayAmount = 0.05 + currentSnowflake.randomSeed * 0.05;
+    const float swayFrequency = 1.0 + currentSnowflake.randomSeed * 0.5;
+    const float currentXNormalized = currentSnowflake.initialPosition.x + sin(uniforms.time * swayFrequency + currentSnowflake.randomSeed * 10.0) * swayAmount;
+
+    // Make closer flakes appear larger
+    const float flakeBaseSize = 0.02; // Base size in NDC
+    const float flakeScreenSize = flakeBaseSize * parallaxFactor;
+
+    const vector_float2 quadVertexOffset = vertices[vid].position; // e.g., from -0.5 to 0.5
+
+    const float aspectRatio = uniforms.resolution.x / uniforms.resolution.y;
+    vector_float2 finalPositionNDC;
+    finalPositionNDC.x = currentXNormalized + (quadVertexOffset.x * flakeScreenSize);
+    finalPositionNDC.y = currentYNormalized + (quadVertexOffset.y * flakeScreenSize * aspectRatio) ; // Apply aspect ratio to y scaling of quad
+
+    out.position = float4(finalPositionNDC.x, finalPositionNDC.y, currentSnowflake.initialPosition.z, 1.0);
+    out.texCoord = vertices[vid].position + 0.5; // Convert -0.5..0.5 to 0..1
+    return out;
+}
+
+// Takes data from the vertex shader (VertexOut) for the current pixel
+// and determines its final color.
+fragment float4 snowFragmentShader(
+    VertexOut in [[stage_in]],
+    constant Uniforms &uniforms [[buffer(0)]] // Uniforms can also be accessed here if needed
+) {
+    const float dist = distance(in.texCoord, float2(0.5));
+    // Create a smooth alpha falloff for a soft circle
+    const float alpha = 0.75 - smoothstep(0.0, 0.5, dist);
+    return float4(1.0, 1.0, 1.0, alpha);
+}


=====================================
modules/gui/macosx/views/VLCSnowEffectView.h
=====================================
@@ -0,0 +1,34 @@
+/*****************************************************************************
+ * VLCSnowEffectView.h: MacOS X interface module
+ *****************************************************************************
+ * Copyright (C) 2025 VLC authors and VideoLAN
+ *
+ * Authors: Claudio Cambra <developer at claudiocambra.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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#import <Cocoa/Cocoa.h>
+#import <MetalKit/MetalKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+ at interface VLCSnowEffectView : NSView<MTKViewDelegate>
+
+ at property MTKView *mtkView;
+
+ at end
+
+NS_ASSUME_NONNULL_END


=====================================
modules/gui/macosx/views/VLCSnowEffectView.m
=====================================
@@ -0,0 +1,238 @@
+/*****************************************************************************
+ * VLCSnowEffectView.m: MacOS X interface module
+ *****************************************************************************
+ * Copyright (C) 2025 VLC authors and VideoLAN
+ *
+ * Authors: Claudio Cambra <developer at claudiocambra.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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#import "VLCSnowEffectView.h"
+
+#import "main/VLCMain.h"
+#import "shaders/VLCShaderTypes.h"
+
+ at interface VLCSnowEffectView ()
+
+ at property (readonly) CFTimeInterval startTime;
+ at property (readonly) id<MTLCommandQueue> commandQueue;
+ at property (readonly) id<MTLFunction> vertexFunction;
+ at property (readonly) id<MTLFunction> fragmentFunction;
+ at property (readonly) id<MTLRenderPipelineState> pipelineState;
+ at property (readonly) id<MTLBuffer> vertexBuffer;
+ at property (readonly) id<MTLBuffer> uniformBuffer;
+ at property (readonly) id<MTLBuffer> snowflakeDataBuffer;
+ at property (readonly) NSUInteger snowflakeCount;
+
+ at end
+
+ at implementation VLCSnowEffectView
+
+- (instancetype)init
+{
+    self = [super init];
+    if (self) {
+        [self setup];
+    }
+    return self;
+}
+
+- (instancetype)initWithCoder:(NSCoder *)coder
+{
+    self = [super initWithCoder:coder];
+    if (self) {
+        [self setup];
+    }
+    return self;
+}
+
+- (void)encodeWithCoder:(nonnull NSCoder *)coder
+{
+    return [super encodeWithCoder:coder];
+}
+
+- (instancetype)initWithFrame:(NSRect)frameRect
+{
+    self = [super initWithFrame:frameRect];
+    if (self) {
+        [self setup];
+    }
+    return self;
+}
+
+- (void)setup
+{
+    self.wantsLayer = YES;
+    self.layer.backgroundColor = NSColor.clearColor.CGColor;
+
+    VLCMain * const main = VLCMain.sharedInstance;
+    const id<MTLDevice> metalDevice = main.metalDevice;
+    NSParameterAssert(metalDevice != nil);
+    NSParameterAssert(main.metalLibrary != nil);
+
+    _mtkView = [[MTKView alloc] initWithFrame:self.bounds device:metalDevice];
+    self.mtkView.delegate = self;
+    self.mtkView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
+    self.mtkView.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
+    self.mtkView.clearColor = MTLClearColorMake(0, 0, 0, 0);
+    ((CAMetalLayer *)self.mtkView.layer).opaque = NO;
+    self.mtkView.layer.backgroundColor = NSColor.clearColor.CGColor;
+    [self addSubview:self.mtkView];
+
+    _commandQueue = [metalDevice newCommandQueue];
+    NSParameterAssert(self.commandQueue != nil);
+    _vertexFunction = [main.metalLibrary newFunctionWithName:@"snowVertexShader"];
+    NSParameterAssert(self.vertexFunction != nil);
+    _fragmentFunction = [main.metalLibrary newFunctionWithName:@"snowFragmentShader"];
+    NSParameterAssert(self.fragmentFunction != nil);
+
+    MTLRenderPipelineDescriptor * const pipelineDescriptor =
+        [[MTLRenderPipelineDescriptor alloc] init];
+    pipelineDescriptor.label = @"Snow Pipeline";
+    pipelineDescriptor.vertexFunction = self.vertexFunction;
+    pipelineDescriptor.fragmentFunction = self.fragmentFunction;
+    pipelineDescriptor.colorAttachments[0].pixelFormat = self.mtkView.colorPixelFormat;
+    pipelineDescriptor.colorAttachments[0].blendingEnabled = YES;
+    pipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
+    pipelineDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
+    pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
+    pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha;
+    pipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOne;
+    pipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOne;
+
+    NSError *error;
+    _pipelineState =
+        [metalDevice newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error];
+    NSParameterAssert(error == nil);
+    NSParameterAssert(pipelineDescriptor != nil);
+
+    // --- Vertex Buffer Setup ---
+    // Example: Define vertices for a single small quad (to be instanced for snowflakes)
+    // You might draw points instead, which simplifies this.
+    // Format: {x, y, u, v} - position and texture coordinate
+    static const VertexIn quadVertices[] = {
+        // Triangle 1 (positions from -0.5 to 0.5 for a unit quad centered at origin)
+        { .position = {-0.5f,  0.5f} }, // Top-left
+        { .position = {-0.5f, -0.5f} }, // Bottom-left
+        { .position = { 0.5f, -0.5f} }, // Bottom-right
+        // Triangle 2
+        { .position = { 0.5f, -0.5f} }, // Bottom-right
+        { .position = { 0.5f,  0.5f} }, // Top-right
+        { .position = {-0.5f,  0.5f} }  // Top-left
+    };
+    _vertexBuffer = [metalDevice newBufferWithBytes:quadVertices
+                                             length:sizeof(quadVertices)
+                                            options:MTLResourceStorageModeShared]; // CPU & GPU
+    NSParameterAssert(self.vertexBuffer != nil);
+
+    // --- Uniform Buffer Setup ---
+    // Create a buffer large enough for your Uniforms struct
+    // The struct definition would typically be in a shared header (.h)
+    // included by both Objective-C and your .metal file.
+    _uniformBuffer = [metalDevice newBufferWithLength:sizeof(Uniforms) // Assumes Uniforms struct exists
+                                              options:MTLResourceStorageModeShared];
+    NSParameterAssert(self.uniformBuffer != nil);
+
+    _snowflakeCount = 200; // Or however many you want
+    Snowflake * const snowflakesArray =
+        (Snowflake *)malloc(sizeof(Snowflake) * self.snowflakeCount);
+    for (NSUInteger i = 0; i < self.snowflakeCount; ++i) {
+        Snowflake flake;
+        flake.initialPosition.x = ((float)arc4random_uniform(2000) / 1000.0f) - 1.0f;
+        flake.initialPosition.y = ((float)arc4random_uniform(2000) / 1000.0f) - 1.0f;
+        flake.initialPosition.z = (float)arc4random_uniform(1000) / 1000.0f;
+        flake.randomSeed = (float)arc4random_uniform(1000) / 1000.0f;
+        snowflakesArray[i] = flake;
+    }
+    _snowflakeDataBuffer = [metalDevice newBufferWithBytes:snowflakesArray // Use self.
+                                                    length:sizeof(Snowflake) * _snowflakeCount
+                                                   options:MTLResourceStorageModeShared];
+    free(snowflakesArray);
+    NSParameterAssert(self.snowflakeDataBuffer != nil);
+
+    self.mtkView.paused = NO;
+    self.mtkView.enableSetNeedsDisplay = NO;
+    _startTime = CACurrentMediaTime();
+}
+
+- (BOOL)isOpaque
+{
+    return NO;
+}
+
+// MARK: - MTKViewDelegate
+
+- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size
+{
+    Uniforms * const uniforms = (Uniforms *)self.uniformBuffer.contents;
+    uniforms->resolution = (vector_float2){(float)size.width, (float)size.height};
+}
+
+- (void)drawInMTKView:(MTKView *)view
+{
+    // 1. Create Command Buffer
+    const id<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer];
+    commandBuffer.label = @"Snow Frame Command Buffer";
+
+    // 2. Get Render Pass Descriptor (specifies the drawing target)
+    MTLRenderPassDescriptor * const renderPassDescriptor = view.currentRenderPassDescriptor;
+    if (!renderPassDescriptor) {
+        NSLog(@"Failed to get render pass descriptor");
+        return; // Skip frame if view isn't ready
+    }
+
+    // 3. Create Render Command Encoder
+    const id<MTLRenderCommandEncoder> encoder =
+        [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
+    encoder.label = @"Snow Render Encoder";
+
+    // 4. Set Pipeline State
+    [encoder setRenderPipelineState:self.pipelineState];
+
+    // 5. Update and Set Uniforms
+    Uniforms * const uniforms = (Uniforms *)[self.uniformBuffer contents];
+    uniforms->time = (float)(CACurrentMediaTime() - _startTime); // Calculate elapsed time
+    uniforms->resolution = (vector_float2){(float)view.drawableSize.width, (float)view.drawableSize.height};
+
+    // Set buffers in indeces matching those defined in the shader
+    [encoder setVertexBuffer:self.uniformBuffer offset:0 atIndex:2];
+    [encoder setVertexBuffer:self.snowflakeDataBuffer offset:0 atIndex:1];
+    [encoder setVertexBuffer:_vertexBuffer offset:0 atIndex:0]; // Assuming buffer(0) for vertices
+
+    [encoder setFragmentBuffer:self.uniformBuffer offset:0 atIndex:0];
+
+    // 6. Draw Call
+    // Draw multiple instances of the quad/point, one for each snowflake.
+    // The vertex shader uses [[instance_id]] to position each one.
+    [encoder drawPrimitives:MTLPrimitiveTypeTriangle // Or MTLPrimitiveTypePoint
+                vertexStart:0
+                vertexCount:6 // 6 vertices for the quad example
+              instanceCount:self.snowflakeCount];
+
+    // 7. End Encoding
+    [encoder endEncoding];
+
+    // 8. Present Drawable
+    if (view.currentDrawable) {
+        [commandBuffer presentDrawable:view.currentDrawable];
+    }
+
+    // 9. Commit Command Buffer
+    [commandBuffer commit];
+    [commandBuffer waitUntilCompleted]; // Optional: Wait for completion (useful for debugging)
+}
+
+ at end



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/6642ae5ef4f0196db272109d36a748e9c634d4f0...b6abafe591117493148655b63256a65cf9616433

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/6642ae5ef4f0196db272109d36a748e9c634d4f0...b6abafe591117493148655b63256a65cf9616433
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list