Skip to content
Snippets Groups Projects
Commit 9ef7f90a authored by George Nachman's avatar George Nachman
Browse files

Add support for glyphs that take more than one cell, spilling into their...

Add support for glyphs that take more than one cell, spilling into their neighbor. This commit probably adds a performance hit to the CPU side of things which I need to work on reducing in the normal case.
parent fe758440
No related branches found
No related tags found
No related merge requests found
Showing with 445 additions and 152 deletions
Loading
Loading
@@ -3305,6 +3305,7 @@
A60BD9181B3F5D76007D7F11 /* OpenDirectory.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenDirectory.framework; path = System/Library/Frameworks/OpenDirectory.framework; sourceTree = SDKROOT; };
A60D85A61A3A8105003AEE22 /* NSPasteboard+iTerm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSPasteboard+iTerm.h"; sourceTree = "<group>"; };
A60D85A71A3A8105003AEE22 /* NSPasteboard+iTerm.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSPasteboard+iTerm.m"; sourceTree = "<group>"; };
A60EBD1A1FB2EC19008C7F5B /* iTermTextureMap+CPP.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "iTermTextureMap+CPP.h"; sourceTree = "<group>"; };
A6184F881BAB3ED70088EF3C /* ColorPicker.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ColorPicker.framework; path = ColorPicker/ColorPicker.framework; sourceTree = "<group>"; };
A61ABBB91AE5F38C004656C2 /* NSDictionary+Profile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Profile.h"; sourceTree = "<group>"; };
A61ABBBA1AE5F38C004656C2 /* NSDictionary+Profile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Profile.m"; sourceTree = "<group>"; };
Loading
Loading
@@ -5530,6 +5531,7 @@
A6E5D20A1FA3C55700EDD002 /* iTermMetalRowData.m */,
A6E5D20D1FA3C57900EDD002 /* iTermMetalFrameData.h */,
A6E5D20E1FA3C57900EDD002 /* iTermMetalFrameData.m */,
A60EBD1A1FB2EC19008C7F5B /* iTermTextureMap+CPP.h */,
);
name = Infrastructure;
sourceTree = "<group>";
Loading
Loading
@@ -45,6 +45,8 @@ extern const CGFloat BOTTOM_MARGIN;
@property (nonatomic, readonly) id<MTLBuffer> offsetBuffer;
@property (nonatomic, strong) id<MTLBuffer> pius;
 
- (instancetype)init NS_UNAVAILABLE;
- (void)setPIUValue:(void *)c coord:(VT100GridCoord)coord;
- (const void *)piuForCoord:(VT100GridCoord)coord;
 
Loading
Loading
Loading
Loading
@@ -33,6 +33,10 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, strong) id<MTLBuffer> vertexBuffer;
@property (nonatomic, readonly, strong) id<MTLRenderPipelineState> pipelineState;
@property (nonatomic, readonly) BOOL skipRenderer;
- (instancetype)initWithConfiguration:(iTermRenderConfiguration *)configuration;
- (instancetype)init NS_UNAVAILABLE;
@end
 
@interface iTermMetalRenderer : NSObject
Loading
Loading
Loading
Loading
@@ -44,10 +44,6 @@
 
- (instancetype)init NS_UNAVAILABLE;
 
- (NSInteger)findOrAllocateIndexOfLockedTextureWithKey:(const iTermMetalGlyphKey *)key
column:(int)column
creation:(NSImage *(^)(int))creation;
- (void)blitNewTexturesFromStagingAreaWithCommandBuffer:(id<MTLCommandBuffer>)commandBuffer;
 
@end
Loading
Loading
@@ -6,11 +6,15 @@
#define DLog(format, ...)
 
#include <list>
#include <map>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
 
// Define as NSLog or DLog to debug locking issues
#define DLogLock(args...)
static const NSInteger iTermTextureMapNumberOfStages = 2;
 
