diff --git a/tests/performance/3DLighting/.classpath b/tests/performance/3DLighting/.classpath index ce2e49a0b60..af570b394de 100644 --- a/tests/performance/3DLighting/.classpath +++ b/tests/performance/3DLighting/.classpath @@ -1,8 +1,13 @@ - - + + + + + + + @@ -12,7 +17,12 @@ - + + + + + + diff --git a/tests/performance/3DLighting/src/main/java/attenuation/Boxes.java b/tests/performance/3DLighting/src/main/java/attenuation/Boxes.java deleted file mode 100644 index 4d136f94f82..00000000000 --- a/tests/performance/3DLighting/src/main/java/attenuation/Boxes.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (c) 2022, 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 - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package attenuation; - -import javafx.beans.binding.When; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.ObjectProperty; -import javafx.scene.Group; -import javafx.scene.control.CheckBox; -import javafx.scene.control.ColorPicker; -import javafx.scene.image.Image; -import javafx.scene.image.WritableImage; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.Pane; -import javafx.scene.paint.Color; -import javafx.scene.paint.PhongMaterial; -import javafx.scene.shape.Box; -import javafx.scene.transform.Rotate; - -class Boxes extends Group { - - private static final PhongMaterial MATERIAL = new PhongMaterial(); - - /** - * Creates a box-like structure with 3 edges. - * - * @param size distance from the center of the box to an edge - */ - Boxes(double size) { - var back = createBox(size); - back.setTranslateZ(size); - var right = createBox(size); - right.setRotationAxis(Rotate.Y_AXIS); - right.setRotate(90); - right.setTranslateX(size * 2); - right.setTranslateZ(-size); - var left = createBox(size); - left.setRotationAxis(Rotate.Y_AXIS); - left.setRotate(90); - left.setTranslateX(-size * 2); - left.setTranslateZ(-size); - getChildren().addAll(left, back, right); - } - - private Box createBox(double size) { - var box = new Box(size * 4, size * 4, 1); - box.setMaterial(MATERIAL); - return box; - } - - static Pane createBoxesControls() { - var diffColorOn = new CheckBox("Diff Color"); - diffColorOn.setSelected(true); - var diffColorPicker = new ColorPicker(Color.WHITE); - MATERIAL.diffuseColorProperty().bind(new When(diffColorOn.selectedProperty()) - .then(diffColorPicker.valueProperty()).otherwise((Color) null)); - - var specColorOn = new CheckBox("Spec Color"); - var specColorPicker = new ColorPicker(Color.BLACK); - MATERIAL.specularColorProperty().bind(new When(specColorOn.selectedProperty()) - .then(specColorPicker.valueProperty()).otherwise((Color) null)); - - var specPower = Controls.createSliderControl("Spec Power", MATERIAL.specularPowerProperty(), 0, 400, MATERIAL.getSpecularPower()); - - var diffMapOn = new CheckBox("Diff Map"); - var diffMapPicker = new ColorPicker(Color.BLACK); - setupMapBindings(MATERIAL.diffuseMapProperty(), diffMapPicker.valueProperty(), diffMapOn.selectedProperty()); - - var specMapOn = new CheckBox("Spec Map"); - var specMapPicker = new ColorPicker(Color.BLACK); - setupMapBindings(MATERIAL.specularMapProperty(), specMapPicker.valueProperty(), specMapOn.selectedProperty()); - - var selfIllumMapOn = new CheckBox("SelfIllum Map"); - var selfIllumMapPicker = new ColorPicker(Color.BLACK); - setupMapBindings(MATERIAL.selfIlluminationMapProperty(), selfIllumMapPicker.valueProperty(), selfIllumMapOn.selectedProperty()); - - var gridPane = new GridPane(); - int row = 0; - gridPane.add(diffColorOn, 0, row); - gridPane.add(diffColorPicker, 1, row); - row++; - gridPane.add(specColorOn, 0, row); - gridPane.add(specColorPicker, 1, row); - row++; - gridPane.add(specPower, 0, row, 2, 1); - row++; - gridPane.add(diffMapOn, 0, row); - gridPane.add(diffMapPicker, 1, row); - row++; - gridPane.add(specMapOn, 0, row); - gridPane.add(specMapPicker, 1, row); - row++; - gridPane.add(selfIllumMapOn, 0, row); - gridPane.add(selfIllumMapPicker, 1, row); - return gridPane; - } - - private static void setupMapBindings(ObjectProperty map, ObjectProperty colorProp, BooleanProperty on) { - var image = createMapImage(colorProp); - map.bind(new When(on).then(image).otherwise((WritableImage) null)); - } - - static Image createMapImage(ObjectProperty colorProp) { - var image = new WritableImage(1, 1); - image.getPixelWriter().setColor(0, 0, colorProp.get()); - colorProp.addListener((obs, ov, nv) -> image.getPixelWriter().setColor(0, 0, nv)); - return image; - } -} diff --git a/tests/performance/3DLighting/src/main/java/attenuation/Controls.java b/tests/performance/3DLighting/src/main/java/attenuation/Controls.java deleted file mode 100644 index 2a5dedd3f4a..00000000000 --- a/tests/performance/3DLighting/src/main/java/attenuation/Controls.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2022, 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 - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package attenuation; - -import java.util.List; - -import javafx.beans.binding.Bindings; -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.ObjectProperty; -import javafx.collections.ObservableList; -import javafx.geometry.Point3D; -import javafx.geometry.Pos; -import javafx.scene.DirectionalLight; -import javafx.scene.LightBase; -import javafx.scene.Node; -import javafx.scene.PointLight; -import javafx.scene.SpotLight; -import javafx.scene.control.CheckBox; -import javafx.scene.control.ColorPicker; -import javafx.scene.control.Label; -import javafx.scene.control.Slider; -import javafx.scene.control.TextField; -import javafx.scene.control.TitledPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Pane; -import javafx.scene.layout.VBox; -import javafx.scene.transform.Rotate; -import javafx.scene.transform.Transform; -import javafx.util.converter.NumberStringConverter; - -final class Controls { - - static TitledPane addPointLightControls(PointLight light) { - var controls = createPointLightControls(light); - return createLightControls(light, controls); - } - - static TitledPane addSpotLightControls(SpotLight light) { - var ia = createSliderControl("inner", light.innerAngleProperty(), 0, 180, light.getInnerAngle()); - var oa = createSliderControl("outer", light.outerAngleProperty(), 0, 180, light.getOuterAngle()); - var fo = createSliderControl("falloff", light.falloffProperty(), -5, 5, light.getFalloff()); - VBox controls = createPointLightControls(light); - controls.getChildren().addAll(ia, oa, fo); - - List directionControls = createDirectionControls(light.getTransforms(), light.directionProperty()); - controls.getChildren().addAll(directionControls); - return createLightControls(light, controls); - } - - private static VBox createPointLightControls(PointLight light) { - var range = createSliderControl("range", light.maxRangeProperty(), 0, 500, 150); - var c = createSliderControl("constant", light.constantAttenuationProperty(), -1, 1, light.getConstantAttenuation()); - var lc = createSliderControl("linear", light.linearAttenuationProperty(), -0.1, 0.1, light.getLinearAttenuation()); - var qc = createSliderControl("quadratic", light.quadraticAttenuationProperty(), -0.01, 0.01, light.getQuadraticAttenuation()); - return new VBox(range, c, lc, qc); - } - - static TitledPane addDirectionalLightControls(DirectionalLight light) { - List directionControls = createDirectionControls(light.getTransforms(), light.directionProperty()); - var controls = new VBox(directionControls.toArray(new Node[0])); - return createLightControls(light, controls); - } - - static TitledPane createLightControls(LightBase light, Pane content) { - var lightOn = new CheckBox(light.getClass().getSimpleName()); - light.lightOnProperty().bind(lightOn.selectedProperty()); - var colorPicker = new ColorPicker(light.getColor()); - light.colorProperty().bind(colorPicker.valueProperty()); - var titleControls = new HBox(5, lightOn, colorPicker); - titleControls.setAlignment(Pos.CENTER_LEFT); - - var titlePane = new TitledPane("", content); - titlePane.setGraphic(titleControls); - titlePane.setExpanded(false); - return titlePane; - } - - private static List createDirectionControls(ObservableList transforms, ObjectProperty directionProperty) { - var transX = new Rotate(0, Rotate.X_AXIS); - var transY = new Rotate(0, Rotate.Y_AXIS); - var transZ = new Rotate(0, Rotate.Z_AXIS); - transforms.addAll(transX, transY, transZ); - var rotX = createSliderControl("rot x", transX.angleProperty(), -180, 180, 0); - var rotY = createSliderControl("rot y", transY.angleProperty(), -180, 180, 0); - var rotZ = createSliderControl("rot z", transZ.angleProperty(), -180, 180, 0); - - var sliderX = createSlider(-5, 5, directionProperty.get().getX()); - var sliderY = createSlider(-5, 5, directionProperty.get().getY()); - var sliderZ = createSlider(-5, 5, directionProperty.get().getZ()); - directionProperty.bind(Bindings.createObjectBinding(() -> - new Point3D(sliderX.getValue(), sliderY.getValue(), sliderZ.getValue()), - sliderX.valueProperty(), sliderY.valueProperty(), sliderZ.valueProperty())); - var dirX = createSliderControl("dir x", sliderX); - var dirY = createSliderControl("dir y", sliderY); - var dirZ = createSliderControl("dir z", sliderZ); - - return List.of(rotX, rotY, rotZ, dirX, dirY, dirZ); - } - - static HBox createSliderControl(String name, DoubleProperty property, double min, double max, double start) { - var slider = createSlider(min, max, start); - property.bind(slider.valueProperty()); - return createSliderControl(name, slider); - } - - private static HBox createSliderControl(String name, Slider slider) { - var tf = createTextField(slider); - return new HBox(5, new Label(name), slider, tf); - } - - private static TextField createTextField(Slider slider) { - var tf = new TextField(); - tf.textProperty().bindBidirectional(slider.valueProperty(), new NumberStringConverter()); - tf.setMaxWidth(50); - return tf; - } - - private static Slider createSlider(double min, double max, double start) { - var slider = new Slider(min, max, start); - slider.setShowTickMarks(true); - slider.setShowTickLabels(true); - return slider; - } -} diff --git a/tests/performance/3DLighting/src/main/java/attenuation/FPSCounter.java b/tests/performance/3DLighting/src/main/java/attenuation/FPSCounter.java deleted file mode 100644 index 70744616a6a..00000000000 --- a/tests/performance/3DLighting/src/main/java/attenuation/FPSCounter.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2020, 2022, 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 - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package attenuation; - -import javafx.animation.AnimationTimer; - -final class FPSCounter extends AnimationTimer { - - private int skipFrames = 100; - private long lastTime = -1; - private long elapsedTime; - private int elapsedFrames; - private long totalElapsedTime; - private int totalElapsedFrames; - - @Override - public void handle(long now) { - if (skipFrames > 0) { - --skipFrames; - return; - } - - if (lastTime < 0) { - lastTime = System.nanoTime(); - elapsedTime = 0; - elapsedFrames = 0; - totalElapsedTime = 0; - totalElapsedFrames = 0; - return; - } - - long currTime = System.nanoTime(); - elapsedTime += currTime - lastTime; - elapsedFrames += 1; - totalElapsedTime += currTime - lastTime; - totalElapsedFrames += 1; - - double elapsedSeconds = elapsedTime / 1e9; - double totalElapsedSeconds = totalElapsedTime / 1e9; - if (elapsedSeconds >= 5.0) { - double fps = elapsedFrames / elapsedSeconds; - System.out.println(); - System.out.println("instant fps: " + fps); - double avgFps = totalElapsedFrames / totalElapsedSeconds; - System.out.println("average fps: " + avgFps); - System.out.flush(); - elapsedTime = 0; - elapsedFrames = 0; - } - - lastTime = currTime; - } - - void reset() { - skipFrames = 100; - lastTime = -1; - elapsedTime = 0; - elapsedFrames = 0; - totalElapsedTime = 0; - totalElapsedFrames = 0; - System.out.println(); - System.out.println(" --------------------- "); - } -} diff --git a/tests/performance/3DLighting/src/main/java/attenuation/LightingSample.java b/tests/performance/3DLighting/src/main/java/attenuation/LightingSample.java deleted file mode 100644 index 600d54dc1db..00000000000 --- a/tests/performance/3DLighting/src/main/java/attenuation/LightingSample.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (c) 2020, 2023, 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 - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package attenuation; - -import javafx.animation.Animation; -import javafx.animation.TranslateTransition; -import javafx.application.Application; -import javafx.scene.Node; -import javafx.scene.Scene; -import javafx.scene.control.Button; -import javafx.scene.control.CheckBox; -import javafx.scene.control.Label; -import javafx.scene.control.ScrollPane; -import javafx.scene.control.Slider; -import javafx.scene.control.TitledPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import javafx.scene.layout.VBox; -import javafx.stage.Stage; -import javafx.util.Duration; -import javafx.util.converter.NumberStringConverter; - -/** - * A sample application for measuring FPS for various 3D nodes with environmental lighting. - *

- * Important: make sure that no other application on your system is rendering heavy graphics, like videos, to a screen, - * as this will corrupt the measurement. - */ -public class LightingSample extends Application { - - private final Environment environment = new Environment(); - private final TranslateTransition animation = createAnimation(); - private final FPSCounter fpsCouner = new FPSCounter(); - - @Override - public void start(Stage stage) throws Exception { - var sphereControls = createSphereControls(); - var meshControls = createMeshControls(); - var boxesControls = createBoxesControls(); - - var playButton = new Button("Start"); - playButton.setOnAction(e -> startMeasurement()); - - var stopButton = new Button("Stop"); - stopButton.setOnAction(e -> stopMeasurement()); - - var animationControls = new HBox(5, playButton, stopButton); - - var defaultLightButton = new CheckBox("Force default light"); - defaultLightButton.setOnAction(e -> environment.forceDefaultLight(defaultLightButton.isSelected())); - - var controls = new VBox(sphereControls, meshControls, animationControls, boxesControls, defaultLightButton); - - environment.ambientLights.forEach(light -> controls.getChildren().add(Controls.createLightControls(light, null))); - environment.pointLights.forEach(light -> controls.getChildren().add(Controls.addPointLightControls(light))); - environment.spotLights.forEach(light -> controls.getChildren().add(Controls.addSpotLightControls(light))); - environment.directionalLights.forEach(light -> controls.getChildren().add(Controls.addDirectionalLightControls(light))); - - var hBox = new HBox(new ScrollPane(controls), environment); - HBox.setHgrow(environment, Priority.ALWAYS); - stage.setScene(new Scene(hBox)); - stage.setWidth(1100); - stage.setHeight(735); - stage.show(); - } - - private HBox createMeshControls() { - var quadSlider = new Slider(100, 5000, 1000); - quadSlider.setMajorTickUnit(100); - setupSlider(quadSlider); - - var quadLabel = new Label(); - quadLabel.textProperty().bindBidirectional(quadSlider.valueProperty(), new NumberStringConverter("#")); - - var mesh = new Button("Mesh"); - mesh.setOnAction(e -> switchTo(environment.createMeshView((int) quadSlider.getValue()))); - - var meshBox = new HBox(mesh, quadSlider, quadLabel); - return meshBox; - } - - private HBox createSphereControls() { - var subdivisionSlider = new Slider(10, 1000, 60); - subdivisionSlider.setMajorTickUnit(50); - setupSlider(subdivisionSlider); - - var subdivisionLabel = new Label(); - subdivisionLabel.textProperty().bindBidirectional(subdivisionSlider.valueProperty(), new NumberStringConverter("#")); - - var sphere = new Button("Sphere"); - sphere.setOnAction(e -> switchTo(environment.createSphere((int) subdivisionSlider.getValue()))); - - var sphereBox = new HBox(sphere, subdivisionSlider, subdivisionLabel); - return sphereBox; - } - - private Node createBoxesControls() { - var box = new Button("Create"); - - var titlePane = new TitledPane("Boxes", new VBox(1, box, new HBox(Boxes.createBoxesControls()))); - - titlePane.setExpanded(false); - box.setOnAction(e -> switchTo(environment.createBoxes())); - return titlePane; - } - - private void setupSlider(Slider slider) { - slider.setMinorTickCount(0); - slider.setShowTickLabels(true); - slider.setShowTickMarks(true); - slider.setSnapToTicks(true); - } - - private TranslateTransition createAnimation() { - var anim = new TranslateTransition(Duration.seconds(2)); - anim.setAutoReverse(true); - anim.setCycleCount(Animation.INDEFINITE); - anim.setFromZ(150); - anim.setToZ(0); - return anim; - } - - private void switchTo(Node node) { - stopMeasurement(); - environment.switchTo(node); - animation.setNode(node); - } - - private void startMeasurement() { - animation.playFromStart(); - fpsCouner.start(); - } - - private void stopMeasurement() { - fpsCouner.stop(); - fpsCouner.reset(); - animation.stop(); - } - - public static void main(String[] args) { - launch(args); - } -} diff --git a/tests/performance/3DLighting/src/main/java/lighting3D/Benchmark.java b/tests/performance/3DLighting/src/main/java/lighting3D/Benchmark.java new file mode 100644 index 00000000000..ce24b5e6c52 --- /dev/null +++ b/tests/performance/3DLighting/src/main/java/lighting3D/Benchmark.java @@ -0,0 +1,182 @@ +package lighting3D; + +import javafx.animation.Animation; +import javafx.animation.AnimationTimer; +import javafx.animation.TranslateTransition; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.Slider; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.HBox; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.scene.text.TextBoundsType; +import javafx.util.Duration; +import javafx.util.converter.NumberStringConverter; + +/** + * Responsible for performance measurements. + */ +final class Benchmark { + + private final static double SPHERE_RADIUS = 50; + + private final Environment environment; + private final FPSCounter fpsCouner = new FPSCounter(); + private final TranslateTransition animation = createAnimation(); + + Benchmark(Environment environment) { + this.environment = environment; + } + + private TranslateTransition createAnimation() { + var anim = new TranslateTransition(Duration.seconds(2)); + anim.setAutoReverse(true); + anim.setCycleCount(Animation.INDEFINITE); + anim.setFromZ(150); + anim.setToZ(0); + return anim; + } + + Button createStopButton() { + var stopGraphic = new Text("⏹"); + stopGraphic.setBoundsType(TextBoundsType.VISUAL); + stopGraphic.setFill(Color.RED); + stopGraphic.setFont(Font.font(20)); + + var stopButton = new Button("", stopGraphic); + stopButton.setPadding(new Insets(2.5)); + stopButton.setAlignment(Pos.CENTER_RIGHT); + stopButton.setTooltip(new Tooltip("Stop measurements")); + stopButton.setOnAction(e -> stopMeasurement()); + return stopButton; + } + + Button createPlayButton() { + var playGraphic = new Text("▶"); + playGraphic.setBoundsType(TextBoundsType.VISUAL); + playGraphic.setFill(Color.GREEN); + playGraphic.setFont(Font.font(40)); + + var playButton = new Button("", playGraphic); + playButton.setPadding(new Insets(1, 2, 2, 3)); + playButton.setTooltip(new Tooltip("Start measurements")); + playButton.setOnAction(e -> startMeasurement()); + return playButton; + } + + HBox createSphereControls() { + var subdivisionSlider = new Slider(10, 1000, 60); + subdivisionSlider.setMajorTickUnit(50); + setupSlider(subdivisionSlider); + + var subdivisionLabel = new Label(); + subdivisionLabel.textProperty().bindBidirectional(subdivisionSlider.valueProperty(), new NumberStringConverter("#")); + + var sphere = new Button("Sphere"); + sphere.setOnAction(e -> switchTo(Models.createSphere(SPHERE_RADIUS, (int) subdivisionSlider.getValue()))); + + return new HBox(sphere, subdivisionSlider, subdivisionLabel); + } + + HBox createMeshControls() { + var quadSlider = new Slider(100, 5000, 1000); + quadSlider.setMajorTickUnit(100); + setupSlider(quadSlider); + + var quadLabel = new Label(); + quadLabel.textProperty().bindBidirectional(quadSlider.valueProperty(), new NumberStringConverter("#")); + + var mesh = new Button("Mesh"); + mesh.setOnAction(e -> switchTo(Models.createMeshView((int) quadSlider.getValue()))); + + return new HBox(mesh, quadSlider, quadLabel); + } + + private void setupSlider(Slider slider) { + slider.setMinorTickCount(0); + slider.setShowTickLabels(true); + slider.setShowTickMarks(true); + slider.setSnapToTicks(true); + } + + private void switchTo(Node node) { + stopMeasurement(); + animation.setNode(node); + environment.switchTo(node); + } + + private void startMeasurement() { + animation.playFromStart(); + fpsCouner.start(); + } + + private void stopMeasurement() { + fpsCouner.stop(); + fpsCouner.reset(); + animation.stop(); + } + + private final class FPSCounter extends AnimationTimer { + + private int skipFrames = 100; + private long lastTime = -1; + private long elapsedTime; + private int elapsedFrames; + private long totalElapsedTime; + private int totalElapsedFrames; + + @Override + public void handle(long now) { + if (skipFrames > 0) { + --skipFrames; + return; + } + + if (lastTime < 0) { + lastTime = System.nanoTime(); + elapsedTime = 0; + elapsedFrames = 0; + totalElapsedTime = 0; + totalElapsedFrames = 0; + return; + } + + long currTime = System.nanoTime(); + elapsedTime += currTime - lastTime; + elapsedFrames += 1; + totalElapsedTime += currTime - lastTime; + totalElapsedFrames += 1; + + double elapsedSeconds = elapsedTime / 1e9; + double totalElapsedSeconds = totalElapsedTime / 1e9; + if (elapsedSeconds >= 5.0) { + double fps = elapsedFrames / elapsedSeconds; + System.out.println(); + System.out.println("instant fps: " + fps); + double avgFps = totalElapsedFrames / totalElapsedSeconds; + System.out.println("average fps: " + avgFps); + System.out.flush(); + elapsedTime = 0; + elapsedFrames = 0; + } + + lastTime = currTime; + } + + private void reset() { + skipFrames = 100; + lastTime = -1; + elapsedTime = 0; + elapsedFrames = 0; + totalElapsedTime = 0; + totalElapsedFrames = 0; + System.out.println(); + System.out.println(" --------------------- "); + } + } +} diff --git a/tests/performance/3DLighting/src/main/java/attenuation/CameraScene3D.java b/tests/performance/3DLighting/src/main/java/lighting3D/CameraScene3D.java similarity index 98% rename from tests/performance/3DLighting/src/main/java/attenuation/CameraScene3D.java rename to tests/performance/3DLighting/src/main/java/lighting3D/CameraScene3D.java index dcf18ca8387..5090dfca5ee 100644 --- a/tests/performance/3DLighting/src/main/java/attenuation/CameraScene3D.java +++ b/tests/performance/3DLighting/src/main/java/lighting3D/CameraScene3D.java @@ -23,7 +23,7 @@ * questions. */ -package attenuation; +package lighting3D; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; @@ -31,7 +31,6 @@ import javafx.beans.property.SimpleDoubleProperty; import javafx.scene.Group; import javafx.scene.PerspectiveCamera; -import javafx.scene.PointLight; import javafx.scene.SceneAntialiasing; import javafx.scene.SubScene; import javafx.scene.input.MouseButton; @@ -40,6 +39,9 @@ import javafx.scene.transform.Rotate; import javafx.scene.transform.Translate; +/** + * Camera controls for a 3D environment. + */ class CameraScene3D extends Pane { public DoubleProperty xPan = new SimpleDoubleProperty(); diff --git a/tests/performance/3DLighting/src/main/java/lighting3D/CaptureUtils.java b/tests/performance/3DLighting/src/main/java/lighting3D/CaptureUtils.java new file mode 100644 index 00000000000..92f3bc61d1f --- /dev/null +++ b/tests/performance/3DLighting/src/main/java/lighting3D/CaptureUtils.java @@ -0,0 +1,54 @@ +package lighting3D; + +import java.awt.Desktop; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import javax.imageio.ImageIO; + +import javafx.embed.swing.SwingFXUtils; +import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.image.Image; + +/** + * Utility class for creating screenshots. + */ +final class CaptureUtils { + + enum Format { + BMP, + GIF, + JPG, + PNG, + TIF + } + + private static final Path FOLDER = Path.of("screenshots"); + + private static int imageNum = 1; + + static void capture(Image fxImage, Format extension) { + BufferedImage image = SwingFXUtils.fromFXImage(fxImage, null); + BufferedImage rgbImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR); + rgbImage.getGraphics().drawImage(image, 0, 0, null); + + String formatName = extension.name().toLowerCase(); + var file = FOLDER.resolve(Path.of("screenshot" + imageNum + "." + formatName)).toAbsolutePath().toFile(); + try { + Files.createDirectories(FOLDER); + if (!ImageIO.write(rgbImage, formatName, file)) { + throw new IOException("No writer found for " + formatName); + } + Desktop.getDesktop().open(FOLDER.toAbsolutePath().toFile()); + } catch (IOException e) { + e.printStackTrace(); + var alert = new Alert(AlertType.ERROR); + alert.setContentText(e.getMessage()); + alert.showAndWait(); + } + imageNum++; + } +} diff --git a/tests/performance/3DLighting/src/main/java/lighting3D/Controls.java b/tests/performance/3DLighting/src/main/java/lighting3D/Controls.java new file mode 100644 index 00000000000..b44214db763 --- /dev/null +++ b/tests/performance/3DLighting/src/main/java/lighting3D/Controls.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2022, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package lighting3D; + +import java.util.List; + +import javafx.beans.binding.Bindings; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.collections.ObservableList; +import javafx.geometry.Point3D; +import javafx.geometry.Pos; +import javafx.geometry.VPos; +import javafx.scene.AmbientLight; +import javafx.scene.DirectionalLight; +import javafx.scene.LightBase; +import javafx.scene.Node; +import javafx.scene.PointLight; +import javafx.scene.SpotLight; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ColorPicker; +import javafx.scene.control.Label; +import javafx.scene.control.Slider; +import javafx.scene.control.TextField; +import javafx.scene.control.TitledPane; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.scene.transform.Rotate; +import javafx.scene.transform.Transform; +import javafx.util.converter.NumberStringConverter; + +/** + * Utility class for creating adjustment controls. + */ +final class Controls { + + static Node createLightControls(AmbientLight light) { + return createTitlePane(light, null); + } + + static Node createLightControls(PointLight light) { + var controls = createPointLightControls(light); + return createTitlePane(light, controls); + } + + static Node createLightControls(SpotLight light) { + var controls = createSpotLightControls(light); + return createTitlePane(light, controls); + } + + static Node createLightControls(DirectionalLight light) { + var controls = createDirectionControls(light.getTransforms(), light.directionProperty()); + return createTitlePane(light, controls); + } + + private static Pane createSpotLightControls(SpotLight light) { + GridPane gridPane = createPointLightControls(light); + + var ia = createSliderControl(light.innerAngleProperty(), 0, 180, light.getInnerAngle()); + var oa = createSliderControl(light.outerAngleProperty(), 0, 180, light.getOuterAngle()); + var fo = createSliderControl(light.falloffProperty(), -5, 5, light.getFalloff()); + gridPane.addRow(gridPane.getRowCount(), createLabel("inner", "Inner angle"), ia); + gridPane.addRow(gridPane.getRowCount(), createLabel("outer", "Outer angle"), oa); + gridPane.addRow(gridPane.getRowCount(), createLabel("falloff", "Falloff factor"), fo); + + GridPane directionControls = createDirectionControls(light.getTransforms(), light.directionProperty()); + var children = List.copyOf(directionControls.getChildren()); + int rowCount = gridPane.getRowCount(); + for (var child : children) { + gridPane.add(child, GridPane.getColumnIndex(child), GridPane.getRowIndex(child) + rowCount); + } + return gridPane; + } + + private static GridPane createPointLightControls(PointLight light) { + var gridPane = new GridPane(); + + var x = createSliderControl(light.translateXProperty(), -100, 100, light.getTranslateX()); + var y = createSliderControl(light.translateYProperty(), -100, 100, light.getTranslateY()); + var z = createSliderControl(light.translateZProperty(), -100, 100, light.getTranslateZ()); + gridPane.addRow(gridPane.getRowCount(), createLabel("x", "Translate x"), x); + gridPane.addRow(gridPane.getRowCount(), createLabel("y", "Translate y"), y); + gridPane.addRow(gridPane.getRowCount(), createLabel("z", "Translate z"), z); + + var range = createSliderControl(light.maxRangeProperty(), 0, 500, 150); + var ca = createSliderControl(light.constantAttenuationProperty(), -1, 1, light.getConstantAttenuation()); + var la = createSliderControl(light.linearAttenuationProperty(), -0.1, 0.1, light.getLinearAttenuation()); + var qa = createSliderControl(light.quadraticAttenuationProperty(), -0.01, 0.01, light.getQuadraticAttenuation()); + gridPane.addRow(gridPane.getRowCount(), createLabel("range", "Range"), range); + gridPane.addRow(gridPane.getRowCount(), createLabel("const", "Constant attenuation factor"), ca); + gridPane.addRow(gridPane.getRowCount(), createLabel("linear", "Linear attenuation factor"), la); + gridPane.addRow(gridPane.getRowCount(), createLabel("quad", "Quadratic attenuation factor"), qa); + + return gridPane; + } + + private static GridPane createDirectionControls(ObservableList transforms, ObjectProperty dirProp) { + var gridPane = new GridPane(); + + var transX = new Rotate(0, Rotate.X_AXIS); + var transY = new Rotate(0, Rotate.Y_AXIS); + var transZ = new Rotate(0, Rotate.Z_AXIS); + transforms.addAll(transX, transY, transZ); + var rotX = createSliderControl(transX.angleProperty(), -180, 180, 0); + var rotY = createSliderControl(transY.angleProperty(), -180, 180, 0); + var rotZ = createSliderControl(transZ.angleProperty(), -180, 180, 0); + gridPane.addRow(gridPane.getRowCount(), createLabel("rot x", "Rotate x"), rotX); + gridPane.addRow(gridPane.getRowCount(), createLabel("rot y", "Rotate y"), rotY); + gridPane.addRow(gridPane.getRowCount(), createLabel("rot z", "Rotate z"), rotZ); + + var sliderX = createSlider(-5, 5, dirProp.get().getX()); + var sliderY = createSlider(-5, 5, dirProp.get().getY()); + var sliderZ = createSlider(-5, 5, dirProp.get().getZ()); + dirProp.bind(Bindings.createObjectBinding(() -> + new Point3D(sliderX.getValue(), sliderY.getValue(), sliderZ.getValue()), + sliderX.valueProperty(), sliderY.valueProperty(), sliderZ.valueProperty())); + var dirX = createSliderControl(sliderX); + var dirY = createSliderControl(sliderY); + var dirZ = createSliderControl(sliderZ); + gridPane.addRow(gridPane.getRowCount(), createLabel("dir x", "Direction x"), dirX); + gridPane.addRow(gridPane.getRowCount(), createLabel("dir y", "Direction y"), dirY); + gridPane.addRow(gridPane.getRowCount(), createLabel("dir z", "Direction z"), dirZ); + + return gridPane; + } + + private static TitledPane createTitlePane(LightBase light, Pane content) { + var lightOn = new CheckBox(light.getClass().getSimpleName()); + light.lightOnProperty().bind(lightOn.selectedProperty()); + var colorPicker = new ColorPicker(light.getColor()); + light.colorProperty().bind(colorPicker.valueProperty()); + var titleControls = new HBox(5, lightOn, colorPicker); + titleControls.setAlignment(Pos.CENTER_LEFT); + + var titlePane = new TitledPane("", content); + titlePane.setGraphic(titleControls); + titlePane.setExpanded(false); + return titlePane; + } + + private static Label createLabel(String name, String tooltipText) { + var label = new Label(name); + label.setTooltip(new Tooltip(tooltipText)); + GridPane.setValignment(label, VPos.TOP); + return label; + } + + static HBox createSliderControl(DoubleProperty property, double min, double max, double start) { + var slider = createSlider(min, max, start); + property.bind(slider.valueProperty()); + return createSliderControl(slider); + } + + private static HBox createSliderControl(Slider slider) { + return new HBox(slider, createTextField(slider)); + } + + private static TextField createTextField(Slider slider) { + var tf = new TextField(); + tf.textProperty().bindBidirectional(slider.valueProperty(), new NumberStringConverter()); + tf.setMaxWidth(50); + return tf; + } + + private static Slider createSlider(double min, double max, double start) { + var slider = new Slider(min, max, start); + slider.setShowTickMarks(true); + slider.setShowTickLabels(true); + return slider; + } +} diff --git a/tests/performance/3DLighting/src/main/java/attenuation/Environment.java b/tests/performance/3DLighting/src/main/java/lighting3D/Environment.java similarity index 52% rename from tests/performance/3DLighting/src/main/java/attenuation/Environment.java rename to tests/performance/3DLighting/src/main/java/lighting3D/Environment.java index 07967339dc4..c5876b574c0 100644 --- a/tests/performance/3DLighting/src/main/java/attenuation/Environment.java +++ b/tests/performance/3DLighting/src/main/java/lighting3D/Environment.java @@ -23,11 +23,12 @@ * questions. */ -package attenuation; +package lighting3D; -import java.util.ArrayList; import java.util.List; +import javafx.beans.property.SimpleObjectProperty; +import javafx.geometry.Insets; import javafx.geometry.Point3D; import javafx.scene.AmbientLight; import javafx.scene.DirectionalLight; @@ -36,48 +37,64 @@ import javafx.scene.Node; import javafx.scene.PointLight; import javafx.scene.SpotLight; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; +import javafx.scene.image.Image; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundImage; +import javafx.scene.layout.BackgroundSize; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.paint.PhongMaterial; -import javafx.scene.shape.MeshView; import javafx.scene.shape.Sphere; -import javafx.scene.shape.TriangleMesh; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.scene.text.TextBoundsType; +import lighting3D.CaptureUtils.Format; +/** + * The 3D environment. Includes the lights and shapes. + */ class Environment extends CameraScene3D { - private final static double LIGHT_REP_RADIUS = 2; - private final static double LIGHT_Z_DIST = 50; - private final static double LIGHT_X_DIST = 50; - - private final static double SPHERE_RADIUS = 50; + static final Image BACKGROUND_IMAGE = new Image(CameraScene3D.class.getResourceAsStream("background.jpg")); + private static final double LIGHT_REP_RADIUS = 2; + private static final double LIGHT_X_DIST = 50; + static final double LIGHT_Z_DIST = 50; private final AmbientLight ambientLight1 = new AmbientLight(Color.WHITE); private final AmbientLight ambientLight2 = new AmbientLight(Color.RED); private final AmbientLight ambientLight3 = new AmbientLight(Color.BLACK); - final List ambientLights = List.of(ambientLight1, ambientLight2, ambientLight3); + private final List ambientLights = List.of(ambientLight1, ambientLight2, ambientLight3); private final DirectionalLight directionalLight1 = new DirectionalLight(Color.RED); private final DirectionalLight directionalLight2 = new DirectionalLight(Color.BLUE); private final DirectionalLight directionalLight3 = new DirectionalLight(Color.MAGENTA); - final List directionalLights = List.of(directionalLight1, directionalLight2, directionalLight3); + private final List directionalLights = List.of(directionalLight1, directionalLight2, directionalLight3); private final PointLight pointLight1 = new PointLight(Color.RED); private final PointLight pointLight2 = new PointLight(Color.BLUE); private final PointLight pointLight3 = new PointLight(Color.MAGENTA); - final List pointLights = List.of(pointLight1, pointLight2, pointLight3); + private final List pointLights = List.of(pointLight1, pointLight2, pointLight3); private final SpotLight spotLight1 = new SpotLight(Color.RED); private final SpotLight spotLight2 = new SpotLight(Color.BLUE); private final SpotLight spotLight3 = new SpotLight(Color.MAGENTA); - final List spotLights = List.of(spotLight1, spotLight2, spotLight3); + private final List spotLights = List.of(spotLight1, spotLight2, spotLight3); - private Group shapeGroup = new Group(); - private Group lightsGroup = new Group(); + private final Group shapeGroup = new Group(); + private final Group lightsGroup = new Group(); Environment() { - setStyle("-fx-background-color: teal"); + setPrefWidth(BACKGROUND_IMAGE.getWidth() / 2.5); + setPrefHeight(BACKGROUND_IMAGE.getHeight() / 2.5); farClip.set(1000); - zoom.set(-350); + zoom.set(-570); ambientLights.forEach(this::addLight); directionalLights.forEach(this::addLight); @@ -102,7 +119,7 @@ private void setupLight(PointLight light) { var lightRep = new Sphere(LIGHT_REP_RADIUS); var lightRepMat = new PhongMaterial(); - lightRepMat.setSelfIlluminationMap(Boxes.createMapImage(light.colorProperty())); + lightRepMat.selfIlluminationMapProperty().bind(light.colorProperty().map(MaterialControls::imageOf)); lightRep.setMaterial(lightRepMat); lightRep.translateXProperty().bind(light.translateXProperty()); lightRep.translateYProperty().bind(light.translateYProperty()); @@ -116,57 +133,50 @@ private void addLight(LightBase light) { lightsGroup.getChildren().add(light); } - void forceDefaultLight(boolean force) { - if (force) { - rootGroup.getChildren().remove(lightsGroup); - } else { - rootGroup.getChildren().add(lightsGroup); - } + Node createLightsControls() { + var controls = new VBox(); + ambientLights.forEach(light -> controls.getChildren().add(Controls.createLightControls(light))); + pointLights.forEach(light -> controls.getChildren().add(Controls.createLightControls(light))); + spotLights.forEach(light -> controls.getChildren().add(Controls.createLightControls(light))); + directionalLights.forEach(light -> controls.getChildren().add(Controls.createLightControls(light))); + return controls; } - - Group createBoxes() { - return new Boxes(LIGHT_Z_DIST); + + Node createBackgroundControls() { + var bgProp = new SimpleObjectProperty(); + var bgSize = new BackgroundSize(BackgroundSize.AUTO, BackgroundSize.AUTO, false, false, false, true); + backgroundProperty().bind(bgProp.map(image -> new Background(new BackgroundImage(image, null, null, null, bgSize)))); + Node controls = MaterialControls.createMapControls(bgProp, MaterialControls.createFileChooser()); + return new HBox(new Label("Background"), controls); } - Sphere createSphere(int subdivisions) { - return new Sphere(SPHERE_RADIUS, subdivisions); + Node createScreenshotControls() { + var formats = new ChoiceBox(); + formats.getItems().addAll(Format.values()); + formats.setValue(Format.PNG); + + var graphic = new Text("📷"); + graphic.setBoundsType(TextBoundsType.VISUAL); + graphic.setFont(Font.font(32)); + + var screenshotButton = new Button("", graphic); + screenshotButton.setPadding(new Insets(2)); + screenshotButton.setTooltip(new Tooltip("Capture screenshot")); + screenshotButton.setOnAction(e -> CaptureUtils.capture(snapshot(null, null), formats.getValue())); + + return new HBox(2, screenshotButton, formats); } - MeshView createMeshView(int quadNum) { - // Points and texCoords array defining a single quad that will - // be referenced by all pairs of triangles in the faces array - final float[] points = { - -75.0f, 75.0f, 0.0f, - 75.0f, 75.0f, 0.0f, - 75.0f, -75.0f, 0.0f, - -75.0f, -75.0f, 0.0f - }; - final float[] texCoords = { - 0.0f, 0.0f, - 1.0f, 0.0f, - 1.0f, 1.0f, - 0.0f, 1.0f - }; - // List of faces defining a single quad (pair of triangles). - // This is replicated for the desired number of quads - var face = List.of( - 0, 0, 1, 1, 2, 2, - 0, 0, 2, 2, 3, 3 - ); - - var faces = new ArrayList(quadNum * face.size()); - for (int i = 0; i < quadNum; i++) { - faces.addAll(face); - } - - var mesh = new TriangleMesh(); - mesh.getPoints().setAll(points); - mesh.getTexCoords().setAll(texCoords); - int[] array = faces.stream().mapToInt(i -> i).toArray(); - mesh.getFaces().setAll(array); - - var mv = new MeshView(mesh); - return mv; + Node createDefaultLightControl() { + var checkBox = new CheckBox("Force default light"); + checkBox.setOnAction(e -> { + if (checkBox.isSelected()) { + rootGroup.getChildren().remove(lightsGroup); + } else { + rootGroup.getChildren().add(lightsGroup); + } + }); + return checkBox; } void switchTo(Node node) { diff --git a/tests/performance/3DLighting/src/main/java/lighting3D/LightingApplication.java b/tests/performance/3DLighting/src/main/java/lighting3D/LightingApplication.java new file mode 100644 index 00000000000..5bb2b6cf1f2 --- /dev/null +++ b/tests/performance/3DLighting/src/main/java/lighting3D/LightingApplication.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2020, 2023, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package lighting3D; + +import javafx.application.Application; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.ContentDisplay; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TitledPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import lighting3D.Models.Model; + +/** + * A utility application for testing 3D features, including lighting, materials, and performance. + *

+ * Important: when measuring performance, make sure that no other application on your system is rendering heavy + * graphics, like videos, to a screen, as this will corrupt the measurement. + */ +public class LightingApplication extends Application { + + private final Environment environment = new Environment(); + private final Benchmark benchmark = new Benchmark(environment); + + @Override + public void start(Stage stage) throws Exception { + Node perfControls = createPerformanceControls(); + Node modelsControls = createModelsControls(); + + Node backgroundControls = environment.createBackgroundControls(); + Node screenshotControls = environment.createScreenshotControls(); + Node defaultLightControl = environment.createDefaultLightControl(); + Node lightsControls = environment.createLightsControls(); + + var controls = new VBox(perfControls, modelsControls, backgroundControls, screenshotControls, + defaultLightControl, lightsControls); + + var hBox = new HBox(new ScrollPane(controls), environment); + HBox.setHgrow(environment, Priority.ALWAYS); + + stage.setScene(new Scene(hBox)); + stage.setTitle("3DLighting"); + stage.show(); + } + + private Node createPerformanceControls() { + var playButton = benchmark.createPlayButton(); + var stopButton = benchmark.createStopButton(); + + Node sphereControls = benchmark.createSphereControls(); + Node meshControls = benchmark.createMeshControls(); + + var titlePane = new TitledPane("Performance", new VBox(sphereControls, meshControls)); + titlePane.setGraphic(new HBox(5, playButton, stopButton)); + titlePane.setContentDisplay(ContentDisplay.RIGHT); + titlePane.setExpanded(false); + return titlePane; + } + + private Node createModelsControls() { + var models = new ChoiceBox(); + models.getItems().addAll(Model.values()); + models.setValue(Model.NONE); + models.setOnAction(e -> environment.switchTo(Models.createModel(models.getValue()))); + + var titlePane = new TitledPane("Models", MaterialControls.create()); + titlePane.setGraphic(models); + titlePane.setContentDisplay(ContentDisplay.RIGHT); + titlePane.setExpanded(false); + return titlePane; + } + + public static void main(String[] args) { + launch(args); + } +} diff --git a/tests/performance/3DLighting/src/main/java/lighting3D/MaterialControls.java b/tests/performance/3DLighting/src/main/java/lighting3D/MaterialControls.java new file mode 100644 index 00000000000..f51cfec5405 --- /dev/null +++ b/tests/performance/3DLighting/src/main/java/lighting3D/MaterialControls.java @@ -0,0 +1,151 @@ +package lighting3D; + +import java.util.Optional; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.geometry.Insets; +import javafx.geometry.VPos; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ColorPicker; +import javafx.scene.control.ContentDisplay; +import javafx.scene.control.Label; +import javafx.scene.control.RadioButton; +import javafx.scene.control.ToggleGroup; +import javafx.scene.image.Image; +import javafx.scene.image.WritableImage; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.paint.PhongMaterial; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.scene.text.TextBoundsType; +import javafx.stage.FileChooser; +import javafx.stage.FileChooser.ExtensionFilter; + +/** + * Utility class for creating material adjustment controls. + */ +final class MaterialControls { + + static final PhongMaterial MATERIAL = new PhongMaterial(); + + static Pane create() { + var diffColorOn = new CheckBox("Diff Color"); + diffColorOn.setSelected(true); + var diffColorPicker = new ColorPicker(Color.WHITE); + MATERIAL.diffuseColorProperty().bind(diffColorOn.selectedProperty().flatMap(s -> s ? diffColorPicker.valueProperty() : null)); + + var specColorOn = new CheckBox("Spec Color"); + var specColorPicker = new ColorPicker(Color.BLACK); + MATERIAL.specularColorProperty().bind(specColorOn.selectedProperty().flatMap(s -> s ? specColorPicker.valueProperty() : null)); + + var specPower = Controls.createSliderControl(MATERIAL.specularPowerProperty(), 0, 400, MATERIAL.getSpecularPower()); + + var chooser = createFileChooser(); + var diffMapControls = createMapControls(MATERIAL.diffuseMapProperty(), chooser); + var specMapControls = createMapControls(MATERIAL.specularMapProperty(), chooser); + var selfIllumMapControls = createMapControls(MATERIAL.selfIlluminationMapProperty(), chooser); + var bumpMapControls = createMapControls(MATERIAL.bumpMapProperty(), chooser); + + var gridPane = new GridPane(); + gridPane.addRow(gridPane.getRowCount(), diffColorOn, diffColorPicker); + gridPane.addRow(gridPane.getRowCount(), specColorOn, specColorPicker); + gridPane.add(new HBox(createLabel("Spec power"), specPower), 0, gridPane.getRowCount(), 2, 1); + gridPane.addRow(gridPane.getRowCount(), createLabel("Diff Map"), diffMapControls); + gridPane.addRow(gridPane.getRowCount(), createLabel("Spec Map"), specMapControls); + gridPane.addRow(gridPane.getRowCount(), createLabel("Self-illum Map"), selfIllumMapControls); + gridPane.addRow(gridPane.getRowCount(), createLabel("Bump Map"), bumpMapControls); + return gridPane; + } + + static FileChooser createFileChooser() { + var chooser = new FileChooser(); + chooser.setTitle("Select image"); + chooser.setSelectedExtensionFilter(new ExtensionFilter("Image", ".bmp", ".jpg", ".jpeg", ".gif", ".png", ".tif", ".tiff")); + return chooser; + } + + private static Node createLabel(String text) { + var label = new Label(text); + GridPane.setValignment(label, VPos.TOP); + return label; + } + + static Node createMapControls(ObjectProperty mapProp, FileChooser chooser) { + var noneButton = new RadioButton("None"); + noneButton.setOnAction(e -> mapProp.set(null)); + noneButton.setSelected(true); + + var imageButton = createImageButton(mapProp, chooser); + var colorButton = createColorButton(mapProp); + + var toggleGroup = new ToggleGroup(); + toggleGroup.getToggles().addAll(noneButton, colorButton, imageButton); + + return new VBox(1, noneButton, colorButton, imageButton); + } + + private static RadioButton createColorButton(ObjectProperty mapProp) { + var picker = new ColorPicker(Color.BLACK); + + var colorButton = new RadioButton(" "); + colorButton.setContentDisplay(ContentDisplay.CENTER); + colorButton.setGraphicTextGap(0); + colorButton.setGraphic(picker); + colorButton.selectedProperty().subscribe(selected -> { + if (selected) { + mapProp.bind(picker.valueProperty().map(MaterialControls::imageOf)); + } else { + mapProp.unbind(); + } + }); + return colorButton; + } + + private static RadioButton createImageButton(ObjectProperty mapProp, FileChooser chooser) { + var chosenImage = new SimpleObjectProperty<>(Environment.BACKGROUND_IMAGE); + + var graphic = new Text("📂"); + graphic.setBoundsType(TextBoundsType.VISUAL); + graphic.setFill(Color.rgb(235, 163, 0)); + graphic.setFont(Font.font(20)); + + var openButton = new Button("", graphic); + openButton.setPadding(new Insets(2, 2, 3, 3)); + openButton.setOnAction(e -> { + Optional.ofNullable(chooser.showOpenDialog(null)).ifPresent(file -> { + try { + chosenImage.setValue(new Image(file.toURI().toURL().toExternalForm())); + chooser.setInitialDirectory(file.getParentFile()); + } catch (Exception e1) { + e1.printStackTrace(); + } + }); + }); + + var imageButton = new RadioButton(" "); + imageButton.setGraphic(openButton); + imageButton.setContentDisplay(ContentDisplay.CENTER); + imageButton.setGraphicTextGap(0); + imageButton.selectedProperty().subscribe(selected -> { + if (selected) { + mapProp.bind(chosenImage); + } else { + mapProp.unbind(); + } + }); + return imageButton; + } + + static Image imageOf(Color color) { + var image = new WritableImage(1, 1); + image.getPixelWriter().setColor(0, 0, color); + return image; + } +} diff --git a/tests/performance/3DLighting/src/main/java/lighting3D/Models.java b/tests/performance/3DLighting/src/main/java/lighting3D/Models.java new file mode 100644 index 00000000000..9c982452d0a --- /dev/null +++ b/tests/performance/3DLighting/src/main/java/lighting3D/Models.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2022, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package lighting3D; + +import java.util.ArrayList; +import java.util.List; + +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.shape.Box; +import javafx.scene.shape.Cylinder; +import javafx.scene.shape.MeshView; +import javafx.scene.shape.Sphere; +import javafx.scene.shape.TriangleMesh; +import javafx.scene.transform.Rotate; + +/** + * Utility class for creating 3D shapes. + */ +final class Models { + + enum Model { + NONE, + BOXES, + BOX, + CYLINDER, + SPHERE + } + + static Node createModel(Model model) { + return switch (model) { + case NONE -> new Group(); + case BOXES -> createBoxes(Environment.LIGHT_Z_DIST); + case BOX -> createBox(Environment.LIGHT_Z_DIST, Environment.LIGHT_Z_DIST); + case CYLINDER -> createCylinder(30, 150, Environment.LIGHT_Z_DIST); + case SPHERE -> createSphere(50, Environment.LIGHT_Z_DIST); + }; + } + + /** + * Creates a box-like structure with 3 edges. + * + * @param size distance from the center of the box to an edge + */ + private static Group createBoxes(double size) { + var back = createBox(size, size); + var right = createBox(size, -size); + right.setRotationAxis(Rotate.Y_AXIS); + right.setRotate(90); + right.setTranslateX(size * 2); + var left = createBox(size, -size); + left.setRotationAxis(Rotate.Y_AXIS); + left.setRotate(90); + left.setTranslateX(-size * 2); + return new Group(left, back , right); + } + + private static Box createBox(double size, double distance) { + var shape = new Box(size * 4, size * 4, 1); + shape.setTranslateZ(distance); + shape.setMaterial(MaterialControls.MATERIAL); + return shape; + } + + private static Cylinder createCylinder(double radius, double height, double distance) { + var shape = new Cylinder(radius, height); + shape.setTranslateZ(distance); + shape.setMaterial(MaterialControls.MATERIAL); + return shape; + } + + static Sphere createSphere(double radius, double distance) { + var shape = new Sphere(radius); + shape.setTranslateZ(distance); + shape.setMaterial(MaterialControls.MATERIAL); + return shape; + } + + static MeshView createMeshView(int quadNum) { + // Points and texCoords array defining a single quad that will + // be referenced by all pairs of triangles in the faces array + final float[] points = { + -75.0f, 75.0f, 0.0f, + 75.0f, 75.0f, 0.0f, + 75.0f, -75.0f, 0.0f, + -75.0f, -75.0f, 0.0f + }; + final float[] texCoords = { + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f + }; + // List of faces defining a single quad (pair of triangles). + // This is replicated for the desired number of quads + var face = List.of( + 0, 0, 1, 1, 2, 2, + 0, 0, 2, 2, 3, 3 + ); + + var faces = new ArrayList(quadNum * face.size()); + for (int i = 0; i < quadNum; i++) { + faces.addAll(face); + } + + var mesh = new TriangleMesh(); + mesh.getPoints().setAll(points); + mesh.getTexCoords().setAll(texCoords); + int[] array = faces.stream().mapToInt(i -> i).toArray(); + mesh.getFaces().setAll(array); + + return new MeshView(mesh); + } +} diff --git a/tests/performance/3DLighting/src/main/resources/lighting3D/background.jpg b/tests/performance/3DLighting/src/main/resources/lighting3D/background.jpg new file mode 100644 index 00000000000..ad5ca97a1e2 Binary files /dev/null and b/tests/performance/3DLighting/src/main/resources/lighting3D/background.jpg differ