Skip to content

Commit bdcf380

Browse files
committed
fix: Memory leaks in GLUIImage and DrawString when using METAL
1 parent 61b5d93 commit bdcf380

File tree

5 files changed

+263
-36
lines changed

5 files changed

+263
-36
lines changed

Ports/iOSPort/nativeSources/CodenameOne_GLViewController.m

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2925,8 +2925,9 @@ - (void)drawFrame:(CGRect)rect
29252925
return;
29262926
}
29272927
#ifdef CN1_USE_METAL
2928-
// Metal rendering path
2928+
// Metal rendering path - use autorelease pool to ensure cleanup each frame
29292929
METALView *metalView = [self metalView];
2930+
@autoreleasepool {
29302931
[metalView beginFrame];
29312932

29322933
if(currentTarget != nil) {
@@ -2947,6 +2948,7 @@ - (void)drawFrame:(CGRect)rect
29472948
#ifndef CN1_USE_ARC
29482949
[cp release];
29492950
#endif
2951+
} // end @autoreleasepool for Metal
29502952
#else
29512953
// OpenGL ES rendering path
29522954
[[self eaglView] setFramebuffer];
@@ -3048,7 +3050,9 @@ - (void)drawFrame:(CGRect)rect
30483050
}
30493051
}
30503052
#ifdef CN1_USE_METAL
3051-
[metalView presentFramebuffer];
3053+
@autoreleasepool {
3054+
[metalView presentFramebuffer];
3055+
}
30523056
#else
30533057
GLErrorLog;
30543058

Ports/iOSPort/nativeSources/DrawString.m

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#import <Metal/Metal.h>
3030
#import <simd/simd.h>
3131
#import "CN1METALTransform.h"
32+
#import "DrawStringMetalTextureCache.h"
3233
#endif
3334

3435
extern float scaleValue;
@@ -196,42 +197,60 @@ -(void)execute {
196197

197198
[encoder setRenderPipelineState:pipelineState];
198199

199-
// Calculate text dimensions
200-
int w = (int)[str sizeWithAttributes:@{NSFontAttributeName: font}].width;
200+
// Check cache first
201+
DrawStringMetalTextureCache *cachedTex = [DrawStringMetalTextureCache checkCache:str f:font c:color a:255];
202+
id<MTLTexture> texture = nil;
203+
int w = -1;
204+
205+
if (cachedTex != nil) {
206+
texture = [cachedTex texture];
207+
w = [cachedTex stringWidth];
208+
} else {
209+
// Calculate text dimensions
210+
w = (int)[str sizeWithAttributes:@{NSFontAttributeName: font}].width;
211+
int h = (int)ceil([font lineHeight] + 1.0 * scaleValue);
212+
int p2w = nextPowerOf2(w);
213+
int p2h = nextPowerOf2(h);
214+
215+
// Create text texture - same as ES2 version
216+
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
217+
void* imageData = malloc(p2h * p2w * 4);
218+
CGContextRef context = CGBitmapContextCreate(imageData, p2w, p2h, 8, 4 * p2w, colorSpace, kCGImageAlphaPremultipliedLast);
219+
// Note: ES2 version has flip commented out, so we don't flip either
220+
CGColorSpaceRelease(colorSpace);
221+
CGContextClearRect(context, CGRectMake(0, 0, p2w, p2h));
222+
223+
UIGraphicsPushContext(context);
224+
UIColor *textColor = UIColorFromRGB(color, 255);
225+
[str drawAtPoint:CGPointZero withAttributes:@{
226+
NSFontAttributeName: font,
227+
NSForegroundColorAttributeName: textColor
228+
}];
229+
UIGraphicsPopContext();
230+
231+
// Create Metal texture
232+
MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
233+
width:p2w
234+
height:p2h
235+
mipmapped:NO];
236+
textureDescriptor.usage = MTLTextureUsageShaderRead;
237+
238+
texture = [[self device] newTextureWithDescriptor:textureDescriptor];
239+
MTLRegion region = MTLRegionMake2D(0, 0, p2w, p2h);
240+
[texture replaceRegion:region mipmapLevel:0 withBytes:imageData bytesPerRow:4 * p2w];
241+
242+
CGContextRelease(context);
243+
free(imageData);
244+
245+
// Cache the texture for future use
246+
[DrawStringMetalTextureCache cache:str f:font t:texture c:color a:255];
247+
}
248+
249+
// Calculate height for vertex positioning
201250
int h = (int)ceil([font lineHeight] + 1.0 * scaleValue);
202251
int p2w = nextPowerOf2(w);
203252
int p2h = nextPowerOf2(h);
204253

