Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
[platform-swt-dev] Image caching on Mac OS X Mountain Lion

Hello all,

I am having an unfortunate problem with my SWT based application on OS X Mountain Lion.

I have written a native lib that attaches to the buffer of an SWT Image, and renders/generates the image contents. One of the reasons I am doing this, is because the image contents are frequently updated. (The other reason is I have very specific needs on text rendering which are not satisfied by SWT.)

I create the SWT Image with the ImageData constructor:

  PaletteData palette = new PaletteData(0xff000000, 0x00ff0000, 0x0000ff00);
			
  ImageData imageData = new ImageData(
      bounds.width, bounds.height, 32, palette);

  fImage = new Image(getDisplay(), imageData);

On SWT-Mac, this creates an NSImage with caching disabled, if I read the code right:

  ...
  handle = (NSImage)new NSImage().alloc();
  NSSize size = new NSSize();
  size.width = width;
  size.height = height;
  handle = handle.initWithSize(size);
  NSBitmapImageRep rep = (NSBitmapImageRep)new NSBitmapImageRep().alloc();
  rep = rep.initWithBitmapDataPlanes(0, width, height, 8, hasAlpha ? 4 : 3,
      hasAlpha, false, OS.NSDeviceRGBColorSpace,
      OS.NSAlphaFirstBitmapFormat | OS.NSAlphaNonpremultipliedBitmapFormat, bpr, 32);
  OS.memmove(rep.bitmapData(), buffer, dataSize);	
  handle.addRepresentation(rep);
  rep.release();
  handle.setCacheMode(OS.NSImageCacheNever);

The last call is the interesting one.

But regardless, the problem I seem to be observing is that caching is interfering with what I am doing. I.e. the initial contents that I render into the Image buffer show up on screen, and any updates don't. As if the OS had copied the buffer somewhere else and doesn't become aware of the changed contents of the original buffer. This is only a problem starting in Mountain Lion. My code works on earlier OS versions.

I am also aware that an NSImage may have multiple buffers associated with it. For example, to support a Retina display, there are supposed to be two buffers which the OS can pick depending on where the image is displayed. My code would currently not support this, but I am not declaring support for high-resolution in my app bundle, and I confirmed this is not the problem.

My fix is to always create a new SWT Image before I generate the updated content. This degrades performance notably, and I would love to skip this part, like I am doing in Windows and Linux.

Below, you can find my code how I attach to the bitmap buffer. It's obfuscated by handling multiple SWT platforms via Reflection, hopefully you can find your way. I am also thankful for any errors you can point out in this snippet.

Any solution to my problem, though? Please do not suggest I don't need to hack around in the SWT guts like I am doing, I really need to, there is no other way.

Thanks a best regards,
-Stephan


---

Class<? extends Image> c = image.getClass();

String platform = SWT.getPlatform();
try {
  if (platform.equals("cocoa")) {
      Class<?> nsImageClass = Class.forName(
          "org.eclipse.swt.internal.cocoa.NSImage");
      Class<?> nsDictionaryClass = Class.forName(
          "org.eclipse.swt.internal.cocoa.NSDictionary");
      Class<?> nsBitmapImageRepClass = Class.forName(
          "org.eclipse.swt.internal.cocoa.NSBitmapImageRep");
      Class<?> idClass = Class.forName(
          "org.eclipse.swt.internal.cocoa.id");
      Object nsImage = c.getDeclaredField("handle").get(image);

      Method bestRepresentationForDeviceMethod
          = nsImageClass.getDeclaredMethod(
              "bestRepresentationForDevice", nsDictionaryClass);
      Object nsImageRepObject
          = bestRepresentationForDeviceMethod.invoke(
              nsImage, new Object[] { null });

      Constructor<?>[] constructors
          = nsBitmapImageRepClass.getConstructors();

      Object nsBitmapImageRepObject = null;

      // Find the constructor that takes an NSImageRep object id
      // and construct the NSBitmapImageRep object.
      for (int i = 0; i < constructors.length; i++) {
          Class<?>[] parameterTypes
              = constructors[i].getParameterTypes();
          if (parameterTypes.length == 1
              && parameterTypes[0] == idClass) {
              nsBitmapImageRepObject = constructors[i].newInstance(
                  nsImageRepObject);
              break;
          }
      }
      if (nsBitmapImageRepObject == null)
          return;

      Method bitmapDataMethod
          = nsBitmapImageRepClass.getDeclaredMethod("bitmapData");
      Method bytesPerRowMethod
          = nsBitmapImageRepClass.getDeclaredMethod("bytesPerRow");

      long data;
      long bpr;

      try {
          // Try 64-Bit SWT version first
          data = (Long) bitmapDataMethod.invoke(
              nsBitmapImageRepObject, (Object[]) null);
          bpr = (Long) bytesPerRowMethod.invoke(
              nsBitmapImageRepObject, (Object[]) null);
      } catch (ClassCastException e) {
          // Retry assuming 32-Bit SWT version
          data = (Integer) bitmapDataMethod.invoke(
              nsBitmapImageRepObject, (Object[]) null);
          bpr = (Integer) bytesPerRowMethod.invoke(
              nsBitmapImageRepObject, (Object[]) null);
      }

      Rectangle bounds = image.getBounds();

      attachToMemory(fAggContext, data, bounds.width, bounds.height, bpr);

  } else if (platform.equals("gtk")) {
      try {
          // 32-Bit SWT
          int surface = c.getDeclaredField("surface").getInt(image);
          if (surface == 0)
              return;
          attachToCairoSurface(fAggContext, surface);
      } catch (NoSuchFieldException e) {
          // 64-Bit SWT
          long surface = c.getDeclaredField("surface").getLong(image);
          if (surface == 0)
              return;
          attachToCairoSurface(fAggContext, surface);
      }
  } else if (platform.equals("win32")) {
      try {
          // 32-Bit SWT
          int handle = c.getDeclaredField("handle").getInt(image);
          if (handle == 0)
              return;
          attachToBitmapHandle(fAggContext, handle);
      } catch (NoSuchFieldException e) {
          // 64-Bit SWT
          long handle = c.getDeclaredField("handle").getLong(image);
          if (handle == 0)
              return;
          attachToBitmapHandle(fAggContext, handle);
      }
  }

} catch (IllegalArgumentException e) {
  e.printStackTrace();
} catch (SecurityException e) {
  e.printStackTrace();
} catch (IllegalAccessException e) {
  e.printStackTrace();
} catch (NoSuchFieldException e) {
  e.printStackTrace();
} catch (ClassNotFoundException e) {
  e.printStackTrace();
} catch (NoSuchMethodException e) {
  e.printStackTrace();
} catch (InvocationTargetException e) {
  e.printStackTrace();
} catch (InstantiationException e) {
  e.printStackTrace();
}

Back to the top