Skip to content

Commit

Permalink
Use dataBuffers for other graphics workloads as well.
Browse files Browse the repository at this point in the history
This does not break Munkiki's Castles, Crash Bandicoot, or any
other jar that was found to be buggy with the older, pre-fixed
get/setDataElements usage. So at least it's faster, and still
safe-ish.

On the other hand, creating a new transformed subimage using the
platformImage constructor now has a little more overhead since
the new subimage CANNOT share the same data array as its original
copy, otherwise the databuffer manipulation would make changes to
BOTH images. Hopefully it's not much of an overhead that it negates
the current optimizations, at least Castlevania: Dawn of Sorrow is
Still slightly faster than before, although it runs with a massive
performance margin even on an 800MHz i5-8250U.
  • Loading branch information
AShiningRay committed Dec 6, 2024
1 parent 8607ad5 commit 578c2dd
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 70 deletions.
76 changes: 33 additions & 43 deletions src/org/recompile/mobile/PlatformGraphics.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,11 @@

import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.awt.BasicStroke;
import java.awt.image.DataBufferInt;

public class PlatformGraphics extends javax.microedition.lcdui.Graphics implements DirectGraphics
{
protected BufferedImage canvas;
private WritableRaster raster;
protected Graphics2D gc;

protected Color awtColor;
Expand Down Expand Up @@ -224,8 +222,8 @@ public void drawRGB(int[] rgbData, int offset, int scanlength, int x, int y, int
}

BufferedImage temp = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
raster = temp.getRaster();
final int[] pixels = new int[width * height];
// Let's get the target pixel array directly from the image's DataBuffer, saving us an array copy and the overhead of setDataElements().
final int[] pixels = ((DataBufferInt) temp.getRaster().getDataBuffer()).getData();
int s, d, pixel;

for (int i = 0; i < height; i++)
Expand All @@ -240,8 +238,6 @@ public void drawRGB(int[] rgbData, int offset, int scanlength, int x, int y, int
}
}

raster.setDataElements(0, 0, width, height, pixels);

gc.drawImage(temp, x, y, null);
}

Expand Down Expand Up @@ -458,49 +454,51 @@ public void drawPixels(byte[] pixels, byte[] transparencyMask, int offset, int s
{
int[] Type1 = {0xFFFFFFFF, 0xFF000000, 0x00FFFFFF, 0x00000000};
int c = 0;
int[] data = null;
BufferedImage temp = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
raster = temp.getRaster();
switch(format)
final int[] data = ((DataBufferInt) temp.getRaster().getDataBuffer()).getData();

switch (format)
{
case -1: // TYPE_BYTE_1_GRAY_VERTICAL // used by Monkiki's Castles
data = new int[width*height];
case -1: // TYPE_BYTE_1_GRAY_VERTICAL - Used by Munkiki's Castles
int ods = offset / scanlength;
int oms = offset % scanlength;
int b = ods % 8; //Bit offset in a byte
for (int yj = 0; yj < height; yj++)
int b = ods % 8; // Bit offset in a byte
for (int yj = 0; yj < height; yj++)
{
int ypos = yj * width;
int tmp = (ods + yj) / 8 * scanlength+oms;
for (int xj = 0; xj < width; xj++)
int tmp = (ods + yj) / 8 * scanlength + oms;
for (int xj = 0; xj < width; xj++)
{
c = ((pixels[tmp + xj]>>b)&1);
if(transparencyMask!=null) { c |= (((transparencyMask[tmp + xj]>>b)&1)^1)<<1; }
data[(yj*width)+xj] = Type1[c];
c = ((pixels[tmp + xj] >> b) & 1);
if (transparencyMask != null)
{
c |= (((transparencyMask[tmp + xj] >> b) & 1) ^ 1) << 1;
}
data[ypos + xj] = Type1[c]; // Set pixel directly in the DataBuffer also removing the need for setDataElements
}
b++;
if(b>7) b=0;
if (b > 7) b = 0;
}
break;

case 1: // TYPE_BYTE_1_GRAY // used by Munkiki's Castles
data = new int[pixels.length*8];

for(int i=(offset/8); i<pixels.length; i++)
break;

case 1: // TYPE_BYTE_1_GRAY - Also used by Munkiki's Castles
for (int i = (offset / 8); i < pixels.length; i++)
{
for(int j=7; j>=0; j--)
for (int j = 7; j >= 0; j--)
{
c = ((pixels[i]>>j)&1);
if(transparencyMask!=null) { c |= (((transparencyMask[i]>>j)&1)^1)<<1; }
data[(i*8)+(7-j)] = Type1[c];
c = ((pixels[i] >> j) & 1);
if (transparencyMask != null)
{
c |= (((transparencyMask[i] >> j) & 1) ^ 1) << 1;
}
data[(i * 8) + (7 - j)] = Type1[c]; // Same as above
}
}
break;
break;

default: Mobile.log(Mobile.LOG_WARNING, PlatformGraphics.class.getPackage().getName() + "." + PlatformGraphics.class.getSimpleName() + ": " + "drawPixels A : Format " + format + " Not Implemented");
}

raster.setDataElements(0, 0, width, height, data);
gc.drawImage(manipulateImage(temp, manipulation), x, y, null);
}

Expand All @@ -519,12 +517,9 @@ public void drawPixels(int[] pixels, boolean transparency, int offset, int scanl
if (offset + width > pixels.length || offset + scanlength * (height - 1) < 0) { throw new ArrayIndexOutOfBoundsException(); }
}

// Create the temporary BufferedImage and get its WritableRaster
// Create the temporary BufferedImage and get its DataBuffer to manipulate it directly.
BufferedImage temp = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
raster = temp.getRaster();

// Prepare pixel data
final int[] data = new int[width * height];
int[] data = ((DataBufferInt) temp.getRaster().getDataBuffer()).getData();

for (int row = 0; row < height; row++)
{
Expand All @@ -537,16 +532,14 @@ public void drawPixels(int[] pixels, boolean transparency, int offset, int scanl
}
}

raster.setDataElements(0, 0, width, height, data);
gc.drawImage(manipulateImage(temp, manipulation), x, y, null);
}

