Commits

Peter Hosey committed ee576e8

Replaced CPUUsageView threaded display and drawing machinery with a couple of CALayers. CPU usage is down to around 0.6–0.7%—still not as good as version 0.5, but improved.

Comments (0)

Files changed (9)

CPU Usage.xcodeproj/project.pbxproj

 		07FA8A990A4E697500960E0E /* NSString+Percentage.m in Sources */ = {isa = PBXBuildFile; fileRef = 07FA8A980A4E697500960E0E /* NSString+Percentage.m */; };
 		3111B73F13C431940096D947 /* Warnings.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 3111B73E13C431940096D947 /* Warnings.xcconfig */; };
 		312281EB13DD9885003FE667 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 312281E913DD9885003FE667 /* MainMenu.xib */; };
+		3137DA4A146F222A00AD9E5B /* CPUUsageLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3137DA49146F222A00AD9E5B /* CPUUsageLayer.m */; };
+		3137DA4D146F36D000AD9E5B /* CPUUsageFrameLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3137DA4C146F36D000AD9E5B /* CPUUsageFrameLayer.m */; };
+		3137DA4F146F3FEA00AD9E5B /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3137DA4E146F3FE900AD9E5B /* QuartzCore.framework */; };
 		316719580CAC026C00E3F695 /* SnappingWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 316719570CAC026C00E3F695 /* SnappingWindow.m */; };
 		31DFF5260C28D9CF00EBBBCC /* PRHGradientView.m in Sources */ = {isa = PBXBuildFile; fileRef = 31DFF5250C28D9CF00EBBBCC /* PRHGradientView.m */; };
 		8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
 		29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
 		3111B73E13C431940096D947 /* Warnings.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
 		312281EA13DD9885003FE667 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = "<group>"; };
+		3137DA48146F222A00AD9E5B /* CPUUsageLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CPUUsageLayer.h; sourceTree = "<group>"; };
+		3137DA49146F222A00AD9E5B /* CPUUsageLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CPUUsageLayer.m; sourceTree = "<group>"; };
+		3137DA4B146F36D000AD9E5B /* CPUUsageFrameLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CPUUsageFrameLayer.h; sourceTree = "<group>"; };
+		3137DA4C146F36D000AD9E5B /* CPUUsageFrameLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CPUUsageFrameLayer.m; sourceTree = "<group>"; };
+		3137DA4E146F3FE900AD9E5B /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
 		316719560CAC026C00E3F695 /* SnappingWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SnappingWindow.h; sourceTree = "<group>"; };
 		316719570CAC026C00E3F695 /* SnappingWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SnappingWindow.m; sourceTree = "<group>"; };
 		31DFF5240C28D9CF00EBBBCC /* PRHGradientView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PRHGradientView.h; sourceTree = "<group>"; };
 			buildActionMask = 2147483647;
 			files = (
 				8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */,
+				3137DA4F146F3FEA00AD9E5B /* QuartzCore.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 				07EE02480A4A2BB200A31AED /* CPUUsageMonitor.m */,
 				07EE02950A4A4F0E00A31AED /* CPUUsageView.h */,
 				07EE02960A4A4F0E00A31AED /* CPUUsageView.m */,
+				3137DA48146F222A00AD9E5B /* CPUUsageLayer.h */,
+				3137DA49146F222A00AD9E5B /* CPUUsageLayer.m */,
+				3137DA4B146F36D000AD9E5B /* CPUUsageFrameLayer.h */,
+				3137DA4C146F36D000AD9E5B /* CPUUsageFrameLayer.m */,
 				07FA8A970A4E697500960E0E /* NSString+Percentage.h */,
 				07FA8A980A4E697500960E0E /* NSString+Percentage.m */,
 				0720203A0A579239007BC356 /* BZGridEnumerator.h */,
 			isa = PBXGroup;
 			children = (
 				1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */,
+				3137DA4E146F3FE900AD9E5B /* QuartzCore.framework */,
 			);
 			name = "Linked Frameworks";
 			sourceTree = "<group>";
 				0720203C0A579239007BC356 /* BZGridEnumerator.m in Sources */,
 				31DFF5260C28D9CF00EBBBCC /* PRHGradientView.m in Sources */,
 				316719580CAC026C00E3F695 /* SnappingWindow.m in Sources */,
