Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ios-packaging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
- '.github/workflows/ios-packaging.yml'
- 'maven/codenameone-maven-plugin/**'
- 'vm/ByteCodeTranslator/**'
- 'Ports/iOSPort/**'
- 'scripts/build-ios-app.sh'
- 'scripts/run-ios-ui-tests.sh'
- 'scripts/run-ios-native-tests.sh'
Expand All @@ -18,6 +19,7 @@ on:
- '.github/workflows/ios-packaging.yml'
- 'maven/codenameone-maven-plugin/**'
- 'vm/ByteCodeTranslator/**'
- 'Ports/iOSPort/**'
- 'scripts/build-ios-app.sh'
- 'scripts/run-ios-ui-tests.sh'
- 'scripts/run-ios-native-tests.sh'
Expand Down Expand Up @@ -68,7 +70,9 @@ jobs:
id: setup_hash
run: |
set -euo pipefail
echo "hash=$(shasum -a 256 scripts/setup-workspace.sh | awk '{print $1}')" >> "$GITHUB_OUTPUT"
SETUP_HASH=$(shasum -a 256 scripts/setup-workspace.sh | awk '{print $1}')
IOS_PORT_HASH=$(find Ports/iOSPort/src -type f -name '*.java' | sort | xargs shasum -a 256 | shasum -a 256 | awk '{print $1}')
echo "hash=${SETUP_HASH}-${IOS_PORT_HASH}" >> "$GITHUB_OUTPUT"

- name: Set TMPDIR
run: echo "TMPDIR=${{ runner.temp }}" >> $GITHUB_ENV
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
import com.codename1.ui.util.ImageIO;
import com.codename1.util.AsyncResource;
import com.codename1.util.FailureCallback;
import com.codename1.util.Simd;
import com.codename1.util.StringUtil;
import com.codename1.util.SuccessCallback;

Expand Down Expand Up @@ -8397,6 +8398,12 @@ public ImageIO getImageIO() {
return null;
}

/// Creates the SIMD implementation for this platform.
/// Ports may override this to provide accelerated SIMD behavior.
public Simd createSimd() {
return new Simd();
}

