mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2025-12-06 09:29:38 +01:00
JBR-7460: fixed (macos) MTLTexturePool GC implementation to release texture memory more promptly (regular young GC freeing not reused textures since 15s) + unified API with new generic AccelTexturePool (C) to be shared with the coming vulkan pipeline (linux)
(cherry picked from commit ea12ccdf5e)
This commit is contained in:
@@ -380,6 +380,7 @@ ifeq ($(call isTargetOs, macosx), true)
|
||||
libawt_lwawt/java2d/metal \
|
||||
include \
|
||||
common/awt/debug \
|
||||
common/java2d \
|
||||
common/java2d/opengl \
|
||||
java.base:libjvm \
|
||||
#
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2024, JetBrains s.r.o.. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@@ -25,50 +26,31 @@
|
||||
|
||||
#ifndef MTLTexturePool_h_Included
|
||||
#define MTLTexturePool_h_Included
|
||||
#include <time.h>
|
||||
#import "MTLUtils.h"
|
||||
|
||||
@class MTLPoolCell;
|
||||
|
||||
@interface MTLTexturePoolItem : NSObject
|
||||
@property (readwrite, retain) id<MTLTexture> texture;
|
||||
@property (readwrite) bool isBusy;
|
||||
@property (readwrite) time_t lastUsed;
|
||||
@property (readwrite) bool isMultiSample;
|
||||
@property (readwrite, assign) MTLTexturePoolItem* prev;
|
||||
@property (readwrite, retain) MTLTexturePoolItem* next;
|
||||
@property (readwrite, assign) MTLPoolCell* cell;
|
||||
|
||||
- (id) initWithTexture:(id<MTLTexture>)tex cell:(MTLPoolCell*)cell;
|
||||
@end
|
||||
#import <Metal/Metal.h>
|
||||
|
||||
@interface MTLPooledTextureHandle : NSObject
|
||||
@property (readonly, assign) id<MTLTexture> texture;
|
||||
@property (readonly) MTLRegion rect;
|
||||
- (void) releaseTexture;
|
||||
@property (readonly, assign) id<MTLTexture> texture;
|
||||
@property (readonly) NSUInteger reqWidth;
|
||||
@property (readonly) NSUInteger reqHeight;
|
||||
|
||||
// used by MTLCommandBufferWrapper.onComplete() to release used textures:
|
||||
- (void) releaseTexture;
|
||||
@end
|
||||
|
||||
// NOTE: owns all MTLTexture objects
|
||||
@interface MTLTexturePool : NSObject
|
||||
@property (readwrite, retain) id<MTLDevice> device;
|
||||
@property (readwrite, retain) id<MTLDevice> device;
|
||||
@property (readwrite) uint64_t memoryAllocated;
|
||||
@property (readwrite) uint64_t totalMemoryAllocated;
|
||||
@property (readwrite) uint32_t allocatedCount;
|
||||
@property (readwrite) uint32_t totalAllocatedCount;
|
||||
@property (readwrite) uint64_t cacheHits;
|
||||
@property (readwrite) uint64_t totalHits;
|
||||
|
||||
- (id) initWithDevice:(id<MTLDevice>)device;
|
||||
- (MTLPooledTextureHandle *) getTexture:(int)width height:(int)height format:(MTLPixelFormat)format;
|
||||
- (MTLPooledTextureHandle *) getTexture:(int)width height:(int)height format:(MTLPixelFormat)format
|
||||
isMultiSample:(bool)isMultiSample;
|
||||
@end
|
||||
- (id) initWithDevice:(id<MTLDevice>)device;
|
||||
|
||||
@interface MTLPoolCell : NSObject
|
||||
@property (readwrite, retain) MTLTexturePoolItem* available;
|
||||
@property (readwrite, assign) MTLTexturePoolItem* availableTail;
|
||||
@property (readwrite, retain) MTLTexturePoolItem* occupied;
|
||||
- (MTLTexturePoolItem *)createItem:(id<MTLDevice>)dev
|
||||
width:(int)width
|
||||
height:(int)height
|
||||
format:(MTLPixelFormat)format
|
||||
isMultiSample:(bool)isMultiSample;
|
||||
- (NSUInteger)cleanIfBefore:(time_t)lastUsedTimeToRemove;
|
||||
- (void)releaseItem:(MTLTexturePoolItem *)item;
|
||||
- (MTLPooledTextureHandle *) getTexture:(int)width height:(int)height format:(MTLPixelFormat)format;
|
||||
@end
|
||||
|
||||
#endif /* MTLTexturePool_h_Included */
|
||||
|
||||
@@ -0,0 +1,823 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2024, JetBrains s.r.o.. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include "time.h"
|
||||
|
||||
#import "AccelTexturePool.h"
|
||||
#import "MTLTexturePool.h"
|
||||
#import "Trace.h"
|
||||
|
||||
#define USE_ACCEL_TEXTURE_POOL 0
|
||||
|
||||
#define TRACE_LOCK 0
|
||||
#define TRACE_TEX 0
|
||||
|
||||
|
||||
/* lock API */
|
||||
ATexturePoolLockPrivPtr* ATexturePoolLock_initImpl() {
|
||||
NSLock* l = [[[NSLock alloc] init] autorelease];
|
||||
[l retain];
|
||||
if (TRACE_LOCK) J2dRlsTraceLn1(J2D_TRACE_VERBOSE, "ATexturePool_initLock: lock=%p", l);
|
||||
return l;
|
||||
}
|
||||
|
||||
void ATexturePoolLock_DisposeImpl(ATexturePoolLockPrivPtr *lock) {
|
||||
NSLock* l = (NSLock*)lock;
|
||||
if (TRACE_LOCK) J2dRlsTraceLn1(J2D_TRACE_VERBOSE, "ATexturePool_DisposeLock: lock=%p", l);
|
||||
[l release];
|
||||
}
|
||||
|
||||
void ATexturePoolLock_lockImpl(ATexturePoolLockPrivPtr *lock) {
|
||||
NSLock* l = (NSLock*)lock;
|
||||
if (TRACE_LOCK) J2dRlsTraceLn1(J2D_TRACE_VERBOSE, "ATexturePool_lock: lock=%p", l);
|
||||
[l lock];
|
||||
if (TRACE_LOCK) J2dRlsTraceLn1(J2D_TRACE_VERBOSE, "ATexturePool_lock: lock=%p - locked", l);
|
||||
}
|
||||
|
||||
void ATexturePoolLock_unlockImpl(ATexturePoolLockPrivPtr *lock) {
|
||||
NSLock* l = (NSLock*)lock;
|
||||
if (TRACE_LOCK) J2dRlsTraceLn1(J2D_TRACE_VERBOSE, "ATexturePool_unlock: lock=%p", l);
|
||||
[l unlock];
|
||||
if (TRACE_LOCK) J2dRlsTraceLn1(J2D_TRACE_VERBOSE, "ATexturePool_unlock: lock=%p - unlocked", l);
|
||||
}
|
||||
|
||||
|
||||
/* Texture allocate/free API */
|
||||
static id<MTLTexture> MTLTexturePool_createTexture(id<MTLDevice> device,
|
||||
int width,
|
||||
int height,
|
||||
long format)
|
||||
{
|
||||
MTLTextureDescriptor *textureDescriptor =
|
||||
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:(MTLPixelFormat)format
|
||||
width:(NSUInteger) width
|
||||
height:(NSUInteger) height
|
||||
mipmapped:NO];
|
||||
// By default:
|
||||
// usage = MTLTextureUsageShaderRead
|
||||
// storage = MTLStorageModeManaged
|
||||
textureDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
|
||||
|
||||
// Use auto-release => MTLTexturePoolItem.dealloc to free texture !
|
||||
id <MTLTexture> texture = (id <MTLTexture>) [[device newTextureWithDescriptor:textureDescriptor] autorelease];
|
||||
[texture retain];
|
||||
|
||||
if (TRACE_TEX) J2dRlsTraceLn4(J2D_TRACE_VERBOSE, "MTLTexturePool_createTexture: created texture: tex=%p, w=%d h=%d, pf=%d",
|
||||
texture, [texture width], [texture height], [texture pixelFormat]);
|
||||
return texture;
|
||||
}
|
||||
|
||||
static int MTLTexturePool_bytesPerPixel(long format) {
|
||||
switch ((MTLPixelFormat)format) {
|
||||
case MTLPixelFormatBGRA8Unorm:
|
||||
return 4;
|
||||
case MTLPixelFormatA8Unorm:
|
||||
return 1;
|
||||
default:
|
||||
J2dRlsTraceLn1(J2D_TRACE_ERROR, "MTLTexturePool_bytesPerPixel: format=%d not supported (4 bytes by default)", format);
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
static void MTLTexturePool_freeTexture(id<MTLDevice> device, id<MTLTexture> texture) {
|
||||
if (TRACE_TEX) J2dRlsTraceLn4(J2D_TRACE_VERBOSE, "MTLTexturePool_freeTexture: free texture: tex=%p, w=%d h=%d, pf=%d",
|
||||
texture, [texture width], [texture height], [texture pixelFormat]);
|
||||
[texture release];
|
||||
}
|
||||
|
||||
/*
|
||||
* Former but updated MTLTexturePool implementation
|
||||
*/
|
||||
|
||||
#define USE_MAX_GPU_DEVICE_MEM 1
|
||||
#define MAX_GPU_DEVICE_MEM (512 * UNIT_MB)
|
||||
#define SCREEN_MEMORY_SIZE_5K (5120 * 4096 * 4) // ~ 84 mb
|
||||
|
||||
#define MAX_POOL_ITEM_LIFETIME_SEC 30
|
||||
|
||||
// 32 pixel
|
||||
#define CELL_WIDTH_BITS 5
|
||||
#define CELL_HEIGHT_BITS 5
|
||||
|
||||
#define CELL_WIDTH_MASK ((1 << CELL_WIDTH_BITS) - 1)
|
||||
#define CELL_HEIGHT_MASK ((1 << CELL_HEIGHT_BITS) - 1)
|
||||
|
||||
#define USE_CEIL_SIZE 1
|
||||
|
||||
#define FORCE_GC 1
|
||||
// force gc (prune old textures):
|
||||
#define FORCE_GC_INTERVAL_SEC (MAX_POOL_ITEM_LIFETIME_SEC * 10)
|
||||
|
||||
// force young gc every 15 seconds (prune only not reused textures):
|
||||
#define YOUNG_GC_INTERVAL_SEC 15
|
||||
#define YOUNG_GC_LIFETIME_SEC (FORCE_GC_INTERVAL_SEC * 2)
|
||||
|
||||
#define TRACE_GC 1
|
||||
#define TRACE_GC_ALIVE 0
|
||||
|
||||
#define TRACE_MEM_API 0
|
||||
#define TRACE_USE_API 0
|
||||
#define TRACE_REUSE 0
|
||||
|
||||
#define INIT_TEST 0
|
||||
#define INIT_TEST_STEP 1
|
||||
#define INIT_TEST_MAX 1024
|
||||
|
||||
/* private definitions */
|
||||
@class MTLPoolCell;
|
||||
|
||||
@interface MTLTexturePoolItem : NSObject
|
||||
@property (readwrite, assign) id<MTLTexture> texture;
|
||||
@property (readwrite, assign) MTLPoolCell* cell;
|
||||
@property (readwrite, assign) MTLTexturePoolItem* prev;
|
||||
@property (readwrite, retain) MTLTexturePoolItem* next;
|
||||
@property (readwrite) time_t lastUsed;
|
||||
@property (readwrite) int width;
|
||||
@property (readwrite) int height;
|
||||
@property (readwrite) MTLPixelFormat format;
|
||||
@property (readwrite) int reuseCount;
|
||||
@property (readwrite) bool isBusy;
|
||||
@end
|
||||
|
||||
@interface MTLPoolCell : NSObject
|
||||
@property (readwrite, retain) MTLTexturePoolItem* available;
|
||||
@property (readwrite, assign) MTLTexturePoolItem* availableTail;
|
||||
@property (readwrite, retain) MTLTexturePoolItem* occupied;
|
||||
@end
|
||||
|
||||
@implementation MTLTexturePoolItem
|
||||
|
||||
@synthesize texture, lastUsed, next, cell, width, height, format, reuseCount, isBusy;
|
||||
|
||||
- (id) initWithTexture:(id<MTLTexture>)tex
|
||||
cell:(MTLPoolCell*)c
|
||||
width:(int)w
|
||||
height:(int)h
|
||||
format:(MTLPixelFormat)f
|
||||
{
|
||||
self = [super init];
|
||||
if (self == nil) return self;
|
||||
self.texture = tex;
|
||||
self.cell = c;
|
||||
self.next = nil;
|
||||
self.prev = nil;
|
||||
self.lastUsed = 0;
|
||||
self.width = w;
|
||||
self.height = h;
|
||||
self.format = f;
|
||||
self.reuseCount = 0;
|
||||
self.isBusy = NO;
|
||||
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "MTLTexturePoolItem_initWithTexture: item = %p", self);
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc {
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn2(J2D_TRACE_INFO, "MTLTexturePoolItem_dealloc: item = %p - reuse: %4d", self, self.reuseCount);
|
||||
|
||||
// use texture (native API) to release allocated texture:
|
||||
MTLTexturePool_freeTexture(nil, self.texture);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void) releaseItem {
|
||||
if (!isBusy) {
|
||||
return;
|
||||
}
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "MTLTexturePoolItem_releaseItem: item = %p", self);
|
||||
|
||||
if (self.cell != nil) {
|
||||
[self.cell releaseCellItem:self];
|
||||
} else {
|
||||
J2dRlsTraceLn1(J2D_TRACE_ERROR, "MTLTexturePoolItem_releaseItem: item = %p (detached)", self);
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
/* MTLPooledTextureHandle API */
|
||||
@implementation MTLPooledTextureHandle
|
||||
{
|
||||
id<MTLTexture> _texture;
|
||||
MTLTexturePoolItem * _poolItem;
|
||||
ATexturePoolHandle* _texHandle;
|
||||
NSUInteger _reqWidth;
|
||||
NSUInteger _reqHeight;
|
||||
}
|
||||
@synthesize texture = _texture, reqWidth = _reqWidth, reqHeight = _reqHeight;
|
||||
|
||||
- (id) initWithPoolItem:(id<MTLTexture>)texture poolItem:(MTLTexturePoolItem *)poolItem reqWidth:(NSUInteger)w reqHeight:(NSUInteger)h {
|
||||
self = [super init];
|
||||
if (self == nil) return self;
|
||||
|
||||
_texture = texture;
|
||||
_poolItem = poolItem;
|
||||
_texHandle = nil;
|
||||
|
||||
_reqWidth = w;
|
||||
_reqHeight = h;
|
||||
|
||||
if (TRACE_USE_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "MTLPooledTextureHandle_initWithPoolItem: handle = %p", self);
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id) initWithTextureHandle:(ATexturePoolHandle*)texHandle {
|
||||
self = [super init];
|
||||
if (self == nil) return self;
|
||||
|
||||
_texture = ATexturePoolHandle_GetTexture(texHandle);
|
||||
_poolItem = nil;
|
||||
_texHandle = texHandle;
|
||||
|
||||
_reqWidth = ATexturePoolHandle_GetRequestedWidth(texHandle);
|
||||
_reqHeight = ATexturePoolHandle_GetRequestedHeight(texHandle);
|
||||
|
||||
if (TRACE_USE_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "MTLPooledTextureHandle_initWithTextureHandle: handle = %p", self);
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) releaseTexture {
|
||||
if (TRACE_USE_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "MTLPooledTextureHandle_ReleaseTexture: handle = %p", self);
|
||||
if (_texHandle != nil) {
|
||||
ATexturePoolHandle_ReleaseTexture(_texHandle);
|
||||
}
|
||||
if (_poolItem != nil) {
|
||||
[_poolItem releaseItem];
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
@implementation MTLPoolCell {
|
||||
MTLTexturePool* _pool;
|
||||
NSLock* _lock;
|
||||
}
|
||||
@synthesize available, availableTail, occupied;
|
||||
|
||||
- (instancetype) init:(MTLTexturePool*)pool {
|
||||
self = [super init];
|
||||
if (self == nil) return self;
|
||||
|
||||
self.available = nil;
|
||||
self.availableTail = nil;
|
||||
self.occupied = nil;
|
||||
_pool = pool;
|
||||
_lock = [[NSLock alloc] init];
|
||||
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "MTLPoolCell_init: cell = %p", self);
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) removeAllItems {
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn(J2D_TRACE_INFO, "MTLPoolCell_removeAllItems");
|
||||
|
||||
MTLTexturePoolItem *cur = self.available;
|
||||
MTLTexturePoolItem *next = nil;
|
||||
while (cur != nil) {
|
||||
next = cur.next;
|
||||
self.available = cur;
|
||||
cur = next;
|
||||
}
|
||||
cur = self.occupied;
|
||||
next = nil;
|
||||
while (cur != nil) {
|
||||
next = cur.next;
|
||||
J2dRlsTraceLn1(J2D_TRACE_ERROR, "MTLPoolCell_removeAllItems: occupied item = %p (detached)", cur);
|
||||
// Do not dispose (may leak) until MTLTexturePoolItem_releaseItem() is called by handle:
|
||||
// mark item as detached:
|
||||
cur.cell = nil;
|
||||
cur = next;
|
||||
self.occupied = cur;
|
||||
}
|
||||
self.availableTail = nil;
|
||||
}
|
||||
|
||||
- (void) removeAvailableItem:(MTLTexturePoolItem*)item {
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "MTLPoolCell_removeAvailableItem: item = %p", item);
|
||||
[item retain];
|
||||
if (item.prev == nil) {
|
||||
self.available = item.next;
|
||||
if (item.next) {
|
||||
item.next.prev = nil;
|
||||
item.next = nil;
|
||||
} else {
|
||||
self.availableTail = item.prev;
|
||||
}
|
||||
} else {
|
||||
item.prev.next = item.next;
|
||||
if (item.next) {
|
||||
item.next.prev = item.prev;
|
||||
item.next = nil;
|
||||
} else {
|
||||
self.availableTail = item.prev;
|
||||
}
|
||||
}
|
||||
[item release];
|
||||
}
|
||||
|
||||
- (void) dealloc {
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "MTLPoolCell_dealloc: cell = %p", self);
|
||||
[_lock lock];
|
||||
@try {
|
||||
[self removeAllItems];
|
||||
} @finally {
|
||||
[_lock unlock];
|
||||
}
|
||||
[_lock release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void) occupyItem:(MTLTexturePoolItem *)item {
|
||||
if (item.isBusy) {
|
||||
return;
|
||||
}
|
||||
if (TRACE_USE_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "MTLPoolCell_occupyItem: item = %p", item);
|
||||
[item retain];
|
||||
if (item.prev == nil) {
|
||||
self.available = item.next;
|
||||
if (item.next) {
|
||||
item.next.prev = nil;
|
||||
} else {
|
||||
self.availableTail = item.prev;
|
||||
}
|
||||
} else {
|
||||
item.prev.next = item.next;
|
||||
if (item.next) {
|
||||
item.next.prev = item.prev;
|
||||
} else {
|
||||
self.availableTail = item.prev;
|
||||
}
|
||||
item.prev = nil;
|
||||
}
|
||||
if (occupied) {
|
||||
occupied.prev = item;
|
||||
}
|
||||
item.next = occupied;
|
||||
self.occupied = item;
|
||||
item.isBusy = YES;
|
||||
[item release];
|
||||
}
|
||||
|
||||
- (void) releaseCellItem:(MTLTexturePoolItem *)item {
|
||||
if (!item.isBusy) return;
|
||||
if (TRACE_USE_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "MTLPoolCell_releaseCellItem: item = %p", item);
|
||||
[_lock lock];
|
||||
@try {
|
||||
[item retain];
|
||||
if (item.prev == nil) {
|
||||
self.occupied = item.next;
|
||||
if (item.next) {
|
||||
item.next.prev = nil;
|
||||
}
|
||||
} else {
|
||||
item.prev.next = item.next;
|
||||
if (item.next) {
|
||||
item.next.prev = item.prev;
|
||||
}
|
||||
item.prev = nil;
|
||||
}
|
||||
if (self.available) {
|
||||
self.available.prev = item;
|
||||
} else {
|
||||
self.availableTail = item;
|
||||
}
|
||||
item.next = self.available;
|
||||
self.available = item;
|
||||
item.isBusy = NO;
|
||||
[item release];
|
||||
} @finally {
|
||||
[_lock unlock];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) addOccupiedItem:(MTLTexturePoolItem *)item {
|
||||
if (TRACE_USE_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "MTLPoolCell_addOccupiedItem: item = %p", item);
|
||||
[_lock lock];
|
||||
@try {
|
||||
_pool.allocatedCount++;
|
||||
_pool.totalAllocatedCount++;
|
||||
|
||||
if (self.occupied) {
|
||||
self.occupied.prev = item;
|
||||
}
|
||||
item.next = self.occupied;
|
||||
self.occupied = item;
|
||||
item.isBusy = YES;
|
||||
} @finally {
|
||||
[_lock unlock];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) cleanIfBefore:(time_t)lastUsedTimeToRemove {
|
||||
[_lock lock];
|
||||
@try {
|
||||
MTLTexturePoolItem *cur = availableTail;
|
||||
while (cur != nil) {
|
||||
MTLTexturePoolItem *prev = cur.prev;
|
||||
if ((cur.reuseCount == 0) || (lastUsedTimeToRemove <= 0) || (cur.lastUsed < lastUsedTimeToRemove)) {
|
||||
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn4(J2D_TRACE_VERBOSE, "MTLPoolCell_cleanIfBefore: remove pool item: tex=%p, w=%d h=%d, elapsed=%d",
|
||||
cur.texture, cur.width, cur.height,
|
||||
time(NULL) - cur.lastUsed);
|
||||
|
||||
const int requestedBytes = cur.width * cur.height * MTLTexturePool_bytesPerPixel(cur.format);
|
||||
// cur is nil after removeAvailableItem:
|
||||
[self removeAvailableItem:cur];
|
||||
_pool.allocatedCount--;
|
||||
_pool.memoryAllocated -= requestedBytes;
|
||||
} else {
|
||||
if (TRACE_MEM_API || TRACE_GC_ALIVE) J2dRlsTraceLn2(J2D_TRACE_INFO, "MTLPoolCell_cleanIfBefore: item = %p - ALIVE - reuse: %4d -> 0",
|
||||
cur, cur.reuseCount);
|
||||
// clear reuse count anyway:
|
||||
cur.reuseCount = 0;
|
||||
}
|
||||
cur = prev;
|
||||
}
|
||||
} @finally {
|
||||
[_lock unlock];
|
||||
}
|
||||
}
|
||||
|
||||
- (MTLTexturePoolItem *) occupyCellItem:(int)width height:(int)height format:(MTLPixelFormat)format {
|
||||
int minDeltaArea = -1;
|
||||
const int requestedPixels = width*height;
|
||||
MTLTexturePoolItem *minDeltaTpi = nil;
|
||||
[_lock lock];
|
||||
@try {
|
||||
for (MTLTexturePoolItem *cur = available; cur != nil; cur = cur.next) {
|
||||
// TODO: use swizzle when formats are not equal:
|
||||
if (cur.format != format) {
|
||||
continue;
|
||||
}
|
||||
if (cur.width < width || cur.height < height) {
|
||||
continue;
|
||||
}
|
||||
const int deltaArea = (const int) (cur.width * cur.height - requestedPixels);
|
||||
if (minDeltaArea < 0 || deltaArea < minDeltaArea) {
|
||||
minDeltaArea = deltaArea;
|
||||
minDeltaTpi = cur;
|
||||
if (deltaArea == 0) {
|
||||
// found exact match in current cell
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (minDeltaTpi) {
|
||||
[self occupyItem:minDeltaTpi];
|
||||
}
|
||||
} @finally {
|
||||
[_lock unlock];
|
||||
}
|
||||
|
||||
if (TRACE_USE_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "MTLPoolCell_occupyCellItem: item = %p", minDeltaTpi);
|
||||
return minDeltaTpi;
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
/* MTLTexturePool API */
|
||||
@implementation MTLTexturePool {
|
||||
void ** _cells;
|
||||
int _poolCellWidth;
|
||||
int _poolCellHeight;
|
||||
uint64_t _maxPoolMemory;
|
||||
uint64_t _memoryAllocated;
|
||||
uint64_t _memoryTotalAllocated;
|
||||
time_t _lastYoungGC;
|
||||
time_t _lastFullGC;
|
||||
time_t _lastGC;
|
||||
ATexturePool* _accelTexturePool;
|
||||
bool _enableGC;
|
||||
}
|
||||
|
||||
@synthesize device, allocatedCount, totalAllocatedCount,
|
||||
memoryAllocated = _memoryAllocated,
|
||||
totalMemoryAllocated = _memoryTotalAllocated;
|
||||
|
||||
- (void) autoTest:(MTLPixelFormat)format {
|
||||
J2dRlsTraceLn1(J2D_TRACE_VERBOSE, "MTLTexturePool_autoTest: step = %d", INIT_TEST_STEP);
|
||||
|
||||
_enableGC = false;
|
||||
|
||||
for (int w = 1; w <= INIT_TEST_MAX; w += INIT_TEST_STEP) {
|
||||
for (int h = 1; h <= INIT_TEST_MAX; h += INIT_TEST_STEP) {
|
||||
/* use auto-release pool to free memory as early as possible */
|
||||
@autoreleasepool {
|
||||
MTLPooledTextureHandle *texHandle = [self getTexture:w height:h format:format];
|
||||
id<MTLTexture> texture = texHandle.texture;
|
||||
if (texture == nil) {
|
||||
J2dRlsTraceLn2(J2D_TRACE_VERBOSE, "MTLTexturePool_autoTest: w= %d h= %d => texture is NULL !", w, h);
|
||||
} else {
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn3(J2D_TRACE_VERBOSE, "MTLTexturePool_autoTest: w=%d h=%d => tex=%p",
|
||||
w, h, texture);
|
||||
}
|
||||
[texHandle releaseTexture];
|
||||
}
|
||||
}
|
||||
}
|
||||
J2dRlsTraceLn2(J2D_TRACE_INFO, "MTLTexturePool_autoTest: before GC: total allocated memory = %lld Mb (total allocs: %d)",
|
||||
_memoryTotalAllocated / UNIT_MB, self.totalAllocatedCount);
|
||||
|
||||
_enableGC = true;
|
||||
|
||||
[self cleanIfNecessary:FORCE_GC_INTERVAL_SEC];
|
||||
|
||||
J2dRlsTraceLn2(J2D_TRACE_INFO, "MTLTexturePool_autoTest: after GC: total allocated memory = %lld Mb (total allocs: %d)",
|
||||
_memoryTotalAllocated / UNIT_MB, self.totalAllocatedCount);
|
||||
}
|
||||
|
||||
- (id) initWithDevice:(id<MTLDevice>)dev {
|
||||
// recommendedMaxWorkingSetSize typically greatly exceeds SCREEN_MEMORY_SIZE_5K constant.
|
||||
// It usually corresponds to the VRAM available to the graphics card
|
||||
uint64_t maxDeviceMemory = dev.recommendedMaxWorkingSetSize;
|
||||
|
||||
self = [super init];
|
||||
if (self == nil) return self;
|
||||
|
||||
if (USE_ACCEL_TEXTURE_POOL) {
|
||||
ATexturePoolLockWrapper *lockWrapper = ATexturePoolLockWrapper_init(&ATexturePoolLock_initImpl,
|
||||
&ATexturePoolLock_DisposeImpl,
|
||||
&ATexturePoolLock_lockImpl,
|
||||
&ATexturePoolLock_unlockImpl);
|
||||
|
||||
_accelTexturePool = ATexturePool_initWithDevice(dev,
|
||||
(jlong)maxDeviceMemory,
|
||||
&MTLTexturePool_createTexture,
|
||||
&MTLTexturePool_freeTexture,
|
||||
&MTLTexturePool_bytesPerPixel,
|
||||
lockWrapper,
|
||||
MTLPixelFormatBGRA8Unorm);
|
||||
}
|
||||
self.device = dev;
|
||||
|
||||
// use (5K) 5120-by-2880 resolution:
|
||||
_poolCellWidth = 5120 >> CELL_WIDTH_BITS;
|
||||
_poolCellHeight = 2880 >> CELL_HEIGHT_BITS;
|
||||
|
||||
const int cellsCount = _poolCellWidth * _poolCellHeight;
|
||||
_cells = (void **)malloc(cellsCount * sizeof(void*));
|
||||
CHECK_NULL_LOG_RETURN(_cells, NULL, "MTLTexturePool_initWithDevice: could not allocate cells");
|
||||
memset(_cells, 0, cellsCount * sizeof(void*));
|
||||
|
||||
_maxPoolMemory = maxDeviceMemory / 2;
|
||||
// Set maximum to handle at least 5K screen size
|
||||
if (_maxPoolMemory < SCREEN_MEMORY_SIZE_5K) {
|
||||
_maxPoolMemory = SCREEN_MEMORY_SIZE_5K;
|
||||
} else if (USE_MAX_GPU_DEVICE_MEM && (_maxPoolMemory > MAX_GPU_DEVICE_MEM)) {
|
||||
_maxPoolMemory = MAX_GPU_DEVICE_MEM;
|
||||
}
|
||||
|
||||
self.allocatedCount = 0;
|
||||
self.totalAllocatedCount = 0;
|
||||
|
||||
_memoryAllocated = 0;
|
||||
_memoryTotalAllocated = 0;
|
||||
|
||||
_enableGC = true;
|
||||
_lastGC = time(NULL);
|
||||
_lastYoungGC = _lastGC;
|
||||
_lastFullGC = _lastGC;
|
||||
|
||||
self.cacheHits = 0;
|
||||
self.totalHits = 0;
|
||||
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn2(J2D_TRACE_INFO, "MTLTexturePool_initWithDevice: pool = %p - maxPoolMemory = %lld", self, _maxPoolMemory);
|
||||
|
||||
if (INIT_TEST) {
|
||||
static bool INIT_TEST_START = true;
|
||||
if (INIT_TEST_START) {
|
||||
INIT_TEST_START = false;
|
||||
[self autoTest:MTLPixelFormatBGRA8Unorm];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc {
|
||||
if (USE_ACCEL_TEXTURE_POOL) {
|
||||
ATexturePoolLockWrapper_Dispose(ATexturePool_getLockWrapper(_accelTexturePool));
|
||||
ATexturePool_Dispose(_accelTexturePool);
|
||||
}
|
||||
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "MTLTexturePool_dealloc: pool = %p", self);
|
||||
|
||||
for (int c = 0; c < _poolCellWidth * _poolCellHeight; ++c) {
|
||||
MTLPoolCell *cell = _cells[c];
|
||||
if (cell != NULL) {
|
||||
[cell release];
|
||||
}
|
||||
}
|
||||
free(_cells);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void) cleanIfNecessary:(int)lastUsedTimeThreshold {
|
||||
time_t lastUsedTimeToRemove =
|
||||
lastUsedTimeThreshold > 0 ?
|
||||
time(NULL) - lastUsedTimeThreshold :
|
||||
lastUsedTimeThreshold;
|
||||
|
||||
if (TRACE_MEM_API || TRACE_GC) {
|
||||
J2dRlsTraceLn2(J2D_TRACE_VERBOSE, "MTLTexturePool_cleanIfNecessary: before GC: allocated memory = %lld Kb (allocs: %d)",
|
||||
_memoryAllocated / UNIT_KB, self.allocatedCount);
|
||||
}
|
||||
|
||||
for (int cy = 0; cy < _poolCellHeight; ++cy) {
|
||||
for (int cx = 0; cx < _poolCellWidth; ++cx) {
|
||||
MTLPoolCell *cell = _cells[cy * _poolCellWidth + cx];
|
||||
if (cell != NULL) {
|
||||
[cell cleanIfBefore:lastUsedTimeToRemove];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (TRACE_MEM_API || TRACE_GC) {
|
||||
J2dRlsTraceLn4(J2D_TRACE_VERBOSE, "MTLTexturePool_cleanIfNecessary: after GC: allocated memory = %lld Kb (allocs: %d) - hits = %lld (%.3lf %% cached)",
|
||||
_memoryAllocated / UNIT_KB, self.allocatedCount,
|
||||
self.totalHits, (self.totalHits != 0) ? (100.0 * self.cacheHits) / self.totalHits : 0.0);
|
||||
// reset hits:
|
||||
self.cacheHits = 0;
|
||||
self.totalHits = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: called from RQ-thread (on blit operations)
|
||||
- (MTLPooledTextureHandle *) getTexture:(int)width height:(int)height format:(MTLPixelFormat)format {
|
||||
if (USE_ACCEL_TEXTURE_POOL) {
|
||||
ATexturePoolHandle* texHandle = ATexturePool_getTexture(_accelTexturePool, width, height, format);
|
||||
CHECK_NULL_RETURN(texHandle, NULL);
|
||||
return [[[MTLPooledTextureHandle alloc] initWithTextureHandle:texHandle] autorelease];
|
||||
}
|
||||
|
||||
const int reqWidth = width;
|
||||
const int reqHeight = height;
|
||||
|
||||
int cellX0 = width >> CELL_WIDTH_BITS;
|
||||
int cellY0 = height >> CELL_HEIGHT_BITS;
|
||||
|
||||
if (USE_CEIL_SIZE) {
|
||||
// use upper cell size to maximize cache hits:
|
||||
const int remX0 = width & CELL_WIDTH_MASK;
|
||||
const int remY0 = height & CELL_HEIGHT_MASK;
|
||||
|
||||
if (remX0 != 0) {
|
||||
cellX0++;
|
||||
}
|
||||
if (remY0 != 0) {
|
||||
cellY0++;
|
||||
}
|
||||
// adjust width / height to cell upper boundaries:
|
||||
width = (cellX0) << CELL_WIDTH_BITS;
|
||||
height = (cellY0) << CELL_HEIGHT_BITS;
|
||||
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn4(J2D_TRACE_VERBOSE, "MTLTexturePool_getTexture: fixed tex size: (%d %d) => (%d %d)",
|
||||
reqWidth, reqHeight, width, height);
|
||||
}
|
||||
|
||||
// 1. clean pool if necessary
|
||||
const int requestedPixels = width * height;
|
||||
const int requestedBytes = requestedPixels * MTLTexturePool_bytesPerPixel(format);
|
||||
const uint64_t neededMemoryAllocated = _memoryAllocated + requestedBytes;
|
||||
|
||||
if (neededMemoryAllocated > _maxPoolMemory) {
|
||||
// release all free textures
|
||||
[self cleanIfNecessary:0];
|
||||
} else {
|
||||
time_t now = time(NULL);
|
||||
// ensure 1s at least:
|
||||
if ((now - _lastGC) > 0) {
|
||||
_lastGC = now;
|
||||
if (neededMemoryAllocated > _maxPoolMemory / 2) {
|
||||
// release only old free textures
|
||||
[self cleanIfNecessary:MAX_POOL_ITEM_LIFETIME_SEC];
|
||||
} else if (FORCE_GC && _enableGC) {
|
||||
if ((now - _lastFullGC) > FORCE_GC_INTERVAL_SEC) {
|
||||
_lastFullGC = now;
|
||||
_lastYoungGC = now;
|
||||
// release only old free textures since last full-gc
|
||||
[self cleanIfNecessary:FORCE_GC_INTERVAL_SEC];
|
||||
} else if ((now - _lastYoungGC) > YOUNG_GC_INTERVAL_SEC) {
|
||||
_lastYoungGC = now;
|
||||
// release only not reused and old textures
|
||||
[self cleanIfNecessary:YOUNG_GC_LIFETIME_SEC];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. find free item
|
||||
const int cellX1 = cellX0 + 1;
|
||||
const int cellY1 = cellY0 + 1;
|
||||
|
||||
// Note: this code (test + resizing) is not thread-safe:
|
||||
if (cellX1 > _poolCellWidth || cellY1 > _poolCellHeight) {
|
||||
const int newCellWidth = cellX1 <= _poolCellWidth ? _poolCellWidth : cellX1;
|
||||
const int newCellHeight = cellY1 <= _poolCellHeight ? _poolCellHeight : cellY1;
|
||||
const int newCellsCount = newCellWidth * newCellHeight;
|
||||
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn2(J2D_TRACE_VERBOSE, "MTLTexturePool_getTexture: resize: %d -> %d",
|
||||
_poolCellWidth * _poolCellHeight, newCellsCount);
|
||||
|
||||
void **newcells = malloc(newCellsCount * sizeof(void*));
|
||||
CHECK_NULL_LOG_RETURN(newcells, NULL, "MTLTexturePool_getTexture: could not allocate newCells");
|
||||
|
||||
const size_t strideBytes = _poolCellWidth * sizeof(void*);
|
||||
for (int cy = 0; cy < _poolCellHeight; ++cy) {
|
||||
void **dst = newcells + cy * newCellWidth;
|
||||
void **src = _cells + cy * _poolCellWidth;
|
||||
memcpy(dst, src, strideBytes);
|
||||
if (newCellWidth > _poolCellWidth)
|
||||
memset(dst + _poolCellWidth, 0, (newCellWidth - _poolCellWidth) * sizeof(void*));
|
||||
}
|
||||
if (newCellHeight > _poolCellHeight) {
|
||||
void **dst = newcells + _poolCellHeight * newCellWidth;
|
||||
memset(dst, 0, (newCellHeight - _poolCellHeight) * newCellWidth * sizeof(void*));
|
||||
}
|
||||
free(_cells);
|
||||
_cells = newcells;
|
||||
_poolCellWidth = newCellWidth;
|
||||
_poolCellHeight = newCellHeight;
|
||||
}
|
||||
|
||||
MTLTexturePoolItem *minDeltaTpi = nil;
|
||||
int minDeltaArea = -1;
|
||||
for (int cy = cellY0; cy < cellY1; ++cy) {
|
||||
for (int cx = cellX0; cx < cellX1; ++cx) {
|
||||
MTLPoolCell * cell = _cells[cy * _poolCellWidth + cx];
|
||||
if (cell != NULL) {
|
||||
MTLTexturePoolItem* tpi = [cell occupyCellItem:width height:height format:format];
|
||||
if (!tpi) {
|
||||
continue;
|
||||
}
|
||||
const int deltaArea = (const int) (tpi.width * tpi.height - requestedPixels);
|
||||
if (minDeltaArea < 0 || deltaArea < minDeltaArea) {
|
||||
minDeltaArea = deltaArea;
|
||||
minDeltaTpi = tpi;
|
||||
if (deltaArea == 0) {
|
||||
// found exact match in current cell
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (minDeltaTpi != nil) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (minDeltaTpi == NULL) {
|
||||
MTLPoolCell* cell = _cells[cellY0 * _poolCellWidth + cellX0];
|
||||
if (cell == NULL) {
|
||||
cell = [[MTLPoolCell alloc] init:self];
|
||||
_cells[cellY0 * _poolCellWidth + cellX0] = cell;
|
||||
}
|
||||
// use device to allocate NEW texture:
|
||||
id <MTLTexture> tex = MTLTexturePool_createTexture(device, width, height, format);
|
||||
|
||||
minDeltaTpi = [[[MTLTexturePoolItem alloc] initWithTexture:tex cell:cell
|
||||
width:width height:height format:format] autorelease];
|
||||
[cell addOccupiedItem: minDeltaTpi];
|
||||
|
||||
_memoryAllocated += requestedBytes;
|
||||
_memoryTotalAllocated += requestedBytes;
|
||||
|
||||
J2dTraceLn6(J2D_TRACE_VERBOSE, "MTLTexturePool_getTexture: created pool item: tex=%p, w=%d h=%d, pf=%d | allocated memory = %lld Kb (allocs: %d)",
|
||||
minDeltaTpi.texture, width, height, format, _memoryAllocated / UNIT_KB, allocatedCount);
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn6(J2D_TRACE_VERBOSE, "MTLTexturePool_getTexture: created pool item: tex=%p, w=%d h=%d, pf=%d | allocated memory = %lld Kb (allocs: %d)",
|
||||
minDeltaTpi.texture, width, height, format, _memoryAllocated / UNIT_KB, allocatedCount);
|
||||
} else {
|
||||
self.cacheHits++;
|
||||
minDeltaTpi.reuseCount++;
|
||||
if (TRACE_REUSE) {
|
||||
J2dRlsTraceLn5(J2D_TRACE_VERBOSE, "MTLTexturePool_getTexture: reused pool item: tex=%p, w=%d h=%d, pf=%d - reuse=%d",
|
||||
minDeltaTpi.texture, width, height, format, minDeltaTpi.reuseCount);
|
||||
}
|
||||
}
|
||||
self.totalHits++;
|
||||
minDeltaTpi.lastUsed = time(NULL);
|
||||
return [[[MTLPooledTextureHandle alloc] initWithPoolItem:minDeltaTpi.texture
|
||||
poolItem:minDeltaTpi reqWidth:reqWidth reqHeight:reqHeight] autorelease];
|
||||
}
|
||||
@end
|
||||
@@ -1,455 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#import "MTLTexturePool.h"
|
||||
#import "Trace.h"
|
||||
|
||||
#define SCREEN_MEMORY_SIZE_5K (5120*4096*4) //~84 mb
|
||||
#define MAX_POOL_ITEM_LIFETIME_SEC 30
|
||||
|
||||
#define CELL_WIDTH_BITS 5 // ~ 32 pixel
|
||||
#define CELL_HEIGHT_BITS 5 // ~ 32 pixel
|
||||
|
||||
@implementation MTLTexturePoolItem
|
||||
|
||||
@synthesize texture, isBusy, lastUsed, isMultiSample, next, cell;
|
||||
|
||||
- (id) initWithTexture:(id<MTLTexture>)tex cell:(MTLPoolCell*)c{
|
||||
self = [super init];
|
||||
if (self == nil) return self;
|
||||
self.texture = tex;
|
||||
isBusy = NO;
|
||||
self.next = nil;
|
||||
self.prev = nil;
|
||||
self.cell = c;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc {
|
||||
[texture release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MTLPooledTextureHandle
|
||||
{
|
||||
MTLRegion _rect;
|
||||
id<MTLTexture> _texture;
|
||||
MTLTexturePoolItem * _poolItem;
|
||||
}
|
||||
@synthesize texture = _texture, rect = _rect;
|
||||
|
||||
- (id) initWithPoolItem:(id<MTLTexture>)texture rect:(MTLRegion)rectangle poolItem:(MTLTexturePoolItem *)poolItem {
|
||||
self = [super init];
|
||||
if (self == nil) return self;
|
||||
|
||||
_rect = rectangle;
|
||||
_texture = texture;
|
||||
_poolItem = poolItem;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) releaseTexture {
|
||||
[_poolItem.cell releaseItem:_poolItem];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MTLPoolCell {
|
||||
NSLock* _lock;
|
||||
}
|
||||
@synthesize available, availableTail, occupied;
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.available = nil;
|
||||
self.availableTail = nil;
|
||||
self.occupied = nil;
|
||||
_lock = [[NSLock alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)occupyItem:(MTLTexturePoolItem *)item {
|
||||
if (item.isBusy) return;
|
||||
[item retain];
|
||||
if (item.prev == nil) {
|
||||
self.available = item.next;
|
||||
if (item.next) {
|
||||
item.next.prev = nil;
|
||||
} else {
|
||||
self.availableTail = item.prev;
|
||||
}
|
||||
} else {
|
||||
item.prev.next = item.next;
|
||||
if (item.next) {
|
||||
item.next.prev = item.prev;
|
||||
} else {
|
||||
self.availableTail = item.prev;
|
||||
}
|
||||
item.prev = nil;
|
||||
}
|
||||
if (occupied) occupied.prev = item;
|
||||
item.next = occupied;
|
||||
self.occupied = item;
|
||||
[item release];
|
||||
item.isBusy = YES;
|
||||
}
|
||||
|
||||
- (void)releaseItem:(MTLTexturePoolItem *)item {
|
||||
[_lock lock];
|
||||
@try {
|
||||
if (!item.isBusy) return;
|
||||
[item retain];
|
||||
if (item.prev == nil) {
|
||||
self.occupied = item.next;
|
||||
if (item.next) item.next.prev = nil;
|
||||
} else {
|
||||
item.prev.next = item.next;
|
||||
if (item.next) item.next.prev = item.prev;
|
||||
item.prev = nil;
|
||||
}
|
||||
if (self.available) {
|
||||
self.available.prev = item;
|
||||
} else {
|
||||
self.availableTail = item;
|
||||
}
|
||||
item.next = self.available;
|
||||
self.available = item;
|
||||
item.isBusy = NO;
|
||||
[item release];
|
||||
} @finally {
|
||||
[_lock unlock];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addOccupiedItem:(MTLTexturePoolItem *)item {
|
||||
if (self.occupied) self.occupied.prev = item;
|
||||
item.next = self.occupied;
|
||||
item.isBusy = YES;
|
||||
self.occupied = item;
|
||||
}
|
||||
|
||||
- (void)removeAvailableItem:(MTLTexturePoolItem*)item {
|
||||
[item retain];
|
||||
if (item.prev == nil) {
|
||||
self.available = item.next;
|
||||
if (item.next) {
|
||||
item.next.prev = nil;
|
||||
item.next = nil;
|
||||
} else {
|
||||
self.availableTail = item.prev;
|
||||
}
|
||||
} else {
|
||||
item.prev.next = item.next;
|
||||
if (item.next) {
|
||||
item.next.prev = item.prev;
|
||||
item.next = nil;
|
||||
} else {
|
||||
self.availableTail = item.prev;
|
||||
}
|
||||
}
|
||||
[item release];
|
||||
}
|
||||
|
||||
- (void)removeAllItems {
|
||||
MTLTexturePoolItem *cur = self.available;
|
||||
while (cur != nil) {
|
||||
cur = cur.next;
|
||||
self.available = cur;
|
||||
}
|
||||
cur = self.occupied;
|
||||
while (cur != nil) {
|
||||
cur = cur.next;
|
||||
self.occupied = cur;
|
||||
}
|
||||
self.availableTail = nil;
|
||||
}
|
||||
|
||||
- (MTLTexturePoolItem *)createItem:(id<MTLDevice>)dev
|
||||
width:(int)width
|
||||
height:(int)height
|
||||
format:(MTLPixelFormat)format
|
||||
isMultiSample:(bool)isMultiSample
|
||||
{
|
||||
MTLTextureDescriptor *textureDescriptor =
|
||||
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format
|
||||
width:(NSUInteger) width
|
||||
height:(NSUInteger) height
|
||||
mipmapped:NO];
|
||||
textureDescriptor.usage = MTLTextureUsageRenderTarget |
|
||||
MTLTextureUsageShaderRead;
|
||||
if (isMultiSample) {
|
||||
textureDescriptor.textureType = MTLTextureType2DMultisample;
|
||||
textureDescriptor.sampleCount = MTLAASampleCount;
|
||||
textureDescriptor.storageMode = MTLStorageModePrivate;
|
||||
}
|
||||
|
||||
id <MTLTexture> tex = (id <MTLTexture>) [[dev newTextureWithDescriptor:textureDescriptor] autorelease];
|
||||
MTLTexturePoolItem* item = [[[MTLTexturePoolItem alloc] initWithTexture:tex cell:self] autorelease];
|
||||
item.isMultiSample = isMultiSample;
|
||||
[_lock lock];
|
||||
@try {
|
||||
[self addOccupiedItem:item];
|
||||
} @finally {
|
||||
[_lock unlock];
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
|
||||
- (NSUInteger)cleanIfBefore:(time_t)lastUsedTimeToRemove {
|
||||
NSUInteger deallocMem = 0;
|
||||
[_lock lock];
|
||||
MTLTexturePoolItem *cur = availableTail;
|
||||
@try {
|
||||
while (cur != nil) {
|
||||
MTLTexturePoolItem *prev = cur.prev;
|
||||
if (lastUsedTimeToRemove <= 0 ||
|
||||
cur.lastUsed < lastUsedTimeToRemove) {
|
||||
#ifdef DEBUG
|
||||
J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE,
|
||||
"MTLTexturePool: remove pool item: tex=%p, w=%d h=%d, elapsed=%d",
|
||||
cur.texture, cur.texture.width, cur.texture.height,
|
||||
time(NULL) - cur.lastUsed);
|
||||
#endif //DEBUG
|
||||
deallocMem += cur.texture.width * cur.texture.height * 4;
|
||||
[self removeAvailableItem:cur];
|
||||
} else {
|
||||
if (lastUsedTimeToRemove > 0) break;
|
||||
}
|
||||
cur = prev;
|
||||
}
|
||||
} @finally {
|
||||
[_lock unlock];
|
||||
}
|
||||
return deallocMem;
|
||||
}
|
||||
|
||||
- (MTLTexturePoolItem *)occupyItem:(int)width height:(int)height format:(MTLPixelFormat)format
|
||||
isMultiSample:(bool)isMultiSample {
|
||||
int minDeltaArea = -1;
|
||||
const int requestedPixels = width*height;
|
||||
MTLTexturePoolItem *minDeltaTpi = nil;
|
||||
[_lock lock];
|
||||
@try {
|
||||
for (MTLTexturePoolItem *cur = available; cur != nil; cur = cur.next) {
|
||||
if (cur.texture.pixelFormat != format
|
||||
|| cur.isMultiSample != isMultiSample) { // TODO: use swizzle when formats are not equal
|
||||
continue;
|
||||
}
|
||||
if (cur.texture.width < width || cur.texture.height < height) {
|
||||
continue;
|
||||
}
|
||||
const int deltaArea = (const int) (cur.texture.width * cur.texture.height - requestedPixels);
|
||||
if (minDeltaArea < 0 || deltaArea < minDeltaArea) {
|
||||
minDeltaArea = deltaArea;
|
||||
minDeltaTpi = cur;
|
||||
if (deltaArea == 0) {
|
||||
// found exact match in current cell
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (minDeltaTpi) {
|
||||
[self occupyItem:minDeltaTpi];
|
||||
}
|
||||
} @finally {
|
||||
[_lock unlock];
|
||||
}
|
||||
return minDeltaTpi;
|
||||
}
|
||||
|
||||
- (void) dealloc {
|
||||
[_lock lock];
|
||||
@try {
|
||||
[self removeAllItems];
|
||||
} @finally {
|
||||
[_lock unlock];
|
||||
}
|
||||
[_lock release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MTLTexturePool {
|
||||
int _memoryTotalAllocated;
|
||||
|
||||
void ** _cells;
|
||||
int _poolCellWidth;
|
||||
int _poolCellHeight;
|
||||
uint64_t _maxPoolMemory;
|
||||
}
|
||||
|
||||
@synthesize device;
|
||||
|
||||
- (id) initWithDevice:(id<MTLDevice>)dev {
|
||||
self = [super init];
|
||||
if (self == nil) return self;
|
||||
|
||||
_memoryTotalAllocated = 0;
|
||||
_poolCellWidth = 10;
|
||||
_poolCellHeight = 10;
|
||||
const int cellsCount = _poolCellWidth * _poolCellHeight;
|
||||
_cells = (void **)malloc(cellsCount * sizeof(void*));
|
||||
memset(_cells, 0, cellsCount * sizeof(void*));
|
||||
self.device = dev;
|
||||
|
||||
// recommendedMaxWorkingSetSize typically greatly exceeds SCREEN_MEMORY_SIZE_5K constant.
|
||||
// It usually corresponds to the VRAM available to the graphics card
|
||||
_maxPoolMemory = self.device.recommendedMaxWorkingSetSize/2;
|
||||
|
||||
// Set maximum to handle at least 5K screen size
|
||||
if (_maxPoolMemory < SCREEN_MEMORY_SIZE_5K) {
|
||||
_maxPoolMemory = SCREEN_MEMORY_SIZE_5K;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc {
|
||||
for (int c = 0; c < _poolCellWidth * _poolCellHeight; ++c) {
|
||||
MTLPoolCell * cell = _cells[c];
|
||||
if (cell != NULL) {
|
||||
[cell release];
|
||||
}
|
||||
}
|
||||
free(_cells);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
// NOTE: called from RQ-thread (on blit operations)
|
||||
- (MTLPooledTextureHandle *) getTexture:(int)width height:(int)height format:(MTLPixelFormat)format {
|
||||
return [self getTexture:width height:height format:format isMultiSample:NO];
|
||||
}
|
||||
|
||||
// NOTE: called from RQ-thread (on blit operations)
|
||||
- (MTLPooledTextureHandle *) getTexture:(int)width height:(int)height format:(MTLPixelFormat)format
|
||||
isMultiSample:(bool)isMultiSample {
|
||||
// 1. clean pool if necessary
|
||||
const int requestedPixels = width*height;
|
||||
const int requestedBytes = requestedPixels*4;
|
||||
if (_memoryTotalAllocated + requestedBytes > _maxPoolMemory) {
|
||||
[self cleanIfNecessary:0]; // release all free textures
|
||||
} else if (_memoryTotalAllocated + requestedBytes > _maxPoolMemory/2) {
|
||||
[self cleanIfNecessary:MAX_POOL_ITEM_LIFETIME_SEC]; // release only old free textures
|
||||
}
|
||||
|
||||
// 2. find free item
|
||||
const int cellX0 = width >> CELL_WIDTH_BITS;
|
||||
const int cellY0 = height >> CELL_HEIGHT_BITS;
|
||||
const int cellX1 = cellX0 + 1;
|
||||
const int cellY1 = cellY0 + 1;
|
||||
if (cellX1 > _poolCellWidth || cellY1 > _poolCellHeight) {
|
||||
const int newCellWidth = cellX1 <= _poolCellWidth ? _poolCellWidth : cellX1;
|
||||
const int newCellHeight = cellY1 <= _poolCellHeight ? _poolCellHeight : cellY1;
|
||||
const int newCellsCount = newCellWidth*newCellHeight;
|
||||
#ifdef DEBUG
|
||||
J2dTraceLn2(J2D_TRACE_VERBOSE, "MTLTexturePool: resize: %d -> %d", _poolCellWidth * _poolCellHeight, newCellsCount);
|
||||
#endif
|
||||
void ** newcells = malloc(newCellsCount*sizeof(void*));
|
||||
const int strideBytes = _poolCellWidth * sizeof(void*);
|
||||
for (int cy = 0; cy < _poolCellHeight; ++cy) {
|
||||
void ** dst = newcells + cy*newCellWidth;
|
||||
void ** src = _cells + cy * _poolCellWidth;
|
||||
memcpy(dst, src, strideBytes);
|
||||
if (newCellWidth > _poolCellWidth)
|
||||
memset(dst + _poolCellWidth, 0, (newCellWidth - _poolCellWidth) * sizeof(void*));
|
||||
}
|
||||
if (newCellHeight > _poolCellHeight) {
|
||||
void ** dst = newcells + _poolCellHeight * newCellWidth;
|
||||
memset(dst, 0, (newCellHeight - _poolCellHeight) * newCellWidth * sizeof(void*));
|
||||
}
|
||||
free(_cells);
|
||||
_cells = newcells;
|
||||
_poolCellWidth = newCellWidth;
|
||||
_poolCellHeight = newCellHeight;
|
||||
}
|
||||
|
||||
MTLTexturePoolItem * minDeltaTpi = nil;
|
||||
int minDeltaArea = -1;
|
||||
for (int cy = cellY0; cy < cellY1; ++cy) {
|
||||
for (int cx = cellX0; cx < cellX1; ++cx) {
|
||||
MTLPoolCell * cell = _cells[cy * _poolCellWidth + cx];
|
||||
if (cell != NULL) {
|
||||
MTLTexturePoolItem* tpi = [cell occupyItem:width height:height
|
||||
format:format isMultiSample:isMultiSample];
|
||||
if (!tpi) continue;
|
||||
const int deltaArea = (const int) (tpi.texture.width * tpi.texture.height - requestedPixels);
|
||||
if (minDeltaArea < 0 || deltaArea < minDeltaArea) {
|
||||
minDeltaArea = deltaArea;
|
||||
minDeltaTpi = tpi;
|
||||
if (deltaArea == 0) {
|
||||
// found exact match in current cell
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (minDeltaTpi != nil) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (minDeltaTpi == NULL) {
|
||||
MTLPoolCell* cell = _cells[cellY0 * _poolCellWidth + cellX0];
|
||||
if (cell == NULL) {
|
||||
cell = [[MTLPoolCell alloc] init];
|
||||
_cells[cellY0 * _poolCellWidth + cellX0] = cell;
|
||||
}
|
||||
minDeltaTpi = [cell createItem:device width:width height:height format:format isMultiSample:isMultiSample];
|
||||
_memoryTotalAllocated += requestedBytes;
|
||||
J2dTraceLn5(J2D_TRACE_VERBOSE, "MTLTexturePool: created pool item: tex=%p, w=%d h=%d, pf=%d | total memory = %d Kb", minDeltaTpi.texture, width, height, format, _memoryTotalAllocated/1024);
|
||||
}
|
||||
|
||||
minDeltaTpi.isBusy = YES;
|
||||
minDeltaTpi.lastUsed = time(NULL);
|
||||
return [[[MTLPooledTextureHandle alloc] initWithPoolItem:minDeltaTpi.texture
|
||||
rect:MTLRegionMake2D(0, 0,
|
||||
minDeltaTpi.texture.width,
|
||||
minDeltaTpi.texture.height)
|
||||
poolItem:minDeltaTpi] autorelease];
|
||||
}
|
||||
|
||||
- (void) cleanIfNecessary:(int)lastUsedTimeThreshold {
|
||||
time_t lastUsedTimeToRemove =
|
||||
lastUsedTimeThreshold > 0 ?
|
||||
time(NULL) - lastUsedTimeThreshold :
|
||||
lastUsedTimeThreshold;
|
||||
for (int cy = 0; cy < _poolCellHeight; ++cy) {
|
||||
for (int cx = 0; cx < _poolCellWidth; ++cx) {
|
||||
MTLPoolCell * cell = _cells[cy * _poolCellWidth + cx];
|
||||
if (cell != NULL) {
|
||||
_memoryTotalAllocated -= [cell cleanIfBefore:lastUsedTimeToRemove];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
829
src/java.desktop/share/native/common/java2d/AccelTexturePool.c
Normal file
829
src/java.desktop/share/native/common/java2d/AccelTexturePool.c
Normal file
@@ -0,0 +1,829 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2024, JetBrains s.r.o.. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "AccelTexturePool.h"
|
||||
#include "jni.h"
|
||||
#include "Trace.h"
|
||||
|
||||
|
||||
#define USE_MAX_GPU_DEVICE_MEM 1
|
||||
#define MAX_GPU_DEVICE_MEM (512 * UNIT_MB)
|
||||
#define SCREEN_MEMORY_SIZE_5K (5120 * 4096 * 4) // ~ 84 mb
|
||||
|
||||
#define MAX_POOL_ITEM_LIFETIME_SEC 30
|
||||
|
||||
// 32 pixel
|
||||
#define CELL_WIDTH_BITS 5
|
||||
#define CELL_HEIGHT_BITS 5
|
||||
|
||||
#define CELL_WIDTH_MASK ((1 << CELL_WIDTH_BITS) - 1)
|
||||
#define CELL_HEIGHT_MASK ((1 << CELL_HEIGHT_BITS) - 1)
|
||||
|
||||
#define USE_CEIL_SIZE 1
|
||||
|
||||
#define FORCE_GC 1
|
||||
// force gc (prune old textures):
|
||||
#define FORCE_GC_INTERVAL_SEC (MAX_POOL_ITEM_LIFETIME_SEC * 10)
|
||||
|
||||
// force young gc every 5 seconds (prune only not reused textures):
|
||||
#define YOUNG_GC_INTERVAL_SEC 15
|
||||
#define YOUNG_GC_LIFETIME_SEC (FORCE_GC_INTERVAL_SEC * 2)
|
||||
|
||||
#define TRACE_GC 1
|
||||
#define TRACE_GC_ALIVE 0
|
||||
|
||||
#define TRACE_MEM_API 0
|
||||
#define TRACE_USE_API 0
|
||||
#define TRACE_REUSE 0
|
||||
|
||||
#define INIT_TEST 0
|
||||
#define INIT_TEST_STEP 1
|
||||
#define INIT_TEST_MAX 1024
|
||||
|
||||
#define LOCK_WRAPPER(cell) (cell->pool->lockWrapper)
|
||||
#define LOCK_WRAPPER_LOCK(cell) (LOCK_WRAPPER(cell)->lockFunc(cell->lock))
|
||||
#define LOCK_WRAPPER_UNLOCK(cell) (LOCK_WRAPPER(cell)->unlockFunc(cell->lock))
|
||||
|
||||
|
||||
/* Private definitions */
|
||||
struct ATexturePoolLockWrapper_ {
|
||||
ATexturePoolLock_init *initFunc;
|
||||
ATexturePoolLock_dispose *disposeFunc;
|
||||
ATexturePoolLock_lock *lockFunc;
|
||||
ATexturePoolLock_unlock *unlockFunc;
|
||||
};
|
||||
|
||||
struct ATexturePoolItem_ {
|
||||
ATexturePool_freeTexture *freeTextureFunc;
|
||||
ADevicePrivPtr *device;
|
||||
ATexturePrivPtr *texture;
|
||||
ATexturePoolCell *cell;
|
||||
ATexturePoolItem *prev;
|
||||
ATexturePoolItem *next;
|
||||
time_t lastUsed;
|
||||
jint width;
|
||||
jint height;
|
||||
jlong format;
|
||||
jint reuseCount;
|
||||
jboolean isBusy;
|
||||
};
|
||||
|
||||
struct ATexturePoolCell_ {
|
||||
ATexturePool *pool;
|
||||
ATexturePoolLockPrivPtr *lock;
|
||||
ATexturePoolItem *available;
|
||||
ATexturePoolItem *availableTail;
|
||||
ATexturePoolItem *occupied;
|
||||
};
|
||||
|
||||
static void ATexturePoolCell_releaseItem(ATexturePoolCell *cell, ATexturePoolItem *item);
|
||||
|
||||
struct ATexturePoolHandle_ {
|
||||
ATexturePrivPtr *texture;
|
||||
ATexturePoolItem *_poolItem;
|
||||
jint reqWidth;
|
||||
jint reqHeight;
|
||||
};
|
||||
|
||||
// NOTE: owns all texture objects
|
||||
struct ATexturePool_ {
|
||||
ATexturePool_createTexture *createTextureFunc;
|
||||
ATexturePool_freeTexture *freeTextureFunc;
|
||||
ATexturePool_bytesPerPixel *bytesPerPixelFunc;
|
||||
ATexturePoolLockWrapper *lockWrapper;
|
||||
ADevicePrivPtr *device;
|
||||
ATexturePoolCell **_cells;
|
||||
jint poolCellWidth;
|
||||
jint poolCellHeight;
|
||||
jlong maxPoolMemory;
|
||||
jlong memoryAllocated;
|
||||
jlong totalMemoryAllocated;
|
||||
jlong allocatedCount;
|
||||
jlong totalAllocatedCount;
|
||||
jlong cacheHits;
|
||||
jlong totalHits;
|
||||
time_t lastGC;
|
||||
time_t lastYoungGC;
|
||||
time_t lastFullGC;
|
||||
jboolean enableGC;
|
||||
};
|
||||
|
||||
|
||||
/* ATexturePoolLockWrapper API */
|
||||
ATexturePoolLockWrapper* ATexturePoolLockWrapper_init(ATexturePoolLock_init *initFunc,
|
||||
ATexturePoolLock_dispose *disposeFunc,
|
||||
ATexturePoolLock_lock *lockFunc,
|
||||
ATexturePoolLock_unlock *unlockFunc)
|
||||
{
|
||||
CHECK_NULL_LOG_RETURN(initFunc, NULL, "ATexturePoolLockWrapper_init: initFunc function is null !");
|
||||
CHECK_NULL_LOG_RETURN(disposeFunc, NULL, "ATexturePoolLockWrapper_init: disposeFunc function is null !");
|
||||
CHECK_NULL_LOG_RETURN(lockFunc, NULL, "ATexturePoolLockWrapper_init: lockFunc function is null !");
|
||||
CHECK_NULL_LOG_RETURN(unlockFunc, NULL, "ATexturePoolLockWrapper_init: unlockFunc function is null !");
|
||||
|
||||
ATexturePoolLockWrapper *lockWrapper = (ATexturePoolLockWrapper*)malloc(sizeof(ATexturePoolLockWrapper));
|
||||
CHECK_NULL_LOG_RETURN(lockWrapper, NULL, "ATexturePoolLockWrapper_init: could not allocate ATexturePoolLockWrapper");
|
||||
|
||||
lockWrapper->initFunc = initFunc;
|
||||
lockWrapper->disposeFunc = disposeFunc;
|
||||
lockWrapper->lockFunc = lockFunc;
|
||||
lockWrapper->unlockFunc = unlockFunc;
|
||||
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePoolLockWrapper_init: lockWrapper = %p", lockWrapper);
|
||||
return lockWrapper;
|
||||
}
|
||||
|
||||
void ATexturePoolLockWrapper_Dispose(ATexturePoolLockWrapper *lockWrapper) {
|
||||
CHECK_NULL(lockWrapper);
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePoolLockWrapper_Dispose: lockWrapper = %p", lockWrapper);
|
||||
|
||||
free(lockWrapper);
|
||||
}
|
||||
|
||||
|
||||
/* ATexturePoolItem API */
|
||||
static ATexturePoolItem* ATexturePoolItem_initWithTexture(ATexturePool_freeTexture *freeTextureFunc,
|
||||
ADevicePrivPtr *device,
|
||||
ATexturePrivPtr *texture,
|
||||
ATexturePoolCell *cell,
|
||||
jint width,
|
||||
jint height,
|
||||
jlong format)
|
||||
{
|
||||
CHECK_NULL_LOG_RETURN(freeTextureFunc, NULL, "ATexturePoolItem_initWithTexture: freeTextureFunc function is null !");
|
||||
CHECK_NULL_RETURN(texture, NULL);
|
||||
CHECK_NULL_RETURN(cell, NULL);
|
||||
|
||||
ATexturePoolItem *item = (ATexturePoolItem*)malloc(sizeof(ATexturePoolItem));
|
||||
CHECK_NULL_LOG_RETURN(item, NULL, "ATexturePoolItem_initWithTexture: could not allocate ATexturePoolItem");
|
||||
|
||||
item->freeTextureFunc = freeTextureFunc;
|
||||
item->device = device;
|
||||
item->texture = texture;
|
||||
item->cell = cell;
|
||||
item->prev = NULL;
|
||||
item->next = NULL;
|
||||
item->lastUsed = 0;
|
||||
item->width = width;
|
||||
item->height = height;
|
||||
item->format = format;
|
||||
item->reuseCount = 0;
|
||||
item->isBusy = JNI_FALSE;
|
||||
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePoolItem_initWithTexture: item = %p", item);
|
||||
return item;
|
||||
}
|
||||
|
||||
static void ATexturePoolItem_Dispose(ATexturePoolItem *item) {
|
||||
CHECK_NULL(item);
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn2(J2D_TRACE_INFO, "ATexturePoolItem_Dispose: item = %p - reuse: %4d", item, item->reuseCount);
|
||||
|
||||
// use texture (native API) to release allocated texture:
|
||||
item->freeTextureFunc(item->device, item->texture);
|
||||
free(item);
|
||||
}
|
||||
|
||||
/* Callback from metal pipeline => multi-thread (cell lock) */
|
||||
static void ATexturePoolItem_ReleaseItem(ATexturePoolItem *item) {
|
||||
CHECK_NULL(item);
|
||||
if (!item->isBusy) {
|
||||
return;
|
||||
}
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePoolItem_ReleaseItem: item = %p", item);
|
||||
|
||||
if IS_NOT_NULL(item->cell) {
|
||||
ATexturePoolCell_releaseItem(item->cell, item);
|
||||
} else {
|
||||
J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePoolItem_ReleaseItem: item = %p (detached)", item);
|
||||
// item marked as detached:
|
||||
ATexturePoolItem_Dispose(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ATexturePoolCell API */
|
||||
static ATexturePoolCell* ATexturePoolCell_init(ATexturePool *pool) {
|
||||
CHECK_NULL_RETURN(pool, NULL);
|
||||
|
||||
ATexturePoolCell *cell = (ATexturePoolCell*)malloc(sizeof(ATexturePoolCell));
|
||||
CHECK_NULL_LOG_RETURN(cell, NULL, "ATexturePoolCell_init: could not allocate ATexturePoolCell");
|
||||
|
||||
cell->pool = pool;
|
||||
|
||||
ATexturePoolLockPrivPtr* lock = LOCK_WRAPPER(cell)->initFunc();
|
||||
CHECK_NULL_LOG_RETURN(lock, NULL, "ATexturePoolCell_init: could not allocate ATexturePoolLockPrivPtr");
|
||||
|
||||
cell->lock = lock;
|
||||
cell->available = NULL;
|
||||
cell->availableTail = NULL;
|
||||
cell->occupied = NULL;
|
||||
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePoolCell_init: cell = %p", cell);
|
||||
return cell;
|
||||
}
|
||||
|
||||
static void ATexturePoolCell_removeAllItems(ATexturePoolCell *cell) {
|
||||
CHECK_NULL(cell);
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn(J2D_TRACE_INFO, "ATexturePoolCell_removeAllItems");
|
||||
|
||||
ATexturePoolItem *cur = cell->available;
|
||||
ATexturePoolItem *next = NULL;
|
||||
while IS_NOT_NULL(cur) {
|
||||
next = cur->next;
|
||||
ATexturePoolItem_Dispose(cur);
|
||||
cur = next;
|
||||
}
|
||||
cell->available = NULL;
|
||||
|
||||
cur = cell->occupied;
|
||||
next = NULL;
|
||||
while IS_NOT_NULL(cur) {
|
||||
next = cur->next;
|
||||
J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePoolCell_removeAllItems: occupied item = %p", cur);
|
||||
// Do not dispose (may leak) until ATexturePoolItem_Release() is called by handle:
|
||||
// mark item as detached:
|
||||
cur->cell = NULL;
|
||||
cur = next;
|
||||
}
|
||||
cell->occupied = NULL;
|
||||
cell->availableTail = NULL;
|
||||
}
|
||||
|
||||
static void ATexturePoolCell_removeAvailableItem(ATexturePoolCell *cell, ATexturePoolItem *item) {
|
||||
CHECK_NULL(cell);
|
||||
CHECK_NULL(item);
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePoolCell_removeAvailableItem: item = %p", item);
|
||||
|
||||
if IS_NULL(item->prev) {
|
||||
cell->available = item->next;
|
||||
if IS_NOT_NULL(item->next) {
|
||||
item->next->prev = NULL;
|
||||
item->next = NULL;
|
||||
} else {
|
||||
cell->availableTail = item->prev;
|
||||
}
|
||||
} else {
|
||||
item->prev->next = item->next;
|
||||
if IS_NOT_NULL(item->next) {
|
||||
item->next->prev = item->prev;
|
||||
item->next = NULL;
|
||||
} else {
|
||||
cell->availableTail = item->prev;
|
||||
}
|
||||
}
|
||||
ATexturePoolItem_Dispose(item);
|
||||
}
|
||||
|
||||
static void ATexturePoolCell_Dispose(ATexturePoolCell *cell) {
|
||||
CHECK_NULL(cell);
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePoolCell_Dispose: cell = %p", cell);
|
||||
|
||||
LOCK_WRAPPER_LOCK(cell);
|
||||
{
|
||||
ATexturePoolCell_removeAllItems(cell);
|
||||
}
|
||||
LOCK_WRAPPER_UNLOCK(cell);
|
||||
|
||||
LOCK_WRAPPER(cell)->disposeFunc(cell->lock);
|
||||
free(cell);
|
||||
}
|
||||
|
||||
/* RQ thread from metal pipeline (cell locked) */
|
||||
static void ATexturePoolCell_occupyItem(ATexturePoolCell *cell, ATexturePoolItem *item) {
|
||||
CHECK_NULL(cell);
|
||||
CHECK_NULL(item);
|
||||
if (item->isBusy) {
|
||||
return;
|
||||
}
|
||||
if (TRACE_USE_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePoolCell_occupyItem: item = %p", item);
|
||||
|
||||
if IS_NULL(item->prev) {
|
||||
cell->available = item->next;
|
||||
if IS_NOT_NULL(item->next) {
|
||||
item->next->prev = NULL;
|
||||
} else {
|
||||
cell->availableTail = item->prev;
|
||||
}
|
||||
} else {
|
||||
item->prev->next = item->next;
|
||||
if IS_NOT_NULL(item->next) {
|
||||
item->next->prev = item->prev;
|
||||
} else {
|
||||
cell->availableTail = item->prev;
|
||||
}
|
||||
item->prev = NULL;
|
||||
}
|
||||
if (cell->occupied) {
|
||||
cell->occupied->prev = item;
|
||||
}
|
||||
item->next = cell->occupied;
|
||||
cell->occupied = item;
|
||||
item->isBusy = JNI_TRUE;
|
||||
}
|
||||
|
||||
/* Callback from native java2D pipeline => multi-thread (cell lock) */
|
||||
static void ATexturePoolCell_releaseItem(ATexturePoolCell *cell, ATexturePoolItem *item) {
|
||||
CHECK_NULL(cell);
|
||||
CHECK_NULL(item);
|
||||
if (!item->isBusy) {
|
||||
return;
|
||||
}
|
||||
if (TRACE_USE_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePoolCell_releaseItem: item = %p", item);
|
||||
|
||||
LOCK_WRAPPER_LOCK(cell);
|
||||
{
|
||||
if IS_NULL(item->prev) {
|
||||
cell->occupied = item->next;
|
||||
if IS_NOT_NULL(item->next) {
|
||||
item->next->prev = NULL;
|
||||
}
|
||||
} else {
|
||||
item->prev->next = item->next;
|
||||
if IS_NOT_NULL(item->next) {
|
||||
item->next->prev = item->prev;
|
||||
}
|
||||
item->prev = NULL;
|
||||
}
|
||||
if IS_NOT_NULL(cell->available) {
|
||||
cell->available->prev = item;
|
||||
} else {
|
||||
cell->availableTail = item;
|
||||
}
|
||||
item->next = cell->available;
|
||||
cell->available = item;
|
||||
item->isBusy = JNI_FALSE;
|
||||
}
|
||||
LOCK_WRAPPER_UNLOCK(cell);
|
||||
}
|
||||
|
||||
static void ATexturePoolCell_addOccupiedItem(ATexturePoolCell *cell, ATexturePoolItem *item) {
|
||||
CHECK_NULL(cell);
|
||||
CHECK_NULL(item);
|
||||
if (TRACE_USE_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePoolCell_addOccupiedItem: item = %p", item);
|
||||
|
||||
LOCK_WRAPPER_LOCK(cell);
|
||||
{
|
||||
cell->pool->allocatedCount++;
|
||||
cell->pool->totalAllocatedCount++;
|
||||
|
||||
if IS_NOT_NULL(cell->occupied) {
|
||||
cell->occupied->prev = item;
|
||||
}
|
||||
item->next = cell->occupied;
|
||||
cell->occupied = item;
|
||||
item->isBusy = JNI_TRUE;
|
||||
}
|
||||
LOCK_WRAPPER_UNLOCK(cell);
|
||||
}
|
||||
|
||||
static void ATexturePoolCell_cleanIfBefore(ATexturePoolCell *cell, time_t lastUsedTimeToRemove) {
|
||||
CHECK_NULL(cell);
|
||||
LOCK_WRAPPER_LOCK(cell);
|
||||
{
|
||||
ATexturePoolItem *cur = cell->availableTail;
|
||||
while IS_NOT_NULL(cur) {
|
||||
ATexturePoolItem *prev = cur->prev;
|
||||
if ((cur->reuseCount == 0) || (lastUsedTimeToRemove <= 0) || (cur->lastUsed < lastUsedTimeToRemove)) {
|
||||
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn4(J2D_TRACE_VERBOSE, "ATexturePoolCell_cleanIfBefore: remove pool item: tex=%p, w=%d h=%d, elapsed=%d",
|
||||
cur->texture, cur->width, cur->height,
|
||||
time(NULL) - cur->lastUsed);
|
||||
|
||||
const int requestedBytes = cur->width * cur->height * cell->pool->bytesPerPixelFunc(cur->format);
|
||||
// cur is NULL after removeAvailableItem:
|
||||
ATexturePoolCell_removeAvailableItem(cell, cur);
|
||||
cell->pool->allocatedCount--;
|
||||
cell->pool->memoryAllocated -= requestedBytes;
|
||||
} else {
|
||||
if (TRACE_MEM_API || TRACE_GC_ALIVE) J2dRlsTraceLn2(J2D_TRACE_INFO, "ATexturePoolCell_cleanIfBefore: item = %p - ALIVE - reuse: %4d -> 0",
|
||||
cur, cur->reuseCount);
|
||||
// clear reuse count anyway:
|
||||
cur->reuseCount = 0;
|
||||
}
|
||||
cur = prev;
|
||||
}
|
||||
}
|
||||
LOCK_WRAPPER_UNLOCK(cell);
|
||||
}
|
||||
|
||||
/* RQ thread from metal pipeline <=> multi-thread callbacks (cell lock) */
|
||||
static ATexturePoolItem* ATexturePoolCell_occupyCellItem(ATexturePoolCell *cell,
|
||||
jint width,
|
||||
jint height,
|
||||
jlong format)
|
||||
{
|
||||
CHECK_NULL_RETURN(cell, NULL);
|
||||
int minDeltaArea = -1;
|
||||
const int requestedPixels = width * height;
|
||||
ATexturePoolItem *minDeltaTpi = NULL;
|
||||
LOCK_WRAPPER_LOCK(cell);
|
||||
{
|
||||
for (ATexturePoolItem *cur = cell->available; IS_NOT_NULL(cur); cur = cur->next) {
|
||||
// TODO: use swizzle when formats are not equal
|
||||
if (cur->format != format) {
|
||||
continue;
|
||||
}
|
||||
if (cur->width < width || cur->height < height) {
|
||||
continue;
|
||||
}
|
||||
const int deltaArea = (const int) (cur->width * cur->height - requestedPixels);
|
||||
if ((minDeltaArea < 0) || (deltaArea < minDeltaArea)) {
|
||||
minDeltaArea = deltaArea;
|
||||
minDeltaTpi = cur;
|
||||
if (deltaArea == 0) {
|
||||
// found exact match in current cell
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if IS_NOT_NULL(minDeltaTpi) {
|
||||
ATexturePoolCell_occupyItem(cell, minDeltaTpi);
|
||||
}
|
||||
}
|
||||
LOCK_WRAPPER_UNLOCK(cell);
|
||||
|
||||
if (TRACE_USE_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePoolCell_occupyCellItem: item = %p", minDeltaTpi);
|
||||
return minDeltaTpi;
|
||||
}
|
||||
|
||||
|
||||
/* ATexturePoolHandle API */
|
||||
static ATexturePoolHandle* ATexturePoolHandle_initWithPoolItem(ATexturePoolItem *item, jint reqWidth, jint reqHeight) {
|
||||
CHECK_NULL_RETURN(item, NULL);
|
||||
ATexturePoolHandle *handle = (ATexturePoolHandle*)malloc(sizeof(ATexturePoolHandle));
|
||||
CHECK_NULL_LOG_RETURN(handle, NULL, "ATexturePoolHandle_initWithPoolItem: could not allocate ATexturePoolHandle");
|
||||
|
||||
handle->texture = item->texture;
|
||||
handle->_poolItem = item;
|
||||
|
||||
handle->reqWidth = reqWidth;
|
||||
handle->reqHeight = reqHeight;
|
||||
|
||||
if (TRACE_USE_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePoolHandle_initWithPoolItem: handle = %p", handle);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/* Callback from metal pipeline => multi-thread (cell lock) */
|
||||
void ATexturePoolHandle_ReleaseTexture(ATexturePoolHandle *handle) {
|
||||
CHECK_NULL(handle);
|
||||
if (TRACE_USE_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePoolHandle_ReleaseTexture: handle = %p", handle);
|
||||
|
||||
ATexturePoolItem_ReleaseItem(handle->_poolItem);
|
||||
free(handle);
|
||||
}
|
||||
|
||||
ATexturePrivPtr* ATexturePoolHandle_GetTexture(ATexturePoolHandle *handle) {
|
||||
CHECK_NULL_RETURN(handle, NULL);
|
||||
if (TRACE_USE_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePoolHandle_GetTexture: handle = %p", handle);
|
||||
return handle->texture;
|
||||
}
|
||||
|
||||
jint ATexturePoolHandle_GetRequestedWidth(ATexturePoolHandle *handle) {
|
||||
CHECK_NULL_RETURN(handle, 0);
|
||||
if (TRACE_USE_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePoolHandle_GetRequestedWidth: handle = %p", handle);
|
||||
return handle->reqWidth;
|
||||
}
|
||||
|
||||
jint ATexturePoolHandle_GetRequestedHeight(ATexturePoolHandle *handle) {
|
||||
CHECK_NULL_RETURN(handle, 0);
|
||||
if (TRACE_USE_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePoolHandle_GetRequestedHeight: handle = %p", handle);
|
||||
return handle->reqHeight;
|
||||
}
|
||||
|
||||
|
||||
/* ATexturePool API */
|
||||
static void ATexturePool_cleanIfNecessary(ATexturePool *pool, int lastUsedTimeThreshold);
|
||||
|
||||
static void ATexturePool_autoTest(ATexturePool *pool, jlong format) {
|
||||
CHECK_NULL(pool);
|
||||
J2dRlsTraceLn1(J2D_TRACE_VERBOSE, "ATexturePool_autoTest: step = %d", INIT_TEST_STEP);
|
||||
|
||||
pool->enableGC = JNI_FALSE;
|
||||
|
||||
for (int w = 1; w <= INIT_TEST_MAX; w += INIT_TEST_STEP) {
|
||||
for (int h = 1; h <= INIT_TEST_MAX; h += INIT_TEST_STEP) {
|
||||
/* use auto-release pool to free memory as early as possible */
|
||||
|
||||
ATexturePoolHandle *texHandle = ATexturePool_getTexture(pool, w, h, format);
|
||||
ATexturePrivPtr *texture = ATexturePoolHandle_GetTexture(texHandle);
|
||||
|
||||
if IS_NULL(texture) {
|
||||
J2dRlsTraceLn2(J2D_TRACE_VERBOSE, "ATexturePool_autoTest: w= %d h= %d => texture is NULL !", w, h);
|
||||
} else {
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn3(J2D_TRACE_VERBOSE, "ATexturePool_autoTest: w=%d h=%d => tex=%p",
|
||||
w, h, texture);
|
||||
}
|
||||
ATexturePoolHandle_ReleaseTexture(texHandle);
|
||||
}
|
||||
}
|
||||
J2dRlsTraceLn2(J2D_TRACE_INFO, "ATexturePool_autoTest: before GC: total allocated memory = %lld Mb (total allocs: %d)",
|
||||
pool->totalMemoryAllocated / UNIT_MB, pool->totalAllocatedCount);
|
||||
|
||||
pool->enableGC = JNI_TRUE;
|
||||
|
||||
ATexturePool_cleanIfNecessary(pool, FORCE_GC_INTERVAL_SEC);
|
||||
|
||||
J2dRlsTraceLn2(J2D_TRACE_INFO, "ATexturePool_autoTest: after GC: total allocated memory = %lld Mb (total allocs: %d)",
|
||||
pool->totalMemoryAllocated / UNIT_MB, pool->totalAllocatedCount);
|
||||
}
|
||||
|
||||
ATexturePool* ATexturePool_initWithDevice(ADevicePrivPtr *device,
|
||||
jlong maxDeviceMemory,
|
||||
ATexturePool_createTexture *createTextureFunc,
|
||||
ATexturePool_freeTexture *freeTextureFunc,
|
||||
ATexturePool_bytesPerPixel *bytesPerPixelFunc,
|
||||
ATexturePoolLockWrapper *lockWrapper,
|
||||
jlong autoTestFormat)
|
||||
{
|
||||
CHECK_NULL_LOG_RETURN(device, NULL, "ATexturePool_initWithDevice: device is null !");
|
||||
CHECK_NULL_LOG_RETURN(createTextureFunc, NULL, "ATexturePool_initWithDevice: createTextureFunc function is null !");
|
||||
CHECK_NULL_LOG_RETURN(freeTextureFunc, NULL, "ATexturePool_initWithDevice: freeTextureFunc function is null !");
|
||||
CHECK_NULL_LOG_RETURN(bytesPerPixelFunc, NULL, "ATexturePool_initWithDevice: bytesPerPixelFunc function is null !");
|
||||
CHECK_NULL_LOG_RETURN(lockWrapper, NULL, "ATexturePool_initWithDevice: lockWrapper is null !");
|
||||
|
||||
ATexturePool *pool = (ATexturePool*)malloc(sizeof(ATexturePool));
|
||||
CHECK_NULL_LOG_RETURN(pool, NULL, "ATexturePool_initWithDevice: could not allocate ATexturePool");
|
||||
|
||||
pool->createTextureFunc = createTextureFunc;
|
||||
pool->freeTextureFunc = freeTextureFunc;
|
||||
pool->bytesPerPixelFunc = bytesPerPixelFunc;
|
||||
pool->lockWrapper = lockWrapper;
|
||||
pool->device = device;
|
||||
|
||||
// use (5K) 5120-by-2880 resolution:
|
||||
pool->poolCellWidth = 5120 >> CELL_WIDTH_BITS;
|
||||
pool->poolCellHeight = 2880 >> CELL_HEIGHT_BITS;
|
||||
|
||||
const int cellsCount = pool->poolCellWidth * pool->poolCellHeight;
|
||||
pool->_cells = (ATexturePoolCell**)malloc(cellsCount * sizeof(void*));
|
||||
CHECK_NULL_LOG_RETURN(pool->_cells, NULL, "ATexturePool_initWithDevice: could not allocate cells");
|
||||
memset(pool->_cells, 0, cellsCount * sizeof(void*));
|
||||
|
||||
pool->maxPoolMemory = maxDeviceMemory / 2;
|
||||
// Set maximum to handle at least 5K screen size
|
||||
if (pool->maxPoolMemory < SCREEN_MEMORY_SIZE_5K) {
|
||||
pool->maxPoolMemory = SCREEN_MEMORY_SIZE_5K;
|
||||
} else if (USE_MAX_GPU_DEVICE_MEM && (pool->maxPoolMemory > MAX_GPU_DEVICE_MEM)) {
|
||||
pool->maxPoolMemory = MAX_GPU_DEVICE_MEM;
|
||||
}
|
||||
|
||||
pool->allocatedCount = 0L;
|
||||
pool->totalAllocatedCount = 0L;
|
||||
|
||||
pool->memoryAllocated = 0L;
|
||||
pool->totalMemoryAllocated = 0L;
|
||||
|
||||
pool->enableGC = JNI_TRUE;
|
||||
pool->lastGC = time(NULL);
|
||||
pool->lastYoungGC = pool->lastGC;
|
||||
pool->lastFullGC = pool->lastGC;
|
||||
|
||||
pool->cacheHits = 0L;
|
||||
pool->totalHits = 0L;
|
||||
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePool_initWithDevice: pool = %p", pool);
|
||||
|
||||
if (INIT_TEST) {
|
||||
static jboolean INIT_TEST_START = JNI_TRUE;
|
||||
if (INIT_TEST_START) {
|
||||
INIT_TEST_START = JNI_FALSE;
|
||||
ATexturePool_autoTest(pool, autoTestFormat);
|
||||
}
|
||||
}
|
||||
return pool;
|
||||
}
|
||||
|
||||
void ATexturePool_Dispose(ATexturePool *pool) {
|
||||
CHECK_NULL(pool);
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePool_Dispose: pool = %p", pool);
|
||||
|
||||
const int cellsCount = pool->poolCellWidth * pool->poolCellHeight;
|
||||
for (int c = 0; c < cellsCount; ++c) {
|
||||
ATexturePoolCell *cell = pool->_cells[c];
|
||||
if IS_NOT_NULL(cell) {
|
||||
ATexturePoolCell_Dispose(cell);
|
||||
}
|
||||
}
|
||||
free(pool->_cells);
|
||||
free(pool);
|
||||
}
|
||||
|
||||
ATexturePoolLockWrapper* ATexturePool_getLockWrapper(ATexturePool *pool) {
|
||||
CHECK_NULL_RETURN(pool, NULL);
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn1(J2D_TRACE_INFO, "ATexturePool_getLockWrapper: pool = %p", pool);
|
||||
return pool->lockWrapper;
|
||||
}
|
||||
|
||||
static void ATexturePool_cleanIfNecessary(ATexturePool *pool, int lastUsedTimeThreshold) {
|
||||
CHECK_NULL(pool);
|
||||
time_t lastUsedTimeToRemove =
|
||||
lastUsedTimeThreshold > 0 ?
|
||||
time(NULL) - lastUsedTimeThreshold :
|
||||
lastUsedTimeThreshold;
|
||||
|
||||
if (TRACE_MEM_API || TRACE_GC) {
|
||||
J2dRlsTraceLn2(J2D_TRACE_VERBOSE, "ATexturePool_cleanIfNecessary: before GC: allocated memory = %lld Kb (allocs: %d)",
|
||||
pool->memoryAllocated / UNIT_KB, pool->allocatedCount);
|
||||
}
|
||||
|
||||
for (int cy = 0; cy < pool->poolCellHeight; ++cy) {
|
||||
for (int cx = 0; cx < pool->poolCellWidth; ++cx) {
|
||||
ATexturePoolCell *cell = pool->_cells[cy * pool->poolCellWidth + cx];
|
||||
if IS_NOT_NULL(cell) {
|
||||
ATexturePoolCell_cleanIfBefore(cell, lastUsedTimeToRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (TRACE_MEM_API || TRACE_GC) {
|
||||
J2dRlsTraceLn4(J2D_TRACE_VERBOSE, "ATexturePool_cleanIfNecessary: after GC: allocated memory = %lld Kb (allocs: %d) - hits = %lld (%.3lf %% cached)",
|
||||
pool->memoryAllocated / UNIT_KB, pool->allocatedCount,
|
||||
pool->totalHits, (pool->totalHits != 0L) ? (100.0 * pool->cacheHits) / pool->totalHits : 0.0);
|
||||
// reset hits:
|
||||
pool->cacheHits = 0L;
|
||||
pool->totalHits = 0L;
|
||||
}
|
||||
}
|
||||
|
||||
ATexturePoolHandle* ATexturePool_getTexture(ATexturePool* pool,
|
||||
jint width,
|
||||
jint height,
|
||||
jlong format)
|
||||
{
|
||||
CHECK_NULL_RETURN(pool, NULL);
|
||||
|
||||
const int reqWidth = width;
|
||||
const int reqHeight = height;
|
||||
|
||||
int cellX0 = width >> CELL_WIDTH_BITS;
|
||||
int cellY0 = height >> CELL_HEIGHT_BITS;
|
||||
|
||||
if (USE_CEIL_SIZE) {
|
||||
// use upper cell size to maximize cache hits:
|
||||
const int remX0 = width & CELL_WIDTH_MASK;
|
||||
const int remY0 = height & CELL_HEIGHT_MASK;
|
||||
|
||||
if (remX0 != 0) {
|
||||
cellX0++;
|
||||
}
|
||||
if (remY0 != 0) {
|
||||
cellY0++;
|
||||
}
|
||||
// adjust width / height to cell upper boundaries:
|
||||
width = (cellX0) << CELL_WIDTH_BITS;
|
||||
height = (cellY0) << CELL_HEIGHT_BITS;
|
||||
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn4(J2D_TRACE_VERBOSE, "ATexturePool_getTexture: fixed tex size: (%d %d) => (%d %d)",
|
||||
reqWidth, reqHeight, width, height);
|
||||
}
|
||||
|
||||
// 1. clean pool if necessary
|
||||
const int requestedPixels = width * height;
|
||||
const int requestedBytes = requestedPixels * pool->bytesPerPixelFunc(format);
|
||||
const jlong neededMemoryAllocated = pool->memoryAllocated + requestedBytes;
|
||||
|
||||
if (neededMemoryAllocated > pool->maxPoolMemory) {
|
||||
// release all free textures
|
||||
ATexturePool_cleanIfNecessary(pool, 0);
|
||||
} else {
|
||||
time_t now = time(NULL);
|
||||
// ensure 1s at least:
|
||||
if ((now - pool->lastGC) > 0) {
|
||||
pool->lastGC = now;
|
||||
if (neededMemoryAllocated > pool->maxPoolMemory / 2) {
|
||||
// release only old free textures
|
||||
ATexturePool_cleanIfNecessary(pool, MAX_POOL_ITEM_LIFETIME_SEC);
|
||||
} else if (FORCE_GC && pool->enableGC) {
|
||||
if ((now - pool->lastFullGC) > FORCE_GC_INTERVAL_SEC) {
|
||||
pool->lastFullGC = now;
|
||||
pool->lastYoungGC = now;
|
||||
// release only old free textures since last full-gc
|
||||
ATexturePool_cleanIfNecessary(pool, FORCE_GC_INTERVAL_SEC);
|
||||
} else if ((now - pool->lastYoungGC) > YOUNG_GC_INTERVAL_SEC) {
|
||||
pool->lastYoungGC = now;
|
||||
// release only not reused and old textures
|
||||
ATexturePool_cleanIfNecessary(pool, YOUNG_GC_LIFETIME_SEC);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. find free item
|
||||
const int cellX1 = cellX0 + 1;
|
||||
const int cellY1 = cellY0 + 1;
|
||||
|
||||
// Note: this code (test + resizing) is not thread-safe:
|
||||
if (cellX1 > pool->poolCellWidth || cellY1 > pool->poolCellHeight) {
|
||||
const int newCellWidth = cellX1 <= pool->poolCellWidth ? pool->poolCellWidth : cellX1;
|
||||
const int newCellHeight = cellY1 <= pool->poolCellHeight ? pool->poolCellHeight : cellY1;
|
||||
const int newCellsCount = newCellWidth*newCellHeight;
|
||||
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn2(J2D_TRACE_VERBOSE, "ATexturePool_getTexture: resize: %d -> %d",
|
||||
pool->poolCellWidth * pool->poolCellHeight, newCellsCount);
|
||||
|
||||
ATexturePoolCell **newCells = (ATexturePoolCell **)malloc(newCellsCount * sizeof(void*));
|
||||
CHECK_NULL_LOG_RETURN(newCells, NULL, "ATexturePool_getTexture: could not allocate newCells");
|
||||
|
||||
const size_t strideBytes = pool->poolCellWidth * sizeof(void*);
|
||||
for (int cy = 0; cy < pool->poolCellHeight; ++cy) {
|
||||
ATexturePoolCell **dst = newCells + cy * newCellWidth;
|
||||
ATexturePoolCell **src = pool->_cells + cy * pool->poolCellWidth;
|
||||
memcpy(dst, src, strideBytes);
|
||||
if (newCellWidth > pool->poolCellWidth)
|
||||
memset(dst + pool->poolCellWidth, 0, (newCellWidth - pool->poolCellWidth) * sizeof(void*));
|
||||
}
|
||||
if (newCellHeight > pool->poolCellHeight) {
|
||||
ATexturePoolCell **dst = newCells + pool->poolCellHeight * newCellWidth;
|
||||
memset(dst, 0, (newCellHeight - pool->poolCellHeight) * newCellWidth * sizeof(void*));
|
||||
}
|
||||
free(pool->_cells);
|
||||
pool->_cells = newCells;
|
||||
pool->poolCellWidth = newCellWidth;
|
||||
pool->poolCellHeight = newCellHeight;
|
||||
}
|
||||
|
||||
ATexturePoolItem *minDeltaTpi = NULL;
|
||||
int minDeltaArea = -1;
|
||||
for (int cy = cellY0; cy < cellY1; ++cy) {
|
||||
for (int cx = cellX0; cx < cellX1; ++cx) {
|
||||
ATexturePoolCell* cell = pool->_cells[cy * pool->poolCellWidth + cx];
|
||||
if IS_NOT_NULL(cell) {
|
||||
ATexturePoolItem *tpi = ATexturePoolCell_occupyCellItem(cell, width, height, format);
|
||||
if IS_NULL(tpi) {
|
||||
continue;
|
||||
}
|
||||
const int deltaArea = (const int) (tpi->width * tpi->height - requestedPixels);
|
||||
if (minDeltaArea < 0 || deltaArea < minDeltaArea) {
|
||||
minDeltaArea = deltaArea;
|
||||
minDeltaTpi = tpi;
|
||||
if (deltaArea == 0) {
|
||||
// found exact match in current cell
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if IS_NOT_NULL(minDeltaTpi) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if IS_NULL(minDeltaTpi) {
|
||||
ATexturePoolCell* cell = pool->_cells[cellY0 * pool->poolCellWidth + cellX0];
|
||||
if IS_NULL(cell) {
|
||||
cell = ATexturePoolCell_init(pool);
|
||||
CHECK_NULL_RETURN(cell, NULL);
|
||||
pool->_cells[cellY0 * pool->poolCellWidth + cellX0] = cell;
|
||||
}
|
||||
// use device to allocate NEW texture:
|
||||
ATexturePrivPtr* tex = pool->createTextureFunc(pool->device, width, height, format);
|
||||
CHECK_NULL_LOG_RETURN(tex, NULL, "ATexturePool_getTexture: createTextureFunc failed to allocate texture !");
|
||||
|
||||
minDeltaTpi = ATexturePoolItem_initWithTexture(pool->freeTextureFunc, pool->device, tex, cell,
|
||||
width, height, format);
|
||||
ATexturePoolCell_addOccupiedItem(cell, minDeltaTpi);
|
||||
|
||||
pool->memoryAllocated += requestedBytes;
|
||||
pool->totalMemoryAllocated += requestedBytes;
|
||||
|
||||
J2dTraceLn6(J2D_TRACE_VERBOSE, "ATexturePool_getTexture: created pool item: tex=%p, w=%d h=%d, pf=%d | allocated memory = %lld Kb (allocs: %d)",
|
||||
minDeltaTpi->texture, width, height, format, pool->memoryAllocated / UNIT_KB, pool->allocatedCount)
|
||||
if (TRACE_MEM_API) J2dRlsTraceLn6(J2D_TRACE_VERBOSE, "ATexturePool_getTexture: created pool item: tex=%p, w=%d h=%d, pf=%d | allocated memory = %lld Kb (allocs: %d)",
|
||||
minDeltaTpi->texture, width, height, format, pool->memoryAllocated / UNIT_KB, pool->allocatedCount)
|
||||
} else {
|
||||
pool->cacheHits++;
|
||||
minDeltaTpi->reuseCount++;
|
||||
if (TRACE_REUSE) {
|
||||
J2dRlsTraceLn5(J2D_TRACE_VERBOSE, "ATexturePool_getTexture: reused pool item: tex=%p, w=%d h=%d, pf=%d - reuse: %4d",
|
||||
minDeltaTpi->texture, width, height, format, minDeltaTpi->reuseCount)
|
||||
}
|
||||
}
|
||||
pool->totalHits++;
|
||||
minDeltaTpi->lastUsed = time(NULL);
|
||||
return ATexturePoolHandle_initWithPoolItem(minDeltaTpi, reqWidth, reqHeight);
|
||||
}
|
||||
139
src/java.desktop/share/native/common/java2d/AccelTexturePool.h
Normal file
139
src/java.desktop/share/native/common/java2d/AccelTexturePool.h
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2024, JetBrains s.r.o.. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#ifndef AccelTexturePool_h_Included
|
||||
#define AccelTexturePool_h_Included
|
||||
|
||||
#include "jni.h"
|
||||
|
||||
|
||||
#define UNIT_KB 1024
|
||||
#define UNIT_MB (UNIT_KB * UNIT_KB)
|
||||
|
||||
|
||||
/* useful macros (from jni_utils.h) */
|
||||
#define IS_NULL(obj) ((obj) == NULL)
|
||||
#define IS_NOT_NULL(obj) ((obj) != NULL)
|
||||
|
||||
#define CHECK_NULL(x) \
|
||||
do { \
|
||||
if ((x) == NULL) { \
|
||||
return; \
|
||||
} \
|
||||
} while (0) \
|
||||
|
||||
#define CHECK_NULL_RETURN(x, y) \
|
||||
do { \
|
||||
if ((x) == NULL) { \
|
||||
return (y); \
|
||||
} \
|
||||
} while (0) \
|
||||
|
||||
#define CHECK_NULL_LOG_RETURN(x, y, msg) \
|
||||
do { \
|
||||
if IS_NULL(x) { \
|
||||
J2dRlsTraceLn(J2D_TRACE_ERROR, (msg)); \
|
||||
return (y); \
|
||||
} \
|
||||
} while (0) \
|
||||
|
||||
|
||||
/* Generic native-specific device (platform specific) */
|
||||
typedef void ADevicePrivPtr;
|
||||
/* Generic native-specific texture (platform specific) */
|
||||
typedef void ATexturePrivPtr;
|
||||
|
||||
|
||||
/* Texture allocate/free API */
|
||||
typedef ATexturePrivPtr* (ATexturePool_createTexture)(ADevicePrivPtr *device,
|
||||
int width,
|
||||
int height,
|
||||
long format);
|
||||
|
||||
typedef void (ATexturePool_freeTexture)(ADevicePrivPtr *device,
|
||||
ATexturePrivPtr *texture);
|
||||
|
||||
typedef int (ATexturePool_bytesPerPixel)(long format);
|
||||
|
||||
|
||||
/* lock API */
|
||||
/* Generic native-specific lock (platform specific) */
|
||||
typedef void ATexturePoolLockPrivPtr;
|
||||
|
||||
typedef ATexturePoolLockPrivPtr* (ATexturePoolLock_init)();
|
||||
|
||||
typedef void (ATexturePoolLock_dispose) (ATexturePoolLockPrivPtr *lock);
|
||||
|
||||
typedef void (ATexturePoolLock_lock) (ATexturePoolLockPrivPtr *lock);
|
||||
|
||||
typedef void (ATexturePoolLock_unlock) (ATexturePoolLockPrivPtr *lock);
|
||||
|
||||
|
||||
/* forward-definitions (private) */
|
||||
typedef struct ATexturePoolLockWrapper_ ATexturePoolLockWrapper;
|
||||
typedef struct ATexturePoolItem_ ATexturePoolItem;
|
||||
typedef struct ATexturePoolHandle_ ATexturePoolHandle;
|
||||
typedef struct ATexturePoolCell_ ATexturePoolCell;
|
||||
typedef struct ATexturePool_ ATexturePool;
|
||||
|
||||
|
||||
/* ATexturePoolLockWrapper API */
|
||||
ATexturePoolLockWrapper* ATexturePoolLockWrapper_init(ATexturePoolLock_init *initFunc,
|
||||
ATexturePoolLock_dispose *disposeFunc,
|
||||
ATexturePoolLock_lock *lockFunc,
|
||||
ATexturePoolLock_unlock *unlockFunc);
|
||||
|
||||
void ATexturePoolLockWrapper_Dispose(ATexturePoolLockWrapper *lockWrapper);
|
||||
|
||||
|
||||
/* ATexturePoolHandle API */
|
||||
void ATexturePoolHandle_ReleaseTexture(ATexturePoolHandle *handle);
|
||||
|
||||
ATexturePrivPtr* ATexturePoolHandle_GetTexture(ATexturePoolHandle *handle);
|
||||
|
||||
jint ATexturePoolHandle_GetRequestedWidth(ATexturePoolHandle *handle);
|
||||
jint ATexturePoolHandle_GetRequestedHeight(ATexturePoolHandle *handle);
|
||||
|
||||
|
||||
/* ATexturePool API */
|
||||
ATexturePool* ATexturePool_initWithDevice(ADevicePrivPtr *device,
|
||||
jlong maxDeviceMemory,
|
||||
ATexturePool_createTexture *createTextureFunc,
|
||||
ATexturePool_freeTexture *freeTextureFunc,
|
||||
ATexturePool_bytesPerPixel *bytesPerPixelFunc,
|
||||
ATexturePoolLockWrapper *lockWrapper,
|
||||
jlong autoTestFormat);
|
||||
|
||||
void ATexturePool_Dispose(ATexturePool *pool);
|
||||
|
||||
ATexturePoolLockWrapper* ATexturePool_getLockWrapper(ATexturePool *pool);
|
||||
|
||||
ATexturePoolHandle* ATexturePool_getTexture(ATexturePool *pool,
|
||||
jint width,
|
||||
jint height,
|
||||
jlong format);
|
||||
|
||||
#endif /* AccelTexturePool_h_Included */
|
||||
Reference in New Issue
Block a user