JBR-9283: bumpCounter() renamed to incrementCounter() + added addStat(window, name, value) used by MTLLayer.m to report blitTexture & nextDrawable timings (ms)

This commit is contained in:
bourgesl
2025-09-03 12:31:49 +02:00
parent 9af13e4e26
commit 31c9da843b
8 changed files with 132 additions and 56 deletions

View File

@@ -33,7 +33,6 @@ import sun.lwawt.macosx.CFLayer;
import sun.util.logging.PlatformLogger;
import java.awt.Component;
import java.awt.GraphicsConfiguration;
import java.awt.Insets;
import java.awt.Window;
@@ -151,11 +150,25 @@ public final class MTLLayer extends CFLayer {
}
}
private final static String[] STAT_NAMES = new String[]{
"java2d.native.mtlLayer.drawInMTLContext", // type = 0
"java2d.native.mtlLayer.nextDrawable" // type = 1
};
private void addStat(int type, double value) {
// Called from the native code when this layer has been presented on screen
Component target = peer.getTarget();
if (target instanceof Window window) {
AWTAccessor.getWindowAccessor().addStat(window,
((type >= 0) && (type < STAT_NAMES.length)) ? STAT_NAMES[type] : "undefined", value);
}
}
private void countNewFrame() {
// Called from the native code when this layer has been presented on screen
Component target = peer.getTarget();
if (target instanceof Window window) {
AWTAccessor.getWindowAccessor().bumpCounter(window, "java2d.native.frames");
AWTAccessor.getWindowAccessor().incrementCounter(window, "java2d.native.frames");
}
}
@@ -165,7 +178,7 @@ public final class MTLLayer extends CFLayer {
// when those attempts are too frequent.
Component target = peer.getTarget();
if (target instanceof Window window) {
AWTAccessor.getWindowAccessor().bumpCounter(window, "java2d.native.framesDropped");
AWTAccessor.getWindowAccessor().incrementCounter(window, "java2d.native.framesDropped");
}
}
}

View File

@@ -73,6 +73,8 @@
- (void) stopRedraw:(MTLContext*)mtlc displayID:(jint)displayID force:(BOOL)force;
- (void) flushBuffer;
- (void) commitCommandBuffer:(MTLContext*)mtlc wait:(BOOL)waitUntilCompleted display:(BOOL)updateDisplay;
- (void) addStatCallback:(int)type value:(double)value;
- (void) countFramePresentedCallback;
- (void) countFrameDroppedCallback;
@end

View File

