Revision: 15158
          http://bibdesk.svn.sourceforge.net/bibdesk/?rev=15158&view=rev
Author:   amaxwell
Date:     2009-04-22 18:35:33 +0000 (Wed, 22 Apr 2009)

Log Message:
-----------
Since tile sizes can now be extended somewhat arbitrarily beyond the maximum 
size, remove usage of max size from memory allocation decisions.  
Require FVImageBuffer objects to be instantiated with a specific size.

Remove caching and API from FVImageBuffer, since it was essentially superseded
by fv_zone.  Standard retain/release semantics can now be used.

Never scale tile buffers down.  Compute the maximum size required based on tile 
regions, and don't assume that raster origin has the largest region.

When allocation fails for the interleaved image buffer, return NULL instead 
of dereferencing it.  Only happens with huge images or scaling up.

Modified Paths:
--------------
    trunk/bibdesk_vendorsrc/amaxwell/FileView/FVCGImageUtilities.h
    trunk/bibdesk_vendorsrc/amaxwell/FileView/FVCGImageUtilities.mm
    trunk/bibdesk_vendorsrc/amaxwell/FileView/FVImageBuffer.h
    trunk/bibdesk_vendorsrc/amaxwell/FileView/FVImageBuffer.m

Modified: trunk/bibdesk_vendorsrc/amaxwell/FileView/FVCGImageUtilities.h
===================================================================
--- trunk/bibdesk_vendorsrc/amaxwell/FileView/FVCGImageUtilities.h      
2009-04-22 17:53:01 UTC (rev 15157)
+++ trunk/bibdesk_vendorsrc/amaxwell/FileView/FVCGImageUtilities.h      
2009-04-22 18:35:33 UTC (rev 15158)
@@ -49,20 +49,6 @@
  @return CGImage size in pixels. */
 FV_PRIVATE_EXTERN NSSize FVCGImageSize(CGImageRef image);
 
-/** @internal @brief Maximum width of tiles.
- 
- Returns the maximum width of tiles that will be created in scaling using 
vImage.
- @warning Only exported for FVImageBuffer; do not use. 
- @return Tile width in pixels. */
-FV_PRIVATE_EXTERN size_t __FVMaximumTileWidth(void);
-
-/** @internal @brief Maximum height of tiles.
- 
- Returns the maximum height of tiles that will be created in scaling using 
vImage.
- @warning Only exported for FVImageBuffer; do not use. 
- @return Tile height in pixels. */
-FV_PRIVATE_EXTERN size_t __FVMaximumTileHeight(void);
-
 /** @internal @brief Resample an image.
  
  This function is used for resampling CGImages or converting an image to be 
compatible with cache limitations (if it uses the wrong colorspace, for 
instance).  Tiling and scaling are performed using vImage, which may give 
unacceptable results at very small scale levels due to limitations in the 
tiling scheme.  However, it should be more memory-efficient than using 
FVCGCreateResampledImageOfSize.  Images returned are always host-order 8-bit 
with alpha channel.

Modified: trunk/bibdesk_vendorsrc/amaxwell/FileView/FVCGImageUtilities.mm
===================================================================
--- trunk/bibdesk_vendorsrc/amaxwell/FileView/FVCGImageUtilities.mm     
2009-04-22 17:53:01 UTC (rev 15157)
+++ trunk/bibdesk_vendorsrc/amaxwell/FileView/FVCGImageUtilities.mm     
2009-04-22 18:35:33 UTC (rev 15158)
@@ -47,6 +47,10 @@
 // http://lists.apple.com/archives/perfoptimization-dev/2005/Mar/msg00041.html
 
 // this is a good size for the MBP, and presumably other vector units
+/*
+ NOTE: MIN/MAX are advisory limits only, and may be exceeded.  Do not make
+ allocation decisions based on these limits anymore.
+ */
 #define DEFAULT_TILE_WIDTH 1024
 #define MAX_TILE_WIDTH     2048
 #define MIN_TILE_WIDTH      512
@@ -91,9 +95,6 @@
     return s;
 }
 
-size_t __FVMaximumTileWidth(void) { return MAX_TILE_WIDTH; }
-size_t __FVMaximumTileHeight(void) { return MAX_TILE_HEIGHT; }
-
 static CGImageRef __FVCopyImageUsingCacheColorspace(CGImageRef image, NSSize 
size)
 {
     [_copyLock lock];
@@ -320,6 +321,10 @@
 {
     const size_t bytesPerSample = 3;
     vImage_Buffer *dstBuffer = destBuffer->buffer;
+    if (region.h * FVPaddedRowBytesForWidth(bytesPerSample, region.w) > 
[destBuffer bufferSize]) {
+        FVLog(@"invalid reshape: %lu height and %lu rowBytes from buffer with 
size %lu", region.h, FVPaddedRowBytesForWidth(bytesPerSample, region.w), 
[destBuffer bufferSize]);
+        HALT;
+    }
     dstBuffer->width = region.w;
     dstBuffer->height = region.h;
     dstBuffer->rowBytes = FVPaddedRowBytesForWidth(bytesPerSample, region.w);
@@ -374,6 +379,10 @@
 {
     const size_t bytesPerSample = 4;
     vImage_Buffer *dstBuffer = destBuffer->buffer;
+    if (region.h * FVPaddedRowBytesForWidth(bytesPerSample, region.w) > 
[destBuffer bufferSize]) {
+        FVLog(@"invalid reshape: %lu height and %lu rowBytes from buffer with 
size %lu", region.h, FVPaddedRowBytesForWidth(bytesPerSample, region.w), 
[destBuffer bufferSize]);
+        HALT;
+    }
     dstBuffer->width = region.w;
     dstBuffer->height = region.h;
     dstBuffer->rowBytes = FVPaddedRowBytesForWidth(bytesPerSample, region.w);
@@ -661,7 +670,7 @@
 {
     // make this call early so we avoid other allocations on this thread in 
case we have to wait on _copyLock
     CFDataRef originalImageData = NULL;
-    const uint8_t *srcBytes = __FVCGImageGetBytePtr(image, NULL);    
+    const uint8_t *srcBytes = __FVCGImageGetBytePtr(image, NULL);   
     if (NULL == srcBytes) {
         // block other threads from copying at the same time; this tends to be 
a large memory hit
         [_copyLock lock];
@@ -681,6 +690,17 @@
     
     size_t destRowBytes = FVPaddedRowBytesForWidth(4, desiredSize.width);
     FVImageBuffer *interleavedImageBuffer = [[FVImageBuffer alloc] 
initWithWidth:desiredSize.width height:desiredSize.height 
rowBytes:destRowBytes];
+    
+    // this is the most likely to fail, since it's the largest contiguous 
allocation
+    if (nil == interleavedImageBuffer) {
+        if (originalImageData) {
+            CFRelease(originalImageData);
+            [_copyLock unlock];
+        }
+        FVLog(@"Unable to allocate memory for image of size %.0f x %.0f", 
desiredSize.width, desiredSize.height);
+        return NULL;
+    }
+    
     vImage_Buffer *interleavedBuffer = interleavedImageBuffer->buffer;
 
     /*
@@ -694,19 +714,34 @@
     
     std::vector <FVRegion> regions = __FVTileRegionsForImage(image, scale);
     
-    // we don't need to retain the tiles, since we don't release them after 
adding to the arrays
-    NSMutableArray *planarTilesA = (NSMutableArray 
*)CFArrayCreateMutable(NULL, 4, NULL);
-    NSMutableArray *planarTilesB = (NSMutableArray 
*)CFArrayCreateMutable(NULL, 4, NULL);
+    // fixed size mutable arrays
+    NSMutableArray *planarTilesA = (NSMutableArray 
*)CFArrayCreateMutable(NULL, 4, &kCFTypeArrayCallBacks);
+    NSMutableArray *planarTilesB = (NSMutableArray 
*)CFArrayCreateMutable(NULL, 4, &kCFTypeArrayCallBacks);
     
+    // first region is not necessarily the largest region anymore; figure out 
the maximum height and width for tiles
+    size_t maxWidth = 0, maxHeight = 0;
+    std::vector<FVRegion>::iterator iter;
+    for (iter = regions.begin(); iter < regions.end(); iter++) {
+        maxWidth = std::max(maxWidth, iter->w);
+        maxHeight = std::max(maxHeight, iter->h);
+    }
+    
+    // if scaling up, allocate extra memory for usage as a scaling destination
+    if (scale > 1) {
+        maxWidth = ceil(scale * maxWidth);
+        maxHeight = ceil(scale * maxHeight);
+    }
+        
     FVImageBuffer *imageBuffer;
     NSUInteger i;
     for (i = 0; i < 4; i++) {
-        // if scaling up, allocate extra memory, since we reuse these buffers  
  
-        imageBuffer = [FVImageBuffer newPlanarBufferWithScale:ceil(scale)];
+        imageBuffer = [[FVImageBuffer alloc] initWithWidth:maxWidth 
height:maxHeight bytesPerSample:1];
         [planarTilesA addObject:imageBuffer];
+        [imageBuffer release];
         
-        imageBuffer = [FVImageBuffer newPlanarBufferWithScale:ceil(scale)];
+        imageBuffer = [[FVImageBuffer alloc] initWithWidth:maxWidth 
height:maxHeight bytesPerSample:1];
         [planarTilesB addObject:imageBuffer];
+        [imageBuffer release];
     }
     imageBuffer = nil;
     
@@ -725,16 +760,16 @@
     NSUInteger regionRowIndex = 0;
     NSMutableArray *currentRegionRow = [NSMutableArray new];
     
-    // first region should be the largest region; this is a temporary buffer 
passed to the planar conversion function
     // NB: not required for the indexed images, since we copy those directly 
to the planar buffers
-    FVImageBuffer *regionBuffer = isIndexedImage ? nil : [[FVImageBuffer 
alloc] initWithWidth:regions[0].w height:regions[0].h bytesPerSample:4];
+    // this is a temporary buffer passed to the planar conversion function
+    FVImageBuffer *regionBuffer = isIndexedImage ? nil : [[FVImageBuffer 
alloc] initWithWidth:maxWidth height:maxHeight bytesPerSample:4];
     
     // maintain these instead of creating new buffers on each pass through the 
loop
     NSUInteger regionColumnIndex = 
__FVGetNumberOfColumnsInRegionVector(regions);
     for (NSUInteger j = 0; j < regionColumnIndex; j++) {
         imageBuffer = [[FVImageBuffer alloc] initWithWidth:(regions[j].w * 
ceil(scale)) height:(regions[j].h * ceil(scale)) bytesPerSample:4];
         [currentRegionRow addObject:imageBuffer];
-        [imageBuffer dispose];
+        [imageBuffer release];
     }
     
     // reset to zero so we can use this as index into currentRegionRow
@@ -841,15 +876,13 @@
     
     // cleanup is safe now
     
-    // call -dispose on the tiles so they're returned to the cache
-    [planarTilesA makeObjectsPerformSelector:@selector(dispose)];
+    // dispose of the tiles
     [planarTilesA release];
-    [planarTilesB makeObjectsPerformSelector:@selector(dispose)];
     [planarTilesB release];
     [currentRegionRow release];
     
     // could probably cache a few of these
-    [regionBuffer dispose];
+    [regionBuffer release];
     
 #if FV_LIMIT_TILEMEMORY_USAGE
     pthread_mutex_lock(&_memoryMutex);
@@ -873,8 +906,8 @@
     CGDataProviderRelease(provider);
     CGColorSpaceRelease(cspace);
     
-    // we need to release this one, since its memory is now transferred to 
NSData (and it's too big to cache)
-    [interleavedImageBuffer dispose];
+    // memory is now transferred to NSData
+    [interleavedImageBuffer release];
     
     return image; 
 }

Modified: trunk/bibdesk_vendorsrc/amaxwell/FileView/FVImageBuffer.h
===================================================================
--- trunk/bibdesk_vendorsrc/amaxwell/FileView/FVImageBuffer.h   2009-04-22 
17:53:01 UTC (rev 15157)
+++ trunk/bibdesk_vendorsrc/amaxwell/FileView/FVImageBuffer.h   2009-04-22 
18:35:33 UTC (rev 15158)
@@ -41,20 +41,10 @@
 
 /** @internal @brief Wrapper around vImage_Buffer.
  
- FVImageBuffer provides a Cocoa object wrapper around a vImage_Buffer 
structure.  It also provides thread-safe caching of tile-sized buffers in order 
to avoid repeated malloc/free overhead, since each thread needs its own 
buffers.  Additionally, it uses a custom allocator to track memory allocations, 
and can report the number of bytes allocated for all threads.
+ FVImageBuffer provides a Cocoa object wrapper around a vImage_Buffer 
structure.  It uses a custom allocator to track memory allocations, and can 
report the number of bytes allocated for all threads.
  
  FVImageBuffer can also be set to not free its underlying vImage_Buffer data 
pointer in dealloc, which allows transfer of ownership of the raw byte pointer 
to another object without copying.  This data pointer will have been allocated 
using the CFAllocator returned by FVImageBuffer::allocator, so 
CFDataCreateWithBytesNoCopy() should be passed this allocator in order to 
ensure proper cleanup.
  
- Cache management is slightly confusing here, but the intended lifecycle goes 
like this:
- 
- @li Create an FVImageBuffer via alloc/init... or new... method
- @li Add to an array, retain/release
- @li Mess with bytes, manipulate vImage_Buffer, etc.
- @li Do more things with the buffer, retain/autorelease
- @li When done with the buffer, call FVImageBuffer::dispose
- 
- <b>Only the first allocation message is balanced by a call to 
FVImageBuffer::dispose</b>.  FVImage::dispose is not a general-purpose 
replacement for release; it is only sent when you are done with the object.  
For now, deal with this weirdness; this one of the reasons  it's a private 
class.
- 
  @warning FVImageBuffer is designed for usage with FVCGImageUtilities.h 
functions and should not be used elsewhere as-is.  FVImageBuffer instances must 
not be shared between threads unless protected by a mutex.
  
  */
@@ -73,32 +63,12 @@
  @return The number of bytes in use by FVImageBuffer instances. */
 + (uint64_t)allocatedBytes;
 
-/** @internal @brief Initialize a new tile buffer.
+/** @internal @brief Raises an exception.
  
- Returns a planar buffer of appropriate size for tiling.  Dimensions are 
(FVCGImageUtilities.h::__FVMaximumTileWidth() x 
FVCGImageUtilities.h::__FVMaximumTileHeight()).
+ Callers must pass an explicit size.
  @return An initialized buffer instance. */
 - (id)init;
 
-/** @internal @brief Get a cached tile buffer.
- 
- Returns a previously cached buffer of the same size as that returned by 
FVImageBuffer::init.  
- @warning You must call FVImageBuffer::dispose to transfer ownership back to 
the cache, or else you will leak the instance. 
- @return An initialized buffer instance. */
-+ (id)new;
-
-/** @internal @brief Scaled tile buffer.
-
- Creates a buffer of the default size with each side multiplied by scale.  
- @warning You must call FVImageBuffer::dispose to transfer ownership back to 
the cache, since this may return a cached instance.
- @return An initialized buffer instance. */
-+ (id)newPlanarBufferWithScale:(double)scale;
-
-/** @internal @brief Transfer ownership.
- 
- Transfers ownership of the receiver back to the cache if it was previously 
cached, or calls release.  
- @warning Must only be called once, and you must not use the object after 
calling this method. */
-- (oneway void)dispose;
-
 /** @internal @brief Designated initializer.
  
  Initializes a new buffer with the specified width, height, and bytes per row.
@@ -127,4 +97,9 @@
  @return A CFAllocator instance. */
 - (CFAllocatorRef)allocator;
 
+/** @internal @brief Internal buffer size.
+ 
+ Use for debugging and asserting.  In case of reshaping the underlying buffer, 
this preserves the original size.  If you change the data pointer in the buffer 
and then call this, you'll get what you deserve. */
+- (size_t)bufferSize;
+
 @end

Modified: trunk/bibdesk_vendorsrc/amaxwell/FileView/FVImageBuffer.m
===================================================================
--- trunk/bibdesk_vendorsrc/amaxwell/FileView/FVImageBuffer.m   2009-04-22 
17:53:01 UTC (rev 15157)
+++ trunk/bibdesk_vendorsrc/amaxwell/FileView/FVImageBuffer.m   2009-04-22 
18:35:33 UTC (rev 15158)
@@ -43,9 +43,7 @@
 #import "FVBitmapContext.h"
 #import "FVAllocator.h"
 
-static NSMutableSet *_availableBuffers = nil;
-static NSMutableSet *_workingBuffers = nil;
-static OSSpinLock _bufferCacheLock = OS_SPINLOCK_INIT;
+
 static CFAllocatorRef _allocator = NULL;
 
 static OSSpinLock _monitorLock = OS_SPINLOCK_INIT;
@@ -105,8 +103,6 @@
 + (void)initialize
 {
     FVINITIALIZE(FVImageBuffer);
-    _availableBuffers = [NSMutableSet new];
-    _workingBuffers = [NSMutableSet new];
 
     // create before _allocator
     _monitoredPointers = CFDictionaryCreateMutable(NULL, 0, NULL, 
&FVIntegerValueDictionaryCallBacks);
@@ -130,36 +126,10 @@
     return _allocatedBytes;
 }
 
-+ (id)new
-{
-    OSSpinLockLock(&_bufferCacheLock);
-    id obj = nil;
-    if ([_availableBuffers count]) {
-        obj = [_availableBuffers anyObject];
-        [_workingBuffers addObject:obj];
-        [_availableBuffers removeObject:obj];
-    }
-    if (nil == obj) {
-        obj = [super new];
-        [_workingBuffers addObject:obj];
-    }
-    OSSpinLockUnlock(&_bufferCacheLock);
-    return obj;
-}
-
-+ (id)newPlanarBufferWithScale:(double)scale
-{
-    NSParameterAssert(scale > 0);
-    if (scale < 1) return [self new];
-    
-    size_t w = __FVMaximumTileWidth() * ceil(scale);
-    size_t h = __FVMaximumTileHeight() * ceil(scale);
-    return [[self allocWithZone:[self zone]] initWithWidth:w height:h 
bytesPerSample:1];
-}
-
 - (id)init
 {
-    return [self initWithWidth:__FVMaximumTileWidth() 
height:__FVMaximumTileHeight() bytesPerSample:1];
+    [self doesNotRecognizeSelector:_cmd];
+    return nil;
 }
 
 // safe initializer for copy, in case there's a mismatch between 
width/height/rowBytes
@@ -223,20 +193,6 @@
     [super dealloc];
 }
 
-- (oneway void)dispose;
-{
-    OSSpinLockLock(&_bufferCacheLock);
-    // if previously cached, return to the cache; otherwise, release
-    if ([_workingBuffers containsObject:self]) {
-        NSAssert(_freeBufferOnDealloc, @"returning buffer to cache after 
transferring contents ownership");
-        [_availableBuffers addObject:self];
-        [_workingBuffers removeObject:self];
-    } else {
-        [self release];
-    }
-    OSSpinLockUnlock(&_bufferCacheLock);
-}
-
 - (void)setFreeBufferOnDealloc:(BOOL)flag;
 {
     if (NO == flag) {
@@ -253,4 +209,6 @@
 
 - (CFAllocatorRef)allocator { return _allocator; }
 
+- (size_t)bufferSize { return _bufferSize; }
+
 @end


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

------------------------------------------------------------------------------
Stay on top of everything new and different, both inside and 
around Java (TM) technology - register by April 22, and save
$200 on the JavaOne (SM) conference, June 2-5, 2009, San Francisco.
300 plus technical and hands-on sessions. Register today. 
Use priority code J9JMT32. http://p.sf.net/sfu/p
_______________________________________________
Bibdesk-commit mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/bibdesk-commit

Reply via email to