Skip to content

A ObjC framework for creating macOS screensavers for macOS 11 Big Sur to current.

Notifications You must be signed in to change notification settings

fuzzywalrus/ScreenSaverKit

Repository files navigation

ScreenSaverKit

ScreenSaverKit is a lightweight helper layer for building macOS ScreenSaver modules without having to re-implement the plumbing that every saver needs. Treat it as a starting point that you can copy into any new screensaver project. It gives you access to both CPU and Metal-accelerated rendering paths, built-in preference management, configuration sheet scaffolding, and a particle system.

There's plenty of demo savers included to illustrate how to use the various features, plus a complete tutorial in tutorial.md that walks you through building your first saver from scratch.

Currently this is in alpha development, so expect possible breaking changes in future releases. Feedback and contributions are welcome!

Hello World Demo

What you get

  • ✅ Automatic default registration and preference persistence
  • ✅ Cross-process preference change monitoring (System Settings ↔ saver engine)
  • ✅ Convenience accessors for reading/writing ScreenSaverDefaults
  • ✅ Proper animation start/stop handling across preview, WallpaperAgent and ScreenSaverEngine hosts
  • ✅ Asset loading helpers, animation timing utilities, entity pooling, and diagnostics hooks
  • ✅ Pre-built configuration sheet scaffolding with preference binding helpers
  • ✅ Hardware-accelerated particle system with Metal rendering support
  • ✅ Color palette management and interpolation utilities
  • ✅ Vector math helpers for smooth animations

Keeping these concerns in one place lets each screensaver focus on drawing and behavior instead of boilerplate.

Starfield Preferences

Getting Started

New to ScreenSaverKit? Check out the complete tutorial for a step-by-step walkthrough that covers building your first screen saver, understanding the code, debugging, and creating your own custom savers.

How to use it

  1. Copy the kit
    Grab the ScreenSaverKit/ directory and drop it into your saver project.

  2. Subclass SSKScreenSaverView

    #import "ScreenSaverKit/SSKScreenSaverView.h"
    
    @interface SimpleLinesView : SSKScreenSaverView
    @end
  3. Provide defaults

    - (NSDictionary<NSString *, id> *)defaultPreferences {
        return @{
            @"lineCount": @200,
            @"colorRate": @0.2
        };
    }
  4. React to preference changes

    Whenever the user changes a setting (even from the System Settings pane) the kit calls back into your saver:

    - (void)preferencesDidChange:(NSDictionary<NSString *, id> *)prefs
                     changedKeys:(NSSet<NSString *> *)changed {
        self.lineCount = [prefs[@"lineCount"] integerValue];
        self.colorRate = [prefs[@"colorRate"] doubleValue];
    
        if ([changed containsObject:@"lineCount"]) {
            [self rebuildLines];
        }
    }
  5. Draw as normal

    Implement drawRect:, animateOneFrame, etc. just as you would in a plain ScreenSaverView subclass. For smooth timing call NSTimeInterval dt = [self advanceAnimationClock]; in -animateOneFrame and use the returned delta.

Helper modules

  • SSKAssetManager – cached bundle resource lookup with extension fallbacks for images/data. Available via self.assetManager on the saver view.
  • SSKAnimationClock – smooth delta-time tracking and FPS reporting. Call NSTimeInterval dt = [self advanceAnimationClock]; inside -animateOneFrame and inspect self.animationClock.framesPerSecond.
  • SSKEntityPool – simple object pooling for sprites/particles. Create pools with makeEntityPoolWithCapacity:factory:.
  • SSKScreenUtilities – helpers for scaling information, wallpaper-host detection, and screen dimensions.
  • SSKDiagnostics – opt-in logging and overlay drawing. Toggle with [SSKDiagnostics setEnabled:YES] and draw overlays inside -drawRect:.
  • SSKPreferenceBinder + SSKConfigurationWindowController – drop-in UI scaffold for settings windows with automatic binding between controls and ScreenSaverDefaults.
  • SSKColorPalette + SSKPaletteManager – shared palette definitions with interpolation helpers and registration per saver module.
  • SSKColorUtilities – convenience serializers/deserializers for storing NSColor instances inside ScreenSaverDefaults.
  • SSKVectorMath – small collection of inline NSPoint helpers (add, scale, reflect, clamp) for animation math.
  • SSKParticleSystem – lightweight particle engine with CPU and Metal-accelerated rendering modes. Supports additive/alpha blending, automatic fade behaviors, and custom per-particle rendering callbacks. Ideal for sparks, trails, explosions, and flowing ribbon effects. See ScreenSaverKit/SSKParticleSystem.md for detailed documentation.
  • SSKMetalParticleRenderer – hardware-accelerated particle renderer using Metal. Automatically handles GPU pipeline setup, drawable management, and instanced rendering for high-performance particle effects.
  • SSKMetalRenderer + SSKMetalEffectStage – extensible Metal post-processing effect system. Register custom effect passes (blur, bloom, color grading, etc.) without modifying framework code. Supports dynamic effect chains with configurable parameters. Built-in blur and bloom effects included. See architecture-docs/EFFECT_IMPLEMENTATION_GUIDE.md for detailed documentation on creating custom Metal shader effects.
  • SSKMetalSpritePass + SSKSprite – 2D sprite rendering system for classic sprite-based screensavers (flying toasters, DVD logo, etc.). Supports position, rotation, non-uniform scaling, horizontal/vertical flipping, color tinting, opacity, z-ordering, and multiple blend modes. Features include sprite animation sequences with loop/ping-pong modes, viewport culling for performance, and pixel-based coordinate system with Retina support. Uses instanced rendering for efficient batch drawing of multiple sprites. See Demos/DVDLogoMetal/README.md for a tutorial.
  • SSKSpriteAnimationSequence – Immutable animation sequence data for sprite sheet animations. Supports grid-based and custom rect-based sequences, loop modes (Once, Loop, PingPong), per-frame durations, and time-based frame lookup. Integrates seamlessly with SSKSprite for automatic UV coordinate updates.
  • SSKMetalRenderDiagnostics – real-time Metal rendering diagnostics overlay. Tracks rendering success/failure rates, displays device/layer/renderer status, and shows FPS. Automatically renders a semi-transparent overlay on your CAMetalLayer for debugging Metal pipeline issues. Perfect for development and troubleshooting GPU initialization problems. See Demos/MetalParticleTest/ for usage example.

Architecture Documentation

For detailed information about ScreenSaverKit's internal architecture, see the Architecture Documentation Index which provides:

  • Architecture Analysis – In-depth technical analysis of the effect chaining system, Metal rendering pipeline, particle system integration, and design patterns
  • Architecture Diagrams – Visual component relationships, rendering pipelines, and dependency graphs
  • Effect Implementation Guide – Step-by-step guide for creating custom Metal effects, understanding the rendering flow, and debugging tips

The architecture docs cover:

  • FX Pass-based architecture (Particle, Blur, Bloom passes)
  • Metal shader organization and compute kernels
  • Texture cache strategy and memory management
  • GPU-accelerated particle spawning with z-depth support
  • Testing architecture and test coverage
  • Performance optimizations and recent improvements

Using Metal-Accelerated Particles

The particle system supports both CPU (Core Graphics) and GPU (Metal) rendering modes:

Quick Start:

// Create particle system
self.particleSystem = [[SSKParticleSystem alloc] initWithCapacity:1024];
self.particleSystem.blendMode = SSKParticleBlendModeAdditive;  // or SSKParticleBlendModeAlpha

// For Metal rendering, set up a CAMetalLayer
self.wantsLayer = YES;
CAMetalLayer *metalLayer = [CAMetalLayer layer];
metalLayer.device = MTLCreateSystemDefaultDevice();
self.layer = metalLayer;
self.metalRenderer = [[SSKMetalParticleRenderer alloc] initWithLayer:metalLayer];

// Spawn particles
[self.particleSystem spawnParticles:100 initializer:^(SSKParticle *particle) {
    particle.position = center;
    particle.velocity = NSMakePoint(cos(angle) * speed, sin(angle) * speed);
    particle.color = [NSColor colorWithHue:hue saturation:0.8 brightness:1.0 alpha:1.0];
    particle.maxLife = 2.0;
    particle.size = 10.0;
    particle.behaviorOptions = SSKParticleBehaviorOptionFadeAlpha | SSKParticleBehaviorOptionFadeSize;
}];

// In animateOneFrame, update and render
[self.particleSystem advanceBy:deltaTime];
[self.particleSystem renderWithMetalRenderer:self.metalRenderer
                                   blendMode:self.particleSystem.blendMode
                                viewportSize:self.bounds.size];

Automatic CPU Fallback: If Metal initialization fails or the renderer returns NO, the particle system automatically falls back to CPU rendering via drawInContext: in your drawRect: method.

See Demos/RibbonFlow/ for a complete working example, and ScreenSaverKit/SSKParticleSystem.md for detailed API documentation.

Using Metal-Accelerated Sprites

The sprite system provides hardware-accelerated 2D rendering for classic sprite-based effects with support for scaling, flipping, animation sequences, and viewport culling.

Quick Start:

// Subclass SSKMetalScreenSaverView for automatic Metal setup
@interface MySpriteSaver : SSKMetalScreenSaverView
@property (nonatomic, strong) SSKSprite *logoSprite;
@property (nonatomic, strong) id<MTLTexture> logoTexture;
@end

// Create sprite in setupMetalRenderer:
- (void)setupMetalRenderer:(SSKMetalRenderer *)renderer {
    [super setupMetalRenderer:renderer];
    
    self.logoSprite = [[SSKSprite alloc] init];
    
    // Use pixel coordinates (convert from points for Retina)
    CGFloat scale = self.window?.backingScaleFactor ?: 1.0;
    [self.logoSprite setPositionInPoints:NSMakePoint(100, 100) scale:scale];
    
    self.logoSprite.size = NSMakeSize(180, 100);  // Size in pixels
    self.logoSprite.scale = CGSizeMake(1.0, 1.0);  // Scale multiplier
    self.logoSprite.colorTint = [NSColor redColor];
    self.logoSprite.opacity = 1.0;
    
    // Load texture from image
    self.logoSprite.image = [NSImage imageNamed:@"logo"];
    self.logoTexture = [self.logoSprite textureForDevice:renderer.device];
}

// Render in renderMetalFrame:deltaTime:
- (void)renderMetalFrame:(SSKMetalRenderer *)renderer deltaTime:(NSTimeInterval)dt {
    [renderer clearWithColor:renderer.clearColor];
    
    // Update sprite animation
    NSPoint pos = self.logoSprite.position;
    pos.x += self.velocity.x * dt;
    CGFloat scale = (self.window != nil) ? self.window.backingScaleFactor : 1.0;
    [self.logoSprite setPositionInPoints:pos scale:scale];
    
    // Draw sprite - renderer handles points-to-pixels conversion internally
    [renderer drawSprites:@[self.logoSprite]
                  texture:self.logoTexture
                blendMode:SSKParticleBlendModeAlpha
             viewportSize:self.bounds.size];
}

Sprite Properties:

  • position – Anchor point position in pixels (not necessarily center)
  • size – Width and height in pixels
  • scale – Scale multiplier (CGSize). Negative values flip the sprite (canonical flip mechanism)
  • flipX/flipY – Convenience properties that modify scale sign
  • rotation – Rotation angle in radians (direction depends on coordinate transform)
  • anchor – Anchor point for rotation/positioning (0,0 = bottom-left, 0.5,0.5 = center, 1,1 = top-right)
  • colorTint – Color multiplied with texture (white = no tint)
  • opacity – Alpha multiplier (0.0-1.0)
  • z – Z-order for depth sorting (higher values render in front)
  • image – Source NSImage for texture creation
  • textureOffset/textureSize – UV coordinates for sprite sheets (normalized 0-1)

Animation System:

// Create animation sequence from grid-based sprite sheet
SSKSpriteAnimationSequence *anim = [SSKSpriteAnimationSequence 
    sequenceWithGridColumns:4 rows:2 frameCount:8 
    duration:0.1 loopMode:SSKAnimationLoopModeLoop];

