From 2e58d52493b9bea7b427bbd69435fb9af711b35c Mon Sep 17 00:00:00 2001 From: Svjatoslav Agejenko Date: Thu, 12 Mar 2026 20:53:14 +0200 Subject: [PATCH] Switch rendering canvas to TYPE_INT_ARGB for better performance. --- .../sixth/e3d/gui/RenderingContext.java | 13 +- .../raster/shapes/basic/Billboard.java | 10 +- .../raster/shapes/basic/GlowingPoint.java | 11 +- .../raster/shapes/basic/line/Line.java | 80 ++++++----- .../basic/solidpolygon/SolidPolygon.java | 48 +++---- .../texturedpolygon/TexturedPolygon.java | 8 +- .../e3d/renderer/raster/texture/Texture.java | 45 ++----- .../raster/texture/TextureBitmap.java | 126 +++++++----------- 8 files changed, 144 insertions(+), 197 deletions(-) diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/RenderingContext.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/RenderingContext.java index d072874..336b61a 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/RenderingContext.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/RenderingContext.java @@ -10,7 +10,7 @@ import eu.svjatoslav.sixth.e3d.gui.humaninput.MouseInteractionController; import java.awt.*; import java.awt.image.BufferedImage; -import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferInt; import java.awt.image.WritableRaster; /** @@ -37,9 +37,10 @@ import java.awt.image.WritableRaster; public class RenderingContext { /** - * The {@link BufferedImage} pixel format used for the rendering buffer (4-byte ABGR). + * The {@link BufferedImage} pixel format used for the rendering buffer. + * TYPE_INT_RGB provides optimal performance for Java2D blitting. */ - public static final int bufferedImageType = BufferedImage.TYPE_4BYTE_ABGR; + public static final int bufferedImageType = BufferedImage.TYPE_INT_RGB; /** * Java2D graphics context for drawing text, anti-aliased shapes, and other @@ -49,9 +50,9 @@ public class RenderingContext { /** * Pixels of the rendering area. - * Each pixel is represented by 4 bytes: alpha, blue, green, red. + * Each pixel is a single int in RGB format: {@code (r << 16) | (g << 8) | b}. */ - public final byte[] pixels; + public final int[] pixels; /** * Width of the rendering area in pixels. @@ -117,7 +118,7 @@ public class RenderingContext { bufferedImage = new BufferedImage(width, height, bufferedImageType); final WritableRaster raster = bufferedImage.getRaster(); - final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer(); + final DataBufferInt dbi = (DataBufferInt) raster.getDataBuffer(); pixels = dbi.getData(); graphics = (Graphics2D) bufferedImage.getGraphics(); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/Billboard.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/Billboard.java index 8ceefc2..ca3d296 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/Billboard.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/Billboard.java @@ -103,7 +103,7 @@ public class Billboard extends AbstractCoordinateShape { if (onScreenCappedXEnd > targetRenderingArea.width) onScreenCappedXEnd = targetRenderingArea.width; - final byte[] targetRenderingAreaBytes = targetRenderingArea.pixels; + final int[] targetRenderingAreaPixels = targetRenderingArea.pixels; final int textureWidth = textureBitmap.width; @@ -112,15 +112,15 @@ public class Billboard extends AbstractCoordinateShape { final int sourceBitmapScanlinePixel = ((textureBitmap.height * (y - onScreenUncappedYStart)) / onScreenUncappedHeight) * textureWidth; - int targetRenderingAreaOffset = ((y * targetRenderingArea.width) + onScreenCappedXStart) * 4; + int targetRenderingAreaOffset = (y * targetRenderingArea.width) + onScreenCappedXStart; for (int x = onScreenCappedXStart; x < onScreenCappedXEnd; x++) { - final int sourceBitmapPixelAddress = (sourceBitmapScanlinePixel + ((textureWidth * (x - onScreenUncappedXStart)) / onScreenUncappedWidth)) * 4; + final int sourceBitmapPixelAddress = sourceBitmapScanlinePixel + ((textureWidth * (x - onScreenUncappedXStart)) / onScreenUncappedWidth); - textureBitmap.drawPixel(sourceBitmapPixelAddress, targetRenderingAreaBytes, targetRenderingAreaOffset); + textureBitmap.drawPixel(sourceBitmapPixelAddress, targetRenderingAreaPixels, targetRenderingAreaOffset); - targetRenderingAreaOffset += 4; + targetRenderingAreaOffset++; } } } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/GlowingPoint.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/GlowingPoint.java index f770b73..59cc454 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/GlowingPoint.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/GlowingPoint.java @@ -76,21 +76,14 @@ public class GlowingPoint extends Billboard { for (int x = 0; x < TEXTURE_RESOLUTION_PIXELS; x++) for (int y = 0; y < TEXTURE_RESOLUTION_PIXELS; y++) { - int address = texture.primaryBitmap.getAddress(x, y); - final int distanceFromCenter = (int) sqrt(pow(halfResolution - x, 2) + pow(halfResolution - y, 2)); int alpha = 255 - ((270 * distanceFromCenter) / halfResolution); if (alpha < 0) alpha = 0; - texture.primaryBitmap.bytes[address] = (byte) alpha; - address++; - texture.primaryBitmap.bytes[address] = (byte) color.b; - address++; - texture.primaryBitmap.bytes[address] = (byte) color.g; - address++; - texture.primaryBitmap.bytes[address] = (byte) color.r; + texture.primaryBitmap.pixels[texture.primaryBitmap.getAddress(x, y)] = + (alpha << 24) | (color.r << 16) | (color.g << 8) | color.b; } return texture; diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/Line.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/Line.java index 9a71bb0..079f0ab 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/Line.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/Line.java @@ -101,14 +101,14 @@ public class Line extends AbstractCoordinateShape { final int drawnWidth = x2 - x1; - int offset = ((y * renderBuffer.width) + x1) * 4; - final byte[] offSreenBufferBytes = renderBuffer.pixels; + int offset = (y * renderBuffer.width) + x1; + final int[] pixels = renderBuffer.pixels; final int lineAlpha = color.a; - final int colorB = color.b; - final int colorG = color.g; final int colorR = color.r; + final int colorG = color.g; + final int colorB = color.b; for (int i = 0; i < drawnWidth; i++) { @@ -117,14 +117,16 @@ public class Line extends AbstractCoordinateShape { final int realLineAlpha = (int) (lineAlpha * alphaMultiplier); final int backgroundAlpha = 255 - realLineAlpha; - offSreenBufferBytes[offset] = (byte) 255; - offset++; - offSreenBufferBytes[offset] = (byte) ((((offSreenBufferBytes[offset] & 0xff) * backgroundAlpha) + (colorB * realLineAlpha)) / 256); - offset++; - offSreenBufferBytes[offset] = (byte) ((((offSreenBufferBytes[offset] & 0xff) * backgroundAlpha) + (colorG * realLineAlpha)) / 256); - offset++; - offSreenBufferBytes[offset] = (byte) ((((offSreenBufferBytes[offset] & 0xff) * backgroundAlpha) + (colorR * realLineAlpha)) / 256); - offset++; + final int dest = pixels[offset]; + final int destR = (dest >> 16) & 0xff; + final int destG = (dest >> 8) & 0xff; + final int destB = dest & 0xff; + + final int newR = ((destR * backgroundAlpha) + (colorR * realLineAlpha)) / 256; + final int newG = ((destG * backgroundAlpha) + (colorG * realLineAlpha)) / 256; + final int newB = ((destB * backgroundAlpha) + (colorB * realLineAlpha)) / 256; + + pixels[offset++] = (newR << 16) | (newG << 8) | newB; d1 += dinc; } @@ -158,12 +160,12 @@ public class Line extends AbstractCoordinateShape { if (lineWidth == 0) return; - final byte[] offSreenBufferBytes = buffer.pixels; + final int[] pixels = buffer.pixels; final int backgroundAlpha = 255 - alpha; - final int blueWithAlpha = color.b * alpha; - final int greenWithAplha = color.g * alpha; final int redWithAlpha = color.r * alpha; + final int greenWithAplha = color.g * alpha; + final int blueWithAlpha = color.b * alpha; for (int relativeX = 0; relativeX <= lineWidth; relativeX++) { final int x = xStart + relativeX; @@ -172,15 +174,18 @@ public class Line extends AbstractCoordinateShape { final int y = yBase + ((relativeX * lineHeight) / lineWidth); if ((y >= 0) && (y < buffer.height)) { - int ramOffset = ((y * buffer.width) + x) * 4; - - offSreenBufferBytes[ramOffset] = (byte) 255; - ramOffset++; - offSreenBufferBytes[ramOffset] = (byte) ((((offSreenBufferBytes[ramOffset] & 0xff) * backgroundAlpha) + blueWithAlpha) / 256); - ramOffset++; - offSreenBufferBytes[ramOffset] = (byte) ((((offSreenBufferBytes[ramOffset] & 0xff) * backgroundAlpha) + greenWithAplha) / 256); - ramOffset++; - offSreenBufferBytes[ramOffset] = (byte) ((((offSreenBufferBytes[ramOffset] & 0xff) * backgroundAlpha) + redWithAlpha) / 256); + int offset = (y * buffer.width) + x; + + final int dest = pixels[offset]; + final int destR = (dest >> 16) & 0xff; + final int destG = (dest >> 8) & 0xff; + final int destB = dest & 0xff; + + final int newR = ((destR * backgroundAlpha) + redWithAlpha) / 256; + final int newG = ((destG * backgroundAlpha) + greenWithAplha) / 256; + final int newB = ((destB * backgroundAlpha) + blueWithAlpha) / 256; + + pixels[offset] = (newR << 16) | (newG << 8) | newB; } } } @@ -214,12 +219,12 @@ public class Line extends AbstractCoordinateShape { if (lineHeight == 0) return; - final byte[] offScreenBufferBytes = buffer.pixels; + final int[] pixels = buffer.pixels; final int backgroundAlpha = 255 - alpha; - final int blueWithAlpha = color.b * alpha; - final int greenWithAlpha = color.g * alpha; final int redWithAlpha = color.r * alpha; + final int greenWithAlpha = color.g * alpha; + final int blueWithAlpha = color.b * alpha; for (int relativeY = 0; relativeY <= lineHeight; relativeY++) { final int y = yStart + relativeY; @@ -228,15 +233,18 @@ public class Line extends AbstractCoordinateShape { final int x = xBase + ((relativeY * lineWidth) / lineHeight); if ((x >= 0) && (x < buffer.width)) { - int ramOffset = ((y * buffer.width) + x) * 4; - - offScreenBufferBytes[ramOffset] = (byte) 255; - ramOffset++; - offScreenBufferBytes[ramOffset] = (byte) ((((offScreenBufferBytes[ramOffset] & 0xff) * backgroundAlpha) + blueWithAlpha) / 256); - ramOffset++; - offScreenBufferBytes[ramOffset] = (byte) ((((offScreenBufferBytes[ramOffset] & 0xff) * backgroundAlpha) + greenWithAlpha) / 256); - ramOffset++; - offScreenBufferBytes[ramOffset] = (byte) ((((offScreenBufferBytes[ramOffset] & 0xff) * backgroundAlpha) + redWithAlpha) / 256); + int offset = (y * buffer.width) + x; + + final int dest = pixels[offset]; + final int destR = (dest >> 16) & 0xff; + final int destG = (dest >> 8) & 0xff; + final int destB = dest & 0xff; + + final int newR = ((destR * backgroundAlpha) + redWithAlpha) / 256; + final int newG = ((destG * backgroundAlpha) + greenWithAlpha) / 256; + final int newB = ((destB * backgroundAlpha) + blueWithAlpha) / 256; + + pixels[offset] = (newR << 16) | (newG << 8) | newB; } } } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidPolygon.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidPolygon.java index eb53029..7cde339 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidPolygon.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidPolygon.java @@ -71,43 +71,37 @@ public class SolidPolygon extends AbstractCoordinateShape { final int width = x2 - x1; - int offset = ((y * renderBuffer.width) + x1) * 4; - final byte[] offScreenBufferBytes = renderBuffer.pixels; + int offset = (y * renderBuffer.width) + x1; + final int[] pixels = renderBuffer.pixels; final int polygonAlpha = color.a; - final int b = color.b; - final int g = color.g; final int r = color.r; + final int g = color.g; + final int b = color.b; - if (polygonAlpha == 255) - for (int i = 0; i < width; i++) { - offScreenBufferBytes[offset] = (byte) 255; - offset++; - offScreenBufferBytes[offset] = (byte) b; - offset++; - offScreenBufferBytes[offset] = (byte) g; - offset++; - offScreenBufferBytes[offset] = (byte) r; - offset++; - } - else { + if (polygonAlpha == 255) { + final int pixel = (r << 16) | (g << 8) | b; + for (int i = 0; i < width; i++) + pixels[offset++] = pixel; + } else { final int backgroundAlpha = 255 - polygonAlpha; - final int blueWithAlpha = b * polygonAlpha; - final int greenWithAlpha = g * polygonAlpha; final int redWithAlpha = r * polygonAlpha; + final int greenWithAlpha = g * polygonAlpha; + final int blueWithAlpha = b * polygonAlpha; for (int i = 0; i < width; i++) { - offScreenBufferBytes[offset] = (byte) 255; - offset++; - offScreenBufferBytes[offset] = (byte) ((((offScreenBufferBytes[offset] & 0xff) * backgroundAlpha) + blueWithAlpha) / 256); - offset++; - offScreenBufferBytes[offset] = (byte) ((((offScreenBufferBytes[offset] & 0xff) * backgroundAlpha) + greenWithAlpha) / 256); - offset++; - offScreenBufferBytes[offset] = (byte) ((((offScreenBufferBytes[offset] & 0xff) * backgroundAlpha) + redWithAlpha) / 256); - offset++; - } + final int dest = pixels[offset]; + final int destR = (dest >> 16) & 0xff; + final int destG = (dest >> 8) & 0xff; + final int destB = dest & 0xff; + + final int newR = ((destR * backgroundAlpha) + redWithAlpha) / 256; + final int newG = ((destG * backgroundAlpha) + greenWithAlpha) / 256; + final int newB = ((destB * backgroundAlpha) + blueWithAlpha) / 256; + pixels[offset++] = (newR << 16) | (newG << 8) | newB; + } } } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/TexturedPolygon.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/TexturedPolygon.java index 45e9b98..c6c325a 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/TexturedPolygon.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/TexturedPolygon.java @@ -99,8 +99,8 @@ public class TexturedPolygon extends AbstractCoordinateShape { if (x2 >= renderBuffer.width) x2 = renderBuffer.width - 1; - int renderBufferOffset = ((y * renderBuffer.width) + x1) * 4; - final byte[] renderBufferBytes = renderBuffer.pixels; + int renderBufferOffset = (y * renderBuffer.width) + x1; + final int[] renderBufferPixels = renderBuffer.pixels; final double twidth = tx2 - tx1; final double theight = ty2 - ty1; @@ -115,10 +115,10 @@ public class TexturedPolygon extends AbstractCoordinateShape { final int textureOffset = textureBitmap.getAddress((int) tx, (int) ty); - textureBitmap.drawPixel(textureOffset, renderBufferBytes, + textureBitmap.drawPixel(textureOffset, renderBufferPixels, renderBufferOffset); - renderBufferOffset += 4; + renderBufferOffset++; } } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/Texture.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/Texture.java index bf319b7..824c415 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/Texture.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/Texture.java @@ -4,11 +4,9 @@ */ package eu.svjatoslav.sixth.e3d.renderer.raster.texture; -import eu.svjatoslav.sixth.e3d.gui.RenderingContext; - import java.awt.*; import java.awt.image.BufferedImage; -import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferInt; import java.awt.image.WritableRaster; import static java.util.Arrays.fill; @@ -95,10 +93,10 @@ public class Texture { upSampled = new TextureBitmap[maxUpscale]; final BufferedImage bufferedImage = new BufferedImage(width, height, - RenderingContext.bufferedImageType); + BufferedImage.TYPE_INT_ARGB); final WritableRaster raster = bufferedImage.getRaster(); - final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer(); + final DataBufferInt dbi = (DataBufferInt) raster.getDataBuffer(); graphics = (Graphics2D) bufferedImage.getGraphics(); graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, @@ -314,10 +312,8 @@ public class Texture { * A helper class that accumulates color values for a given area of a bitmap */ public static class ColorAccumulator { - // Accumulated color values public int r, g, b, a; - // Number of pixels that have been accumulated public int pixelCount = 0; /** @@ -329,19 +325,11 @@ public class Texture { */ public void accumulate(final TextureBitmap bitmap, final int x, final int y) { - int address = bitmap.getAddress(x, y); - - a += bitmap.bytes[address] & 0xff; - address++; - - b += bitmap.bytes[address] & 0xff; - address++; - - g += bitmap.bytes[address] & 0xff; - address++; - - r += bitmap.bytes[address] & 0xff; - + final int pixel = bitmap.pixels[bitmap.getAddress(x, y)]; + a += (pixel >> 24) & 0xff; + r += (pixel >> 16) & 0xff; + g += (pixel >> 8) & 0xff; + b += pixel & 0xff; pixelCount++; } @@ -365,18 +353,11 @@ public class Texture { */ public void storeResult(final TextureBitmap bitmap, final int x, final int y) { - int address = bitmap.getAddress(x, y); - - bitmap.bytes[address] = (byte) (a / pixelCount); - address++; - - bitmap.bytes[address] = (byte) (b / pixelCount); - address++; - - bitmap.bytes[address] = (byte) (g / pixelCount); - address++; - - bitmap.bytes[address] = (byte) (r / pixelCount); + final int avgA = a / pixelCount; + final int avgR = r / pixelCount; + final int avgG = g / pixelCount; + final int avgB = b / pixelCount; + bitmap.pixels[bitmap.getAddress(x, y)] = (avgA << 24) | (avgR << 16) | (avgG << 8) | avgB; } } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/TextureBitmap.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/TextureBitmap.java index a2d253d..7adb04f 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/TextureBitmap.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/TextureBitmap.java @@ -7,12 +7,11 @@ package eu.svjatoslav.sixth.e3d.renderer.raster.texture; import eu.svjatoslav.sixth.e3d.renderer.raster.Color; /** - * Represents a single resolution level of a texture as a raw byte array. + * Represents a single resolution level of a texture as a raw int array. * - *

Each pixel is stored as 4 consecutive bytes in ABGR order: - * Alpha, Blue, Green, Red. This byte ordering matches the - * {@link java.awt.image.BufferedImage#TYPE_4BYTE_ABGR} format used by - * the rendering pipeline.

+ *

Each pixel is stored as a single int in ARGB format: + * {@code (alpha << 24) | (red << 16) | (green << 8) | blue}. + * This matches the {@link java.awt.image.BufferedImage#TYPE_INT_ARGB} format.

* *

{@code TextureBitmap} is used internally by {@link Texture} to represent * individual mipmap levels. The {@link #multiplicationFactor} records the @@ -22,7 +21,7 @@ import eu.svjatoslav.sixth.e3d.renderer.raster.Color; * *

This class provides low-level pixel operations including:

*