205-
// Create text texture - same as ES2 version
206-
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
207-
void* imageData = malloc(p2h * p2w * 4);
208-
CGContextRef context = CGBitmapContextCreate(imageData, p2w, p2h, 8, 4 * p2w, colorSpace, kCGImageAlphaPremultipliedLast);
209-
// Note: ES2 version has flip commented out, so we don't flip either
210-
CGColorSpaceRelease(colorSpace);
211-
CGContextClearRect(context, CGRectMake(0, 0, p2w, p2h));
212-
213-
UIGraphicsPushContext(context);
214-
UIColor *textColor = UIColorFromRGB(color, 255);
215-
[str drawAtPoint:CGPointZero withAttributes:@{
216-
NSFontAttributeName: font,
217-
NSForegroundColorAttributeName: textColor
218-
}];
219-
UIGraphicsPopContext();
220-
221-
// Create Metal texture
222-
MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
223-
width:p2w
224-
height:p2h
225-
mipmapped:NO];
226-
textureDescriptor.usage = MTLTextureUsageShaderRead;
227-
228-
id<MTLTexture> texture = [[self device] newTextureWithDescriptor:textureDescriptor];
229-
MTLRegion region = MTLRegionMake2D(0, 0, p2w, p2h);
230-
[texture replaceRegion:region mipmapLevel:0 withBytes:imageData bytesPerRow:4 * p2w];
231-
232-
CGContextRelease(context);
233-
free(imageData);
234-
235254
// Create vertex data (position + texCoord interleaved)
236255
typedef struct {
237256
float position[2];
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
* This code is free software; you can redistribute it and/or modify it
5+
* under the terms of the GNU General Public License version 2 only, as
6+
* published by the Free Software Foundation. Codename One designates this
7+
* particular file as subject to the "Classpath" exception as provided
8+
* by Oracle in the LICENSE file that accompanied this code.
9+
*
10+
* This code is distributed in the hope that it will be useful, but WITHOUT
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13+
* version 2 for more details (a copy is included in the LICENSE file that
14+
* accompanied this code).
15+
*
16+
* You should have received a copy of the GNU General Public License version
17+
* 2 along with this work; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19+
*
20+
* Please contact Codename One through http://www.codenameone.com/ if you
21+
* need additional information or have any questions.
22+
*/
23+
#import <Foundation/Foundation.h>
24+
#import <UIKit/UIKit.h>
25+
26+
#ifdef CN1_USE_METAL
27+
#import <Metal/Metal.h>
28+
29+
@interface DrawStringMetalTextureCache : NSObject {
30+
NSDate *lastAccess;
31+
id<MTLTexture> texture;
32+
NSString *str;
33+
UIFont *font;
34+
int color;
35+
int alpha;
36+
int stringWidth;
37+
}
38+
39+
-(BOOL)isEqual:(id)object;
40+
-(id)initWithString:(NSString*)s f:(UIFont*)f t:(id<MTLTexture>)t c:(int)c a:(int)a;
41+
+(void)cache:(NSString*)s f:(UIFont*)f t:(id<MTLTexture>)t c:(int)c a:(int)a;
42+
+(DrawStringMetalTextureCache*)checkCache:(NSString*)s f:(UIFont*)f c:(int)c a:(int)a;
43+
+(void)flushDeleted;
44+
-(int)stringWidth;
45+
-(id<MTLTexture>)texture;
46+
47+
@end
48+
49+
#endif
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
* This code is free software; you can redistribute it and/or modify it
5+
* under the terms of the GNU General Public License version 2 only, as
6+
* published by the Free Software Foundation. Codename One designates this
7+
* particular file as subject to the "Classpath" exception as provided
8+
* by Oracle in the LICENSE file that accompanied this code.
9+
*
10+
* This code is distributed in the hope that it will be useful, but WITHOUT
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13+
* version 2 for more details (a copy is included in the LICENSE file that
14+
* accompanied this code).
15+
*
16+
* You should have received a copy of the GNU General Public License version
17+
* 2 along with this work; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19+
*
20+
* Please contact Codename One through http://www.codenameone.com/ if you
21+
* need additional information or have any questions.
22+
*/
23+
#import "DrawStringMetalTextureCache.h"
24+
25+
#ifdef CN1_USE_METAL
26+
27+
#import "ExecutableOp.h"
28+
#include "xmlvm.h"
29+
30+
@implementation DrawStringMetalTextureCache
31+
32+
static int MAX_CACHE_SIZE = 100;
33+
34+
-(id)initWithString:(NSString*)s f:(UIFont*)f t:(id<MTLTexture>)t c:(int)c a:(int)a {
35+
stringWidth = -1;
36+
str = s;
37+
font = f;
38+
#ifndef CN1_USE_ARC
39+
[str retain];
40+
[font retain];
41+
lastAccess = [[NSDate date] retain];
42+
[t retain];
43+
#else
44+
lastAccess = [NSDate date];
45+
#endif
46+
texture = t;
47+
color = c;
48+
alpha = a;
49+
if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
50+
MAX_CACHE_SIZE = 200;
51+
}
52+
return self;
53+
}
54+
55+
static NSMutableArray* cachedStrings = nil;
56+
static NSMutableArray* pendingDeleteStrings = nil;
57+
58+
-(int)stringWidth {
59+
if (stringWidth == -1) {
60+
stringWidth = [str sizeWithAttributes:@{NSFontAttributeName: font}].width;
61+
}
62+
return stringWidth;
63+
}
64+
65+
-(id<MTLTexture>)texture {
66+
return texture;
67+
}
68+
69+
+(void)cache:(NSString*)s f:(UIFont*)f t:(id<MTLTexture>)t c:(int)c a:(int)a {
70+
DrawStringMetalTextureCache* d = [[DrawStringMetalTextureCache alloc] initWithString:s f:f t:t c:c a:a];
71+
if(cachedStrings == nil) {
72+
cachedStrings = [[NSMutableArray alloc] init];
73+
[cachedStrings addObject:d];
74+
pendingDeleteStrings = [[NSMutableArray alloc] init];
75+
#ifndef CN1_USE_ARC
76+
[d release];
77+
#endif
78+
return;
79+
}
80+
if([cachedStrings count] < MAX_CACHE_SIZE) {
81+
[cachedStrings addObject:d];
82+
#ifndef CN1_USE_ARC
83+
[d release];
84+
#endif
85+
} else {
86+
// need to pick an element in the array to remove
87+
DrawStringMetalTextureCache* oldest = d;
88+
for(DrawStringMetalTextureCache* entry in cachedStrings) {
89+
if([oldest->lastAccess compare:entry->lastAccess] == NSOrderedDescending) {
90+
oldest = entry;
91+
}
92+
}
93+
[pendingDeleteStrings addObject:oldest];
94+
[cachedStrings removeObject:oldest];
95+
[cachedStrings addObject:d];
96+
#ifndef CN1_USE_ARC
97+
[d release];
98+
#endif
99+
}
100+
}
101+
102+
+(void)flushDeleted {
103+
[pendingDeleteStrings removeAllObjects];
104+
}
105+
106+
-(BOOL)isEqual:(id)object {
107+
if(object == self) {
108+
return YES;
109+
}
110+
DrawStringMetalTextureCache* o = (DrawStringMetalTextureCache*)object;
111+
return color == o->color &&
112+
alpha == o->alpha && [str isEqualToString:o->str] &&
113+
[font isEqual:o->font];
114+
}
115+
116+
+(DrawStringMetalTextureCache*)checkCache:(NSString*)s f:(UIFont*)f c:(int)c a:(int)a {
117+
DrawStringMetalTextureCache* tmp = [[DrawStringMetalTextureCache alloc] initWithString:s f:f t:nil c:c a:a];
118+
for(DrawStringMetalTextureCache* d in cachedStrings) {
119+
if([tmp isEqual:d]) {
120+
#ifndef CN1_USE_ARC
121+
[d->lastAccess release];
122+
#endif
123+
d->lastAccess = [NSDate date];
124+
#ifndef CN1_USE_ARC
125+
[d->lastAccess retain];
126+
[tmp release];
127+
#endif
128+
return d;
129+
}
130+
}
131+
#ifndef CN1_USE_ARC
132+
[tmp release];
133+
#endif
134+
return nil;
135+
}
136+
137+
#ifndef CN1_USE_ARC
138+
-(void)dealloc {
139+
[str release];
140+
[font release];
141+
[lastAccess release];
142+
[texture release];
143+
[super dealloc];
144+
}
145+
#endif
146+
147+
@end
148+
149+
#endif

Ports/iOSPort/nativeSources/GLUIImage.m

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,10 @@ -(void)setImage:(UIImage*)i {
188188
#endif
189189
#ifdef CN1_USE_METAL
190190
if(metalTexture != nil) {
191-
metalTexture = nil; // ARC or manual release will handle cleanup
191+
#ifndef CN1_USE_ARC
192+
[metalTexture release];
193+
#endif
194+
metalTexture = nil;
192195
}
193196
#else
194197
if(textureName != 0) {
@@ -225,7 +228,10 @@ -(void)dealloc {
225228
}
226229
#ifdef CN1_USE_METAL
227230
if(metalTexture != nil) {
228-
metalTexture = nil; // ARC or manual cleanup
231+
#ifndef CN1_USE_ARC
232+
[metalTexture release];
233+
#endif
234+
metalTexture = nil;
229235
}
230236
#else
231237
if(textureName != 0) {

0 commit comments

Comments
 (0)