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:
bourgesl
2024-08-09 11:40:53 +02:00
committed by jbrbot
parent bfb1b1a41e
commit ea609d5cc5
6 changed files with 1810 additions and 491 deletions

View File

@@ -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 \
#

View File

@@ -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 */

View File

@@ -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

View File

@@ -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

View 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);
}

View 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 */