Рисование CGLayer со временем замедляется - PullRequest
1 голос
/ 10 марта 2011

Может кто-нибудь объяснить, почему перерисовка за пределами экрана CGLayer может привести к замедлению рендеринга со временем? Позвольте мне показать вам тест, который я создал для иллюстрации проблемы.

@implementation DrawView


- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        //setup frame rate monitoring
        fps = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 100, 20)];
        fps.textColor = [UIColor whiteColor];
        fps.font = [UIFont boldSystemFontOfSize:15];
        fps.text = @"0 fps";
        [self addSubview:fps];
        frames = 0;
        lastRecord = [NSDate timeIntervalSinceReferenceDate];

        //create a cglayer and draw the background graphic to it
        CGContextRef context = UIGraphicsGetCurrentContext();
        cacheLayer = CGLayerCreateWithContext(context, self.bounds.size, NULL);

        CGImageRef background = [[UIImage imageNamed:@"background.jpg"] CGImage];
        CGContextRef cacheContext = CGLayerGetContext(cacheLayer);
        CGContextDrawImage(cacheContext, CGRectMake(0, 0, 768, 1024), background);

        //initialize cgimage stamp
        stamp = [[UIImage imageNamed:@"stamp.png"] CGImage];
        stampTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/60 target:self selector:@selector(stamp) userInfo:nil repeats:YES];
    }
    return self;
}

- (void) stamp {
    //calculate fps
    NSTimeInterval interval = [NSDate timeIntervalSinceReferenceDate];
    NSTimeInterval diff = interval-lastRecord;
    if (diff > 1.0) {
        float rate = frames/diff;
        frames = 0;
        lastRecord = [NSDate timeIntervalSinceReferenceDate];
        fps.text = [NSString stringWithFormat:@"%0.1f fps", rate];
    }
    //stamp the offscreen cglayer with the cgimage graphic
    CGRect stampRect = CGRectMake(0, 0, 200, 200);
    CGContextRef cacheContext = CGLayerGetContext(cacheLayer);
    CGContextDrawImage(cacheContext, stampRect, stamp);
    [self setNeedsDisplayInRect:stampRect];
}

- (void)drawRect:(CGRect)dirtyRect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextDrawLayerInRect(context, self.bounds, cacheLayer);
    frames++;
}

Когда я запускаю этот тест в симуляторе ipad или на устройстве, он начинается с 40 кадров в секунду и падает с постоянной скоростью в течение 10 секунд, пока не будет работать со скоростью 3 кадра в секунду. Почему это происходит? Разве это не должно работать с постоянной частотой кадров? Какое решение позволило бы мне «печатать» изображение снова и снова, поддерживая постоянную частоту кадров?

Ответы [ 2 ]

5 голосов
/ 10 марта 2011

Ваша проблема в том, что вы создали свой слой с пустым контекстом.UIGraphicsGetCurrentContext() действительно только во время цикла отрисовки.Вы вызываете его вне цикла отрисовки, поэтому он равен NULL, поэтому слой ничего не может кэшировать.Меня до сих пор удивляет, насколько сильно это портит производительность с течением времени.Я подозреваю, что в CoreGraphics может быть ошибка;можно подумать, что это будет просто медленно;не "никогда не замедляется".Но, тем не менее, он не предназначен для такой работы.

Если вы создадите растровый контекст и используете его для своего слоя, вы получите 60 кадров в секунду.Вам не нужно отказываться от CGLayer здесь.

Все, что я сделал, чтобы вернуться к 60fps, это заменил это:

CGContextRef context = UIGraphicsGetCurrentContext();

на:

CGContextRef context = CreateBitmapContext(self.bounds.size);

Где CreateBitmapContext() - это функция, которая возвращает то же самое, что настроено вашим createBitmapContext.

0 голосов
/ 10 марта 2011

Я нашел решение, хотя, казалось бы, нелогичное, быстрее создать кешированный контекст растрового изображения, а затем скопировать из него изображение для рисования в текущем контексте.

- (void) createBitmapContext {
    // Create the bitmap context
    void *          bitmapData;
    int             bitmapByteCount;
    int             bitmapBytesPerRow;
    CGSize          size = self.bounds.size;

    // Declare the number of bytes per row. Each pixel in the bitmap in this
    // example is represented by 4 bytes; 8 bits each of red, green, blue, and
    // alpha.
    bitmapBytesPerRow   = (size.width * 4);
    bitmapByteCount     = (bitmapBytesPerRow * size.height);

    // Allocate memory for image data. This is the destination in memory
    // where any drawing to the bitmap context will be rendered.
    bitmapData = malloc( bitmapByteCount );
    if (bitmapData == NULL)
    {
        //return nil;
    }
    cacheContext = CGBitmapContextCreate (bitmapData, size.width, size.height,8,bitmapBytesPerRow,
                                           CGColorSpaceCreateDeviceRGB(),kCGImageAlphaNoneSkipFirst);   
}


- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        //setup frame rate monitoring
        fps = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 100, 20)];
        fps.textColor = [UIColor whiteColor];
        fps.font = [UIFont boldSystemFontOfSize:15];
        fps.text = @"0 fps";
        [self addSubview:fps];
        frames = 0;
        lastRecord = [NSDate timeIntervalSinceReferenceDate];

        //create a bitmap context and draw the background graphic to it
        [self createBitmapContext];

        CGImageRef background = [[UIImage imageNamed:@"background.jpg"] CGImage];
        CGContextDrawImage(cacheContext, CGRectMake(0, 0, 768, 1024), background);

        //initialize cgimage stamp
        stamp = [[UIImage imageNamed:@"stamp.png"] CGImage];
        stampTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/60 target:self selector:@selector(stamp) userInfo:nil repeats:YES];
    }
    return self;
}

- (void) stamp {
    //calculate fps
    NSTimeInterval interval = [NSDate timeIntervalSinceReferenceDate];
    NSTimeInterval diff = interval-lastRecord;
    if (diff > 1.0) {
        float rate = frames/diff;
        frames = 0;
        lastRecord = [NSDate timeIntervalSinceReferenceDate];
        fps.text = [NSString stringWithFormat:@"%0.1f fps", rate];
    }
    //stamp the offscreen bitmap context with the cgimage graphic
    CGRect stampRect = CGRectMake(0, 0, 200, 200);
    CGContextDrawImage(cacheContext, stampRect, stamp);
    [self setNeedsDisplayInRect:stampRect];
}

- (void)drawRect:(CGRect)dirtyRect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGImageRef cacheImage = CGBitmapContextCreateImage(cacheContext);
    CGContextDrawImage(context, self.bounds, cacheImage);
    CGImageRelease(cacheImage);
    frames++;
}
...