public void drawPixels(short[] pixels, boolean transparency, int offset, int scanlength, int x, int y, int width, int height, int manipulation, int format)
{
BufferedImage temp = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
raster = temp.getRaster();
int[] data = ((DataBufferInt) temp.getRaster().getDataBuffer()).getData();

final int[] data = new int[pixels.length];
// Prepare the pixel data
for (int row = 0; row < height; row++)
{
Expand All @@ -555,12 +548,9 @@ public void drawPixels(short[] pixels, boolean transparency, int offset, int sca
int index = offset + row * scanlength + col;
data[row * width + col] = pixelToColor(pixels[index], format);
if (!transparency) { data[row * width + col] &= 0x00FFFFFF; } // Clear the alpha channel

}
}

// Set the pixel data directly into the raster
raster.setDataElements(0, 0, width, height, data);
gc.drawImage(manipulateImage(temp, manipulation), x, y, null);
}

Expand Down
76 changes: 49 additions & 27 deletions src/org/recompile/mobile/PlatformImage.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,9 @@
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.image.WritableRaster;

public class PlatformImage extends javax.microedition.lcdui.Image
{
private WritableRaster raster;
protected BufferedImage canvas;
protected PlatformGraphics gc;

Expand Down Expand Up @@ -198,7 +196,22 @@ public PlatformImage(int[] rgb, int Width, int Height, boolean processAlpha)
public PlatformImage(Image image, int x, int y, int Width, int Height, int transform)
{
// Create Image From Sub-Image, Transformed //
BufferedImage sub = image.platformImage.canvas.getSubimage(x, y, Width, Height);
BufferedImage sub = new BufferedImage(Width, Height, BufferedImage.TYPE_INT_ARGB);

// Get the original pixel data from the source image
final int[] sourcePixels = ((DataBufferInt) image.platformImage.canvas.getRaster().getDataBuffer()).getData();

// Create an array to hold the sub-image pixel data
final int[] subPixels = new int[Width * Height];

// Copy pixel data directly
for (int j = 0; j < Height; j++)
{
System.arraycopy(sourcePixels, (y + j) * image.platformImage.canvas.getWidth() + x, subPixels, j * Width, Width);
}

// Set the pixel data for the new sub-image
sub.getRaster().setDataElements(0, 0, Width, Height, subPixels);

canvas = transformImage(sub, transform);
createGraphics();
Expand All @@ -215,19 +228,15 @@ public void getRGB(int[] rgbData, int offset, int scanlength, int x, int y, int
if (width <= 0 || height <= 0) { return; } // No pixels to copy
if (x < 0 || y < 0 || x + width > canvas.getWidth() || y + height > canvas.getHeight())
{
throw new IllegalArgumentException("Requested area exceeds bounds of the image");
throw new IllegalArgumentException("getRGB Requested area exceeds bounds of the image");
}
if (Math.abs(scanlength) < width)
{
throw new IllegalArgumentException("scanlength must be >= width");
}

// Temporary array to hold the raw pixel data
int[] tempData = new int[width * height];

raster = canvas.getRaster();
raster.getDataElements(x, y, width, height, tempData);

int[] tempData = ((DataBufferInt) canvas.getRaster().getDataBuffer()).getData();
// Copy the data into rgbData, taking scanlength into account
for (int row = 0; row < height; row++)
{
Expand All @@ -238,18 +247,30 @@ public void getRGB(int[] rgbData, int offset, int scanlength, int x, int y, int
}
}

public int getARGB(int x, int y) { return canvas.getRGB(x, y); }

public int getPixel(int x, int y)
{
int[] rgbData = { 0 };
canvas.getRGB(x, y, 1, 1, rgbData, 0, 1);
return rgbData[0];
public int getARGB(int x, int y)
{
if (x < 0 || y < 0 || x >= canvas.getWidth() || y >= canvas.getHeight())
{
throw new IllegalArgumentException("Requested area exceeds bounds of the image");
}

// Get the raw pixel data array directly from the canvas
int[] pixels = ((DataBufferInt) canvas.getRaster().getDataBuffer()).getData();
return pixels[y * canvas.getWidth() + x];
}

public int getPixel(int x, int y) { return getARGB(x, y); }

public void setPixel(int x, int y, int color)
{
canvas.setRGB(x, y, color);
if (x < 0 || y < 0 || x >= canvas.getWidth() || y >= canvas.getHeight())
{
throw new IllegalArgumentException("Requested area exceeds bounds of the image");
}

// Get the raw pixel data array directly from the canvas
int[] pixels = ((DataBufferInt) canvas.getRaster().getDataBuffer()).getData();
pixels[y * canvas.getWidth() + x] = color;
}

public static final BufferedImage transformImage(final BufferedImage image, final int transform)
Expand All @@ -263,9 +284,9 @@ public static final BufferedImage transformImage(final BufferedImage image, fina
BufferedImage transimage = null;
if(transform == Sprite.TRANS_ROT90 || transform == Sprite.TRANS_ROT270 || transform == Sprite.TRANS_MIRROR_ROT90 || transform == Sprite.TRANS_MIRROR_ROT270)
{
transimage = new BufferedImage(height, width, BufferedImage.TYPE_INT_ARGB); // Non-Math.PI rotations require width and height to be swapped
transimage = new BufferedImage(height, width, image.getType()); // Non-Math.PI rotations require width and height to be swapped
}
else { transimage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); }
else { transimage = new BufferedImage(width, height, image.getType()); }

// We know the data is of TYPE_INT_ARGB, so just get it directly instead of checking for its type
final int[] sourceData = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
Expand All @@ -284,7 +305,7 @@ public static final BufferedImage transformImage(final BufferedImage image, fina
}
//dumpImage(image, "");
//dumpImage(transimage, "_rot90");
return transimage;
break;

case Sprite.TRANS_ROT180:
for (int y = 0; y < height; y++)
Expand All @@ -297,7 +318,7 @@ public static final BufferedImage transformImage(final BufferedImage image, fina
}
//dumpImage(image, "");
//dumpImage(transimage, "_rot180");
return transimage;
break;

case Sprite.TRANS_ROT270:
for (int y = 0; y < height; y++)
Expand All @@ -309,7 +330,7 @@ public static final BufferedImage transformImage(final BufferedImage image, fina
}
//dumpImage(image, "");
//dumpImage(transimage, "_rot270");
return transimage;
break;

case Sprite.TRANS_MIRROR:
/*
Expand Down Expand Up @@ -340,7 +361,7 @@ public static final BufferedImage transformImage(final BufferedImage image, fina
}
//dumpImage(image, "");
//dumpImage(transimage, "_mirror");
return transimage;
break;

case Sprite.TRANS_MIRROR_ROT90:
for (int y = 0; y < height; y++)
Expand All @@ -353,7 +374,7 @@ public static final BufferedImage transformImage(final BufferedImage image, fina
}
//dumpImage(image, "");
//dumpImage(transimage, "_mirror90");
return transimage;
break;

case Sprite.TRANS_MIRROR_ROT180: // Basically mirror vertically (an arrow pointing up will then point down).
for (int y = 0; y < height; y++) // Due to this, we copy entire rows at once instead of going pixel by pixel
Expand All @@ -362,7 +383,7 @@ public static final BufferedImage transformImage(final BufferedImage image, fina
}
//dumpImage(image, "");
//dumpImage(transimage, "_mirror180");
return transimage;
break;


case Sprite.TRANS_MIRROR_ROT270:
Expand All @@ -376,9 +397,10 @@ public static final BufferedImage transformImage(final BufferedImage image, fina
}
//dumpImage(image, "");
//dumpImage(transimage, "_mirror270");
return transimage;
break;
}
return image;

return transimage;
}

// TODO: Turn this into a setting. Being able to dump image data would be nice.
Expand Down

0 comments on commit 578c2dd

Please sign in to comment.