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

Add support for emoji. The fragment function now knows about which cells are recolorable.

parent 9ef7f90a
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -183,6 +183,11 @@ namespace std {
}
 
namespace iTerm2 {
struct TextureEntry {
int index;
BOOL emoji;
};
class TextureMapStage {
private:
// Maps stage indexes that need blitting to their destination indices.
Loading
Loading
@@ -223,19 +228,19 @@ namespace iTerm2 {
}
 
// Looks up the key in the LRU, promoting it only if it hasn't been used before this frame.
const int *lookup(const GlyphKey &key, cache::lru_cache<GlyphKey, int> *lru) {
const int *valuePtr;
const TextureEntry *lookup(const GlyphKey &key, cache::lru_cache<GlyphKey, TextureEntry> *lru) {
const TextureEntry *valuePtr;
if (_usedThisFrame.find(key) != _usedThisFrame.end()) {
// key was already used this frame. Don't promote it in the LRU.
valuePtr = lru->peek(key);
} else {
// first use of key this frame. Promote it and record its use.
const int *value = lru->get(key);
const TextureEntry *value = lru->get(key);
_usedThisFrame.insert(key);
valuePtr = value;
}
if (valuePtr) {
_lockedIndexes.push_back(*valuePtr);
_lockedIndexes.push_back(valuePtr->index);
}
return valuePtr;
}
Loading
Loading
@@ -249,7 +254,7 @@ namespace iTerm2 {
// c -> 2 1 c
 
// Maps a character description to its index in a texture sprite sheet.
cache::lru_cache<GlyphKey, int> _lru;
cache::lru_cache<GlyphKey, TextureEntry> _lru;
 
// Tracks which glyph key is at which index.
std::vector<GlyphKey *> _entries;
Loading
Loading
@@ -265,13 +270,15 @@ namespace iTerm2 {
public:
explicit TextureMap(const int capacity) : _lru(capacity), _locks(capacity), _capacity(capacity) { }
 
inline int get_index(const GlyphKey &key, TextureMapStage *textureMapStage, std::map<int, int> *related) {
const int *value;
value = textureMapStage->lookup(key, &_lru);
inline int get_index(const GlyphKey &key, TextureMapStage *textureMapStage, std::map<int, int> *related, BOOL *emoji) {
const TextureEntry *value = textureMapStage->lookup(key, &_lru);
if (value == nullptr) {
return -1;
} else {
const int index = *value;
const TextureEntry &entry = *value;
const int &index = entry.index;
*emoji = entry.emoji;
*related = _relatedIndexes[index];
if (related->size() == 1) {
_locks[index]++;
Loading
Loading
@@ -287,14 +294,15 @@ namespace iTerm2 {
}
}
 
inline std::pair<int, int> allocate_index(const GlyphKey &key, TextureMapStage *stage) {
inline std::pair<int, int> allocate_index(const GlyphKey &key, TextureMapStage *stage, const BOOL emoji) {
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 TextureEntry entry = { .index = index, .emoji = emoji };
_lru.put(key, entry);
 
const int stageIndex = stage->will_blit(index);
return std::make_pair(stageIndex, index);
Loading
Loading
@@ -322,7 +330,7 @@ namespace iTerm2 {
inline int produce() {
if (_lru.size() == _capacity) {
// Recycle the value of the least-recently used GlyphKey.
return _lru.get_lru().second;
return _lru.get_lru().second.index;
} else {
return _lru.size();
}
Loading
Loading
@@ -440,21 +448,22 @@ namespace iTerm2 {
textureMapStage:(iTerm2::TextureMapStage *)textureMapStage
stageArray:(iTermTextureArray *)stageArray
relations:(std::map<int, int> *)relations
creation:(NSDictionary<NSNumber *, NSImage *> *(NS_NOESCAPE ^)(int x))creation {
emoji:(BOOL *)emoji
creation:(NSDictionary<NSNumber *, NSImage *> *(NS_NOESCAPE ^)(int x, BOOL *emoji))creation {
const iTerm2::GlyphKey glyphKey(key, 4);
int index = _textureMap->get_index(glyphKey, textureMapStage, relations);
int index = _textureMap->get_index(glyphKey, textureMapStage, relations, emoji);
if (index >= 0) {
DLog(@"%@: locked existing texture %@", self.label, @(index));
return index;
} else {
NSDictionary<NSNumber *, NSImage *> *images = creation(column);
NSDictionary<NSNumber *, NSImage *> *images = creation(column, emoji);
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);
auto stageAndGlobalIndex = _textureMap->allocate_index(newGlyphKey, textureMapStage, *emoji);
if (result < 0) {
result = stageAndGlobalIndex.second;
}
Loading
Loading
@@ -526,12 +535,14 @@ namespace iTerm2 {
- (NSInteger)findOrAllocateIndexOfLockedTextureWithKey:(const iTermMetalGlyphKey *)key
column:(int)column
relations:(std::map<int, int> *)relations
creation:(NSDictionary<NSNumber *, NSImage *> *(NS_NOESCAPE ^)(int x))creation {
emoji:(BOOL *)emoji
creation:(NSDictionary<NSNumber *, NSImage *> *(NS_NOESCAPE ^)(int x, BOOL *emoji))creation {
return [_textureMap findOrAllocateIndexOfLockedTextureWithKey:key
column:column
textureMapStage:_textureMapStage
stageArray:_stageArray
relations:relations
emoji:emoji
creation:creation];
}
 
Loading
Loading
Loading
Loading
@@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
attributesData:(NSData *)attributesData
row:(int)row
backgroundColorData:(NSData *)backgroundColorData // array of vector_float4 background colors.
creation:(NSDictionary<NSNumber *, NSImage *> *(NS_NOESCAPE ^)(int x))creation;
creation:(NSDictionary<NSNumber *, NSImage *> *(NS_NOESCAPE ^)(int x, BOOL *emoji))creation;
- (void)willDrawWithDefaultBackgroundColor:(vector_float4)defaultBackgroundColor;
- (void)didComplete;
 
Loading
Loading
Loading
Loading
@@ -85,7 +85,7 @@ typedef struct {
attributesData:(NSData *)attributesData
row:(int)row
backgroundColorData:(nonnull NSData *)backgroundColorData
creation:(NSDictionary<NSNumber *, NSImage *> *(NS_NOESCAPE ^)(int x))creation {
creation:(NSDictionary<NSNumber *, NSImage *> *(NS_NOESCAPE ^)(int x, BOOL *emoji))creation {
assert(row == _backgroundColorDataArray.count);
[_backgroundColorDataArray addObject:backgroundColorData];
const int width = self.cellConfiguration.gridSize.width;
Loading
Loading
@@ -102,6 +102,7 @@ typedef struct {
 
NSInteger lastIndex = 0;
std::map<int, int> lastRelations;
BOOL lastEmoji = NO;
for (int x = 0; x < count; x++) {
if (!glyphKeys[x].drawable) {
continue;
Loading
Loading
@@ -109,9 +110,11 @@ typedef struct {
std::map<int, int> relations;
NSInteger index;
BOOL retained;
BOOL emoji;
if (x > 0 && !memcmp(&glyphKeys[x], &glyphKeys[x-1], sizeof(*glyphKeys))) {
index = lastIndex;
relations = lastRelations;
emoji = lastEmoji;
// 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;
Loading
Loading
@@ -119,6 +122,7 @@ typedef struct {
index = [_stage findOrAllocateIndexOfLockedTextureWithKey:&glyphKeys[x]
column:x
relations:&relations
emoji:&emoji
creation:creation];
retained = YES;
}
Loading
Loading
@@ -134,6 +138,7 @@ typedef struct {
MTLOrigin origin = [array offsetForIndex:index];
piu->textureOffset = (vector_float2){ origin.x * w, origin.y * h };
piu->textColor = attributes[x].foregroundColor;
piu->remapColors = !emoji;
if (part == 4) {
piu->backgroundColor = attributes[x].backgroundColor;
} else {
Loading
Loading
@@ -154,10 +159,12 @@ typedef struct {
piu->textureOffset = (vector_float2){ origin.x * w, origin.y * h };
piu->textColor = attributes[x].foregroundColor;
piu->backgroundColor = attributes[x].backgroundColor;
piu->remapColors = !emoji;
[self addIndex:index retained:retained];
}
lastIndex = index;
lastRelations = relations;
lastEmoji = emoji;
}
}
 
Loading
Loading
Loading
Loading
@@ -40,6 +40,9 @@ typedef struct {
// Values in 0-1. These will be composited over what's already rendered.
vector_float4 backgroundColor;
vector_float4 textColor;
// This is true for text and false for emoji.
bool remapColors;
} iTermTextPIU;
 
typedef struct {
Loading
Loading
Loading
Loading
@@ -11,6 +11,7 @@ typedef struct {
float2 backgroundTextureCoordinate;
float4 textColor;
float4 backgroundColor;
bool recolor;
} iTermTextVertexFunctionOutput;
 
vertex iTermTextVertexFunctionOutput
Loading
Loading
@@ -34,6 +35,7 @@ iTermTextVertexShader(uint vertexID [[ vertex_id ]],
out.textureCoordinate = vertexArray[vertexID].textureCoordinate + perInstanceUniforms[iid].textureOffset;
out.textColor = perInstanceUniforms[iid].textColor;
out.backgroundColor = perInstanceUniforms[iid].backgroundColor;
out.recolor = perInstanceUniforms[iid].remapColors;
 
return out;
}
Loading
Loading
@@ -47,6 +49,9 @@ iTermTextFragmentShaderSolidBackground(iTermTextVertexFunctionOutput in [[stage_
 
const half4 bwColor = texture.sample(textureSampler, in.textureCoordinate);
 
if (!in.recolor) {
return static_cast<float4>(bwColor);
}
if (bwColor.x == 1 && bwColor.y == 1 && bwColor.z == 1) {
discard_fragment();
}
Loading
Loading
@@ -160,6 +165,9 @@ iTermTextFragmentShaderWithBlending(iTermTextVertexFunctionOutput in [[stage_in]
 
const half4 bwColor = texture.sample(textureSampler, in.textureCoordinate);
 
if (!in.recolor) {
return static_cast<float4>(bwColor);
}
if (bwColor.x == 1 && bwColor.y == 1 && bwColor.z == 1) {
discard_fragment();
}
Loading
Loading
Loading
Loading
@@ -39,7 +39,8 @@ NS_ASSUME_NONNULL_BEGIN
 
- (NSDictionary<NSNumber *, NSImage *> *)metalImagesForGlyphKey:(iTermMetalGlyphKey *)glyphKey
size:(CGSize)size
scale:(CGFloat)scale;
scale:(CGFloat)scale
emoji:(BOOL *)emoji;
 
// 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
@@ -322,10 +322,11 @@ static const NSInteger iTermMetalDriverMaximumNumberOfFramesInFlight = 3;
attributesData:rowData.attributesData
row:rowData.y
backgroundColorData:rowData.backgroundColorData
creation:^NSDictionary<NSNumber *,NSImage *> * _Nonnull(int x) {
creation:^NSDictionary<NSNumber *,NSImage *> * _Nonnull(int x, BOOL *emoji) {
return [frameData.perFrameState metalImagesForGlyphKey:&glyphKeys[x]
size:cellSize
scale:scale];
scale:scale
emoji:emoji];
}];
[backgroundState setColorData:rowData.backgroundColorData
row:rowData.y
Loading
Loading
Loading
Loading
@@ -265,6 +265,8 @@ int decode_utf8_char(const unsigned char * restrict datap,
- (NSRect)it_boundingRectWithSize:(NSSize)bounds attributes:(NSDictionary *)attributes truncated:(BOOL *)truncated;
- (void)it_drawInRect:(CGRect)rect attributes:(NSDictionary *)attributes;
 
- (BOOL)startsWithEmoji;
@end
 
@interface NSMutableString (iTerm)
Loading
Loading
Loading
Loading
@@ -1818,6 +1818,25 @@ static TECObjectRef CreateTECConverterForUTF8Variants(TextEncodingVariant varian
CGContextRestoreGState(ctx);
}
 
- (BOOL)startsWithEmoji {
static dispatch_once_t onceToken;
static NSMutableCharacterSet *emojiSet;
dispatch_once(&onceToken, ^{
emojiSet = [[NSMutableCharacterSet alloc] init];
void (^addRange)(NSUInteger, NSUInteger) = ^(NSUInteger first, NSUInteger last){
[emojiSet addCharactersInRange:NSMakeRange(first, last - first + 1)];
};
addRange(0x1F600, 0x1F64F); // Emoticons
addRange(0x1F300, 0x1F5FF); // Misc Symbols and Pictographs
addRange(0x1F680, 0x1F6FF); // Transport and Map
addRange(0x2600, 0x26FF); // Misc symbols
addRange(0x2700, 0x27BF); // Dingbats
addRange(0xFE00, 0xFE0F); // Variation Selectors
addRange(0x1F900, 0x1F9FF); // Supplemental Symbols and Pictographs
});
return [emojiSet longCharacterIsMember:[self firstCharacter]];
}
@end
 
@implementation NSMutableString (iTerm)
Loading
Loading
Loading
Loading
@@ -15,6 +15,7 @@
#import "iTermTextDrawingHelper.h"
#import "NSColor+iTerm.h"
#import "NSImage+iTerm.h"
#import "NSStringITerm.h"
#import "PTYFontInfo.h"
#import "PTYTextView.h"
#import "VT100Screen.h"
Loading
Loading
@@ -610,7 +611,8 @@ static BOOL iTermTextDrawingHelperIsCharacterDrawable(screen_char_t *c,
 
- (NSDictionary<NSNumber *,NSImage *> *)metalImagesForGlyphKey:(iTermMetalGlyphKey *)glyphKey
size:(CGSize)size
scale:(CGFloat)scale {
scale:(CGFloat)scale
emoji:(nonnull BOOL *)emoji {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
 
BOOL fakeBold = NO;
Loading
Loading
@@ -635,8 +637,9 @@ static BOOL iTermTextDrawingHelperIsCharacterDrawable(screen_char_t *c,
baselineOffset:fontInfo.baselineOffset
scale:scale
useThinStrokes:glyphKey->thinStrokes
colorSpace:colorSpace
image:&image];
colorSpace:colorSpace
image:&image
emoji:emoji];
CGColorSpaceRelease(colorSpace);
 
if (image == nil) {
Loading
Loading
@@ -669,7 +672,8 @@ static BOOL iTermTextDrawingHelperIsCharacterDrawable(screen_char_t *c,
scale:scale
useThinStrokes:glyphKey->thinStrokes
colorSpace:colorSpace
image:&image];
image:&image
emoji:emoji];
if (image) {
result[@(i)] = image;
}
Loading
Loading
@@ -688,7 +692,8 @@ static BOOL iTermTextDrawingHelperIsCharacterDrawable(screen_char_t *c,
scale:(CGFloat)scale
useThinStrokes:(BOOL)useThinStrokes
colorSpace:(CGColorSpaceRef)colorSpace
image:(NSImage **)imagePtr {
image:(NSImage **)imagePtr
emoji:(BOOL *)emoji {
CGContextRef ctx = CGBitmapContextCreate(NULL,
size.width,
size.height,
Loading
Loading
@@ -704,7 +709,8 @@ static BOOL iTermTextDrawingHelperIsCharacterDrawable(screen_char_t *c,
baselineOffset:baselineOffset
scale:scale
useThinStrokes:glyphKey->thinStrokes
context:ctx];
context:ctx
emoji:emoji];
 
CGImageRef imageRef = CGBitmapContextCreateImage(ctx);
 
Loading
Loading
@@ -722,16 +728,36 @@ static BOOL iTermTextDrawingHelperIsCharacterDrawable(screen_char_t *c,
baselineOffset:(CGFloat)baselineOffset
scale:(CGFloat)scale
useThinStrokes:(BOOL)useThinStrokes
context:(CGContextRef)cgContext {
// Fill the background with white.
CGContextSetRGBFillColor(cgContext, 1, 1, 1, 1);
CGContextFillRect(cgContext, CGRectMake(0, 0, size.width, size.height));
context:(CGContextRef)cgContext
emoji:(BOOL *)emoji {
DLog(@"Draw %@ of size %@", string, NSStringFromSize(size));
if (string.length == 0) {
if (emoji) {
*emoji = NO;
}
CGContextSetRGBFillColor(cgContext, 1, 1, 1, 1);
CGContextFillRect(cgContext, CGRectMake(0, 0, size.width, size.height));
return CGRectZero;
}
 
BOOL fillBackground = YES;
if (emoji) {
*emoji = [string startsWithEmoji];
if (*emoji) {
fillBackground = NO;
CGContextSetRGBFillColor(cgContext, 1, 1, 1, 0);
CGContextFillRect(cgContext, CGRectMake(0, 0, size.width, size.height));
}
}
if (fillBackground) {
// Fill the background with white. This is necessary for subpixel antialiasing. The fragment
// function also discards all fragments that are all-white when remapping colors. We don't
// do this for emoji so we can preserve the alpha values (so it will blend with background
// images).
CGContextSetRGBFillColor(cgContext, 1, 1, 1, 1);
CGContextFillRect(cgContext, CGRectMake(0, 0, size.width, size.height));
}
static NSMutableParagraphStyle *paragraphStyle;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Loading
Loading
@@ -770,10 +796,13 @@ static BOOL iTermTextDrawingHelperIsCharacterDrawable(screen_char_t *c,
}
 
const CGFloat ty = offset.y - baselineOffset * scale;
CGAffineTransform textMatrix = CGAffineTransformMake(scale, 0.0,
c, scale,
offset.x, ty);
CGContextSetTextMatrix(cgContext, textMatrix);
if (!emoji || !*emoji) {
// Can't use this with emoji.
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);
Loading
Loading
@@ -800,7 +829,20 @@ static BOOL iTermTextDrawingHelperIsCharacterDrawable(screen_char_t *c,
}
 
CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName);
CTFontDrawGlyphs(runFont, buffer, (NSPoint *)positions, length, cgContext);
if (emoji && *emoji) {
// You have to use the CTM with emoji. CGContextSetTextMatrix doesn't work.
CGContextSaveGState(cgContext);
CGContextConcatCTM(cgContext, CTFontGetMatrix(runFont));
CGContextTranslateCTM(cgContext, offset.x, ty);
CGContextScaleCTM(cgContext, scale, scale);
CTFontDrawGlyphs(runFont, buffer, (NSPoint *)positions, length, cgContext);
CGContextRestoreGState(cgContext);
} else {
CTFontDrawGlyphs(runFont, buffer, (NSPoint *)positions, length, cgContext);
}
}
 
CGRect frame = CTLineGetImageBounds(lineRef, cgContext);
Loading
Loading
Loading
Loading
@@ -19,6 +19,7 @@
- (NSInteger)findOrAllocateIndexOfLockedTextureWithKey:(const iTermMetalGlyphKey *)key
column:(int)column
relations:(std::map<int, int> *)relations
creation:(NSDictionary<NSNumber *, NSImage *> *(NS_NOESCAPE ^)(int x))creation;
emoji:(BOOL *)emoji
creation:(NSDictionary<NSNumber *, NSImage *> *(NS_NOESCAPE ^)(int x, BOOL *emoji))creation;
 
@end
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