// Assign to sprite
self.logoSprite.animation = anim;
self.logoSprite.animationRate = 1.0;  // Playback speed
self.logoSprite.animationPlaying = YES;

// Advance animation each frame
- (void)renderMetalFrame:(SSKMetalRenderer *)renderer deltaTime:(NSTimeInterval)dt {
    [self.logoSprite advanceAnimationByTime:dt];
    // ... render ...
}

Coordinate System:

  • All sprite coordinates and sizes are in pixels (not points)
  • On Retina displays, multiply points by window.backingScaleFactor or layer.contentsScale
  • Use setPositionInPoints:scale: helper to convert from points to pixels
  • When using SSKMetalRenderer.drawSprites:, pass viewportSize in points—the renderer automatically uses the render target's pixel dimensions internally

Advanced Features:

  • Viewport Culling – Enable spritePass.cullingEnabled = YES to skip off-screen sprites
  • Z-Sorting – Pass sortByZ:YES to drawSprites: for automatic back-to-front sorting
  • Sprite Sheets – Use setTextureRectInPixels:textureSize: for atlas-based sprites
  • Flip/Mirror – Set scale.width < 0 for horizontal flip, scale.height < 0 for vertical flip, or use flipX/flipY convenience properties

Blend Modes:

  • SSKParticleBlendModeAlpha – Standard alpha blending for opaque/transparent sprites (uses premultiplied alpha; color tinting is correctly applied with proper alpha scaling)
  • SSKParticleBlendModeAdditive – Additive blending for glow effects

API Changes:

  • SSKMetalSpritePass methods now use viewportPixels: parameter to clarify pixel-based coordinates
  • Old viewportSize: methods on SSKMetalSpritePass are deprecated but still work (forwarders provided)
  • SSKMetalRenderer.drawSprites: accepts viewportSize: in points for backward compatibility—it automatically converts to pixels using the render target dimensions

See Demos/DVDLogoMetal/ for a complete working example with bounce physics, color cycling, and flip-on-bounce effects.

Debugging Metal Rendering

For troubleshooting Metal rendering issues, use SSKMetalRenderDiagnostics:

// Create diagnostics helper
self.renderDiagnostics = [[SSKMetalRenderDiagnostics alloc] init];

// Attach to your Metal layer
[self.renderDiagnostics attachToMetalLayer:self.metalLayer];

// Update status as you initialize components
self.renderDiagnostics.deviceStatus = [NSString stringWithFormat:@"Device: %@", device.name];
self.renderDiagnostics.layerStatus = @"Layer: configured";
self.renderDiagnostics.rendererStatus = @"Renderer: ready";

// In animateOneFrame, record rendering attempts
BOOL renderSuccess = [self.particleSystem renderWithMetalRenderer:self.metalRenderer
                                                         blendMode:self.particleSystem.blendMode
                                                      viewportSize:self.bounds.size];
[self.renderDiagnostics recordMetalAttemptWithSuccess:renderSuccess];

// Update overlay with custom info
NSArray *extraInfo = @[
    [NSString stringWithFormat:@"Particles: %lu", self.particleSystem.aliveParticleCount]
];
[self.renderDiagnostics updateOverlayWithTitle:@"My Saver"
                                    extraLines:extraInfo
                               framesPerSecond:self.animationClock.framesPerSecond];

The diagnostics overlay displays:

  • Device: Metal device name and capabilities (e.g., "Apple M1", low power status)
  • Layer: CAMetalLayer configuration status
  • Renderer: SSKMetalRenderer initialization state
  • Drawable: Drawable availability and acquisition success
  • Metal successes / fallbacks: Running counter of rendering attempts
  • FPS: Current frame rate

Toggle the overlay on/off with:

self.renderDiagnostics.overlayEnabled = NO;  // Hide overlay
self.renderDiagnostics.overlayEnabled = YES; // Show overlay (default)

See Demos/MetalParticleTest/ for a complete diagnostic implementation example, or Demos/MetalDiagnostic/ for a low-level Metal sanity checker that tests device, layer, and drawable initialization.

Testing and Performance

ScreenSaverKit includes comprehensive testing and performance evaluation tools, plus major performance optimizations for the particle system.

Performance Optimizations (December 2024)

ScreenSaverKit now includes three major performance optimizations:

  1. Alive Particle Tracking - Automatic ~100x speedup for sparse particle systems
  2. Async Rendering Mode - Optional previous-frame rendering to eliminate GPU waits
  3. Indirect Rendering - Optional GPU-side instance buffer building

See PERFORMANCE_OPTIMIZATIONS.md for complete documentation on how to use these features and maximize performance.

Quick Start:

SSKParticleSystem *system = [[SSKParticleSystem alloc] initWithCapacity:10000];
system.metalSimulationRenderMode = SSKMetalSimulationRenderModePreviousFrame;

SSKMetalParticleRenderer *renderer = [[SSKMetalParticleRenderer alloc] initWithLayer:layer];
renderer.useIndirectRendering = YES;

Testing Tools

See PERFORMANCE_TESTING.md for detailed documentation on:

  • Unit Tests (Tests/) - Functional correctness tests for core components
  • Performance Benchmark Screensaver (Demos/PerformanceBenchmark/) - Real-time metrics visualization with configurable test scenarios
  • Standalone Benchmark Tool (Tools/Benchmark/) - Automated performance regression testing with JSON/CSV output

The performance testing suite helps verify functionality, measure frame rates, identify bottlenecks, and track performance regressions across different hardware configurations.

Starter template

DVD Logo Demo

  • TemplateSaverView.h/.m – a minimal saver that animates a few shapes and responds to preference changes. Copy and rename these files to kick off a new project.

  • TemplateInfo.plist – barebones bundle metadata. Update the identifiers and version fields to match your saver.

  • Makefile.demo – shows how to compile a .saver bundle using the template view plus SSKScreenSaverView. Run make -f ScreenSaverKit/Makefile.demo from your project root (or copy it beside your sources) and tweak the variables at the top for your module name and bundle ID.

  • The template demonstrates the configuration sheet helpers (sliders + checkbox), diagnostics overlay toggling, and the animation clock workflow.

  • Demos/HelloWorld/ – a ready-to-build "Hello, World" saver that bounces text around the screen with optional colour cycling. Build it via make -f Demos/HelloWorld/Makefile. See tutorial.md for a complete walkthrough using this demo.

  • Demos/Starfield/ – a classic faux-3D starfield with optional motion blur and drifting trajectory changes. Build it via make -f Demos/Starfield/Makefile.

  • Demos/SimpleLines/ – layered drifting lines with palette selection and adjustable colour cycling speed. Build it via make -f Demos/SimpleLines/Makefile.

  • Demos/DVDlogo/ – retro floating DVD logo with solid or rotating palette colour modes, adjustable size, speed, colour cycling, and optional random start behaviour. It also uses a multi-file project structure to demo a more advanced project structure. Build it via make -f Demos/DVDlogo/Makefile.

  • Demos/DVDLogoMetal/ – Metal-accelerated version of the DVD logo screensaver demonstrating the 2D sprite rendering system. Uses SSKMetalSpritePass for GPU-accelerated sprite rendering with color cycling on bounce. Includes tutorial documentation explaining sprite rendering concepts. Build it via make -f Demos/DVDLogoMetal/Makefile. See Demos/DVDLogoMetal/README.md for detailed documentation.

  • Demos/RibbonFlow/ – flowing additive ribbons inspired by the classic Apple Flurry screensaver. Demonstrates Metal-accelerated particle rendering with the SSKParticleSystem and SSKMetalParticleRenderer working together for smooth, GPU-powered effects. Build it via make -f Demos/RibbonFlow/Makefile.

  • Demos/Rain/ – classic retro rain animation screensaver with GPU-accelerated particle spawning and optional z-depth perspective effects. Demonstrates simple particle-based effects, adjustable rain angle/speed/density, and hardware-accelerated z-depth calculations for realistic 3D depth illusion. Features include brightness control, width/length customization, and optional FPS counter. Perfect example of using spawnParticlesGPU:parameters: with z-depth support. Build it via make -f Demos/Rain/Makefile. See Demos/Rain/README.md for detailed documentation.

  • Demos/MetalParticleTest/ – diagnostic particle fountain with automatic Metal/CPU fallback. Shows real-time rendering statistics, particle counts, and detailed Metal pipeline status. Perfect for testing GPU availability and debugging Metal particle renderer issues. Build it via make -f Demos/MetalParticleTest/Makefile.

  • Demos/MetalDiagnostic/ – low-level Metal sanity checker that displays device capabilities, layer configuration, drawable status, and command buffer lifecycle on-screen. Useful for diagnosing Metal initialization issues or verifying hardware support. Build it via make -f Demos/MetalDiagnostic/Makefile.

  • scripts/install-and-refresh.sh – convenience script that builds, installs, and restarts the relevant macOS services (legacyScreenSaver, WallpaperAgent, ScreenSaverEngine) so macOS immediately sees your latest bundle. Usage:

    ./scripts/install-and-refresh.sh Demos/Starfield
    ./scripts/install-and-refresh.sh ScreenSaverKit -f Makefile.demo

    The first argument is the directory containing the saver Makefile; any additional arguments are passed straight through to each make invocation.

  • scripts/refresh-screensaver-services.sh – lightweight helper that clears all macOS screen saver caches and optionally relaunches ScreenSaverEngine. Clears: System Settings, legacyScreenSaver, WallpaperAgent, ScreenSaverEngine, cfprefsd (preferences daemon), iconservicesd (icon cache), and lsd (Launch Services). Use when you've already installed a bundle:

    ./scripts/refresh-screensaver-services.sh         # clear caches
    ./scripts/refresh-screensaver-services.sh --launch # clear caches + relaunch preview