+				3137DA4A146F222A00AD9E5B /* CPUUsageLayer.m in Sources */,
+				3137DA4D146F36D000AD9E5B /* CPUUsageFrameLayer.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

CPU Usage_Prefix.pch

 
 #ifdef __OBJC__
 #	import <Cocoa/Cocoa.h>
+#	import <QuartzCore/QuartzCore.h>
 #endif

CPUUsageFrameLayer.h

+//
+//  CPUUsageFrameLayer.h
+//  CPU Usage
+//
+//  Created by Peter Hosey on 2011-11-12.
+//  Copyright (c) 2011 Peter Hosey. All rights reserved.
+//
+
+@interface CPUUsageFrameLayer : CALayer
+
+@property(copy) NSColor *frameColor;
+
+@end

CPUUsageFrameLayer.m

+//
+//  CPUUsageFrameLayer.m
+//  CPU Usage
+//
+//  Created by Peter Hosey on 2011-11-12.
+//  Copyright (c) 2011 Peter Hosey. All rights reserved.
+//
+
+#import "CPUUsageFrameLayer.h"
+
+@implementation CPUUsageFrameLayer
+
+@synthesize frameColor;
+
+- (void) drawInContext:(CGContextRef)ctx {
+	NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:NO];
+
+	NSGraphicsContext *formerCurrentContext = [NSGraphicsContext currentContext];
+	[NSGraphicsContext setCurrentContext:context];
+
+	[self.frameColor set]; //Not setStroke, for unknown reasons.
+	NSFrameRectWithWidth(self.bounds, 2.0f);
+
+	[NSGraphicsContext setCurrentContext:formerCurrentContext];
+}
+
+@end
+//
+//  CPUUsageLayer.h
+//  CPU Usage
+//
+//  Created by Peter Hosey on 2011-11-12.
+//  Copyright (c) 2011 Peter Hosey. All rights reserved.
+//
+
+@interface CPUUsageLayer : CALayer
+
+@property(strong) NSGradient *textColorGradient;
+@property(strong) NSGradient *backgroundColorGradient;
+
+@property NSUInteger processorIndex;
+@property float processorUsageFraction;
+
+@end
+//
+//  CPUUsageLayer.m
+//  CPU Usage
+//
+//  Created by Peter Hosey on 2011-11-12.
+//  Copyright (c) 2011 Peter Hosey. All rights reserved.
+//
+
+#import "CPUUsageLayer.h"
+
+#import "NSString+Percentage.h"
+
+@implementation CPUUsageLayer
+
+@synthesize textColorGradient;
+@synthesize backgroundColorGradient;
+
+@synthesize processorIndex;
+@synthesize processorUsageFraction;
+
+- (void)drawInContext:(CGContextRef)ctx {
+	NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:NO];
+	NSGraphicsContext *formerCurrentContext = [NSGraphicsContext currentContext];
+	[NSGraphicsContext setCurrentContext:context];
+
+	NSRect bounds = [self bounds];
+
+	NSColor *backgroundColorPremultiplied = [self.backgroundColorGradient interpolatedColorAtLocation:self.processorUsageFraction];
+	[backgroundColorPremultiplied setFill];
+	NSRectFill(bounds); //Not bounds, solely because we can get away with it.
+
+	NSColor *textColor = [self.textColorGradient interpolatedColorAtLocation:self.processorUsageFraction];
+	//This is associated with the text storage for the additive (second) show, as well as used to compute the alpha for the subtractive (first) show.
+	float alpha = [textColor alphaComponent];
+
+	float widthScale  = bounds.size.width  / 32.0f;
+	float heightScale = bounds.size.height / 32.0f;
+
+	float pointSize = [NSFont smallSystemFontSize] * MIN(widthScale, heightScale);
+	NSFont *drawingFont = [NSFont boldSystemFontOfSize:pointSize];
+	NSMutableParagraphStyle *pStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
+	[pStyle setAlignment:NSCenterTextAlignment];
+	NSMutableDictionary *drawingAttributes = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+		drawingFont, NSFontAttributeName,
+		[self.textColorGradient interpolatedColorAtLocation:0.5f], NSForegroundColorAttributeName,
+		[NSColor blackColor], NSStrokeColorAttributeName,
+		pStyle, NSParagraphStyleAttributeName,
+		nil];
+	[pStyle release];
+
+	NSRange glyphRange1;
+	NSRange glyphRange2;
+
+	[drawingAttributes setObject:textColor forKey:NSForegroundColorAttributeName];
+
+	NSString *str = [NSString stringWithPercentageFromFraction:self.processorUsageFraction CPUNumber:(unsigned)(self.processorIndex)];
+	NSTextStorage *storage1 = [[[NSTextStorage alloc] initWithString:str attributes:drawingAttributes] autorelease];
+
+	NSLayoutManager *manager1 = [[[NSLayoutManager alloc] init] autorelease];
+	[manager1 setBackgroundLayoutEnabled:NO]; //Needed for thread-safety.
+	[storage1 addLayoutManager:manager1];
+
+	NSSize containerSize = { bounds.size.width * 2.0f, bounds.size.height }; //Fix bogus line-wrapping (part 1 of 2)
+	NSTextContainer *container1 = [[[NSTextContainer alloc] initWithContainerSize:containerSize] autorelease];
+	[manager1 addTextContainer:container1];
+
+	glyphRange1 = [manager1 glyphRangeForTextContainer:container1];
+
+	NSRect textBounds = [manager1 boundingRectForGlyphRange:glyphRange1 inTextContainer:container1];
+
+	NSMutableDictionary *secondDrawingAttributes = [[drawingAttributes mutableCopy] autorelease];
+	[secondDrawingAttributes setObject:[textColor colorWithAlphaComponent:1.0f - alpha] forKey:NSForegroundColorAttributeName];
+	NSTextStorage *storage2 = [[[NSTextStorage alloc] initWithString:str attributes:secondDrawingAttributes] autorelease];
+	[storage2 setAttributes:secondDrawingAttributes range:(NSRange){ 0U, [storage2 length] }];
+
+	NSLayoutManager *manager2 = [[[NSLayoutManager alloc] init] autorelease];
+	[manager2 setBackgroundLayoutEnabled:NO]; //Needed for thread-safety.
+	[storage2 addLayoutManager:manager2];
+
+	//Same containerSize as above.
+	NSTextContainer *container2 = [[[NSTextContainer alloc] initWithContainerSize:containerSize] autorelease];
+	[manager2 addTextContainer:container2];
+
+	glyphRange2 = [manager2 glyphRangeForTextContainer:container2];
+
+	NSPoint drawingOrigin = {
+		bounds.size.width  * -0.5f, //Fix bogus line-wrapping (part 2 of 2)
+		bounds.size.height * 0.5f - textBounds.size.height * 0.75f
+	};
+
+	//We want the final text to have opacity = (usage * textOpacity).
+	//Thus, when we un-draw the text in the second pass, we must draw with alpha = 1 - (usage * opacity), so that the final opacity will be (usage * textOpacity) - alpha.
+	if(alpha < 0.99f) {
+		[context saveGraphicsState];
+
+		[context setCompositingOperation:NSCompositeDestinationOut];
+
+		[manager2 drawGlyphsForGlyphRange:glyphRange2 atPoint:drawingOrigin];
+
+		[context restoreGraphicsState];
+	}
+
+	//Now draw the text.
+	[manager1 drawGlyphsForGlyphRange:glyphRange1 atPoint:drawingOrigin];
+
+	[NSGraphicsContext setCurrentContext:formerCurrentContext];
+}
+
+- (NSString *) description {
+	return [NSString stringWithFormat:@"<%@ %p for %u%% on CPU #%u>", [self class], self, (unsigned)(self.processorUsageFraction * 100.0f), (unsigned)self.processorIndex];
+}
+
+/*
+- (id <CAAction>) actionForKey:(NSString *)event {
+	//Never, ever animate.
+	return nil;
+}
+ */
+
+@end

CPUUsageMonitor.m

 	CPUUsageView *usageView;
 	while((usageView = [usageViewsEnum nextObject])) {
 		[usageView setDrawsFrame:YES];
-		[usageView setNeedsThreadedDisplay:YES];
 	}
 
 	[window setIgnoresMouseEvents:NO];
 	CPUUsageView *usageView;
 	while((usageView = [usageViewsEnum nextObject])) {
 		[usageView setDrawsFrame:NO];
-		[usageView setNeedsThreadedDisplay:YES];
 	}
 	
 	[window setIgnoresMouseEvents:YES];
 	[viewInDockIcon setCPUUsage:CPUUsage[i]];
 	[CPUUsageLock unlock];
 
-	if(shouldDrawToWindow && [view needsThreadedDisplay]) {
-		[view threadedDisplay];
-	}
-	//Don't draw any Dock icon view here anymore—with NSDockTile, that must happen on the main thread.
-
 	if(threadsRemainingToDie) {
 		[timer invalidate];
 
 @property(assign) unsigned CPUNumber;
 @property(assign) float CPUUsage;
 
-@property(nonatomic, assign) BOOL needsThreadedDisplay;
-
-- (void) threadedDisplay;
-
 @end
 
 #import "NSString+Percentage.h"
 
+#import "CPUUsageLayer.h"
+#import "CPUUsageFrameLayer.h"
+
 enum { CPUUsageNumCacheElements = 101U };
 
+@interface CPUUsageView ()
+@property(strong) NSMutableArray *usageLayers;
+@property(strong) CPUUsageFrameLayer *frameLayer;
+@end
+
 @implementation CPUUsageView
 {
 	NSColor *backgroundColorPremultiplied;
 	NSFont *drawingFont;
 	NSMutableDictionary *drawingAttributes;
 
-	//Implementation detail: As of Tiger, NSCFMutableArray is a deque (according to the CFLite sources). This makes random access inefficient. Thus, my use of a C array instead. Violation of MJD #11908 acknowledged.
-	NSTextStorage *textStorages1[CPUUsageNumCacheElements];
-	//The next two arrays contain objects that are owned by other objects (containers by mgrs; mgrs by storages). As such, they are not retained by the view.
-	NSLayoutManager *layoutManagers1[CPUUsageNumCacheElements];
-	NSTextContainer *textContainers1[CPUUsageNumCacheElements];
-	NSRange glyphRanges1[CPUUsageNumCacheElements];
-
-	//Same as above, but each of these is a text storage that gets subtracted (NSCompositeDestinationOut) just before drawing the other text storage, when alpha < 1.
-	//Each layout manager is owned by exactly one text storage; thus, we have two sets of all four arrays, not just the first array.
-	NSTextStorage *textStorages2[CPUUsageNumCacheElements];
-	NSLayoutManager *layoutManagers2[CPUUsageNumCacheElements];
-	NSTextContainer *textContainers2[CPUUsageNumCacheElements];
-	NSRange glyphRanges2[CPUUsageNumCacheElements];
-
-	//Caches of things that we don't need to compute every time we draw.
-	NSColor *textColors[CPUUsageNumCacheElements];
-	NSRect boundingRects[CPUUsageNumCacheElements];
-
 	/*It is unsafe for one thread to work with objects while another thread is releasing them.
 	 *Hence this lock. It is locked by two threads:
 	 *	The drawing thread, in -drawRect:
 @synthesize CPUNumber;
 @synthesize CPUUsage;
 
-//setNeedsDisplay: schedules the view to display on the main thread, which concentrates work on the main thread and (I believe) causes a rare crash.
-//The individual monitor threads set and check needsThreadedDisplay instead, in order to solve both problems.
-@synthesize needsThreadedDisplay;
+@synthesize usageLayers;
+@synthesize frameLayer;
 
 + (void)initialize {
 	[self exposeBinding:@"textColorGradient"];
 		textFractionOfWidth  = astrSize.width  / 32.0f;
 		textFractionOfHeight = astrSize.height / 32.0f;
 		scaleFromLineHeightToFontSize = astrSize.height / pointSize;
+
+		CALayer *rootLayer = [[[CALayer alloc] init] autorelease];
+		NSRect bounds = [self bounds];
+		rootLayer.bounds = bounds;
+		self.layer = rootLayer;
+		[self setWantsLayer:YES];
+
+		self.frameLayer = [[[CPUUsageFrameLayer alloc] init] autorelease];
+		self.frameLayer.frame = bounds;
+		self.frameLayer.hidden = !(self.drawsFrame);
+		[rootLayer addSublayer:self.frameLayer];
+
+		self.usageLayers = [NSMutableArray arrayWithCapacity:CPUUsageNumCacheElements];
+		for (NSUInteger idx = 0; idx < CPUUsageNumCacheElements; ++idx) {
+			CPUUsageLayer *layer = [[CPUUsageLayer alloc] init];
+			layer.frame = bounds;
+			layer.textColorGradient = self.textColorGradient;
+			layer.backgroundColorGradient = self.backgroundColorGradient;
+			layer.processorUsageFraction = idx / 100.0f;
+			layer.hidden = YES;
+
+			[rootLayer insertSublayer:layer below:self.frameLayer];
+			[self.usageLayers addObject:layer];
+
+			[layer release];
+		}
 	}
 	return self;
 }
 - (void) releaseAllCacheElements {
-	for(unsigned i = 0U; i < CPUUsageNumCacheElements; ++i) {
-		[textStorages1[i] release];
-		textStorages1[i] = nil;
-		[textStorages2[i] release];
-		textStorages2[i] = nil;
-
-		//The text storages release the layout managers and text containers, but we should also set those to nil.
-		layoutManagers1[i] = nil;
-		textContainers1[i] = nil;
-		layoutManagers2[i] = nil;
-		textContainers2[i] = nil;
-
-		[textColors[i] release];
-		textColors[i] = nil;
-	}
+	[self.usageLayers makeObjectsPerformSelector:@selector(setNeedsDisplay)];
 }
 - (void)dealloc {
 	[textColorGradient release];
 
 #pragma mark Graphics
 
-- (void) threadedDisplay {
-	[self lockFocus];
-	[self drawRect:[self bounds]];
-	[[NSGraphicsContext currentContext] flushGraphics];
-	[self unlockFocus];
-	[self setNeedsThreadedDisplay:NO];
-}
-
-- (void)drawRect:(NSRect)rect {
-	[propertiesLock lock];
-
-	NSRectClip(rect);
-	NSRect bounds = [self bounds];
-
-	[backgroundColorPremultiplied setFill];
-	NSRectFill(rect); //Not bounds, solely because we can get away with it.
-
-	if(drawsFrame) {
-		NSColor *color = nil;
-		[backgroundColorGradient getColor:&color location:NULL atIndex:1U];
-		[color set]; //Not setStroke, for unknown reasons.
-		NSFrameRectWithWidth(bounds, 2.0f);
-	}
-
-	//The index for all the parallel arrays we have as instance variables—primarily, the arrays of text storages.
-	unsigned textStorageIndex = CPUUsage * 100.0f;
-
-	NSColor *textColor = textColors[textStorageIndex];
-	if (!textColor)
-		textColor = textColors[textStorageIndex] = [[textColorGradient interpolatedColorAtLocation:CPUUsage] retain];
-	//This is associated with the text storage for the additive (second) show, as well as used to compute the alpha for the subtractive (first) show.
-	float alpha = [textColor alphaComponent];
-
-	NSTextStorage *storage1;
-	NSLayoutManager *manager1;
-	NSRange glyphRange1;
-	NSTextStorage *storage2;
-	NSLayoutManager *manager2;
-	NSRange glyphRange2;
-
-	storage1 = textStorages1[textStorageIndex];
-	if(storage1) {
-		manager1 = layoutManagers1[textStorageIndex];
-		glyphRange1 = glyphRanges1[textStorageIndex];
-
-		manager2 = layoutManagers2[textStorageIndex];
-		glyphRange2 = glyphRanges2[textStorageIndex];
-	} else {
-		[drawingAttributes setObject:textColor forKey:NSForegroundColorAttributeName];
-
-		NSString *str = [NSString stringWithPercentageFromFraction:CPUUsage CPUNumber:CPUNumber];
-		storage1 = [[NSTextStorage alloc] initWithString:str attributes:drawingAttributes];
-
-		manager1 = [[[NSLayoutManager alloc] init] autorelease];
-		[manager1 setBackgroundLayoutEnabled:NO]; //Needed for thread-safety.
-		[storage1 addLayoutManager:manager1];
-
-		NSSize containerSize = { bounds.size.width * 2.0f, bounds.size.height }; //Fix bogus line-wrapping (part 1 of 2)
-		NSTextContainer *container1 = [[[NSTextContainer alloc] initWithContainerSize:containerSize] autorelease];
-		[manager1 addTextContainer:container1];
-
-		glyphRange1 = [manager1 glyphRangeForTextContainer:container1];
-
-		textStorages1[textStorageIndex] = storage1;
-		layoutManagers1[textStorageIndex] = manager1;
-		textContainers1[textStorageIndex] = container1;
-		glyphRanges1[textStorageIndex] = glyphRange1;
-
-		boundingRects[textStorageIndex] = [manager1 boundingRectForGlyphRange:glyphRange1 inTextContainer:container1];
-
-		NSMutableDictionary *secondDrawingAttributes = [[drawingAttributes mutableCopy] autorelease];
-		[secondDrawingAttributes setObject:[textColor colorWithAlphaComponent:1.0f - alpha] forKey:NSForegroundColorAttributeName];
-		storage2 = [[NSTextStorage alloc] initWithString:str attributes:secondDrawingAttributes];
-		[storage2 setAttributes:secondDrawingAttributes range:(NSRange){ 0U, [storage2 length] }];
-
-		manager2 = [[[NSLayoutManager alloc] init] autorelease];
-		[manager2 setBackgroundLayoutEnabled:NO]; //Needed for thread-safety.
-		[storage2 addLayoutManager:manager2];
-
-		//Same containerSize as above.
-		NSTextContainer *container2 = [[[NSTextContainer alloc] initWithContainerSize:containerSize] autorelease];
-		[manager2 addTextContainer:container2];
-
-		glyphRange2 = [manager2 glyphRangeForTextContainer:container2];
-
-		textStorages2[textStorageIndex] = storage2;
-		layoutManagers2[textStorageIndex] = manager2;
-		textContainers2[textStorageIndex] = container2;
-		glyphRanges2[textStorageIndex] = glyphRange2;
-	}
-
-	NSPoint drawingOrigin = {
-		bounds.size.width  * -0.5f, //Fix bogus line-wrapping (part 2 of 2)
-		bounds.size.height * 0.5f - boundingRects[textStorageIndex].size.height * 0.5f
-	};
-
-	//We want the final text to have opacity = (usage * textOpacity).
-	//Thus, when we un-draw the text in the second pass, we must draw with alpha = 1 - (usage * opacity), so that the final opacity will be (usage * textOpacity) - alpha.
-	if(alpha < 0.99f) {
-		NSGraphicsContext *context = [NSGraphicsContext currentContext];
-		[context saveGraphicsState];
-
-		[context setCompositingOperation:NSCompositeDestinationOut];
-
-		[manager2 drawGlyphsForGlyphRange:glyphRange2 atPoint:drawingOrigin];
-
-		[context restoreGraphicsState];
-	}
-
-	//Now draw the text.
-	[manager1 drawGlyphsForGlyphRange:glyphRange1 atPoint:drawingOrigin];
-
-	[propertiesLock unlock];
-}
-
 - (void)setFrame:(NSRect)newFrame {
 	[propertiesLock lock];
 
 		[textColorGradient release];
 		textColorGradient = [newTextColorGradient retain];
 
-		[drawingAttributes setObject:[textColorGradient interpolatedColorAtLocation:CPUUsage] forKey:NSForegroundColorAttributeName];
+		[self.usageLayers setValue:textColorGradient forKey:@"textColorGradient"];
 		[self releaseAllCacheElements];
-		[self setNeedsThreadedDisplay:YES];
 
 		[propertiesLock unlock];
 	}
 		[backgroundColorPremultiplied autorelease];
 		backgroundColorPremultiplied = [[backgroundColorGradient interpolatedColorAtLocation:CPUUsage] retain];
 
-		[self setNeedsThreadedDisplay:YES];
+		[self.usageLayers setValue:backgroundColorGradient forKey:@"backgroundColorGradient"];
+		[self releaseAllCacheElements];
+
+		self.frameLayer.frameColor = backgroundColorPremultiplied;
+		[self.frameLayer setNeedsDisplay];
 
 		[propertiesLock unlock];
 	}
 
 	CPUNumber = newCPUNumber;
 
+	[self.usageLayers setValue:[NSNumber numberWithUnsignedInt:CPUNumber] forKey:@"processorIndex"];
 	[self releaseAllCacheElements];
-	[self setNeedsThreadedDisplay:YES];
 
 	[propertiesLock unlock];
 }
 - (void)setCPUUsage:(float)newCPUUsage {
 	[propertiesLock lock];
 
+	NSUInteger oldUsageIndex = CPUUsage * 100.0f;
+
 	CPUUsage = newCPUUsage;
 
-	NSColor *oldBackgroundColorPremultiplied = [[backgroundColorPremultiplied retain] autorelease];
-	NSColor *newBackgroundColorPremultiplied = [backgroundColorGradient interpolatedColorAtLocation:CPUUsage];
-	BOOL colorChanged = ![newBackgroundColorPremultiplied isEqual:oldBackgroundColorPremultiplied];
+	NSUInteger newUsageIndex = CPUUsage * 100.0f;
 
-	[backgroundColorPremultiplied release];
-	backgroundColorPremultiplied = [newBackgroundColorPremultiplied retain];
-
-	//Only set the view as needing a redraw if either the CPU usage number or the background color changed.
-	//If the CPU usage changed, then the color changed as well; thus, we only need to test the latter.
-	if (colorChanged)
-		[self setNeedsThreadedDisplay:YES];
+	if (oldUsageIndex != newUsageIndex) {
+		NSArray *layers = self.usageLayers;
+		CPUUsageLayer *oldLayer = [layers objectAtIndex:oldUsageIndex];
+		CPUUsageLayer *newLayer = [layers objectAtIndex:newUsageIndex];
+		[CATransaction setDisableActions:YES];
+		oldLayer.hidden = YES;
+		newLayer.hidden = NO;
+		[CATransaction setDisableActions:YES];
+	}
 
 	[propertiesLock unlock];
 }
 
-//Workaround to make the view never display on the main thread, yet still display on its own thread.
-- (void) setNeedsDisplay:(BOOL)flag {
-	[self setNeedsThreadedDisplay:flag];
-}
-- (void) setNeedsDisplayInRect:(NSRect)rect {
-	[self setNeedsThreadedDisplay:YES];
+- (void) setDrawsFrame:(BOOL)flag {
+	[propertiesLock lock];
+
+	drawsFrame = flag;
+
+	self.frameLayer.hidden = !drawsFrame;
+
+	[propertiesLock unlock];
 }
 
 @end