CGColor internals
Asked Answered
M

1

6

I hope to understand internals of CoreFoundation CGColor object with this research. I could find a sample definition of CGColor structure from free quartz project which seems to match the IOS declaration(relying on my researchs).

typedef struct CGColor {
        CFRuntimeBase obj;

        CFTypeID colorID;
        CGColorSpaceRef colorSpace;
        CGPatternRef pattern;
        size_t numberOfComponents;
        CGFloat *components;
} *CGColorRef;

(colorID field is named as nextID by free quartz but i think its intended as a unique identifier for the color by IOS so its not a kind of next identifier.)

A globally thread safe unique value is hold which is incremented by 1 for each CGColor object created and assigned to the colorID member. Only the undocumented CGColorGetIdentifier() function returns this value. (I have a guess about monotonically increasing id value, it may improve performance while translating between device to calibrated color lookups or vice versa.)

I have checked CoreGraphics and its resource libraries. I have found that only ripc_GetColor (libRIP.A.dylib) function calls the CGColorGetIdentifier() function.

Call stack for CGColorGetIdentifier;(with hope of helping to make inferences about colorID)

0   com.apple.CoreGraphics CGColorGetIdentifier + 0
1   libRIP.A.dylib          ripc_GetColor + 112
2   libRIP.A.dylib          ripc_DrawGlyphs + 1740
3   com.apple.CoreGraphics  CGContextDelegateDrawGlyphs + 108
4   com.apple.CoreGraphics  drawGlyphs + 284
5   com.apple.CoreGraphics  CGContextShowGlyphsWithAdvances + 208

For the current color graphics context operation, ripc_GetColor() computes some transformations for the current stroke/fill color and it caches these transformations with the reference and colorID of this color.

So, for the next graphics context operation, ripc_GetColor() compares the previously cached and current reference and colorID values to skip color transformations which were already cached for the last graphics context operation.

We know that the reference(memory address) of a released object could be used while creating another object. So just checking the reference will not be enough that same color object is valid but we need to compare contents or some kind of hash value. So, we could use the unique identifier values for this purpose.

However, an identifier could be in use for a single object and its reference, so it is enough to compare only ids. But, both refs and ids are used. I don't think that engineers overlooked such a simple and crucial thing.

So, i try to find out the necessity of comparing both ids and refs while comparing just ids would be enough.

Is it left over from a previous approach so could not be abandoned totally?

Mesmerism answered 26/10, 2013 at 14:31 Comment(9)
Have you looked into CFTypeID? developer.apple.com/library/mac/documentation/corefoundation/…Hafnium
sorry, this is unrelated to CFTypeID.Mesmerism
You are plumbing implementation details. Academically interesting, but not generally useful in production code unless you've found a specific behavior whose performance is so bad as to be blocking you.Biofeedback
@Biofeedback what exactly is the point of your comment?Confined
@BrennanVincent. Creating code that relies on implementation details is subject to breakage.Biofeedback
@Biofeedback I think everyone already knows that. Maybe he was just intellectually curious about how things work?Confined
@BrennanVincent given the number of bugs I've fixed over the years where developers shipped code that depends on internal, reverse engineered, implementation details, it warrants restating. Repeatedly. But, yes, I did say academically interesting.Biofeedback
@Biofeedback Academic curiosity aside: on Apple systems, which don't have a great reputation for well-documented and complete APIs, it's often necessary to depend on internal reverse-engineered implementation details, since nothing else works. Even more often, it's necessary to understand internal implementation details to even figure out how something is supposed to work, since the documentation is so bad. Thus it's often good to know about them.Confined
Understand? Sure. Rely on in terms of reverse engineering and building your code to use the internal implementation? No. In my decades of experience with these APIs, the worst code to maintain has been the code that has done exactly that.Biofeedback
D
1

If I understand correctly, you're asking why someone might implement a cache as

void DoSomethingWith(CGColorRef c)
{
    static CGColorRef cached_c = NULL;
    static CFTypeID cached_colorID;

    if (c == cached_c && c->colorID == cached_colorID) ...

instead of merely

void DoSomethingWith(CGColorRef c)
{
    static CFTypeID cached_colorID = 0;

    if (c->colorID == cached_colorID) ...

? Well, two obvious reasons are

  • Random access memory isn't random access. Dereferencing c is probably a slow operation (a cache miss wastes many nanoseconds), so if we can save those nanoseconds 90% of the time by prepending a cheap pointer comparison, let's do it.

  • How do you initialize cached_colorID? In the first implementation above, if we assume that the user respects the API contract and always passes in a non-null c, then once we know c == cached_c, then we also know that cached_c != NULL and therefore we have a meaningful value in cached_colorID. In the second implementation, if the first c the user passes in happens to have c->colorID == 0, then we'll incorrectly believe that we've seen it before, and madcap hijinks ensue.

I don't know if either of these is the reason Apple did what you're looking at... but they seem like solid possibilities, don't they?

Dipteran answered 3/11, 2013 at 0:12 Comment(1)
* First the second reason, it is unrelated because c->colorID it starts from 1. * And about first reason, it is fairly sensible. When both refs and ids are equal, there will be an extra comparison in first implementation but i assume a comparison is far less cheaper than a memory read so its negligible!..(am i right?) When refs are not equal, in first implementation there will be only one comparison and memory will not be read. However, i don't know if it's worth to optimize out a single memory read while there will be a bunch of memory reads by transformation calculations.Mesmerism

© 2022 - 2024 — McMap. All rights reserved.