⚠️ macOS caching note: System Settings aggressively caches screen saver bundles at multiple levels (preferences, icons, bundle metadata, and Launch Services database). If you rebuild but don't see changes, run the install-and-refresh script which automatically clears all caches, or use the refresh script after manually installing your bundle.

Updating the demo savers after kit changes

If you tweak code inside ScreenSaverKit/, rebuild any demos you want to test so they pick up the new implementation:

cd Demos/Starfield && make clean all
cd Demos/SimpleLines && make clean all

After installing the refreshed bundle, restart the caching daemons to force macOS to load the new bits:

./scripts/refresh-screensaver-services.sh
./scripts/refresh-screensaver-services.sh --launch   # optionally relaunches the preview

This mirrors the workflow shown earlier (make …, then refresh) and avoids the “preview updated, full screen is stale” confusion that can happen otherwise.

Building the demo saver

# From the project root (or wherever you copied the kit)
make -f ScreenSaverKit/Makefile.demo clean all
  • Outputs a bundle at ScreenSaverKit/DemoBuild/TemplateSaver.saver.

  • Compiles a universal binary that supports both arm64 (Apple Silicon) and x86_64 (Intel) via the -arch flags already in Makefile.demo.

  • Verify the architectures with:

    file ScreenSaverKit/DemoBuild/TemplateSaver.saver/Contents/MacOS/TemplateSaver
  • Install locally for testing:

    make -f ScreenSaverKit/Makefile.demo run

Update SCREENSAVER_NAME, BUNDLE_ID, and PRINCIPAL_CLASS at the top of the Makefile when you adapt the template for your own saver.

Signing and notarizing

macOS Ventura and newer require downloaded screen savers to be signed with a Developer ID certificate (and ideally notarized) before they will load without warnings. Replace the placeholder values below with your own Team ID and bundle details.

# Sign the bundle
codesign --force --timestamp --options runtime \
  --identifier com.example.templatesaver \
  --sign "Developer ID Application: Your Name (TEAMID)" \
  ScreenSaverKit/DemoBuild/TemplateSaver.saver

# Optional: verify signature
codesign --verify --strict --verbose=2 ScreenSaverKit/DemoBuild/TemplateSaver.saver

# Optional: zip bundle then submit for notarization (requires a notarytool profile)
ditto -c -k --keepParent ScreenSaverKit/DemoBuild/TemplateSaver.saver \
  ScreenSaverKit/DemoBuild/TemplateSaver.saver.zip
xcrun notarytool submit ScreenSaverKit/DemoBuild/TemplateSaver.saver.zip \
  --keychain-profile your-notary-profile --wait

If you follow the same structure as the demo Makefile, you can reuse the root-level sign, zip, and notarize targets (update the variables to match your saver).

Preference helpers

Use the provided convenience methods when you want to manipulate preferences manually:

  • - (ScreenSaverDefaults *)preferences;
  • - (NSDictionary<NSString *, id> *)currentPreferences;
  • - (void)setPreferenceValue:(id)value forKey:(NSString *)key;
  • - (void)removePreferenceForKey:(NSString *)key;
  • - (void)resetPreferencesToDefaults;

Adapting the Makefile

The root Makefile already includes ScreenSaverKit/SSKScreenSaverView.m in the build. When starting a new saver:

  1. Update SCREENSAVER_NAME, BUNDLE_ID, and Info.plist to match your saver.
  2. Add your own .m files to the SOURCES list.
  3. Run make or make test to produce a .saver bundle.

Updating existing savers

To migrate an older saver code base:

  1. Replace ScreenSaverView superclass usages with SSKScreenSaverView.
  2. Remove any custom preference polling timers – the kit handles it now.
  3. Move default registration into -defaultPreferences.
  4. Migrate preference reload code into -preferencesDidChange:changedKeys:.

Use these steps to retrofit the kit into existing code and keep the rendering logic focused on your unique saver behavior.

Troubleshooting

Metal rendering shows black screen or doesn't activate

Symptoms: Metal particle system renders black screen, or always falls back to CPU mode.

Common causes:

  1. Fragment shader not receiving instance data - Ensure the instance buffer is bound to both vertex AND fragment shaders:

    [encoder setVertexBuffer:instanceBuffer offset:0 atIndex:1];
    [encoder setFragmentBuffer:instanceBuffer offset:0 atIndex:1];  // Don't forget this!
  2. Particles spawning "dead" - Particles must start with life = 0.0, not life = maxLife:

    particle.life = 0.0;          // ✅ Correct - particle starts alive
    particle.maxLife = 2.0;
    // NOT: particle.life = particle.maxLife;  // ❌ Particle spawns already dead
  3. Layer not attached before renderer initialization - Wait for view to be in window:

    - (void)viewDidMoveToWindow {
        [super viewDidMoveToWindow];
        if (self.window) {
            [self setupMetalRenderer];  // Only after window attachment
        }
    }
  4. Check Console.app for Metal shader compilation errors - Filter for "SSKMetalParticleRenderer" to see detailed error messages.

Changes not appearing after rebuild

Symptoms: Rebuilt screen saver but System Settings shows old version.

Solution: Run the cache refresh script:

./scripts/refresh-screensaver-services.sh

Or use the full install-and-refresh workflow:

./scripts/install-and-refresh.sh Demos/YourSaver

Screen saver doesn't appear in System Settings

Symptoms: Bundle installed to ~/Library/Screen Savers/ but doesn't show up in list.

Common causes:

  1. Bundle not properly formed - Verify with: ls -la ~/Library/Screen\ Savers/YourSaver.saver/Contents/MacOS/
  2. Info.plist issues - Ensure NSPrincipalClass matches your view class name exactly
  3. Launch Services database stale - The refresh script now clears this automatically

Preferences not updating in real-time

Symptoms: Changes in System Settings don't appear until restarting preview.

Solution: The kit polls preferences every 2 seconds. Changes should appear automatically. If not:

  1. Verify you implemented preferencesDidChange:changedKeys:
  2. Check that defaultPreferences returns the correct keys
  3. Ensure you're not caching values that should update

About

A ObjC framework for creating macOS screensavers for macOS 11 Big Sur to current.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published