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.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,58 +25,54 @@
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
* 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
* alignment or bounds checks for performance reasons. Therefore, it is
* the caller's responsibility to ensure that all put() calls are properly
* aligned and within bounds:
* - int and float values must be aligned on 4-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
* 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.
*/
public class RenderBuffer {
public final class RenderBuffer {
/**
* These constants represent the size of various data types (in bytes).
*/
protected static final long SIZEOF_BYTE = 1L;
protected static final long SIZEOF_SHORT = 2L;
protected static final long SIZEOF_INT = 4L;
protected static final long SIZEOF_FLOAT = 4L;
protected static final long SIZEOF_LONG = 8L;
protected static final long SIZEOF_DOUBLE = 8L;
private static final int SIZEOF_BYTE = Byte.BYTES;
private static final int SIZEOF_SHORT = Short.BYTES;
private static final int SIZEOF_INT = Integer.BYTES;
private static final int SIZEOF_FLOAT = Float.BYTES;
private static final int SIZEOF_LONG = Long.BYTES;
private static final int SIZEOF_DOUBLE = Double.BYTES;
/**
* Represents the number of elements at which we have empirically
* determined that the average cost of a JNI call exceeds the expense
* of an element by element copy. In other words, if the number of
* elements in an array to be copied exceeds this value, then we should
* 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.)
* Measurements show that using the copy API from a segment backed by a heap
* array gets reliably faster than individual puts around a length of 10.
* However the time is miniscule in the context of what it is used for
* and much more than adequate, so no problem expected if this changes over time.
*/
private static final int COPY_FROM_ARRAY_THRESHOLD = 6;
private static final int COPY_FROM_ARRAY_THRESHOLD = 10;
protected final Unsafe unsafe;
protected final long baseAddress;
protected final long endAddress;
protected long curAddress;
protected final int capacity;
private final MemorySegment segment;
private int curOffset;
protected RenderBuffer(int numBytes) {
unsafe = Unsafe.getUnsafe();
curAddress = baseAddress = unsafe.allocateMemory(numBytes);
endAddress = baseAddress + numBytes;
capacity = numBytes;
private RenderBuffer(int numBytes) {
segment = Arena.global().allocate(numBytes, SIZEOF_DOUBLE);
curOffset = 0;
}
/**
@ -90,7 +86,7 @@ public class RenderBuffer {
* Returns the base address of the underlying memory buffer.
*/
public final long getAddress() {
return baseAddress;
return segment.address();
}
/**
@ -99,27 +95,27 @@ public class RenderBuffer {
*/
public final int capacity() {
return capacity;
return (int)segment.byteSize();
}
public final int remaining() {
return (int)(endAddress - curAddress);
return (capacity() - curOffset);
}
public final int position() {
return (int)(curAddress - baseAddress);
return curOffset;
}
public final void position(long numBytes) {
curAddress = baseAddress + numBytes;
public final void position(int bytePos) {
curOffset = bytePos;
}
public final void clear() {
curAddress = baseAddress;
curOffset = 0;
}
public final RenderBuffer skip(long numBytes) {
curAddress += numBytes;
public final RenderBuffer skip(int numBytes) {
curOffset += numBytes;
return this;
}
@ -128,8 +124,8 @@ public class RenderBuffer {
*/
public final RenderBuffer putByte(byte x) {
unsafe.putByte(curAddress, x);
curAddress += SIZEOF_BYTE;
segment.set(JAVA_BYTE, curOffset, x);
curOffset += SIZEOF_BYTE;
return this;
}
@ -139,10 +135,8 @@ public class RenderBuffer {
public RenderBuffer put(byte[] x, int offset, int length) {
if (length > COPY_FROM_ARRAY_THRESHOLD) {
long offsetInBytes = offset * SIZEOF_BYTE + Unsafe.ARRAY_BYTE_BASE_OFFSET;
long lengthInBytes = length * SIZEOF_BYTE;
unsafe.copyMemory(x, offsetInBytes, null, curAddress, lengthInBytes);
position(position() + lengthInBytes);
MemorySegment.copy(x, offset, segment, JAVA_BYTE, curOffset, length);
position(position() + length * SIZEOF_BYTE);
} else {
int end = offset + length;
for (int i = offset; i < end; i++) {
@ -158,8 +152,8 @@ public class RenderBuffer {
public final RenderBuffer putShort(short x) {
// assert (position() % SIZEOF_SHORT == 0);
unsafe.putShort(curAddress, x);
curAddress += SIZEOF_SHORT;
segment.set(JAVA_SHORT, curOffset, x);
curOffset += SIZEOF_SHORT;
return this;
}
@ -170,10 +164,8 @@ public class RenderBuffer {
public RenderBuffer put(short[] x, int offset, int length) {
// assert (position() % SIZEOF_SHORT == 0);
if (length > COPY_FROM_ARRAY_THRESHOLD) {
long offsetInBytes = offset * SIZEOF_SHORT + Unsafe.ARRAY_SHORT_BASE_OFFSET;
long lengthInBytes = length * SIZEOF_SHORT;
unsafe.copyMemory(x, offsetInBytes, null, curAddress, lengthInBytes);
position(position() + lengthInBytes);
MemorySegment.copy(x, offset, segment, JAVA_SHORT, curOffset, length);
position(position() + length * SIZEOF_SHORT);
} else {
int end = offset + length;
for (int i = offset; i < end; i++) {
@ -188,15 +180,15 @@ public class RenderBuffer {
*/
public final RenderBuffer putInt(int pos, int x) {
// assert (baseAddress + pos % SIZEOF_INT == 0);
unsafe.putInt(baseAddress + pos, x);
// assert (getAddress() + pos % SIZEOF_INT == 0);
segment.set(JAVA_INT, pos, x);
return this;
}
public final RenderBuffer putInt(int x) {
// assert (position() % SIZEOF_INT == 0);
unsafe.putInt(curAddress, x);
curAddress += SIZEOF_INT;
segment.set(JAVA_INT, curOffset, x);
curOffset += SIZEOF_INT;
return this;
}
@ -207,10 +199,8 @@ public class RenderBuffer {
public RenderBuffer put(int[] x, int offset, int length) {
// assert (position() % SIZEOF_INT == 0);
if (length > COPY_FROM_ARRAY_THRESHOLD) {
long offsetInBytes = offset * SIZEOF_INT + Unsafe.ARRAY_INT_BASE_OFFSET;
long lengthInBytes = length * SIZEOF_INT;
unsafe.copyMemory(x, offsetInBytes, null, curAddress, lengthInBytes);
position(position() + lengthInBytes);
MemorySegment.copy(x, offset, segment, JAVA_INT, curOffset, length);
position(position() + length * SIZEOF_INT);
} else {
int end = offset + length;
for (int i = offset; i < end; i++) {
@ -226,8 +216,8 @@ public class RenderBuffer {
public final RenderBuffer putFloat(float x) {
// assert (position() % SIZEOF_FLOAT == 0);
unsafe.putFloat(curAddress, x);
curAddress += SIZEOF_FLOAT;
segment.set(JAVA_FLOAT, curOffset, x);
curOffset += SIZEOF_FLOAT;
return this;
}
@ -238,10 +228,8 @@ public class RenderBuffer {
public RenderBuffer put(float[] x, int offset, int length) {
// assert (position() % SIZEOF_FLOAT == 0);
if (length > COPY_FROM_ARRAY_THRESHOLD) {
long offsetInBytes = offset * SIZEOF_FLOAT + Unsafe.ARRAY_FLOAT_BASE_OFFSET;
long lengthInBytes = length * SIZEOF_FLOAT;
unsafe.copyMemory(x, offsetInBytes, null, curAddress, lengthInBytes);
position(position() + lengthInBytes);
MemorySegment.copy(x, offset, segment, JAVA_FLOAT, curOffset, length);
position(position() + length * SIZEOF_FLOAT);
} else {
int end = offset + length;
for (int i = offset; i < end; i++) {
@ -257,8 +245,8 @@ public class RenderBuffer {
public final RenderBuffer putLong(long x) {
// assert (position() % SIZEOF_LONG == 0);
unsafe.putLong(curAddress, x);
curAddress += SIZEOF_LONG;
segment.set(JAVA_LONG, curOffset, x);
curOffset += SIZEOF_LONG;
return this;
}
@ -269,10 +257,8 @@ public class RenderBuffer {
public RenderBuffer put(long[] x, int offset, int length) {
// assert (position() % SIZEOF_LONG == 0);
if (length > COPY_FROM_ARRAY_THRESHOLD) {
long offsetInBytes = offset * SIZEOF_LONG + Unsafe.ARRAY_LONG_BASE_OFFSET;
long lengthInBytes = length * SIZEOF_LONG;
unsafe.copyMemory(x, offsetInBytes, null, curAddress, lengthInBytes);
position(position() + lengthInBytes);
MemorySegment.copy(x, offset, segment, JAVA_LONG, curOffset, length);
position(position() + length * SIZEOF_LONG);
} else {
int end = offset + length;
for (int i = offset; i < end; i++) {
@ -288,8 +274,8 @@ public class RenderBuffer {
public final RenderBuffer putDouble(double x) {
// assert (position() % SIZEOF_DOUBLE == 0);
unsafe.putDouble(curAddress, x);
curAddress += SIZEOF_DOUBLE;
segment.set(JAVA_DOUBLE, curOffset, x);
curOffset += SIZEOF_DOUBLE;
return this;
}
}