/// Workaround for XMLVM bug
public boolean instanceofObjArray(Object o) {
return o instanceof Object[];
Expand Down
6 changes: 6 additions & 0 deletions CodenameOne/src/com/codename1/ui/CN.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import com.codename1.ui.events.WindowEvent;
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.geom.Rectangle;
import com.codename1.util.Simd;
import com.codename1.util.RunnableWithResultSync;

import java.io.IOException;
Expand Down Expand Up @@ -1032,6 +1033,11 @@ public static String getPlatformName() {
return Display.impl.getPlatformName();
}

/// Returns the SIMD API for the current platform.
public static Simd getSimd() {
return Display.getInstance().getSimd();
}


/// Opens the device Dialer application with the given phone number
///
Expand Down
15 changes: 15 additions & 0 deletions CodenameOne/src/com/codename1/ui/Display.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import com.codename1.ui.util.EventDispatcher;
import com.codename1.ui.util.ImageIO;
import com.codename1.util.AsyncResource;
import com.codename1.util.Simd;
import com.codename1.util.RunnableWithResultSync;
import com.codename1.util.SuccessCallback;

Expand Down Expand Up @@ -216,6 +217,7 @@ public final class Display extends CN1Constants {
long time;
private int transitionDelay = -1;
private String selectedVirtualKeyboard = null;
private Simd simd;
private CrashReport crashReporter;
private EventDispatcher errorHandler;
private boolean inNativeUI;
Expand Down Expand Up @@ -343,6 +345,7 @@ public static void init(Object m) {
commandBehaviour = impl.getCommandBehavior();
}
impl = (CodenameOneImplementation) ImplementationFactory.getInstance().createImplementation();
INSTANCE.simd = null;

impl.setDisplayLock(lock);
impl.initImpl(m);
Expand Down Expand Up @@ -493,6 +496,18 @@ CodenameOneImplementation getImplementation() {
return impl;
}

/// Returns the SIMD API instance bound to the current implementation.
public Simd getSimd() {
if (simd == null) {
Simd created = impl.createSimd();
if (created == null) {
created = new Simd();
}
simd = created;
}
return simd;
}

/// Indicates the maximum frames the API will try to draw every second
/// by default this is set to 10. The advantage of limiting
/// framerate is to allow the CPU to perform other tasks besides drawing.
Expand Down
164 changes: 138 additions & 26 deletions CodenameOne/src/com/codename1/ui/Image.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.util.EventDispatcher;
import com.codename1.ui.util.ImageIO;
import com.codename1.util.Simd;

import java.io.IOException;
import java.io.InputStream;
Expand All @@ -44,6 +45,7 @@
///
/// @author Chen Fishbein
public class Image implements ActionSource {
private static boolean simdOptimizationsEnabled = Simd.get().isSupported();
int transform;
private EventDispatcher listeners;
private Object rgbCache;
Expand All @@ -57,6 +59,23 @@ public class Image implements ActionSource {
private byte[] svgData;
private String imageName;

/// Indicates whether Image SIMD optimizations are enabled. When unset this defaults
/// to the current platform SIMD support.
public static boolean isSimdOptimizationsEnabled() {
return simdOptimizationsEnabled;
}

/// Enables or disables Image SIMD optimizations explicitly.
public static void setSimdOptimizationsEnabled(boolean enabled) {
simdOptimizationsEnabled = enabled;
}

/// Clears the explicit Image SIMD override and restores the default behavior of
/// using SIMD whenever it is supported by the current platform.
public static void resetSimdOptimizationsEnabled() {
simdOptimizationsEnabled = Simd.get().isSupported();
}

/// Subclasses may use this and point to an underlying native image which might be
/// null for a case of an image that doesn't use native drawing
///
Expand All @@ -73,6 +92,14 @@ protected Image(Object image) {
this(Display.impl.createImage(imageArray, w, h));
}

/// Creates an image while preserving the supplied RGB array in the weak RGB cache
/// so follow-up SIMD image operations can reuse it without extracting pixels again.
private static Image createImageWithRgbCache(int[] imageArray, int w, int h) {
Image out = new Image(imageArray, w, h);
out.rgbCache = Display.getInstance().createSoftWeakRef(imageArray);
return out;
}

/// Indicates whether the underlying platform supports creating an SVG Image
///
/// #### Returns
Expand Down Expand Up @@ -1053,9 +1080,20 @@ public boolean isSVG() {
public Object createMask() {
int[] rgb = getRGBCached();
int rlen = rgb.length;
byte[] mask = new byte[rlen];
for (int iter = 0; iter < rlen; iter++) {
mask[iter] = (byte) (rgb[iter] & 0xff);
byte[] mask;
if (isSimdOptimizationsEnabled() && rlen >= 16) {
Simd simd = Simd.get();
mask = simd.allocByte(rlen);
// Single-pass: copy rgb into a registered scratch once and pack the
// whole array in one native call to amortize dispatch/validation cost.
int[] scratch = simd.allocaInt(rlen);
System.arraycopy(rgb, 0, scratch, 0, rlen);
simd.packIntToByteTruncate(scratch, 0, mask, 0, rlen);
} else {
mask = new byte[rlen];
for (int iter = 0; iter < rlen; iter++) {
mask[iter] = (byte) (rgb[iter] & 0xff);
}
}
return new IndexedImage(getWidth(), getHeight(), null, mask);
}
Expand Down Expand Up @@ -1130,7 +1168,7 @@ public Image applyMask(Object mask, int x, int y) {

}
}
return createImage(rgb, imgWidth, getHeight());
return createImageWithRgbCache(rgb, imgWidth, getHeight());
}

/// Applies the given alpha mask onto this image and returns the resulting image
Expand All @@ -1156,13 +1194,26 @@ public Image applyMask(Object mask) {
if (mWidth != getWidth() || mHeight != getHeight()) {
throw new IllegalArgumentException("Mask and image sizes don't match");
}
int mdlen = maskData.length;
for (int iter = 0; iter < mdlen; iter++) {
int maskAlpha = maskData[iter] & 0xff;
maskAlpha = (maskAlpha << 24) & 0xff000000;
rgb[iter] = (rgb[iter] & 0xffffff) | maskAlpha;
if (isSimdOptimizationsEnabled() && maskData.length >= 16) {
Simd simd = Simd.get();
int total = maskData.length;
// `rgb` came from getRGB() / allocateRgbArray() and is therefore a
// Simd-registered array, so we can operate on it in place. Mask data
// may have been produced outside of createMask (e.g. deserialized
// IndexedImage), so copy it once into a registered scratch.
byte[] maskBuf = simd.allocaByte(total);
System.arraycopy(maskData, 0, maskBuf, 0, total);
// rgb[i] = (rgb[i] & 0x00ffffff) | ((maskBuf[i] & 0xff) << 24)
simd.replaceTopByteFromUnsignedBytes(rgb, 0, maskBuf, 0, rgb, 0, total);
} else {
int mdlen = maskData.length;
for (int iter = 0; iter < mdlen; iter++) {
int maskAlpha = maskData[iter] & 0xff;
maskAlpha = (maskAlpha << 24) & 0xff000000;
rgb[iter] = (rgb[iter] & 0xffffff) | maskAlpha;
}
}
return createImage(rgb, mWidth, mHeight);
return createImageWithRgbCache(rgb, mWidth, mHeight);
}

/// Applies the given alpha mask onto this image and returns the resulting image
Expand Down Expand Up @@ -1306,14 +1357,19 @@ public Image modifyAlpha(byte alpha) {
int h = getHeight();
int size = w * h;
int[] arr = getRGB();
int alphaInt = (((int) alpha) << 24) & 0xff000000;
for (int iter = 0; iter < size; iter++) {
int currentAlpha = (arr[iter] >> 24) & 0xff;
if (currentAlpha != 0) {
arr[iter] = (arr[iter] & 0xffffff) | alphaInt;
if (isSimdOptimizationsEnabled() && size >= 16) {
int alphaInt = (((int) alpha) << 24) & 0xff000000;
replaceAlphaPreserveTransparentSimd(arr, 0, alphaInt, size);
} else {
int alphaInt = (((int) alpha) << 24) & 0xff000000;
for (int iter = 0; iter < size; iter++) {
int currentAlpha = (arr[iter] >> 24) & 0xff;
if (currentAlpha != 0) {
arr[iter] = (arr[iter] & 0xffffff) | alphaInt;
}
}
}
Image i = new Image(arr, w, h);
Image i = createImageWithRgbCache(arr, w, h);
i.opaqueTested = true;
i.opaque = false;
return i;
Expand Down Expand Up @@ -1351,7 +1407,7 @@ public Image modifyAlphaWithTranslucency(byte alpha) {
}
}
}
Image i = new Image(arr, w, h);
Image i = createImageWithRgbCache(arr, w, h);
i.opaqueTested = true;
i.opaque = false;
return i;
Expand All @@ -1376,18 +1432,23 @@ public Image modifyAlpha(byte alpha, int removeColor) {
int w = getWidth();
int h = getHeight();
int size = w * h;
int[] arr = new int[size];
int[] arr = allocateRgbArray(size);
getRGB(arr, 0, 0, 0, w, h);
int alphaInt = (((int) alpha) << 24) & 0xff000000;
for (int iter = 0; iter < size; iter++) {
if ((arr[iter] & 0xff000000) != 0) {
arr[iter] = (arr[iter] & 0xffffff) | alphaInt;
if (removeColor == (0xffffff & arr[iter])) {
arr[iter] = 0;
if (isSimdOptimizationsEnabled() && size >= 16) {
int alphaInt = (((int) alpha) << 24) & 0xff000000;
replaceAlphaPreserveTransparentRemoveColorSimd(arr, 0, alphaInt, removeColor, size);
} else {
int alphaInt = (((int) alpha) << 24) & 0xff000000;
for (int iter = 0; iter < size; iter++) {
if ((arr[iter] & 0xff000000) != 0) {
arr[iter] = (arr[iter] & 0xffffff) | alphaInt;
if (removeColor == (0xffffff & arr[iter])) {
arr[iter] = 0;
}
}
}
}
Image i = new Image(arr, w, h);
Image i = createImageWithRgbCache(arr, w, h);
i.opaqueTested = true;
i.opaque = false;
return i;
Expand Down Expand Up @@ -1606,11 +1667,62 @@ int[] getRGBCache() {
int[] getRGBImpl() {
int width = getWidth();
int height = getHeight();
int[] rgbData = new int[width * height];
int[] rgbData = allocateRgbArray(width * height);
getRGB(rgbData, 0, 0, 0, width, height);
return rgbData;
}

/// Allocates an ARGB pixel array. The returned array is always a Simd-registered
/// buffer (allocated via `Simd.allocInt`) regardless of whether SIMD optimizations
/// are currently enabled - heap allocation cost is essentially the same and this
/// lets downstream Simd-using code skip defensive working copies. For very small
/// images that fall under the Simd minimum size (16) we fall back to a plain
/// `int[]` since `Simd.allocInt` requires `size >= 16`.
static int[] allocateRgbArray(int size) {
if (size >= 16) {
return Simd.get().allocInt(size);
}
return new int[size];
}

/// Replaces the alpha (top byte) of every non-fully-transparent pixel in `arr`
/// with the alpha bits in `alphaInt`, leaving fully-transparent pixels unchanged.
/// Implemented as a single fused pass via `Simd.blendByMaskTestNonzero`, which is
/// equivalent to `arr[i] = (arr[i] & 0xff000000) != 0 ? (arr[i] & 0x00ffffff) | alphaMask : arr[i]`.
///
/// `arr` MUST be a Simd-registered int array (i.e. obtained via
/// `allocateRgbArray` / `Simd.allocInt`); the helper operates on it directly without
/// an intermediate working copy.
static void replaceAlphaPreserveTransparentSimd(int[] arr, int arrOffset, int alphaInt, int length) {
int alphaMask = alphaInt & 0xff000000;
Simd.get().blendByMaskTestNonzero(arr, arrOffset, 0xff000000, 0x00ffffff, alphaMask, arr, arrOffset, length);
}

/// Replaces the alpha of every non-fully-transparent pixel with `alphaInt`
/// and additionally zeroes any pixel whose RGB component matches `removeColor`
/// (low 24 bits) after the alpha replacement. Implemented as a single fused pass via
/// `Simd.blendByMaskTestNonzeroSubstituteOnKeepEq`, equivalent to:
/// ```
/// arr[i] = (arr[i] & 0xff000000) == 0 ? arr[i]
/// : (arr[i] & 0x00ffffff) == removeColor ? 0
/// : (arr[i] & 0x00ffffff) | alphaMask
/// ```
///
/// `arr` MUST be a Simd-registered int array (see
/// `replaceAlphaPreserveTransparentSimd`).
static void replaceAlphaPreserveTransparentRemoveColorSimd(int[] arr, int arrOffset, int alphaInt, int removeColor, int length) {
int alphaMask = alphaInt & 0xff000000;
int rgbOnly = removeColor & 0x00ffffff;
Simd.get().blendByMaskTestNonzeroSubstituteOnKeepEq(
arr, arrOffset,
0xff000000, // testMask: select pixels whose alpha was non-zero
0x00ffffff, // trueKeepMask: keep the RGB bits
alphaMask, // trueOrValue: splice in the new alpha
rgbOnly, // removeMatch: kept-bits == removeColor
0, // removeValue: zero out matching pixels
arr, arrOffset, length);
}

/// Scales the image to the given width while updating the height based on the
/// aspect ratio of the width
///
Expand Down
2 changes: 1 addition & 1 deletion CodenameOne/src/com/codename1/ui/IndexedImage.java
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ byte[] scaleArray(byte[] sourceArray, int width, int height) {
@Override
int[] getRGBImpl() {
int rlen = width * height;
int[] rgb = new int[rlen];
int[] rgb = Image.allocateRgbArray(rlen);

for (int iter = 0; iter < rlen; iter++) {
int i = imageDataByte[iter] & 0xff;
Expand Down
17 changes: 11 additions & 6 deletions CodenameOne/src/com/codename1/ui/RGBImage.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,18 @@ public Image rotate(int degrees) {
/// {@inheritDoc}
@Override
public Image modifyAlpha(byte alpha) {
int[] arr = new int[rgb.length];
int[] arr = Image.allocateRgbArray(rgb.length);
System.arraycopy(rgb, 0, arr, 0, rgb.length);
int alphaInt = (((int) alpha) << 24) & 0xff000000;
int rlen = rgb.length;
for (int iter = 0; iter < rlen; iter++) {
if ((arr[iter] & 0xff000000) != 0) {
arr[iter] = (arr[iter] & 0xffffff) | alphaInt;
if (Image.isSimdOptimizationsEnabled() && arr.length >= 16) {
int alphaInt = (((int) alpha) << 24) & 0xff000000;
Image.replaceAlphaPreserveTransparentSimd(arr, 0, alphaInt, arr.length);
} else {
int alphaInt = (((int) alpha) << 24) & 0xff000000;
int rlen = rgb.length;
for (int iter = 0; iter < rlen; iter++) {
if ((arr[iter] & 0xff000000) != 0) {
arr[iter] = (arr[iter] & 0xffffff) | alphaInt;
}
}
}
return new RGBImage(arr, width, height);
Expand Down
Loading
Loading