Fix RCO menu coordinates and smooth video pacing

RCO object positions are screen-center with +Y up, but the renderer drew
them as top-left, pushing menu items off-screen. Convert to top-left in
the 480x272 design space before drawing.

Also use precise parkNanos pacing and reuse per-frame ARGB/WritableImage
buffers to remove the 1.4MB allocations per video frame.
This commit is contained in:
2026-09-15 23:45:30 -04:00
parent 7d601dd1fd
commit 0a16d3d3ec
3 changed files with 43 additions and 13 deletions
@@ -35,6 +35,7 @@ import javax.sound.sampled.SourceDataLine;
import java.io.IOException;
import java.nio.file.Path;
import java.util.concurrent.locks.LockSupport;
/** JavaFX application entry point for the UMD player. */
public final class PlayerApp extends Application {
@@ -60,6 +61,10 @@ public final class PlayerApp extends Application {
private volatile long clockOffsetNanos = 0;
private long pauseStartNanos = 0;
private final WritableImage[] videoImages = new WritableImage[2];
private final int[][] argbBuffers = new int[2][];
private int videoBufferIndex = 0;
@Override
public void start(Stage stage) {
// Gradle's --args splits on whitespace; rejoin so paths with spaces work.
@@ -197,11 +202,17 @@ public final class PlayerApp extends Application {
PlaybackEngine.VideoFrame frame;
while ((frame = engine.nextVideoFrame()) != null) {
int[] argb = YuvConverter.yuvj420ToArgb(frame.width(), frame.height(),
int index = videoBufferIndex++ & 1;
int[] argb = argbBuffers[index];
int pixelCount = frame.width() * frame.height();
if (argb == null || argb.length < pixelCount) {
argb = argbBuffers[index] = new int[pixelCount];
}
YuvConverter.yuvj420ToArgb(frame.width(), frame.height(),
frame.yPlane(), frame.yStride(),
frame.uPlane(), frame.uStride(),
frame.vPlane(), frame.vStride());
blitVideo(argb, frame.width(), frame.height());
frame.vPlane(), frame.vStride(), argb);
blitVideo(argb, index, frame.width(), frame.height());
checkPaused();
long targetNanos = baseNanos + clockOffsetNanos + (frame.pts() - basePts) * 1_000_000_000L / PlaybackEngine.MPEG_TIMESTAMP;
@@ -275,10 +286,10 @@ public final class PlayerApp extends Application {
}
}
private static void sleepUntil(long targetNanos) throws InterruptedException {
long sleepNanos = targetNanos - System.nanoTime();
if (sleepNanos > 0) {
Thread.sleep(sleepNanos / 1_000_000, (int) (sleepNanos % 1_000_000));
private static void sleepUntil(long targetNanos) {
long remaining;
while ((remaining = targetNanos - System.nanoTime()) > 0) {
LockSupport.parkNanos(remaining);
}
}
@@ -330,10 +341,15 @@ public final class PlayerApp extends Application {
}
}
private void blitVideo(int[] argb, int width, int height) {
WritableImage image = new WritableImage(width, height);
private void blitVideo(int[] argb, int index, int width, int height) {
WritableImage image = videoImages[index];
if (image == null || (int) image.getWidth() != width || (int) image.getHeight() != height) {
image = new WritableImage(width, height);
videoImages[index] = image;
}
image.getPixelWriter().setPixels(0, 0, width, height, PixelFormat.getIntArgbInstance(), argb, 0, width);
final WritableImage current = image;
Platform.runLater(() -> {
GraphicsContext g = videoCanvas.getGraphicsContext2D();
double canvasWidth = videoCanvas.getWidth();
@@ -357,7 +373,7 @@ public final class PlayerApp extends Application {
}
double x = (canvasWidth - drawW) / 2.0;
double y = (canvasHeight - drawH) / 2.0;
g.drawImage(image, x, y, drawW, drawH);
g.drawImage(current, x, y, drawW, drawH);
});
}
@@ -129,7 +129,14 @@ public final class RcoRenderer implements Display {
int drawHeight = display.getHeight();
double w = drawWidth > 0 ? drawWidth : image.getWidth();
double h = drawHeight > 0 ? drawHeight : image.getHeight();
g.drawImage(image, display.getX(), display.getY(), w, h);
// RCO positions are screen-center coordinates with +Y up (OpenGL-like).
// Convert to top-left within the 480x272 design space.
double cx = display.getX();
double cy = display.getY();
double x = 240.0 + (cx - w / 2.0);
double y = 136.0 - (cy + h / 2.0);
g.drawImage(image, x, y, w, h);
g.setGlobalAlpha(1.0);
}