/*
Loading
Loading
@@ -72,6 +76,13 @@ namespace cache {
}
}
 
// Returns the key-value pair of the least-recently used key and its associated value.
key_value_pair_t get_lru(void) const {
auto last = _cache_items_list.end();
last--;
return *last;
}
inline const value_t *get(const key_t& key) {
auto it = _cache_items_map.find(key);
if (it == _cache_items_map.end()) {
Loading
Loading
@@ -125,15 +136,18 @@ namespace iTerm2 {
class GlyphKey {
private:
iTermMetalGlyphKey _repr;
// Glyphs larger than once cell are broken into multiple parts.
int _part;
 
GlyphKey();
 
public:
explicit GlyphKey(const iTermMetalGlyphKey *repr) : _repr(*repr) { }
GlyphKey(const iTermMetalGlyphKey *repr, int part) : _repr(*repr), _part(part) { }
 
// Copy constructor
GlyphKey(const GlyphKey &other) {
_repr = other._repr;
_part = other._part;
}
 
inline bool operator==(const GlyphKey &other) const {
Loading
Loading
@@ -141,7 +155,8 @@ namespace iTerm2 {
_repr.isComplex == other._repr.isComplex &&
_repr.image == other._repr.image &&
_repr.boxDrawing == other._repr.boxDrawing &&
_repr.thinStrokes == other._repr.thinStrokes);
_repr.thinStrokes == other._repr.thinStrokes &&
_part == other._part);
}
 
inline std::size_t get_hash() const {
Loading
Loading
@@ -152,6 +167,7 @@ namespace iTerm2 {
hash_combine(seed, _repr.boxDrawing);
hash_combine(seed, _repr.thinStrokes);
hash_combine(seed, _repr.thinStrokes);
hash_combine(seed, _part);
return seed;
}
};
Loading
Loading
@@ -202,10 +218,7 @@ namespace iTerm2 {
_nextStageIndex = 0;
}
 
void unlock_all(std::vector<int> *locks) {
for (auto it = _lockedIndexes.begin(); it != _lockedIndexes.end(); it++) {
(*locks)[*it]--;
}
void unlock_all() {
_lockedIndexes.clear();
}
 
Loading
Loading
@@ -244,34 +257,88 @@ namespace iTerm2 {
// Maps an index to the lock count. Values > 0 are locked.
std::vector<int> _locks;
 
// Maps an index to a map from part to related index.
std::unordered_map<int, std::map<int, int>> _relatedIndexes;
// Maximum number of entries.
const int _capacity;
public:
explicit TextureMap(const int capacity) : _lru(capacity), _locks(capacity), _capacity(capacity) { }
 
inline int get_index(const GlyphKey &key, TextureMapStage *textureMapStage) {
inline int get_index(const GlyphKey &key, TextureMapStage *textureMapStage, std::map<int, int> *related) {
const int *value;
value = textureMapStage->lookup(key, &_lru);
if (value == nullptr) {
return -1;
} else {
const int index = *value;
_locks[index]++;
*related = _relatedIndexes[index];
if (related->size() == 1) {
_locks[index]++;
DLogLock(@"Get %d sets lock to %d", index, _locks[index]);
} else {
for (auto kvp : *related) {
const int i = kvp.second;
_locks[i]++;
DLogLock(@"Lock related %d; lock is now %d", i, _locks[i]);
}
}
return index;
}
}
 
inline std::pair<int, int> allocate_index(const GlyphKey &key, TextureMapStage *stage) {
const int index = _lru.size() + 1;
const int index = produce();
assert(_locks[index] == 0);
remove_relations(index);
_locks[index]++;
DLogLock(@"Allocate %d sets lock to %d", index, _locks[index]);
assert(index <= _capacity);
_lru.put(key, index);
const int stageIndex = stage->will_blit(index);
return std::make_pair(stageIndex, index);
}
 
void unlock_stage(TextureMapStage *stage) {
stage->unlock_all(&_locks);
stage->unlock_all();
}
inline void unlock(int index) {
_locks[index]--;
DLogLock(@"Unlock %d sets lock to %d", index, _locks[index]);
assert(_locks[index] >= 0);
}
void define_class(const std::map<int, int> &relation) {
for (auto kvp : relation) {
const int i = kvp.second;
_relatedIndexes[i] = relation;
}
}
private:
// Return the next index to use. Either a never-before used one or the least-recently used one.
inline int produce() {
if (_lru.size() == _capacity) {
// Recycle the value of the least-recently used GlyphKey.
return _lru.get_lru().second;
} else {
return _lru.size();
}
}
inline void remove_relations(const int index) {
// Remove all relations of the index that will be recycled.
auto it = _relatedIndexes.find(index);
if (it != _relatedIndexes.end()) {
auto temp = it->second;
for (auto kvp : temp) {
const int i = kvp.second;
assert(_locks[i] == 0);
_relatedIndexes.erase(i);
}
}
}
};
}
Loading
Loading
@@ -355,6 +422,12 @@ namespace iTerm2 {
}
}
 
- (void)unlockIndexes:(const std::vector<int> &)indexes {
for (int i : indexes) {
_textureMap->unlock(i);
}
}
- (iTermTextureArray *)newTextureArrayForStageWithDevice:(id<MTLDevice>)device {
return [[iTermTextureArray alloc] initWithTextureWidth:_cellSize.width
textureHeight:_cellSize.height
Loading
Loading
@@ -366,21 +439,33 @@ namespace iTerm2 {
column:(int)column
textureMapStage:(iTerm2::TextureMapStage *)textureMapStage
stageArray:(iTermTextureArray *)stageArray
creation:(NSImage *(^)(int))creation {
const iTerm2::GlyphKey glyphKey(key);
int index = _textureMap->get_index(glyphKey, textureMapStage);
relations:(std::map<int, int> *)relations
creation:(NSDictionary<NSNumber *, NSImage *> *(NS_NOESCAPE ^)(int x))creation {
const iTerm2::GlyphKey glyphKey(key, 4);
int index = _textureMap->get_index(glyphKey, textureMapStage, relations);
if (index >= 0) {
DLog(@"%@: locked existing texture %@", self.label, @(index));
return index;
} else {
NSImage *image = creation(column);
if (image != nil) {
auto stageAndGlobalIndex = _textureMap->allocate_index(glyphKey, textureMapStage);
DLog(@"%@: create and stage new texture %@", self.label, @(index));
DLog(@"Stage %@ at %@", key, @(index));
[stageArray setSlice:stageAndGlobalIndex.first withImage:image];
return stageAndGlobalIndex.second;
NSDictionary<NSNumber *, NSImage *> *images = creation(column);
if (images.count) {
__block NSInteger result = -1;
std::map<int, int> newRelations;
for (NSNumber *part in images) {
NSImage *image = images[part];
const iTerm2::GlyphKey newGlyphKey(key, part.intValue);
auto stageAndGlobalIndex = _textureMap->allocate_index(newGlyphKey, textureMapStage);
if (result < 0) {
result = stageAndGlobalIndex.second;
}
newRelations[part.intValue] = stageAndGlobalIndex.second;
DLog(@"%@: create and stage new texture %@", self.label, @(index));
DLog(@"Stage %@ at %@", key, @(index));
[stageArray setSlice:stageAndGlobalIndex.first withImage:image];
}
_textureMap->define_class(newRelations);
*relations = newRelations;
return result;
} else {
return -1;
}
Loading
Loading
@@ -440,11 +525,13 @@ namespace iTerm2 {
 
- (NSInteger)findOrAllocateIndexOfLockedTextureWithKey:(const iTermMetalGlyphKey *)key
column:(int)column
creation:(NSImage *(^)(int))creation {
relations:(std::map<int, int> *)relations
creation:(NSDictionary<NSNumber *, NSImage *> *(NS_NOESCAPE ^)(int x))creation {
return [_textureMap findOrAllocateIndexOfLockedTextureWithKey:key
column:column
textureMapStage:_textureMapStage
stageArray:_stageArray
relations:relations
creation:creation];
}
 
Loading
Loading
Loading
Loading
@@ -13,8 +13,9 @@ NS_ASSUME_NONNULL_BEGIN
count:(int)count
attributesData:(NSData *)attributesData
row:(int)row
creation:(NSImage *(NS_NOESCAPE ^)(int x))creation;
- (void)willDraw;
backgroundColorData:(NSData *)backgroundColorData // array of vector_float4 background colors.
creation:(NSDictionary<NSNumber *, NSImage *> *(NS_NOESCAPE ^)(int x))creation;
- (void)willDrawWithDefaultBackgroundColor:(vector_float4)defaultBackgroundColor;
- (void)didComplete;
 
@end
Loading
Loading
Loading
Loading
@@ -8,36 +8,60 @@ extern "C" {
#import "iTermSubpixelModelBuilder.h"
#import "iTermTextureArray.h"
#import "iTermTextureMap.h"
#import "iTermTextureMap+CPP.h"
#import "NSDictionary+iTerm.h"
#import <unordered_map>
#include <vector>
typedef struct {
iTermTextPIU *piu;
int x;
int y;
} iTermTextFixup;
 
@interface iTermTextRendererTransientState ()
 
@property (nonatomic, readonly) NSIndexSet *indexes;
@property (nonatomic, strong) iTermTextureMap *textureMap;
@property (nonatomic, readonly) NSInteger numberOfInstances;
 
- (void)addIndex:(NSInteger)index;
@end
 
@implementation iTermTextRendererTransientState {
iTermTextureMapStage *_stage;
NSMutableIndexSet *_indexes;
dispatch_group_t _group;
id<MTLCommandBuffer> _commandBuffer;
std::vector<int> *_locks;
NSMutableArray<NSData *> *_backgroundColorDataArray;
std::vector<iTermTextFixup> *_fixups;
}
 
- (instancetype)init {
self = [super init];
- (instancetype)initWithConfiguration:(iTermRenderConfiguration *)configuration {
self = [super initWithConfiguration:configuration];
if (self) {
_indexes = [NSMutableIndexSet indexSet];
_group = dispatch_group_create();
_locks = new std::vector<int>();
_backgroundColorDataArray = [NSMutableArray array];
_fixups = new std::vector<iTermTextFixup>();
}
return self;
}
 
- (void)willDraw {
- (void)dealloc {
delete _locks;
delete _fixups;
}
- (void)willDrawWithDefaultBackgroundColor:(vector_float4)defaultBackgroundColor {
// Fix up the background color of parts of glyphs that are drawn outside their cell.
const int numRows = _backgroundColorDataArray.count;
const int width = [_backgroundColorDataArray.firstObject length] / sizeof(iTermBackgroundColorPIU);
for (auto &fixup : *_fixups) {
if (fixup.y >= 0 && fixup.y < numRows && fixup.x >= 0 && fixup.x < width) {
NSData *data = _backgroundColorDataArray[fixup.y];
const vector_float4 *backgroundColors = (vector_float4 *)data.bytes;
fixup.piu->backgroundColor = backgroundColors[fixup.x];
} else {
fixup.piu->backgroundColor = defaultBackgroundColor;
}
}
[_stage blitNewTexturesFromStagingAreaWithCommandBuffer:_commandBuffer];
}
 
Loading
Loading
@@ -60,7 +84,10 @@ extern "C" {
count:(int)count
attributesData:(NSData *)attributesData
row:(int)row
creation:(NSImage *(NS_NOESCAPE ^)(int x))creation {
backgroundColorData:(nonnull NSData *)backgroundColorData
creation:(NSDictionary<NSNumber *, NSImage *> *(NS_NOESCAPE ^)(int x))creation {
assert(row == _backgroundColorDataArray.count);
[_backgroundColorDataArray addObject:backgroundColorData];
const int width = self.cellConfiguration.gridSize.width;
assert(count <= width);
const iTermMetalGlyphKey *glyphKeys = (iTermMetalGlyphKey *)glyphKeysData.bytes;
Loading
Loading
@@ -69,35 +96,80 @@ extern "C" {
const float h = 1.0 / _textureMap.array.atlasSize.height;
iTermTextureArray *array = _textureMap.array;
iTermTextPIU *pius = (iTermTextPIU *)self.pius.contents;
const float yOffset = (self.cellConfiguration.gridSize.height - row - 1) * self.cellConfiguration.cellSize.height;
const float cellHeight = self.cellConfiguration.cellSize.height;
const float cellWidth = self.cellConfiguration.cellSize.width;
const float yOffset = (self.cellConfiguration.gridSize.height - row - 1) * cellHeight;
 
NSInteger lastIndex = 0;
std::map<int, int> lastRelations;
for (int x = 0; x < count; x++) {
pius[_numberOfInstances].offset = simd_make_float2(x * self.cellConfiguration.cellSize.width,
yOffset);
if (!glyphKeys[x].drawable) {
continue;
}
std::map<int, int> relations;
NSInteger index;
BOOL retained;
if (x > 0 && !memcmp(&glyphKeys[x], &glyphKeys[x-1], sizeof(*glyphKeys))) {
index = lastIndex;
relations = lastRelations;
// When the glyphKey is repeated there's no need to acquire another lock.
// If we get here, both this and the preceding glyphKey are drawable.
retained = NO;
} else {
index = [_stage findOrAllocateIndexOfLockedTextureWithKey:&glyphKeys[x]
column:x
relations:&relations
creation:creation];
retained = YES;
}
if (index >= 0) {
static int dxs[] = { -1, 0, 1, -1, 0, 1, -1, 0, 1 };
static int dys[] = { -1, -1, -1, 0, 0, 0, 1, 1, 1 };
if (relations.size() > 1) {
for (auto &kvp : relations) {
const int part = kvp.first;
const int index = kvp.second;
iTermTextPIU *piu = &pius[_numberOfInstances];
piu->offset = simd_make_float2((x + dxs[part]) * cellWidth,
dys[part] * cellHeight + yOffset);
MTLOrigin origin = [array offsetForIndex:index];
piu->textureOffset = (vector_float2){ origin.x * w, origin.y * h };
piu->textColor = attributes[x].foregroundColor;
if (part == 4) {
piu->backgroundColor = attributes[x].backgroundColor;
} else {
iTermTextFixup fixup = {
.piu = piu,
.x = x + dxs[part],
.y = row - dys[part]
};
_fixups->push_back(fixup);
}
[self addIndex:index retained:retained];
}
} else if (index >= 0) {
iTermTextPIU *piu = &pius[_numberOfInstances];
piu->offset = simd_make_float2(x * self.cellConfiguration.cellSize.width,
yOffset);
MTLOrigin origin = [array offsetForIndex:index];
piu->textureOffset = (vector_float2){ origin.x * w, origin.y * h };
piu->textColor = attributes[x].foregroundColor;
piu->backgroundColor = attributes[x].backgroundColor;
[self addIndex:index];
[self addIndex:index retained:retained];
}
lastIndex = index;
lastRelations = relations;
}
}
 
- (void)didComplete {
assert(_locks);
assert(_stage);
[_textureMap returnStage:_stage];
[_textureMap unlockIndexes:*_locks];
_stage = nil;
delete _locks;
_locks = nil;
}
 
- (nonnull NSMutableData *)modelData {
Loading
Loading
@@ -107,8 +179,10 @@ extern "C" {
return _modelData;
}
 
- (void)addIndex:(NSInteger)index {
[_indexes addIndex:index];
- (void)addIndex:(NSInteger)index retained:(BOOL)retained {
if (retained) {
_locks->push_back(index);
}
_numberOfInstances++;
}
 
Loading
Loading
@@ -136,7 +210,6 @@ extern "C" {
// The fragment function assumes we use the value 17 here. It's
// convenient that 17 evenly divides 255 (17 * 15 = 255).
float stride = 255.0/17.0;
int i = 0;
for (float textColor = 0; textColor < 256; textColor += stride) {
for (float backgroundColor = 0; backgroundColor < 256; backgroundColor += stride) {
iTermSubpixelModel *model = [[iTermSubpixelModelBuilder sharedInstance] modelForForegoundColor:MIN(MAX(0, textColor / 255.0), 1)
Loading
Loading
@@ -172,6 +245,9 @@ extern "C" {
- (void)createTransientStateForCellConfiguration:(iTermCellRenderConfiguration *)configuration
commandBuffer:(id<MTLCommandBuffer>)commandBuffer
completion:(void (^)(__kindof iTermMetalRendererTransientState * _Nonnull))completion {
// NOTE: Any time a glyph overflows its bounds into a neighboring cell it's possible the strokes will intersect.
// I haven't thought of a way to make that look good yet without having to do one draw pass per overflow glyph that
// blends using the output of the preceding passes.
_cellRenderer.fragmentFunctionName = configuration.usingIntermediatePass ? @"iTermTextFragmentShaderWithBlending" : @"iTermTextFragmentShaderSolidBackground";
[_cellRenderer createTransientStateForCellConfiguration:configuration
commandBuffer:commandBuffer
Loading
Loading
Loading
Loading
@@ -26,6 +26,7 @@ NS_ASSUME_NONNULL_BEGIN
@protocol iTermMetalDriverDataSourcePerFrameState<NSObject>
 
@property (nonatomic, readonly) VT100GridSize gridSize;
@property (nonatomic, readonly) vector_float4 defaultBackgroundColor;
 
- (void)metalGetGlyphKeys:(iTermMetalGlyphKey *)glyphKeys
attributes:(iTermMetalGlyphAttributes *)attributes
Loading
Loading
@@ -36,9 +37,9 @@ NS_ASSUME_NONNULL_BEGIN
 
- (nullable iTermMetalCursorInfo *)metalDriverCursorInfo;
 
- (NSImage *)metalImageForGlyphKey:(iTermMetalGlyphKey *)glyphKey
size:(CGSize)size
scale:(CGFloat)scale;
- (NSDictionary<NSNumber *, NSImage *> *)metalImagesForGlyphKey:(iTermMetalGlyphKey *)glyphKey
size:(CGSize)size
scale:(CGFloat)scale;
 
// Returns the background image or nil. If there's a background image, fill in blending and tiled.
- (NSImage *)metalBackgroundImageGetBlending:(CGFloat *)blending tiled:(BOOL *)tiled;
Loading
Loading
Loading
Loading
@@ -158,13 +158,16 @@ static const NSInteger iTermMetalDriverMaximumNumberOfFramesInFlight = 3;
}
 
iTermMetalFrameData *frameData = [self newFrameData];
[frameData loadFromView:view];
if (!frameData.drawable) {
NSLog(@" abort: no drawable available");
return;
}
 
@synchronized(self) {
[_currentFrames addObject:frameData];
}
 
[frameData loadFromView:view];
dispatch_async(_queue, ^{
[self prepareRenderersWithFrameData:frameData view:view];
});
Loading
Loading
@@ -318,10 +321,11 @@ static const NSInteger iTermMetalDriverMaximumNumberOfFramesInFlight = 3;
count:rowData.numberOfDrawableGlyphs
attributesData:rowData.attributesData
row:rowData.y
creation:^NSImage *(int x) {
return [frameData.perFrameState metalImageForGlyphKey:&glyphKeys[x]
size:cellSize
scale:scale];
backgroundColorData:rowData.backgroundColorData
creation:^NSDictionary<NSNumber *,NSImage *> * _Nonnull(int x) {
return [frameData.perFrameState metalImagesForGlyphKey:&glyphKeys[x]
size:cellSize
scale:scale];
}];
[backgroundState setColorData:rowData.backgroundColorData
row:rowData.y
Loading
Loading
@@ -329,7 +333,7 @@ static const NSInteger iTermMetalDriverMaximumNumberOfFramesInFlight = 3;
}];
 
// Tell the text state that it's done getting row data.
[textState willDraw];
[textState willDrawWithDefaultBackgroundColor:frameData.perFrameState.defaultBackgroundColor];
}
 
- (void)drawRenderer:(id<iTermMetalRenderer>)renderer
Loading
Loading
Loading
Loading
@@ -19,11 +19,7 @@
#include <sys/sysctl.h>
 
int iTermProcPidInfoWrapper(int pid, int flavor, uint64_t arg, void *buffer, int buffersize) {
__block int result;
BOOL timeout = [[iTermCallWithTimeout instanceForIdentifier:@"pidinfo"] executeWithTimeout:0.5 block:^{
result = proc_pidinfo(pid, flavor, arg, buffer, buffersize);
}];
return timeout ? -1 : result;
return proc_pidinfo(pid, flavor, arg, buffer, buffersize);
}
 
@implementation iTermLSOF {
Loading
Loading
Loading
Loading
@@ -14,6 +14,7 @@
#import "iTermSmartCursorColor.h"
#import "iTermTextDrawingHelper.h"
#import "NSColor+iTerm.h"
#import "NSImage+iTerm.h"
#import "PTYFontInfo.h"
#import "PTYTextView.h"
#import "VT100Screen.h"
Loading
Loading
@@ -281,6 +282,14 @@ static BOOL iTermTextDrawingHelperIsCharacterDrawable(screen_char_t *c,
return _gridSize;
}
 
- (vector_float4)defaultBackgroundColor {
NSColor *color = [_colorMap colorForKey:kColorMapBackground];
return simd_make_float4((float)color.redComponent,
(float)color.greenComponent,
(float)color.blueComponent,
1);
}
// Private queue
- (nullable iTermMetalCursorInfo *)metalDriverCursorInfo {
return _cursorInfo;
Loading
Loading
@@ -404,6 +413,9 @@ static BOOL iTermTextDrawingHelperIsCharacterDrawable(screen_char_t *c,
_blinkingItemsVisible,
_blinkAllowed)) {
lastDrawableGlyph = x;
glyphKeys[x].drawable = YES;
} else {
glyphKeys[x].drawable = NO;
}
}
 
Loading
Loading
@@ -596,21 +608,10 @@ static BOOL iTermTextDrawingHelperIsCharacterDrawable(screen_char_t *c,
return kColorMapInvalid;
}
 
- (NSImage *)metalImageForGlyphKey:(iTermMetalGlyphKey *)glyphKey
size:(CGSize)size
scale:(CGFloat)scale {
- (NSDictionary<NSNumber *,NSImage *> *)metalImagesForGlyphKey:(iTermMetalGlyphKey *)glyphKey
size:(CGSize)size
scale:(CGFloat)scale {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef ctx = CGBitmapContextCreate(NULL,
size.width,
size.height,
8,
size.width * 4,
colorSpace,
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host);
CGColorSpaceRelease(colorSpace);
CGContextSetRGBFillColor(ctx, 0, 0, 0, 0);
CGContextFillRect(ctx, CGRectMake(0, 0, size.width, size.height));
 
BOOL fakeBold = NO;
BOOL fakeItalic = NO;
Loading
Loading
@@ -625,101 +626,199 @@ static BOOL iTermTextDrawingHelperIsCharacterDrawable(screen_char_t *c,
renderItalic:&fakeItalic];
NSFont *font = fontInfo.font;
assert(font);
[self drawString:CharToStr(glyphKey->code, glyphKey->isComplex)
font:font
size:size
baselineOffset:fontInfo.baselineOffset
scale:scale
useThinStrokes:glyphKey->thinStrokes
context:ctx];
NSImage *image;
CGRect rect = [self drawGlyphKey:glyphKey
font:font
size:size
offset:CGPointZero
baselineOffset:fontInfo.baselineOffset
scale:scale
useThinStrokes:glyphKey->thinStrokes
colorSpace:colorSpace
image:&image];
CGColorSpaceRelease(colorSpace);
if (image == nil) {
return nil;
}
NSMutableDictionary<NSNumber *, NSImage *> *result = [NSMutableDictionary dictionary];
result[@4] = image;
// Check the eight cells surrounding and see if the glyph spills into them and output additional images if so.
// The key identifies which neighboring cell.
// 0 1 2
// 3 4 5
// 6 7 8
int i = 0;
for (int y = 0; y < 3; y++) {
for (int x = 0; x < 3; x++) {
if (i == 4) {
i++;
continue;
}
CGRect quadrant = CGRectMake((x - 1) * size.width, (y - 1) * size.height, size.width, size.height);
if (CGRectIntersectsRect(quadrant, rect)) {
image = nil;
[self drawGlyphKey:glyphKey
font:font
size:size
offset:CGPointMake(-quadrant.origin.x, -quadrant.origin.y)
baselineOffset:fontInfo.baselineOffset
scale:scale
useThinStrokes:glyphKey->thinStrokes
colorSpace:colorSpace
image:&image];
if (image) {
result[@(i)] = image;
}
}
i++;
}
}
return result;
}
- (CGRect)drawGlyphKey:(iTermMetalGlyphKey *)glyphKey
font:(NSFont *)font
size:(CGSize)size
offset:(CGPoint)offset
baselineOffset:(CGFloat)baselineOffset
scale:(CGFloat)scale
useThinStrokes:(BOOL)useThinStrokes
colorSpace:(CGColorSpaceRef)colorSpace
image:(NSImage **)imagePtr {
CGContextRef ctx = CGBitmapContextCreate(NULL,
size.width,
size.height,
8,
size.width * 4,
colorSpace,
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host);
CGRect rect = [self drawStringUsingCoreText:CharToStr(glyphKey->code, glyphKey->isComplex)
font:font
size:size
offset:offset
baselineOffset:baselineOffset
scale:scale
useThinStrokes:glyphKey->thinStrokes
context:ctx];
 
CGImageRef imageRef = CGBitmapContextCreateImage(ctx);
 
return [[NSImage alloc] initWithCGImage:imageRef size:size];
*imagePtr = [[NSImage alloc] initWithCGImage:imageRef size:size];
return rect;
}
 
#pragma mark - Letter Drawing
 
- (void)drawString:(NSString *)string
font:(NSFont *)font
size:(CGSize)size
baselineOffset:(CGFloat)baselineOffset
scale:(CGFloat)scale
useThinStrokes:(BOOL)useThinStrokes
context:(CGContextRef)ctx {
- (CGRect)drawStringUsingCoreText:(NSString *)string
font:(NSFont *)font
size:(CGSize)size
offset:(CGPoint)offset
baselineOffset:(CGFloat)baselineOffset
scale:(CGFloat)scale
useThinStrokes:(BOOL)useThinStrokes
context:(CGContextRef)cgContext {
// Fill the background with white.
CGContextSetRGBFillColor(ctx, 1, 1, 1, 1);
CGContextFillRect(ctx, CGRectMake(0, 0, size.width, size.height));
CGContextSetRGBFillColor(cgContext, 1, 1, 1, 1);
CGContextFillRect(cgContext, CGRectMake(0, 0, size.width, size.height));
 
DLog(@"Draw %@ of size %@", string, NSStringFromSize(size));
if (string.length == 0) {
return;
return CGRectZero;
}
CGGlyph glyphs[string.length];
const NSUInteger numCodes = string.length;
unichar characters[numCodes];
[string getCharacters:characters];
BOOL ok = CTFontGetGlyphsForCharacters((CTFontRef)font,
characters,
glyphs,
numCodes);
if (!ok) {
// TODO: fall back and use core text
// assert(NO);
return;
static NSMutableParagraphStyle *paragraphStyle;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineBreakMode = NSLineBreakByClipping;
paragraphStyle.tabStops = @[];
paragraphStyle.baseWritingDirection = NSWritingDirectionLeftToRight;
});
// TODO: Figure out how to support ligatures.
NSDictionary *attributes = @{ (NSString *)kCTLigatureAttributeName: @0,
(NSString *)kCTForegroundColorAttributeName: (id)[[NSColor blackColor] CGColor],
NSFontAttributeName: font,
NSParagraphStyleAttributeName: paragraphStyle };
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:attributes];
CTLineRef lineRef;
lineRef = CTLineCreateWithAttributedString((CFAttributedStringRef)attributedString);
CFArrayRef runs = CTLineGetGlyphRuns(lineRef);
CGContextSetShouldAntialias(cgContext, YES);
CGContextSetFillColorWithColor(cgContext, [[NSColor blackColor] CGColor]);
CGContextSetStrokeColorWithColor(cgContext, [[NSColor blackColor] CGColor]);
CGFloat c = 0.0;
#warning Suport fake bold and fake italic
const BOOL fakeItalic = NO;
if (fakeItalic) {
c = 0.2;
}
 
// TODO: fake italic, fake bold, optional anti-aliasing, thin strokes, faint
const BOOL antiAlias = YES;
CGContextSetShouldAntialias(ctx, antiAlias);
// This is how the subpixel model builder sets up subpixel AA. Maybe it shouldn't? But these
// two functions need to do the same thing.
CGContextSetAllowsFontSubpixelQuantization(ctx, YES);
CGContextSetShouldSubpixelQuantizeFonts(ctx, YES);
CGContextSetAllowsFontSubpixelPositioning(ctx, YES);
CGContextSetShouldSubpixelPositionFonts(ctx, YES);
CGContextSetShouldSmoothFonts(ctx, YES);
size_t length = numCodes;
// TODO: This is slow. Avoid doing it.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
CGContextSelectFont(ctx,
[[font fontName] UTF8String],
[font pointSize],
kCGEncodingMacRoman);
#pragma clang diagnostic pop
int savedFontSmoothingStyle = 0;
if (useThinStrokes) {
CGContextSetShouldSmoothFonts(ctx, YES);
CGContextSetShouldSmoothFonts(cgContext, YES);
// This seems to be available at least on 10.8 and later. The only reference to it is in
// WebKit. This causes text to render just a little lighter, which looks nicer.
savedFontSmoothingStyle = CGContextGetFontSmoothingStyle(ctx);
CGContextSetFontSmoothingStyle(ctx, 16);
CGContextSetFontSmoothingStyle(cgContext, 16);
}
 
// TODO: could use extended srgb on macOS 10.12+
CGContextSetFillColorSpace(ctx, CGColorSpaceCreateWithName(kCGColorSpaceSRGB));
const CGFloat components[4] = { 0.0, 0.0, 0.0, 1.0 };
CGContextSetFillColor(ctx, components);
double y = -baselineOffset * scale;
// Flip vertically and translate to (x, y).
CGContextSetTextMatrix(ctx, CGAffineTransformMake(scale, 0.0,
0, scale,
0, y));
CGPoint points[length];
for (int i = 0; i < length; i++) {
points[i].x = 0;
points[i].y = 0;
}
CGContextShowGlyphsAtPositions(ctx, glyphs, points, length);
if (useThinStrokes) {
CGContextSetFontSmoothingStyle(ctx, savedFontSmoothingStyle);
const CGFloat ty = offset.y - baselineOffset * scale;
CGAffineTransform textMatrix = CGAffineTransformMake(scale, 0.0,
c, scale,
offset.x, ty);
CGContextSetTextMatrix(cgContext, textMatrix);
for (CFIndex j = 0; j < CFArrayGetCount(runs); j++) {
CTRunRef run = CFArrayGetValueAtIndex(runs, j);
size_t length = CTRunGetGlyphCount(run);
const CGGlyph *buffer = CTRunGetGlyphsPtr(run);
if (!buffer) {
NSMutableData *tempBuffer =
[[NSMutableData alloc] initWithLength:sizeof(CGGlyph) * length];
CTRunGetGlyphs(run, CFRangeMake(0, length), (CGGlyph *)tempBuffer.mutableBytes);
buffer = tempBuffer.mutableBytes;
}
NSMutableData *positionsBuffer =
[[NSMutableData alloc] initWithLength:sizeof(CGPoint) * length];
CTRunGetPositions(run, CFRangeMake(0, length), (CGPoint *)positionsBuffer.mutableBytes);
CGPoint *positions = positionsBuffer.mutableBytes;
const CFIndex *glyphIndexToCharacterIndex = CTRunGetStringIndicesPtr(run);
if (!glyphIndexToCharacterIndex) {
NSMutableData *tempBuffer =
[[NSMutableData alloc] initWithLength:sizeof(CFIndex) * length];
CTRunGetStringIndices(run, CFRangeMake(0, length), (CFIndex *)tempBuffer.mutableBytes);
glyphIndexToCharacterIndex = (CFIndex *)tempBuffer.mutableBytes;
}
CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName);
CTFontDrawGlyphs(runFont, buffer, (NSPoint *)positions, length, cgContext);
}
CGRect frame = CTLineGetImageBounds(lineRef, cgContext);
frame.origin.y += baselineOffset;
frame.origin.x *= scale;
frame.origin.y *= scale;
frame.size.width *= scale;
frame.size.height *= scale;
// This is set to cut off subpixels that spill into neighbors as an optimization.
CGPoint min = CGPointMake(ceil(CGRectGetMinX(frame)),
ceil(CGRectGetMinY(frame)));
CGPoint max = CGPointMake(floor(CGRectGetMaxX(frame)),
floor(CGRectGetMaxY(frame)));
frame = CGRectMake(min.x, min.y, max.x - min.x, max.y - min.y);
CFRelease(lineRef);
return frame;
}
 
#pragma mark - Color
Loading
Loading
Loading
Loading
@@ -13,6 +13,7 @@ typedef struct {
BOOL image;
BOOL boxDrawing;
BOOL thinStrokes;
BOOL drawable; // If this is NO it will be ignored
} iTermMetalGlyphKey;
 
typedef struct {
Loading
Loading
//
// iTermTextureMap+CPP.h
// iTerm2
//
// Created by George Nachman on 11/7/17.
//
#include <map>
#include <vector>
#import "iTermTextureMap.h"
@interface iTermTextureMap (CPP)
- (void)unlockIndexes:(const std::vector<int> &)indexes;
@end
@interface iTermTextureMapStage (CPP)
- (NSInteger)findOrAllocateIndexOfLockedTextureWithKey:(const iTermMetalGlyphKey *)key
column:(int)column
relations:(std::map<int, int> *)relations
creation:(NSDictionary<NSNumber *, NSImage *> *(NS_NOESCAPE ^)(int x))creation;
@end
q⃝
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment