package com.grapeshot.halfnes; import com.grapeshot.halfnes.ui.ControllerImpl; import com.grapeshot.halfnes.ui.GUIInterface; import com.grapeshot.halfnes.ui.OnScreenMenu; import com.grapeshot.halfnes.video.NesColors; import java.nio.ByteBuffer; import java.util.List; import javafx.application.Application; import javafx.application.Platform; import javafx.geometry.Insets; import javafx.geometry.Rectangle2D; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.image.PixelWriter; import javafx.scene.image.WritablePixelFormat; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCombination; import javafx.scene.paint.Color; import javafx.scene.transform.Scale; import javafx.stage.Stage; /** * @author Stephen Chin - steveonjava@gmail.com */ public class JavaFXNES extends Application implements GUIInterface { // Set the overscan insets to match your config // And make sure your framebuffer is set to: // * screen.width + overscan.right // * screen.height + overscan.bottom //overscan for PC private static final Insets overscan = new Insets(0, 0, 0, 0); //overscan for Pi screen //private static final Insets overscan = new Insets(-59, 160, 150, 0); private static final Insets extraOverscan = new Insets(8, 0, 8, 0); private NES nes; private Canvas gameCanvas; private Stage stage; private OnScreenMenu menu; @Override public void start(Stage stage) throws Exception { this.stage = stage; //Rectangle2D bounds = Screen.getPrimary().getBounds(); Rectangle2D bounds = new Rectangle2D(0,0,640,480); gameCanvas = new Canvas(256, 240); stage.addEventHandler(javafx.stage.WindowEvent.WINDOW_CLOSE_REQUEST, e -> nes.quit()); menu = new OnScreenMenu(this); //menu.setPadding(extraOverscan); menu.setPrefWidth(256); menu.setPrefHeight(240); Group root = new Group(gameCanvas, menu); Scene scene = new Scene(root, bounds.getWidth(), bounds.getHeight(), Color.BLACK); stage.setScene(scene); //stage.setFullScreen(true); stage.setFullScreenExitKeyCombination(KeyCombination.valueOf("F11")); stage.addEventHandler(javafx.scene.input.KeyEvent.KEY_PRESSED, e -> { if (e.getCode().equals(KeyCode.ESCAPE)) { menu.show(); } }); root.setLayoutX(overscan.getRight() - overscan.getLeft() - extraOverscan.getLeft() * bounds.getWidth() / 256); root.setLayoutY(overscan.getBottom() - overscan.getTop() - extraOverscan.getTop() * bounds.getHeight() / 240); root.getTransforms().add(new Scale( (bounds.getWidth() - (overscan.getRight() - overscan.getLeft())) / (256 - extraOverscan.getLeft() - extraOverscan.getRight()), (bounds.getHeight() - (overscan.getBottom() - overscan.getTop())) / (240 - extraOverscan.getTop() - extraOverscan.getBottom()))); nes = new NES(this); ControllerImpl padController1 = new ControllerImpl(scene, 0); ControllerImpl padController2 = new ControllerImpl(scene, 1); padController1.startEventQueue(); padController2.startEventQueue(); nes.setControllers(padController1, padController2); final List<String> params = getParameters().getRaw(); new Thread(() -> { if (params.isEmpty()) { nes.run(); } else { nes.run(params.get(0)); } }, "Game Thread").start(); } public static void main(String[] args) { JInputHelper.setupJInput(); launch(args); } @Override public NES getNes() { return nes; } @Override public void setNES(NES nes) { this.nes = nes; } final byte[] buffer = new byte[256 * 240 * 4]; final WritablePixelFormat<ByteBuffer> format = WritablePixelFormat.getByteBgraPreInstance(); private final long[] frametimes = new long[60]; private int frametimeptr = 0; private double fps; @Override public void setFrame(int[] nespixels, int[] bgcolor, boolean dotcrawl) { Platform.runLater(() -> { frametimes[frametimeptr] = nes.getFrameTime(); ++frametimeptr; frametimeptr %= frametimes.length; if (frametimeptr == 0) { long averageframes = 0; for (long l : frametimes) { averageframes += l; } averageframes /= frametimes.length; fps = 1E9 / averageframes; stage.setTitle(String.format("HalfNES %s, %2.2f fps", // + ((nes.frameskip > 0) ? " frameskip " + nes.frameskip : ""), NES.VERSION, // nes.getCurrentRomName(), fps)); } PixelWriter writer = gameCanvas.getGraphicsContext2D().getPixelWriter(); for (int i = 0; i < nespixels.length; i++) { byte[] colbytes = NesColors.colbytes[(nespixels[i] & 0x1c0) >> 6][nespixels[i] & 0x3f]; System.arraycopy(colbytes, 0, buffer, i * 4, 3); } writer.setPixels(0, 0, 256, 240, format, buffer, 0, 256 * 4); }); } @Override public void messageBox(String message) { System.out.println("message = " + message); } @Override public void run() { Platform.runLater(() -> { stage.show(); menu.show(); }); } @Override public void render() { // whatever... } public void loadROMs(String path) { menu.loadROMs(path); } }