@@ -255,18 +255,14 @@ BOOL MTLLayer_isExtraRedrawEnabled() {
return;
}
CFTimeInterval beforeDrawableTime = (TRACE_DISPLAY_INFO) ? CACurrentMediaTime() : 0.0;
CFTimeInterval nextDrawableTime = 0.0;
const CFTimeInterval beforeDrawableTime = CACurrentMediaTime();
[_lockDrawable lock];
@try {
if (self->_nextDrawableRef != nil) {
mtlDrawable = self->_nextDrawableRef;
self->_nextDrawableRef = nil;
if (TRACE_DISPLAY_INFO) {
nextDrawableTime = beforeDrawableTime;
beforeDrawableTime = self->_drawableTime;
}
// TODO: use or validate timestamp in self->_drawableTime ?
}
} @finally {
[_lockDrawable unlock];
@@ -299,23 +295,22 @@ BOOL MTLLayer_isExtraRedrawEnabled() {
// free the running flag:
self.asyncNextDrawableRunning = false;
[self release];
});
// TODO: adjust timeout according to the layer's display rate (see displaylink interval) using tick interval estimate
const long asyncTimeout = TIMEOUT_MS; // multiply by 1/60 scale depending on monitor freq (displaySync=true) ?
dispatch_async(concurrentQueue, async_block);
// returns zero if the dispatch block completed within the specified timeout, or non-zero if the block timed out.
// TODO: adjust timeout according to the layer's display rate (see displaylink interval)
intptr_t status = dispatch_block_wait(async_block, dispatch_time(DISPATCH_TIME_NOW, TIMEOUT_MS));
nextDrawableTime = (TRACE_DISPLAY_INFO) ? CACurrentMediaTime() : 0.0;
intptr_t status = dispatch_block_wait(async_block, dispatch_time(DISPATCH_TIME_NOW, asyncTimeout));
Block_release(async_block);
if (status != 0) {
#if TRACE_DISPLAY_ON
const CFTimeInterval nextDrawableTimeout = (nextDrawableTime - beforeDrawableTime);
const CFTimeInterval nextDrawableTimeout = (CACurrentMediaTime() - beforeDrawableTime);
J2dRlsTraceLn(J2D_TRACE_VERBOSE, "[%.6lf] MTLLayer_blitTexture: nextDrawable failed (status = %d)"
" - nextDrawableTimeout = %.3lf ms ",
CACurrentMediaTime(), status, 1000.0 * nextDrawableTimeout);
@@ -336,20 +331,23 @@ BOOL MTLLayer_isExtraRedrawEnabled() {
return;
}
}
const CFTimeInterval nextDrawableTime = CACurrentMediaTime();
const CFTimeInterval nextDrawableLatency = (nextDrawableTime - beforeDrawableTime);
if (nextDrawableLatency > 0.0) {
[self addStatCallback:1 value:1000.0 * nextDrawableLatency]; // See MTLLayer.STAT_NAMES[1]
#if TRACE_DISPLAY_ON
const CFTimeInterval nextDrawableLatency = (nextDrawableTime - beforeDrawableTime);
if (nextDrawableLatency > 0.0) {
self.avgNextDrawableTime = nextDrawableLatency * a + self.avgNextDrawableTime * (1.0 - a);
}
J2dRlsTraceLn(J2D_TRACE_VERBOSE,
"[%.6lf] MTLLayer_blitTexture: drawable(%d) acquired"
" - nextDrawableLatency = %.3lf ms - average = %.3lf ms",
CACurrentMediaTime(), mtlDrawable.drawableID,
1000.0 * nextDrawableLatency, 1000.0 * self.avgNextDrawableTime
);
#endif
J2dRlsTraceLn(J2D_TRACE_VERBOSE,
"[%.6lf] MTLLayer_blitTexture: drawable(%d) acquired"
" - nextDrawableLatency = %.3lf ms - average = %.3lf ms",
CACurrentMediaTime(), mtlDrawable.drawableID,
1000.0 * nextDrawableLatency, 1000.0 * self.avgNextDrawableTime
);
#endif
}
id<MTLCommandBuffer> commandBuf = [self.ctx createBlitCommandBuffer];
if (commandBuf == nil) {
J2dTraceLn(J2D_TRACE_VERBOSE, "MTLLayer.blitTexture: commandBuf is null");
@@ -503,8 +501,15 @@ BOOL MTLLayer_isExtraRedrawEnabled() {
return;
}
const CFTimeInterval beforeMethod = CACurrentMediaTime();
(*env)->CallVoidMethod(env, javaLayerLocalRef, jm_drawInMTLContext);
CHECK_EXCEPTION();
const CFTimeInterval drawInMTLContextLatency = (CACurrentMediaTime() - beforeMethod);
if (drawInMTLContextLatency > 0.0) {
[self addStatCallback:0 value:1000.0 * drawInMTLContextLatency]; // See MTLLayer.STAT_NAMES[0]
}
(*env)->DeleteLocalRef(env, javaLayerLocalRef);
}
@@ -630,6 +635,20 @@ BOOL MTLLayer_isExtraRedrawEnabled() {
}
}
- (void) addStatCallback:(int)type value:(double)value {
// attach the current thread to the JVM if necessary, and get an env
JNIEnv* env = [ThreadUtilities getJNIEnvUncached];
GET_MTL_LAYER_CLASS();
DECLARE_METHOD(jm_addStatFrame, jc_JavaLayer, "addStat", "(ID)V");
jobject javaLayerLocalRef = (*env)->NewLocalRef(env, self.javaLayer);
if (javaLayerLocalRef != NULL) {
(*env)->CallVoidMethod(env, javaLayerLocalRef, jm_addStatFrame, (jint)type, (jdouble)value);
CHECK_EXCEPTION();
(*env)->DeleteLocalRef(env, javaLayerLocalRef);
}
}
- (void) countFrameDroppedCallback {
// attach the current thread to the JVM if necessary, and get an env
JNIEnv* env = [ThreadUtilities getJNIEnvUncached];

View File

@@ -4237,13 +4237,13 @@ public class Window extends Container implements Accessible {
private final static long NANO_IN_SEC = java.util.concurrent.TimeUnit.SECONDS.toNanos(1);
public void bumpCounter(final Window w, final String counterName) {
public void incrementCounter(final Window w, final String counterName) {
if (USE_COUNTERS) {
Objects.requireNonNull(w);
Objects.requireNonNull(counterName);
final long curTimeNanos = System.nanoTime();
// use try-catch to avoid throwing runtime exception to native JNI callers !
// use try-catch to avoid throwing runtime exception to native JNI callers!
try {
PerfCounter newCounter, prevCounter;
synchronized (w.perfCounters) {
@@ -4264,20 +4264,33 @@ public class Window extends Container implements Accessible {
synchronized (w.perfCountersPrev) {
w.perfCountersPrev.put(counterName, newCounter);
}
synchronized (w.perfStats) {
StatDouble stat = w.perfStats.computeIfAbsent(counterName, StatDouble::new);
stat.add(valPerSecond);
// update global stats (no reset):
stat = w.perfStats.computeIfAbsent(counterName + STATS_ALL_SUFFIX, StatDouble::new);
stat.add(valPerSecond);
}
addStat(w, counterName, valPerSecond);
if (TRACE_COUNTERS) {
dumpCounter(counterName, valPerSecond);
}
}
}
} catch (RuntimeException re) {
perfLog.severe("bumpCounter: failed", re);
perfLog.severe("incrementCounter: failed", re);
}
}
}
public void addStat(final Window w, final String statName, final double value) {
if (USE_COUNTERS && Double.isFinite(value)) {
Objects.requireNonNull(w);
Objects.requireNonNull(statName);
// use try-catch to avoid throwing runtime exception to native JNI callers!
try {
synchronized (w.perfStats) {
StatDouble stat = w.perfStats.computeIfAbsent(statName, StatDouble::new);
stat.add(value);
stat = w.perfStats.computeIfAbsent(statName + STATS_ALL_SUFFIX, StatDouble::new);
stat.add(value);
}
} catch (RuntimeException re) {
perfLog.severe("addStat: failed", re);
}
}
}

View File

@@ -27,10 +27,7 @@ package javax.swing;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.VolatileImage;
import java.awt.peer.WindowPeer;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
@@ -42,14 +39,12 @@ import sun.java2d.SunGraphicsEnvironment;
import com.sun.java.swing.SwingUtilities3;
import java.awt.geom.AffineTransform;
import java.util.stream.Collectors;
import sun.java2d.SunGraphics2D;
import sun.java2d.pipe.Region;
import sun.swing.SwingAccessor;
import sun.swing.SwingUtilities2;
import sun.swing.SwingUtilities2.RepaintListener;
import java.util.stream.Collectors;
/**
* This class manages repaint requests, allowing the number
@@ -723,7 +718,7 @@ public class RepaintManager
.filter(Objects::nonNull)
.distinct()
.forEach(w -> AWTAccessor.getWindowAccessor()
.bumpCounter(w, "swing.RepaintManager.updateWindows"));
.incrementCounter(w, "swing.RepaintManager.updateWindows"));
if (Toolkit.getDefaultToolkit() instanceof SunToolkit sunToolkit &&
sunToolkit.needUpdateWindow()) {
@@ -742,14 +737,14 @@ public class RepaintManager
for (Window window : windows) {
AWTAccessor.getWindowAccessor().updateWindow(window);
AWTAccessor.getWindowAccessor().bumpCounter(window, "swing.RepaintManager.updateWindows");
AWTAccessor.getWindowAccessor().incrementCounter(window, "swing.RepaintManager.updateWindows");
}
} else {
dirtyComponents.keySet().stream()
.map(c -> c instanceof Window w ? w : SwingUtilities.getWindowAncestor(c))
.filter(Objects::nonNull)
.forEach(w -> AWTAccessor.getWindowAccessor()
.bumpCounter(w, "swing.RepaintManager.updateWindows"));
.incrementCounter(w, "swing.RepaintManager.updateWindows"));
}
}

View File

@@ -37,7 +37,6 @@ import java.awt.event.InputEvent;
import java.awt.event.InvocationEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.image.BufferStrategy;
import java.awt.peer.ComponentPeer;
@@ -331,7 +330,8 @@ public final class AWTAccessor {
/* JBR Window counters API */
boolean countersEnabled(Window w);
void bumpCounter(Window w, String counterName);
void incrementCounter(Window w, String counterName);
void addStat(Window w, String statName, double value);
long getCounter(Window w, String counterName);
double getCounterPerSecond(Window w, String counterName);

View File

@@ -36,7 +36,6 @@ import java.util.Objects;
import sun.awt.AWTAccessor;
import sun.awt.wl.WLComponentPeer;
import sun.awt.wl.WLGraphicsConfig;
import sun.awt.wl.WLSMGraphicsConfig;
import sun.java2d.SurfaceData;
import sun.java2d.loops.SurfaceType;
@@ -189,7 +188,7 @@ public class WLSMSurfaceData extends SurfaceData implements WLSurfaceDataExt, WL
private void countNewFrame() {
// Called from the native code when this surface data has been sent to the Wayland server
if (target instanceof Window window) {
AWTAccessor.getWindowAccessor().bumpCounter(window, "java2d.native.frames");
AWTAccessor.getWindowAccessor().incrementCounter(window, "java2d.native.frames");
}
}
@@ -198,7 +197,7 @@ public class WLSMSurfaceData extends SurfaceData implements WLSurfaceDataExt, WL
// the Wayland server, but that attempt was not successful. This can happen, for example,
// when those attempts are too frequent.
if (target instanceof Window window) {
AWTAccessor.getWindowAccessor().bumpCounter(window, "java2d.native.framesDropped");
AWTAccessor.getWindowAccessor().incrementCounter(window, "java2d.native.framesDropped");
}
}

View File

@@ -101,12 +101,17 @@ public final class RenderPerfTest {
private final static String VERSION = "RenderPerfTest 2024.02";
private static final HashSet<String> ignoredRegexps = new HashSet<>();
private static final HashSet<String> ignoredTests = new HashSet<>();
static {
// add ignored tests here
// ignoredTests.add("testMyIgnoredTest");
ignoredTests.add("testCalibration"); // not from command line
ignoredRegexps.add("Sw");
ignoredRegexps.add("XOR");
}
private final static String EXEC_MODE_ROBOT = "robot";
@@ -2083,14 +2088,6 @@ public final class RenderPerfTest {
}
}
}
if (testCases.isEmpty()) {
for (Method m : RenderPerfTest.class.getDeclaredMethods()) {
if (m.getName().startsWith("test") && !ignoredTests.contains(m.getName())) {
testCases.add(m);
}
}
testCases.sort(Comparator.comparing(Method::getName));
}
if (help) {
help();
@@ -2127,6 +2124,43 @@ public final class RenderPerfTest {
System.out.print("##############################################################\n");
}
if (testCases.isEmpty()) {
System.out.println("# No tests selected.");
if (!ignoredRegexps.isEmpty()) {
System.out.print("# Ignored expressions: ");
System.out.print(ignoredRegexps);
System.out.println();
}
System.out.print("# Ignored tests: ");
for (Method m : RenderPerfTest.class.getDeclaredMethods()) {
if (m.getName().startsWith("test") && !ignoredTests.contains(m.getName())) {
boolean add = true;
if (!ignoredRegexps.isEmpty()) {
for (final String expr : ignoredRegexps) {
if (m.getName().contains(expr)) {
System.out.printf("%s ", m.getName());
add = false;
break;
}
}
}
if (add) {
testCases.add(m);
}
}
}
System.out.println();
testCases.sort(Comparator.comparing(Method::getName));
}
System.out.println("# Running tests: ");
for (Method m : testCases) {
System.out.print(m.getName());
System.out.print(" ");
}
System.out.println();
// Graphics Configuration handling:
final Set<String> fontNames = new LinkedHashSet<>();
final Map<String, GraphicsConfiguration> gcByID = new LinkedHashMap<>();
@@ -2220,6 +2254,7 @@ public final class RenderPerfTest {
}
System.out.println();
// Run tests:
final List<RenderPerfTest> instances = new ArrayList<>();
int retCode = 0;
try {