With IMGUI and Retina setting API?

In Koreographer we render our own Waveforms using GL Lines. This has worked great up through Unity 5.3. In the current Unity 5.4b11 we’re seeing those lines drawing at single pixel widths. This is great except the IMGUI width for the “container” is the same as non-retina displays. This means that when we iterate between “pixels” we’re actually jumping by two. We need to come up with a method to work with this system but step one is figuring out when we’re in Normal versus HighDPI mode.

What APIs exist to help us identify whether we’re in a HighDPI/Retina context and what the scale is so that we may handle these situations?

Hi Eric,

We were facing the same problem when rendering the preview curves for audio clips. What we did was to use EditorGUIUtility.pixelsPerPoint to get the number of pixels between points on a grid that is equivalent between non-Retina and Retina screens, thus the reciprocal value of this gives you the pixel size. Beware of numerical errors when calculating the x-positions! I’ve posted a simplified version of the code we use for the min/max preview curves below. As you can see we add a little epsilon value to the x-coordinate to prevent falling from one pixel to the previous one. You might also be able to get around this by using doubles.

The easiest thing though is probably to just call AudioCurveRendering.DrawMinMaxFilledCurve from your code (which will also do color gradients on the edges). See the custom GUIs in the native audio plugins SDK for other use cases of the AudioCurveRendering functions.

Jan

// This slight adjustment is needed to make sure that numerical imprecision doesn't suddenly snap to the wrong pixel (causing vertical gaps and overdrawn lines)
private static float pixelEpsilon = 0.005f;

public static void DrawMinMaxFilledCurve (Rect r, AudioMinMaxCurveAndColorEvaluator eval)
{
    float pixelScale = (float)EditorGUIUtility.pixelsPerPoint;
    float pixelSize = 1.0f / pixelScale;
    float pixelWidth = Mathf.Ceil (r.width) * pixelScale;
    float startx = Mathf.Floor (r.x) + pixelEpsilon;
    float wx = 1.0f / (float)(pixelWidth - 1);
    float minValue, maxValue;
    float ys = r.height * 0.5f;
    float cy = r.y + ys;
    for (int x = 0; x < pixelWidth; x++)
    {
        float nx = startx + x * pixelSize;
        Color color;
        eval (x * wx, out color, out minValue, out maxValue);
        DrawLine (nx, cy - minValue * ys, cy - maxValue * ys, color);
    }
}

Hi @janm_unity3d !

Thanks for the response. This is very useful stuff. Since posting this question another Unity dev pointed me at the EditorGUIUtility.pixelsPerPoint API. I’d meant to pop back to this forum to post about it but I haven’t yet had a chance to do the work to verify how to use it. Your example is very helpful!

That’s an interesting point about the epsilon offset. Will have to see if and how we’re affected but will keep this in mind.

And also, why don’t you just do “x += pixelSize” in your for-loop, thereby removing an unnecessary “pixelWidth” number of multiplications?

Easy? Sure! Fast? Hmm…

Our waveform rendering code is specifically optimized for cache coherency. There’s a lot of stack pushing/popping that goes on with your inner loops that we try to avoid on our end. This way we also get to control the point cache ourselves, including how it gets calculated: as a quick test I did a multi-threaded approach to waveform generation and the UI was buttery smooth but the lack of waveform preview during recalculation was too jarring. We may revisit this in the future in some form, however :slight_smile: Beyond that we also provide both MinMax and RMS curves as they are good at showing different kinds of information.

This is a great resource! Thanks for pointing it out! :smile: