8337237: Use FFM instead of Unsafe for Java 2D RenderBuffer class

Reviewed-by: jvernee, jdv
This commit is contained in:
Phil Race 2024-08-14 17:58:24 +00:00
parent 6a39014795
commit c0384b6f35

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,58 +25,54 @@
package sun.java2d.pipe; package sun.java2d.pipe;
import jdk.internal.misc.Unsafe; import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import static java.lang.foreign.ValueLayout.*;
/** /**
* The RenderBuffer class is a simplified, high-performance, Unsafe wrapper * The RenderBuffer class is a simplified, high-performance class
* used for buffering rendering operations in a single-threaded rendering * used for buffering rendering operations in a single-threaded rendering
* environment. It's functionality is similar to the ByteBuffer and related * environment. Its functionality is similar to the ByteBuffer and related
* NIO classes. However, the methods in this class perform little to no * NIO classes. However, the methods in this class perform little to no
* alignment or bounds checks for performance reasons. Therefore, it is * alignment or bounds checks for performance reasons. Therefore, it is
* the caller's responsibility to ensure that all put() calls are properly * the caller's responsibility to ensure that all put() calls are properly
* aligned and within bounds: * aligned and within bounds:
* - int and float values must be aligned on 4-byte boundaries * - int and float values must be aligned on 4-byte boundaries
* - long and double values must be aligned on 8-byte boundaries * - long and double values must be aligned on 8-byte boundaries
* Failure to do so will result in exceptions from the FFM API, or worse.
* *
* This class only includes the bare minimum of methods to support * This class only includes the bare minimum of methods to support
* single-threaded rendering. For example, there is no put(double[]) method * single-threaded rendering. For example, there is no put(double[]) method
* because we currently have no need for such a method in the STR classes. * because we currently have no need for such a method in the STR classes.
*/ */
public class RenderBuffer { public final class RenderBuffer {
/** /**
* These constants represent the size of various data types (in bytes). * These constants represent the size of various data types (in bytes).
*/ */
protected static final long SIZEOF_BYTE = 1L; private static final int SIZEOF_BYTE = Byte.BYTES;
protected static final long SIZEOF_SHORT = 2L; private static final int SIZEOF_SHORT = Short.BYTES;
protected static final long SIZEOF_INT = 4L; private static final int SIZEOF_INT = Integer.BYTES;
protected static final long SIZEOF_FLOAT = 4L; private static final int SIZEOF_FLOAT = Float.BYTES;
protected static final long SIZEOF_LONG = 8L; private static final int SIZEOF_LONG = Long.BYTES;
protected static final long SIZEOF_DOUBLE = 8L; private static final int SIZEOF_DOUBLE = Double.BYTES;
/** /**
* Represents the number of elements at which we have empirically * Measurements show that using the copy API from a segment backed by a heap
* determined that the average cost of a JNI call exceeds the expense * array gets reliably faster than individual puts around a length of 10.
* of an element by element copy. In other words, if the number of * However the time is miniscule in the context of what it is used for
* elements in an array to be copied exceeds this value, then we should * and much more than adequate, so no problem expected if this changes over time.
* use the copyFromArray() method to complete the bulk put operation.
* (This value can be adjusted if the cost of JNI downcalls is reduced
* in a future release.)
*/ */
private static final int COPY_FROM_ARRAY_THRESHOLD = 6; private static final int COPY_FROM_ARRAY_THRESHOLD = 10;
protected final Unsafe unsafe; private final MemorySegment segment;
protected final long baseAddress; private int curOffset;
protected final long endAddress;
protected long curAddress;
protected final int capacity;
protected RenderBuffer(int numBytes) { private RenderBuffer(int numBytes) {
unsafe = Unsafe.getUnsafe(); segment = Arena.global().allocate(numBytes, SIZEOF_DOUBLE);
curAddress = baseAddress = unsafe.allocateMemory(numBytes); curOffset = 0;
endAddress = baseAddress + numBytes;
capacity = numBytes;
} }
/** /**
@ -90,7 +86,7 @@ public class RenderBuffer {
* Returns the base address of the underlying memory buffer. * Returns the base address of the underlying memory buffer.
*/ */
public final long getAddress() { public final long getAddress() {
return baseAddress; return segment.address();
} }
/** /**
@ -99,27 +95,27 @@ public class RenderBuffer {
*/ */
public final int capacity() { public final int capacity() {
return capacity; return (int)segment.byteSize();
} }
public final int remaining() { public final int remaining() {
return (int)(endAddress - curAddress); return (capacity() - curOffset);
} }
public final int position() { public final int position() {
return (int)(curAddress - baseAddress); return curOffset;
} }
public final void position(long numBytes) { public final void position(int bytePos) {
curAddress = baseAddress + numBytes; curOffset = bytePos;
} }
public final void clear() { public final void clear() {
curAddress = baseAddress; curOffset = 0;
} }
public final RenderBuffer skip(long numBytes) { public final RenderBuffer skip(int numBytes) {
curAddress += numBytes; curOffset += numBytes;
return this; return this;
} }
@ -128,8 +124,8 @@ public class RenderBuffer {
*/ */
public final RenderBuffer putByte(byte x) { public final RenderBuffer putByte(byte x) {
unsafe.putByte(curAddress, x); segment.set(JAVA_BYTE, curOffset, x);
curAddress += SIZEOF_BYTE; curOffset += SIZEOF_BYTE;
return this; return this;
} }
@ -139,10 +135,8 @@ public class RenderBuffer {
public RenderBuffer put(byte[] x, int offset, int length) { public RenderBuffer put(byte[] x, int offset, int length) {
if (length > COPY_FROM_ARRAY_THRESHOLD) { if (length > COPY_FROM_ARRAY_THRESHOLD) {
long offsetInBytes = offset * SIZEOF_BYTE + Unsafe.ARRAY_BYTE_BASE_OFFSET; MemorySegment.copy(x, offset, segment, JAVA_BYTE, curOffset, length);
long lengthInBytes = length * SIZEOF_BYTE; position(position() + length * SIZEOF_BYTE);
unsafe.copyMemory(x, offsetInBytes, null, curAddress, lengthInBytes);
position(position() + lengthInBytes);
} else { } else {
int end = offset + length; int end = offset + length;
for (int i = offset; i < end; i++) { for (int i = offset; i < end; i++) {
@ -158,8 +152,8 @@ public class RenderBuffer {
public final RenderBuffer putShort(short x) { public final RenderBuffer putShort(short x) {
// assert (position() % SIZEOF_SHORT == 0); // assert (position() % SIZEOF_SHORT == 0);
unsafe.putShort(curAddress, x); segment.set(JAVA_SHORT, curOffset, x);
curAddress += SIZEOF_SHORT; curOffset += SIZEOF_SHORT;
return this; return this;
} }
@ -170,10 +164,8 @@ public class RenderBuffer {
public RenderBuffer put(short[] x, int offset, int length) { public RenderBuffer put(short[] x, int offset, int length) {
// assert (position() % SIZEOF_SHORT == 0); // assert (position() % SIZEOF_SHORT == 0);
if (length > COPY_FROM_ARRAY_THRESHOLD) { if (length > COPY_FROM_ARRAY_THRESHOLD) {
long offsetInBytes = offset * SIZEOF_SHORT + Unsafe.ARRAY_SHORT_BASE_OFFSET; MemorySegment.copy(x, offset, segment, JAVA_SHORT, curOffset, length);
long lengthInBytes = length * SIZEOF_SHORT; position(position() + length * SIZEOF_SHORT);
unsafe.copyMemory(x, offsetInBytes, null, curAddress, lengthInBytes);
position(position() + lengthInBytes);
} else { } else {
int end = offset + length; int end = offset + length;
for (int i = offset; i < end; i++) { for (int i = offset; i < end; i++) {
@ -188,15 +180,15 @@ public class RenderBuffer {
*/ */
public final RenderBuffer putInt(int pos, int x) { public final RenderBuffer putInt(int pos, int x) {
// assert (baseAddress + pos % SIZEOF_INT == 0); // assert (getAddress() + pos % SIZEOF_INT == 0);
unsafe.putInt(baseAddress + pos, x); segment.set(JAVA_INT, pos, x);
return this; return this;
} }
public final RenderBuffer putInt(int x) { public final RenderBuffer putInt(int x) {
// assert (position() % SIZEOF_INT == 0); // assert (position() % SIZEOF_INT == 0);
unsafe.putInt(curAddress, x); segment.set(JAVA_INT, curOffset, x);
curAddress += SIZEOF_INT; curOffset += SIZEOF_INT;
return this; return this;
} }
@ -207,10 +199,8 @@ public class RenderBuffer {
public RenderBuffer put(int[] x, int offset, int length) { public RenderBuffer put(int[] x, int offset, int length) {
// assert (position() % SIZEOF_INT == 0); // assert (position() % SIZEOF_INT == 0);
if (length > COPY_FROM_ARRAY_THRESHOLD) { if (length > COPY_FROM_ARRAY_THRESHOLD) {
long offsetInBytes = offset * SIZEOF_INT + Unsafe.ARRAY_INT_BASE_OFFSET; MemorySegment.copy(x, offset, segment, JAVA_INT, curOffset, length);
long lengthInBytes = length * SIZEOF_INT; position(position() + length * SIZEOF_INT);
unsafe.copyMemory(x, offsetInBytes, null, curAddress, lengthInBytes);
position(position() + lengthInBytes);
} else { } else {
int end = offset + length; int end = offset + length;
for (int i = offset; i < end; i++) { for (int i = offset; i < end; i++) {
@ -226,8 +216,8 @@ public class RenderBuffer {
public final RenderBuffer putFloat(float x) { public final RenderBuffer putFloat(float x) {
// assert (position() % SIZEOF_FLOAT == 0); // assert (position() % SIZEOF_FLOAT == 0);
unsafe.putFloat(curAddress, x); segment.set(JAVA_FLOAT, curOffset, x);
curAddress += SIZEOF_FLOAT; curOffset += SIZEOF_FLOAT;
return this; return this;
} }
@ -238,10 +228,8 @@ public class RenderBuffer {
public RenderBuffer put(float[] x, int offset, int length) { public RenderBuffer put(float[] x, int offset, int length) {
// assert (position() % SIZEOF_FLOAT == 0); // assert (position() % SIZEOF_FLOAT == 0);
if (length > COPY_FROM_ARRAY_THRESHOLD) { if (length > COPY_FROM_ARRAY_THRESHOLD) {
long offsetInBytes = offset * SIZEOF_FLOAT + Unsafe.ARRAY_FLOAT_BASE_OFFSET; MemorySegment.copy(x, offset, segment, JAVA_FLOAT, curOffset, length);
long lengthInBytes = length * SIZEOF_FLOAT; position(position() + length * SIZEOF_FLOAT);
unsafe.copyMemory(x, offsetInBytes, null, curAddress, lengthInBytes);
position(position() + lengthInBytes);
} else { } else {
int end = offset + length; int end = offset + length;
for (int i = offset; i < end; i++) { for (int i = offset; i < end; i++) {
@ -257,8 +245,8 @@ public class RenderBuffer {
public final RenderBuffer putLong(long x) { public final RenderBuffer putLong(long x) {
// assert (position() % SIZEOF_LONG == 0); // assert (position() % SIZEOF_LONG == 0);
unsafe.putLong(curAddress, x); segment.set(JAVA_LONG, curOffset, x);
curAddress += SIZEOF_LONG; curOffset += SIZEOF_LONG;
return this; return this;
} }
@ -269,10 +257,8 @@ public class RenderBuffer {
public RenderBuffer put(long[] x, int offset, int length) { public RenderBuffer put(long[] x, int offset, int length) {
// assert (position() % SIZEOF_LONG == 0); // assert (position() % SIZEOF_LONG == 0);
if (length > COPY_FROM_ARRAY_THRESHOLD) { if (length > COPY_FROM_ARRAY_THRESHOLD) {
long offsetInBytes = offset * SIZEOF_LONG + Unsafe.ARRAY_LONG_BASE_OFFSET; MemorySegment.copy(x, offset, segment, JAVA_LONG, curOffset, length);
long lengthInBytes = length * SIZEOF_LONG; position(position() + length * SIZEOF_LONG);
unsafe.copyMemory(x, offsetInBytes, null, curAddress, lengthInBytes);
position(position() + lengthInBytes);
} else { } else {
int end = offset + length; int end = offset + length;
for (int i = offset; i < end; i++) { for (int i = offset; i < end; i++) {
@ -288,8 +274,8 @@ public class RenderBuffer {
public final RenderBuffer putDouble(double x) { public final RenderBuffer putDouble(double x) {
// assert (position() % SIZEOF_DOUBLE == 0); // assert (position() % SIZEOF_DOUBLE == 0);
unsafe.putDouble(curAddress, x); segment.set(JAVA_DOUBLE, curOffset, x);
curAddress += SIZEOF_DOUBLE; curOffset += SIZEOF_DOUBLE;
return this; return this;
} }
} }