Path: blob/master/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLTexurePool.m
66646 views
/*1* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425#import "MTLTexturePool.h"26#import "Trace.h"2728#define SCREEN_MEMORY_SIZE_5K (5120*4096*4) //~84 mb29#define MAX_POOL_ITEM_LIFETIME_SEC 303031#define CELL_WIDTH_BITS 5 // ~ 32 pixel32#define CELL_HEIGHT_BITS 5 // ~ 32 pixel3334@implementation MTLTexturePoolItem3536@synthesize texture, isBusy, lastUsed, isMultiSample, next, cell;3738- (id) initWithTexture:(id<MTLTexture>)tex cell:(MTLPoolCell*)c{39self = [super init];40if (self == nil) return self;41self.texture = tex;42isBusy = NO;43self.next = nil;44self.prev = nil;45self.cell = c;46return self;47}4849- (void) dealloc {50[texture release];51[super dealloc];52}5354@end5556@implementation MTLPooledTextureHandle57{58MTLRegion _rect;59id<MTLTexture> _texture;60MTLTexturePoolItem * _poolItem;61}62@synthesize texture = _texture, rect = _rect;6364- (id) initWithPoolItem:(id<MTLTexture>)texture rect:(MTLRegion)rectangle poolItem:(MTLTexturePoolItem *)poolItem {65self = [super init];66if (self == nil) return self;6768_rect = rectangle;69_texture = texture;70_poolItem = poolItem;71return self;72}7374- (void) releaseTexture {75[_poolItem.cell releaseItem:_poolItem];76}7778@end7980@implementation MTLPoolCell {81NSLock* _lock;82}83@synthesize available, availableTail, occupied;8485- (instancetype)init {86self = [super init];87if (self) {88self.available = nil;89self.availableTail = nil;90self.occupied = nil;91_lock = [[NSLock alloc] init];92}93return self;94}9596- (void)occupyItem:(MTLTexturePoolItem *)item {97if (item.isBusy) return;98[item retain];99if (item.prev == nil) {100self.available = item.next;101if (item.next) {102item.next.prev = nil;103} else {104self.availableTail = item.prev;105}106} else {107item.prev.next = item.next;108if (item.next) {109item.next.prev = item.prev;110} else {111self.availableTail = item.prev;112}113item.prev = nil;114}115if (occupied) occupied.prev = item;116item.next = occupied;117self.occupied = item;118[item release];119item.isBusy = YES;120}121122- (void)releaseItem:(MTLTexturePoolItem *)item {123[_lock lock];124@try {125if (!item.isBusy) return;126[item retain];127if (item.prev == nil) {128self.occupied = item.next;129if (item.next) item.next.prev = nil;130} else {131item.prev.next = item.next;132if (item.next) item.next.prev = item.prev;133item.prev = nil;134}135if (self.available) {136self.available.prev = item;137} else {138self.availableTail = item;139}140item.next = self.available;141self.available = item;142item.isBusy = NO;143[item release];144} @finally {145[_lock unlock];146}147}148149- (void)addOccupiedItem:(MTLTexturePoolItem *)item {150if (self.occupied) self.occupied.prev = item;151item.next = self.occupied;152item.isBusy = YES;153self.occupied = item;154}155156- (void)removeAvailableItem:(MTLTexturePoolItem*)item {157[item retain];158if (item.prev == nil) {159self.available = item.next;160if (item.next) {161item.next.prev = nil;162item.next = nil;163} else {164self.availableTail = item.prev;165}166} else {167item.prev.next = item.next;168if (item.next) {169item.next.prev = item.prev;170item.next = nil;171} else {172self.availableTail = item.prev;173}174}175[item release];176}177178- (void)removeAllItems {179MTLTexturePoolItem *cur = self.available;180while (cur != nil) {181cur = cur.next;182self.available = cur;183}184cur = self.occupied;185while (cur != nil) {186cur = cur.next;187self.occupied = cur;188}189self.availableTail = nil;190}191192- (MTLTexturePoolItem *)createItem:(id<MTLDevice>)dev193width:(int)width194height:(int)height195format:(MTLPixelFormat)format196isMultiSample:(bool)isMultiSample197{198MTLTextureDescriptor *textureDescriptor =199[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format200width:(NSUInteger) width201height:(NSUInteger) height202mipmapped:NO];203textureDescriptor.usage = MTLTextureUsageRenderTarget |204MTLTextureUsageShaderRead;205if (isMultiSample) {206textureDescriptor.textureType = MTLTextureType2DMultisample;207textureDescriptor.sampleCount = MTLAASampleCount;208textureDescriptor.storageMode = MTLStorageModePrivate;209}210211id <MTLTexture> tex = (id <MTLTexture>) [[dev newTextureWithDescriptor:textureDescriptor] autorelease];212MTLTexturePoolItem* item = [[[MTLTexturePoolItem alloc] initWithTexture:tex cell:self] autorelease];213item.isMultiSample = isMultiSample;214[_lock lock];215@try {216[self addOccupiedItem:item];217} @finally {218[_lock unlock];219}220return item;221}222223224- (NSUInteger)cleanIfBefore:(time_t)lastUsedTimeToRemove {225NSUInteger deallocMem = 0;226[_lock lock];227MTLTexturePoolItem *cur = availableTail;228@try {229while (cur != nil) {230MTLTexturePoolItem *prev = cur.prev;231if (lastUsedTimeToRemove <= 0 ||232cur.lastUsed < lastUsedTimeToRemove) {233#ifdef DEBUG234J2dTraceImpl(J2D_TRACE_VERBOSE, JNI_TRUE,235"MTLTexturePool: remove pool item: tex=%p, w=%d h=%d, elapsed=%d",236cur.texture, cur.texture.width, cur.texture.height,237time(NULL) - cur.lastUsed);238#endif //DEBUG239deallocMem += cur.texture.width * cur.texture.height * 4;240[self removeAvailableItem:cur];241} else {242if (lastUsedTimeToRemove > 0) break;243}244cur = prev;245}246} @finally {247[_lock unlock];248}249return deallocMem;250}251252- (MTLTexturePoolItem *)occupyItem:(int)width height:(int)height format:(MTLPixelFormat)format253isMultiSample:(bool)isMultiSample {254int minDeltaArea = -1;255const int requestedPixels = width*height;256MTLTexturePoolItem *minDeltaTpi = nil;257[_lock lock];258@try {259for (MTLTexturePoolItem *cur = available; cur != nil; cur = cur.next) {260if (cur.texture.pixelFormat != format261|| cur.isMultiSample != isMultiSample) { // TODO: use swizzle when formats are not equal262continue;263}264if (cur.texture.width < width || cur.texture.height < height) {265continue;266}267const int deltaArea = (const int) (cur.texture.width * cur.texture.height - requestedPixels);268if (minDeltaArea < 0 || deltaArea < minDeltaArea) {269minDeltaArea = deltaArea;270minDeltaTpi = cur;271if (deltaArea == 0) {272// found exact match in current cell273break;274}275}276}277278if (minDeltaTpi) {279[self occupyItem:minDeltaTpi];280}281} @finally {282[_lock unlock];283}284return minDeltaTpi;285}286287- (void) dealloc {288[_lock lock];289@try {290[self removeAllItems];291} @finally {292[_lock unlock];293}294[_lock release];295[super dealloc];296}297298@end299300@implementation MTLTexturePool {301int _memoryTotalAllocated;302303void ** _cells;304int _poolCellWidth;305int _poolCellHeight;306uint64_t _maxPoolMemory;307}308309@synthesize device;310311- (id) initWithDevice:(id<MTLDevice>)dev {312self = [super init];313if (self == nil) return self;314315_memoryTotalAllocated = 0;316_poolCellWidth = 10;317_poolCellHeight = 10;318const int cellsCount = _poolCellWidth * _poolCellHeight;319_cells = (void **)malloc(cellsCount * sizeof(void*));320memset(_cells, 0, cellsCount * sizeof(void*));321self.device = dev;322323// recommendedMaxWorkingSetSize typically greatly exceeds SCREEN_MEMORY_SIZE_5K constant.324// It usually corresponds to the VRAM available to the graphics card325_maxPoolMemory = self.device.recommendedMaxWorkingSetSize/2;326327// Set maximum to handle at least 5K screen size328if (_maxPoolMemory < SCREEN_MEMORY_SIZE_5K) {329_maxPoolMemory = SCREEN_MEMORY_SIZE_5K;330}331332return self;333}334335- (void) dealloc {336for (int c = 0; c < _poolCellWidth * _poolCellHeight; ++c) {337MTLPoolCell * cell = _cells[c];338if (cell != NULL) {339[cell release];340}341}342free(_cells);343[super dealloc];344}345346// NOTE: called from RQ-thread (on blit operations)347- (MTLPooledTextureHandle *) getTexture:(int)width height:(int)height format:(MTLPixelFormat)format {348return [self getTexture:width height:height format:format isMultiSample:NO];349}350351// NOTE: called from RQ-thread (on blit operations)352- (MTLPooledTextureHandle *) getTexture:(int)width height:(int)height format:(MTLPixelFormat)format353isMultiSample:(bool)isMultiSample {354// 1. clean pool if necessary355const int requestedPixels = width*height;356const int requestedBytes = requestedPixels*4;357if (_memoryTotalAllocated + requestedBytes > _maxPoolMemory) {358[self cleanIfNecessary:0]; // release all free textures359} else if (_memoryTotalAllocated + requestedBytes > _maxPoolMemory/2) {360[self cleanIfNecessary:MAX_POOL_ITEM_LIFETIME_SEC]; // release only old free textures361}362363// 2. find free item364const int cellX0 = width >> CELL_WIDTH_BITS;365const int cellY0 = height >> CELL_HEIGHT_BITS;366const int cellX1 = cellX0 + 1;367const int cellY1 = cellY0 + 1;368if (cellX1 > _poolCellWidth || cellY1 > _poolCellHeight) {369const int newCellWidth = cellX1 <= _poolCellWidth ? _poolCellWidth : cellX1;370const int newCellHeight = cellY1 <= _poolCellHeight ? _poolCellHeight : cellY1;371const int newCellsCount = newCellWidth*newCellHeight;372#ifdef DEBUG373J2dTraceLn2(J2D_TRACE_VERBOSE, "MTLTexturePool: resize: %d -> %d", _poolCellWidth * _poolCellHeight, newCellsCount);374#endif375void ** newcells = malloc(newCellsCount*sizeof(void*));376const int strideBytes = _poolCellWidth * sizeof(void*);377for (int cy = 0; cy < _poolCellHeight; ++cy) {378void ** dst = newcells + cy*newCellWidth;379void ** src = _cells + cy * _poolCellWidth;380memcpy(dst, src, strideBytes);381if (newCellWidth > _poolCellWidth)382memset(dst + _poolCellWidth, 0, (newCellWidth - _poolCellWidth) * sizeof(void*));383}384if (newCellHeight > _poolCellHeight) {385void ** dst = newcells + _poolCellHeight * newCellWidth;386memset(dst, 0, (newCellHeight - _poolCellHeight) * newCellWidth * sizeof(void*));387}388free(_cells);389_cells = newcells;390_poolCellWidth = newCellWidth;391_poolCellHeight = newCellHeight;392}393394MTLTexturePoolItem * minDeltaTpi = nil;395int minDeltaArea = -1;396for (int cy = cellY0; cy < cellY1; ++cy) {397for (int cx = cellX0; cx < cellX1; ++cx) {398MTLPoolCell * cell = _cells[cy * _poolCellWidth + cx];399if (cell != NULL) {400MTLTexturePoolItem* tpi = [cell occupyItem:width height:height401format:format isMultiSample:isMultiSample];402if (!tpi) continue;403const int deltaArea = (const int) (tpi.texture.width * tpi.texture.height - requestedPixels);404if (minDeltaArea < 0 || deltaArea < minDeltaArea) {405minDeltaArea = deltaArea;406minDeltaTpi = tpi;407if (deltaArea == 0) {408// found exact match in current cell409break;410}411}412}413}414if (minDeltaTpi != nil) {415break;416}417}418419if (minDeltaTpi == NULL) {420MTLPoolCell* cell = _cells[cellY0 * _poolCellWidth + cellX0];421if (cell == NULL) {422cell = [[MTLPoolCell alloc] init];423_cells[cellY0 * _poolCellWidth + cellX0] = cell;424}425minDeltaTpi = [cell createItem:device width:width height:height format:format isMultiSample:isMultiSample];426_memoryTotalAllocated += requestedBytes;427J2dTraceLn5(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);428}429430minDeltaTpi.isBusy = YES;431minDeltaTpi.lastUsed = time(NULL);432return [[[MTLPooledTextureHandle alloc] initWithPoolItem:minDeltaTpi.texture433rect:MTLRegionMake2D(0, 0,434minDeltaTpi.texture.width,435minDeltaTpi.texture.height)436poolItem:minDeltaTpi] autorelease];437}438439- (void) cleanIfNecessary:(int)lastUsedTimeThreshold {440time_t lastUsedTimeToRemove =441lastUsedTimeThreshold > 0 ?442time(NULL) - lastUsedTimeThreshold :443lastUsedTimeThreshold;444for (int cy = 0; cy < _poolCellHeight; ++cy) {445for (int cx = 0; cx < _poolCellWidth; ++cx) {446MTLPoolCell * cell = _cells[cy * _poolCellWidth + cx];447if (cell != NULL) {448_memoryTotalAllocated -= [cell cleanIfBefore:lastUsedTimeToRemove];449}450}451}452}453454@end455456457