refactor(examples): extract GraphicsBenchmark into dedicated package
authorSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sun, 15 Mar 2026 03:21:20 +0000 (05:21 +0200)
committerSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sun, 15 Mar 2026 03:21:20 +0000 (05:21 +0200)
Split the monolithic GraphicsBenchmark class into separate files in a
new benchmark package: BenchmarkTest interface, individual test classes
(SolidCubesTest, TexturedCubesTest, WireframeCubesTest), TestResult,
and TexturedCube helper.

doc/index.org
src/main/java/eu/svjatoslav/sixth/e3d/examples/GraphicsBenchmark.java [deleted file]
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/BenchmarkTest.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/GraphicsBenchmark.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/SolidCubesTest.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TestResult.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TexturedCube.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TexturedCubesTest.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/WireframeCubesTest.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/package-info.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java

index db97fed..2ad0fd7 100644 (file)
@@ -193,31 +193,25 @@ partitioned [[https://en.wikipedia.org/wiki/Octree][octree]] is used to compress
 the scene to raytrace current view through compressed voxel
 datastructure.
 
-** Fill-rate test
+** Graphics Benchmark
 :PROPERTIES:
-:CUSTOM_ID: fill-rate-test
+:CUSTOM_ID: graphics-benchmark
 :ID:       e5f6a7b8-c9d0-1234-ef01-345678901234
 :END:
 
-A benchmark for polygon rasterization performance testing. This demo
-tests pixel fill throughput by rendering 40 large screen-filling quads
-(600x600) layered at different Z depths.
+An automated graphics benchmark that measures the engine's rendering
+performance across different rendering modes.
 
-The low vertex count (4 vertices per quad) combined with high overdraw
-isolates the pixel fill cost from vertex processing overhead, allowing
-you to measure pure rasterization performance.
+The benchmark creates a 16x16x16 grid of cubes (4096 total) and runs
+three tests sequentially, each for 30 seconds:
 
-Controls:
-| key | result              |
-|-----+---------------------|
-| 1   | Solid colored quads |
-| 2   | Textured quads      |
+- *Solid Cubes* - Tests solid-color polygon rasterization
+- *Textured Cubes* - Tests textured polygon rendering with texture sampling
+- *Wireframe Cubes* - Tests line rendering performance
 
-Compare FPS between solid and textured modes to see exactly what
-texture sampling costs on your hardware.
+The camera follows a deterministic orbital path around the scene,
+ensuring reproducible results across runs.
 
-
-Example test run:
 #+begin_example
 ================================================================================
                          GRAPHICS BENCHMARK RESULTS
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/GraphicsBenchmark.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/GraphicsBenchmark.java
deleted file mode 100644 (file)
index 0071d47..0000000
+++ /dev/null
@@ -1,460 +0,0 @@
-/*
- * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
- * This project is released under Creative Commons Zero (CC0) license.
- */
-
-package eu.svjatoslav.sixth.e3d.examples;
-
-import eu.svjatoslav.sixth.e3d.geometry.Point2D;
-import eu.svjatoslav.sixth.e3d.geometry.Point3D;
-import eu.svjatoslav.sixth.e3d.gui.Camera;
-import eu.svjatoslav.sixth.e3d.gui.FrameListener;
-import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
-import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
-import eu.svjatoslav.sixth.e3d.math.Vertex;
-import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
-import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
-import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance;
-import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.texturedpolygon.TexturedPolygon;
-import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape;
-import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCube;
-import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.WireframeBox;
-import eu.svjatoslav.sixth.e3d.renderer.raster.texture.Texture;
-
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-
-/**
- * Automated graphics benchmark that tests the engine's rendering performance.
- * Runs multiple tests sequentially, each for a fixed duration, and outputs
- * reproducible benchmark results to standard output.
- * 
- * <p>The benchmark creates a 16x16x16 grid of cubes (4096 total) with the camera
- * following a deterministic orbital path. Each test runs for 30 seconds.</p>
- * 
- * @see SolidPolygonCube
- * @see TexturedPolygon
- */
-public class GraphicsBenchmark implements FrameListener {
-
-    private static final int WINDOW_WIDTH = 1920;
-    private static final int WINDOW_HEIGHT = 1080;
-    private static final int GRID_SIZE = 16;
-    private static final double SPACING = 80;
-    private static final double CUBE_SIZE = 25;
-    private static final double ORBIT_DISTANCE = 1200;
-    private static final double ORBIT_SPEED = 0.0003;
-    private static final double WOBBLE_AMPLITUDE = 800;
-    private static final int TEXTURE_COUNT = 20;
-    private static final int TEST_DURATION_MS = 30000;
-    private static final long RANDOM_SEED = 42;
-
-    private ViewFrame viewFrame;
-    private ViewPanel viewPanel;
-    private ShapeCollection shapes;
-    private final Random random = new Random(RANDOM_SEED);
-    private Camera camera;
-
-    private final Texture[] textures = new Texture[TEXTURE_COUNT];
-    private final int[] cubeTextureIndices = new int[GRID_SIZE * GRID_SIZE * GRID_SIZE];
-
-    private double orbitAngle = 0;
-    private long testStartTime;
-    private long frameCount;
-    private BenchmarkTest currentTest;
-    private boolean testFinished = false;
-    private final List<TestResult> results = new ArrayList<>();
-    private final List<BenchmarkTest> tests = new ArrayList<>();
-
-    /**
-     * Interface for a single benchmark test.
-     * Implementations define how to set up and tear down the test scene.
-     */
-    public interface BenchmarkTest {
-        String getName();
-        void setup(ShapeCollection shapes, Texture[] textures, int[] cubeTextureIndices);
-        void teardown(ShapeCollection shapes);
-    }
-
-    /**
-     * Holds the results of a single benchmark test.
-     */
-    public static class TestResult {
-        final String testName;
-        final long frameCount;
-        final double durationSeconds;
-        final double averageFps;
-
-        TestResult(String testName, long frameCount, double durationSeconds) {
-            this.testName = testName;
-            this.frameCount = frameCount;
-            this.durationSeconds = durationSeconds;
-            this.averageFps = frameCount / durationSeconds;
-        }
-    }
-
-    /**
-     * Benchmark test for solid-color cubes.
-     */
-    public static class SolidCubesTest implements BenchmarkTest {
-        private final Random random = new Random(RANDOM_SEED);
-        private final List<Object> cubes = new ArrayList<>();
-
-        @Override
-        public String getName() {
-            return "Solid Cubes";
-        }
-
-        @Override
-        public void setup(ShapeCollection shapes, Texture[] textures, int[] cubeTextureIndices) {
-            random.setSeed(RANDOM_SEED);
-            double offset = -(GRID_SIZE - 1) * SPACING / 2;
-
-            for (int x = 0; x < GRID_SIZE; x++) {
-                for (int y = 0; y < GRID_SIZE; y++) {
-                    for (int z = 0; z < GRID_SIZE; z++) {
-                        double px = offset + x * SPACING;
-                        double py = offset + y * SPACING;
-                        double pz = offset + z * SPACING;
-
-                        Color color = new Color(
-                                50 + random.nextInt(150),
-                                100 + random.nextInt(155),
-                                50 + random.nextInt(150),
-                                40
-                        );
-                        SolidPolygonCube cube = new SolidPolygonCube(
-                                new Point3D(px, py, pz),
-                                CUBE_SIZE,
-                                color
-                        );
-                        shapes.addShape(cube);
-                        cubes.add(cube);
-                    }
-                }
-            }
-        }
-
-        @Override
-        public void teardown(ShapeCollection shapes) {
-            for (Object cube : cubes) {
-                shapes.getShapes().remove(cube);
-            }
-            cubes.clear();
-        }
-    }
-
-    /**
-     * Benchmark test for textured cubes.
-     */
-    public static class TexturedCubesTest implements BenchmarkTest {
-        private final List<Object> cubes = new ArrayList<>();
-
-        @Override
-        public String getName() {
-            return "Textured Cubes";
-        }
-
-        @Override
-        public void setup(ShapeCollection shapes, Texture[] textures, int[] cubeTextureIndices) {
-            double offset = -(GRID_SIZE - 1) * SPACING / 2;
-            int idx = 0;
-
-            for (int x = 0; x < GRID_SIZE; x++) {
-                for (int y = 0; y < GRID_SIZE; y++) {
-                    for (int z = 0; z < GRID_SIZE; z++) {
-                        double px = offset + x * SPACING;
-                        double py = offset + y * SPACING;
-                        double pz = offset + z * SPACING;
-
-                        Texture tex = textures[cubeTextureIndices[idx]];
-                        TexturedCube cube = new TexturedCube(
-                                new Point3D(px, py, pz),
-                                CUBE_SIZE,
-                                tex
-                        );
-                        shapes.addShape(cube);
-                        cubes.add(cube);
-                        idx++;
-                    }
-                }
-            }
-        }
-
-        @Override
-        public void teardown(ShapeCollection shapes) {
-            for (Object cube : cubes) {
-                shapes.getShapes().remove(cube);
-            }
-            cubes.clear();
-        }
-    }
-
-    /**
-     * Benchmark test for wireframe cubes.
-     */
-    public static class WireframeCubesTest implements BenchmarkTest {
-        private final Random random = new Random(RANDOM_SEED);
-        private final List<Object> cubes = new ArrayList<>();
-
-        @Override
-        public String getName() {
-            return "Wireframe Cubes";
-        }
-
-        @Override
-        public void setup(ShapeCollection shapes, Texture[] textures, int[] cubeTextureIndices) {
-            random.setSeed(RANDOM_SEED);
-            double offset = -(GRID_SIZE - 1) * SPACING / 2;
-
-            for (int x = 0; x < GRID_SIZE; x++) {
-                for (int y = 0; y < GRID_SIZE; y++) {
-                    for (int z = 0; z < GRID_SIZE; z++) {
-                        double px = offset + x * SPACING;
-                        double py = offset + y * SPACING;
-                        double pz = offset + z * SPACING;
-
-                        Color color = new Color(
-                                100 + random.nextInt(155),
-                                100 + random.nextInt(155),
-                                100 + random.nextInt(155)
-                        );
-                        LineAppearance appearance = new LineAppearance(1.5, color);
-
-                        Point3D center = new Point3D(px, py, pz);
-                        Point3D p1 = new Point3D(center.x - CUBE_SIZE, center.y - CUBE_SIZE, center.z - CUBE_SIZE);
-                        Point3D p2 = new Point3D(center.x + CUBE_SIZE, center.y + CUBE_SIZE, center.z + CUBE_SIZE);
-
-                        WireframeBox cube = new WireframeBox(p1, p2, appearance);
-                        shapes.addShape(cube);
-                        cubes.add(cube);
-                    }
-                }
-            }
-        }
-
-        @Override
-        public void teardown(ShapeCollection shapes) {
-            for (Object cube : cubes) {
-                shapes.getShapes().remove(cube);
-            }
-            cubes.clear();
-        }
-    }
-
-    /**
-     * Entry point for the graphics benchmark.
-     * @param args command line arguments (ignored)
-     */
-    public static void main(String[] args) {
-        new GraphicsBenchmark();
-    }
-
-    /**
-     * Constructs and runs the graphics benchmark.
-     */
-    public GraphicsBenchmark() {
-        initializeWindow();
-        initializeTextures();
-        initializeCubeTextureIndices();
-        registerTests();
-        startNextTest();
-    }
-
-    private void initializeWindow() {
-        viewFrame = new ViewFrame(WINDOW_WIDTH, WINDOW_HEIGHT);
-        viewPanel = viewFrame.getViewPanel();
-        viewPanel.setFrameRate(0);
-        shapes = viewPanel.getRootShapeCollection();
-        viewPanel.addFrameListener(this);
-        camera = viewPanel.getCamera();
-    }
-
-    private void initializeTextures() {
-        for (int i = 0; i < TEXTURE_COUNT; i++) {
-            textures[i] = createGlowTexture(
-                    50 + random.nextInt(200),
-                    50 + random.nextInt(200),
-                    50 + random.nextInt(200)
-            );
-        }
-    }
-
-    private void initializeCubeTextureIndices() {
-        random.setSeed(RANDOM_SEED);
-        for (int i = 0; i < cubeTextureIndices.length; i++) {
-            cubeTextureIndices[i] = random.nextInt(TEXTURE_COUNT);
-        }
-    }
-
-    private void registerTests() {
-        tests.add(new SolidCubesTest());
-        tests.add(new TexturedCubesTest());
-        tests.add(new WireframeCubesTest());
-    }
-
-    private void startNextTest() {
-        int nextIndex = results.size();
-        if (nextIndex >= tests.size()) {
-            finishBenchmark();
-            return;
-        }
-
-        currentTest = tests.get(nextIndex);
-        testFinished = false;
-        orbitAngle = 0;
-        frameCount = 0;
-        testStartTime = System.currentTimeMillis();
-
-        currentTest.setup(shapes, textures, cubeTextureIndices);
-    }
-
-    private void finishBenchmark() {
-        currentTest = null;
-        viewFrame.dispose();
-        printResults();
-        System.exit(0);
-    }
-
-    private void printResults() {
-        String separator = "================================================================================";
-        String thinSeparator = "--------------------------------------------------------------------------------";
-
-        System.out.println(separator);
-        System.out.println("                         GRAPHICS BENCHMARK RESULTS");
-        System.out.println(separator);
-        System.out.println("Date:       " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
-        System.out.println("Resolution: " + WINDOW_WIDTH + "x" + WINDOW_HEIGHT);
-        System.out.println("Cubes:      " + (GRID_SIZE * GRID_SIZE * GRID_SIZE) + " (" + GRID_SIZE + "x" + GRID_SIZE + "x" + GRID_SIZE + " grid)");
-        System.out.println("Duration:   " + (TEST_DURATION_MS / 1000) + " seconds per test");
-        System.out.println();
-        System.out.println(thinSeparator);
-        System.out.printf("%-28s %-11s %-9s %-10s%n", "Test", "Duration", "Frames", "Avg FPS");
-        System.out.println(thinSeparator);
-
-        for (TestResult result : results) {
-            System.out.printf("%-28s %-11s %-9d %-10.2f%n",
-                    result.testName,
-                    String.format("%.2fs", result.durationSeconds),
-                    result.frameCount,
-                    result.averageFps);
-        }
-
-        System.out.println(separator);
-    }
-
-    private Texture createGlowTexture(int r, int g, int b) {
-        int texSize = 64;
-        Texture texture = new Texture(texSize, texSize, 2);
-
-        java.awt.Graphics2D gr = texture.graphics;
-        gr.setBackground(new java.awt.Color(r, g, b, 80));
-        gr.clearRect(0, 0, texSize, texSize);
-
-        int glowWidth = 6;
-        for (int i = 0; i < glowWidth; i++) {
-            int intensity = (int) (255.0 * (glowWidth - i) / glowWidth);
-            java.awt.Color glowColor = new java.awt.Color(
-                    Math.min(255, r + intensity),
-                    Math.min(255, g + intensity),
-                    Math.min(255, b + intensity),
-                    200 - i * 30
-            );
-            gr.setColor(glowColor);
-            gr.drawRect(i, i, texSize - 1 - 2 * i, texSize - 1 - 2 * i);
-        }
-
-        gr.dispose();
-        texture.resetResampledBitmapCache();
-        return texture;
-    }
-
-    @Override
-    public boolean onFrame(ViewPanel viewPanel, int millisecondsSinceLastFrame) {
-        if (currentTest == null) {
-            return true;
-        }
-
-        orbitAngle += ORBIT_SPEED * millisecondsSinceLastFrame;
-
-        double x = Math.sin(orbitAngle) * ORBIT_DISTANCE;
-        double z = Math.cos(orbitAngle) * ORBIT_DISTANCE;
-        double y = Math.sin(orbitAngle * 1.8934) * WOBBLE_AMPLITUDE;
-
-        camera.getTransform().setTranslation(new Point3D(x, y, z));
-        camera.lookAt(new Point3D(0, 0, 0));
-
-        frameCount++;
-
-        long elapsed = System.currentTimeMillis() - testStartTime;
-        if (elapsed >= TEST_DURATION_MS && !testFinished) {
-            testFinished = true;
-            double durationSeconds = elapsed / 1000.0;
-            results.add(new TestResult(currentTest.getName(), frameCount, durationSeconds));
-
-            currentTest.teardown(shapes);
-            startNextTest();
-        }
-
-        return true;
-    }
-
-    /**
-     * A cube composed of textured polygons.
-     */
-    private static class TexturedCube extends AbstractCompositeShape {
-
-        public TexturedCube(Point3D center, double size, Texture texture) {
-            double s = size;
-            Point3D p1 = new Point3D(center.x - s, center.y - s, center.z - s);
-            Point3D p7 = new Point3D(center.x + s, center.y + s, center.z + s);
-
-            Point3D p2 = new Point3D(p7.x, p1.y, p1.z);
-            Point3D p3 = new Point3D(p7.x, p1.y, p7.z);
-            Point3D p4 = new Point3D(p1.x, p1.y, p7.z);
-            Point3D p5 = new Point3D(p1.x, p7.y, p1.z);
-            Point3D p6 = new Point3D(p7.x, p7.y, p1.z);
-            Point3D p8 = new Point3D(p1.x, p7.y, p7.z);
-
-            Point2D t00 = new Point2D(0, 0);
-            Point2D t10 = new Point2D(64, 0);
-            Point2D t01 = new Point2D(0, 64);
-            Point2D t11 = new Point2D(64, 64);
-
-            addTexturedFace(p1, p2, p3, p4, t00, t10, t11, t01, texture);
-            addTexturedFace(p5, p8, p7, p6, t00, t01, t11, t10, texture);
-            addTexturedFace(p1, p5, p6, p2, t00, t01, t11, t10, texture);
-            addTexturedFace(p3, p7, p8, p4, t00, t10, t11, t01, texture);
-            addTexturedFace(p1, p4, p8, p5, t00, t10, t11, t01, texture);
-            addTexturedFace(p2, p6, p7, p3, t00, t01, t11, t10, texture);
-
-            setBackfaceCulling(true);
-        }
-
-        private void addTexturedFace(Point3D p1, Point3D p2, Point3D p3, Point3D p4,
-                                     Point2D t1, Point2D t2, Point2D t3, Point2D t4,
-                                     Texture texture) {
-            TexturedPolygon tri1 = new TexturedPolygon(
-                    new Vertex(p1, t1),
-                    new Vertex(p2, t2),
-                    new Vertex(p3, t3),
-                    texture
-            );
-            tri1.setBackfaceCulling(true);
-
-            TexturedPolygon tri2 = new TexturedPolygon(
-                    new Vertex(p1, t1),
-                    new Vertex(p3, t3),
-                    new Vertex(p4, t4),
-                    texture
-            );
-            tri2.setBackfaceCulling(true);
-
-            addShape(tri1);
-            addShape(tri2);
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/BenchmarkTest.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/BenchmarkTest.java
new file mode 100644 (file)
index 0000000..13326c3
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.benchmark;
+
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.texture.Texture;
+
+/**
+ * Interface for a single benchmark test.
+ * Implementations define how to set up and tear down the test scene.
+ */
+public interface BenchmarkTest {
+
+    /**
+     * Returns the display name for this benchmark test.
+     * @return the test name
+     */
+    String getName();
+
+    /**
+     * Sets up the test scene by adding shapes to the collection.
+     * @param shapes the shape collection to populate
+     * @param textures available textures for textured tests
+     * @param cubeTextureIndices texture index for each cube
+     */
+    void setup(ShapeCollection shapes, Texture[] textures, int[] cubeTextureIndices);
+
+    /**
+     * Tears down the test scene by removing all shapes added during setup.
+     * @param shapes the shape collection to clean up
+     */
+    void teardown(ShapeCollection shapes);
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/GraphicsBenchmark.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/GraphicsBenchmark.java
new file mode 100644 (file)
index 0000000..5d9a77d
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.benchmark;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.gui.Camera;
+import eu.svjatoslav.sixth.e3d.gui.FrameListener;
+import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
+import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.texture.Texture;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Automated graphics benchmark that tests the engine's rendering performance.
+ * Runs multiple tests sequentially, each for a fixed duration, and outputs
+ * reproducible benchmark results to standard output.
+ * 
+ * <p>The benchmark creates a 16x16x16 grid of cubes (4096 total) with the camera
+ * following a deterministic orbital path. Each test runs for 30 seconds.</p>
+ * 
+ * <p>Available tests:</p>
+ * <ul>
+ *   <li>{@link SolidCubesTest} - Solid-color polygon rendering</li>
+ *   <li>{@link TexturedCubesTest} - Textured polygon rendering</li>
+ *   <li>{@link WireframeCubesTest} - Line rendering</li>
+ * </ul>
+ */
+public class GraphicsBenchmark implements FrameListener {
+
+    private static final int WINDOW_WIDTH = 1920;
+    private static final int WINDOW_HEIGHT = 1080;
+    private static final int GRID_SIZE = 16;
+    private static final double ORBIT_DISTANCE = 1200;
+    private static final double ORBIT_SPEED = 0.0003;
+    private static final double WOBBLE_AMPLITUDE = 800;
+    private static final int TEXTURE_COUNT = 20;
+    private static final int TEST_DURATION_MS = 30000;
+    private static final long RANDOM_SEED = 42;
+
+    private ViewFrame viewFrame;
+    private ViewPanel viewPanel;
+    private ShapeCollection shapes;
+    private final Random random = new Random(RANDOM_SEED);
+    private Camera camera;
+
+    private final Texture[] textures = new Texture[TEXTURE_COUNT];
+    private final int[] cubeTextureIndices = new int[GRID_SIZE * GRID_SIZE * GRID_SIZE];
+
+    private double orbitAngle = 0;
+    private long testStartTime;
+    private long frameCount;
+    private BenchmarkTest currentTest;
+    private boolean testFinished = false;
+    private final List<TestResult> results = new ArrayList<>();
+    private final List<BenchmarkTest> tests = new ArrayList<>();
+
+    /**
+     * Entry point for the graphics benchmark.
+     * @param args command line arguments (ignored)
+     */
+    public static void main(String[] args) {
+        new GraphicsBenchmark();
+    }
+
+    /**
+     * Constructs and runs the graphics benchmark.
+     */
+    public GraphicsBenchmark() {
+        initializeWindow();
+        initializeTextures();
+        initializeCubeTextureIndices();
+        registerTests();
+        startNextTest();
+    }
+
+    private void initializeWindow() {
+        viewFrame = new ViewFrame(WINDOW_WIDTH, WINDOW_HEIGHT);
+        viewPanel = viewFrame.getViewPanel();
+        viewPanel.setFrameRate(0);
+        shapes = viewPanel.getRootShapeCollection();
+        viewPanel.addFrameListener(this);
+        camera = viewPanel.getCamera();
+    }
+
+    private void initializeTextures() {
+        for (int i = 0; i < TEXTURE_COUNT; i++) {
+            textures[i] = createGlowTexture(
+                    50 + random.nextInt(200),
+                    50 + random.nextInt(200),
+                    50 + random.nextInt(200)
+            );
+        }
+    }
+
+    private void initializeCubeTextureIndices() {
+        random.setSeed(RANDOM_SEED);
+        for (int i = 0; i < cubeTextureIndices.length; i++) {
+            cubeTextureIndices[i] = random.nextInt(TEXTURE_COUNT);
+        }
+    }
+
+    private void registerTests() {
+        tests.add(new SolidCubesTest());
+        tests.add(new TexturedCubesTest());
+        tests.add(new WireframeCubesTest());
+    }
+
+    private void startNextTest() {
+        int nextIndex = results.size();
+        if (nextIndex >= tests.size()) {
+            finishBenchmark();
+            return;
+        }
+
+        currentTest = tests.get(nextIndex);
+        testFinished = false;
+        orbitAngle = 0;
+        frameCount = 0;
+        testStartTime = System.currentTimeMillis();
+
+        currentTest.setup(shapes, textures, cubeTextureIndices);
+    }
+
+    private void finishBenchmark() {
+        currentTest = null;
+        viewFrame.dispose();
+        printResults();
+    }
+
+    private void printResults() {
+        String separator = "================================================================================";
+        String thinSeparator = "--------------------------------------------------------------------------------";
+
+        System.out.println(separator);
+        System.out.println("                         GRAPHICS BENCHMARK RESULTS");
+        System.out.println(separator);
+        System.out.println("Date:       " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
+        System.out.println("Resolution: " + WINDOW_WIDTH + "x" + WINDOW_HEIGHT);
+        System.out.println("Cubes:      " + (GRID_SIZE * GRID_SIZE * GRID_SIZE) + " (" + GRID_SIZE + "x" + GRID_SIZE + "x" + GRID_SIZE + " grid)");
+        System.out.println("Duration:   " + (TEST_DURATION_MS / 1000) + " seconds per test");
+        System.out.println();
+        System.out.println(thinSeparator);
+        System.out.printf("%-28s %-11s %-9s %-10s%n", "Test", "Duration", "Frames", "Avg FPS");
+        System.out.println(thinSeparator);
+
+        for (TestResult result : results) {
+            System.out.printf("%-28s %-11s %-9d %-10.2f%n",
+                    result.testName,
+                    String.format("%.2fs", result.durationSeconds),
+                    result.frameCount,
+                    result.averageFps);
+        }
+
+        System.out.println(separator);
+    }
+
+    private Texture createGlowTexture(int r, int g, int b) {
+        int texSize = 64;
+        Texture texture = new Texture(texSize, texSize, 2);
+
+        java.awt.Graphics2D gr = texture.graphics;
+        gr.setBackground(new java.awt.Color(r, g, b, 80));
+        gr.clearRect(0, 0, texSize, texSize);
+
+        int glowWidth = 6;
+        for (int i = 0; i < glowWidth; i++) {
+            int intensity = (int) (255.0 * (glowWidth - i) / glowWidth);
+            java.awt.Color glowColor = new java.awt.Color(
+                    Math.min(255, r + intensity),
+                    Math.min(255, g + intensity),
+                    Math.min(255, b + intensity),
+                    200 - i * 30
+            );
+            gr.setColor(glowColor);
+            gr.drawRect(i, i, texSize - 1 - 2 * i, texSize - 1 - 2 * i);
+        }
+
+        gr.dispose();
+        texture.resetResampledBitmapCache();
+        return texture;
+    }
+
+    @Override
+    public boolean onFrame(ViewPanel viewPanel, int millisecondsSinceLastFrame) {
+        if (currentTest == null) {
+            return true;
+        }
+
+        orbitAngle += ORBIT_SPEED * millisecondsSinceLastFrame;
+
+        double x = Math.sin(orbitAngle) * ORBIT_DISTANCE;
+        double z = Math.cos(orbitAngle) * ORBIT_DISTANCE;
+        double y = Math.sin(orbitAngle * 1.8934) * WOBBLE_AMPLITUDE;
+
+        camera.getTransform().setTranslation(new Point3D(x, y, z));
+        camera.lookAt(new Point3D(0, 0, 0));
+
+        frameCount++;
+
+        long elapsed = System.currentTimeMillis() - testStartTime;
+        if (elapsed >= TEST_DURATION_MS && !testFinished) {
+            testFinished = true;
+            double durationSeconds = elapsed / 1000.0;
+            results.add(new TestResult(currentTest.getName(), frameCount, durationSeconds));
+
+            currentTest.teardown(shapes);
+            startNextTest();
+        }
+
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/SolidCubesTest.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/SolidCubesTest.java
new file mode 100644 (file)
index 0000000..ca1a7dc
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.benchmark;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCube;
+import eu.svjatoslav.sixth.e3d.renderer.raster.texture.Texture;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Benchmark test for solid-color cubes.
+ * Renders a grid of cubes with random semi-transparent colors to test
+ * solid polygon rasterization performance.
+ */
+public class SolidCubesTest implements BenchmarkTest {
+
+    private static final int GRID_SIZE = 16;
+    private static final double SPACING = 80;
+    private static final double CUBE_SIZE = 25;
+    private static final long RANDOM_SEED = 42;
+
+    private final Random random = new Random(RANDOM_SEED);
+    private final List<Object> cubes = new ArrayList<>();
+
+    @Override
+    public String getName() {
+        return "Solid Cubes";
+    }
+
+    @Override
+    public void setup(ShapeCollection shapes, Texture[] textures, int[] cubeTextureIndices) {
+        random.setSeed(RANDOM_SEED);
+        double offset = -(GRID_SIZE - 1) * SPACING / 2;
+
+        for (int x = 0; x < GRID_SIZE; x++) {
+            for (int y = 0; y < GRID_SIZE; y++) {
+                for (int z = 0; z < GRID_SIZE; z++) {
+                    double px = offset + x * SPACING;
+                    double py = offset + y * SPACING;
+                    double pz = offset + z * SPACING;
+
+                    Color color = new Color(
+                            50 + random.nextInt(150),
+                            100 + random.nextInt(155),
+                            50 + random.nextInt(150),
+                            40
+                    );
+                    SolidPolygonCube cube = new SolidPolygonCube(
+                            new Point3D(px, py, pz),
+                            CUBE_SIZE,
+                            color
+                    );
+                    shapes.addShape(cube);
+                    cubes.add(cube);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void teardown(ShapeCollection shapes) {
+        for (Object cube : cubes) {
+            shapes.getShapes().remove(cube);
+        }
+        cubes.clear();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TestResult.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TestResult.java
new file mode 100644 (file)
index 0000000..9c7eef2
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.benchmark;
+
+/**
+ * Holds the results of a single benchmark test.
+ */
+public class TestResult {
+
+    /** The name of the benchmark test. */
+    public final String testName;
+
+    /** Total number of frames rendered during the test. */
+    public final long frameCount;
+
+    /** Duration of the test in seconds. */
+    public final double durationSeconds;
+
+    /** Average frames per second achieved during the test. */
+    public final double averageFps;
+
+    /**
+     * Creates a test result record.
+     * @param testName the name of the test
+     * @param frameCount total frames rendered
+     * @param durationSeconds test duration in seconds
+     */
+    public TestResult(String testName, long frameCount, double durationSeconds) {
+        this.testName = testName;
+        this.frameCount = frameCount;
+        this.durationSeconds = durationSeconds;
+        this.averageFps = frameCount / durationSeconds;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TexturedCube.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TexturedCube.java
new file mode 100644 (file)
index 0000000..ecb883e
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.benchmark;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point2D;
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.math.Vertex;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.texturedpolygon.TexturedPolygon;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape;
+import eu.svjatoslav.sixth.e3d.renderer.raster.texture.Texture;
+
+/**
+ * A cube composed of textured polygons.
+ * Used by the {@link TexturedCubesTest} benchmark to measure texture rendering performance.
+ */
+public class TexturedCube extends AbstractCompositeShape {
+
+    /**
+     * Creates a textured cube centered at the given position.
+     * @param center the center position of the cube
+     * @param size half the edge length of the cube
+     * @param texture the texture to apply to all faces
+     */
+    public TexturedCube(Point3D center, double size, Texture texture) {
+        double s = size;
+        Point3D p1 = new Point3D(center.x - s, center.y - s, center.z - s);
+        Point3D p7 = new Point3D(center.x + s, center.y + s, center.z + s);
+
+        Point3D p2 = new Point3D(p7.x, p1.y, p1.z);
+        Point3D p3 = new Point3D(p7.x, p1.y, p7.z);
+        Point3D p4 = new Point3D(p1.x, p1.y, p7.z);
+        Point3D p5 = new Point3D(p1.x, p7.y, p1.z);
+        Point3D p6 = new Point3D(p7.x, p7.y, p1.z);
+        Point3D p8 = new Point3D(p1.x, p7.y, p7.z);
+
+        Point2D t00 = new Point2D(0, 0);
+        Point2D t10 = new Point2D(64, 0);
+        Point2D t01 = new Point2D(0, 64);
+        Point2D t11 = new Point2D(64, 64);
+
+        addTexturedFace(p1, p2, p3, p4, t00, t10, t11, t01, texture);
+        addTexturedFace(p5, p8, p7, p6, t00, t01, t11, t10, texture);
+        addTexturedFace(p1, p5, p6, p2, t00, t01, t11, t10, texture);
+        addTexturedFace(p3, p7, p8, p4, t00, t10, t11, t01, texture);
+        addTexturedFace(p1, p4, p8, p5, t00, t10, t11, t01, texture);
+        addTexturedFace(p2, p6, p7, p3, t00, t01, t11, t10, texture);
+
+        setBackfaceCulling(true);
+    }
+
+    private void addTexturedFace(Point3D p1, Point3D p2, Point3D p3, Point3D p4,
+                                 Point2D t1, Point2D t2, Point2D t3, Point2D t4,
+                                 Texture texture) {
+        TexturedPolygon tri1 = new TexturedPolygon(
+                new Vertex(p1, t1),
+                new Vertex(p2, t2),
+                new Vertex(p3, t3),
+                texture
+        );
+        tri1.setBackfaceCulling(true);
+
+        TexturedPolygon tri2 = new TexturedPolygon(
+                new Vertex(p1, t1),
+                new Vertex(p3, t3),
+                new Vertex(p4, t4),
+                texture
+        );
+        tri2.setBackfaceCulling(true);
+
+        addShape(tri1);
+        addShape(tri2);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TexturedCubesTest.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TexturedCubesTest.java
new file mode 100644 (file)
index 0000000..15b99f8
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.benchmark;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.texture.Texture;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Benchmark test for textured cubes.
+ * Renders a grid of cubes with textures to test textured polygon rendering
+ * and texture sampling performance.
+ */
+public class TexturedCubesTest implements BenchmarkTest {
+
+    private static final int GRID_SIZE = 16;
+    private static final double SPACING = 80;
+    private static final double CUBE_SIZE = 25;
+
+    private final List<Object> cubes = new ArrayList<>();
+
+    @Override
+    public String getName() {
+        return "Textured Cubes";
+    }
+
+    @Override
+    public void setup(ShapeCollection shapes, Texture[] textures, int[] cubeTextureIndices) {
+        double offset = -(GRID_SIZE - 1) * SPACING / 2;
+        int idx = 0;
+
+        for (int x = 0; x < GRID_SIZE; x++) {
+            for (int y = 0; y < GRID_SIZE; y++) {
+                for (int z = 0; z < GRID_SIZE; z++) {
+                    double px = offset + x * SPACING;
+                    double py = offset + y * SPACING;
+                    double pz = offset + z * SPACING;
+
+                    Texture tex = textures[cubeTextureIndices[idx]];
+                    TexturedCube cube = new TexturedCube(
+                            new Point3D(px, py, pz),
+                            CUBE_SIZE,
+                            tex
+                    );
+                    shapes.addShape(cube);
+                    cubes.add(cube);
+                    idx++;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void teardown(ShapeCollection shapes) {
+        for (Object cube : cubes) {
+            shapes.getShapes().remove(cube);
+        }
+        cubes.clear();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/WireframeCubesTest.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/WireframeCubesTest.java
new file mode 100644 (file)
index 0000000..c76a184
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.benchmark;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.WireframeBox;
+import eu.svjatoslav.sixth.e3d.renderer.raster.texture.Texture;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Benchmark test for wireframe cubes.
+ * Renders a grid of wireframe cube outlines to test line rendering performance.
+ */
+public class WireframeCubesTest implements BenchmarkTest {
+
+    private static final int GRID_SIZE = 16;
+    private static final double SPACING = 80;
+    private static final double CUBE_SIZE = 25;
+    private static final long RANDOM_SEED = 42;
+
+    private final Random random = new Random(RANDOM_SEED);
+    private final List<Object> cubes = new ArrayList<>();
+
+    @Override
+    public String getName() {
+        return "Wireframe Cubes";
+    }
+
+    @Override
+    public void setup(ShapeCollection shapes, Texture[] textures, int[] cubeTextureIndices) {
+        random.setSeed(RANDOM_SEED);
+        double offset = -(GRID_SIZE - 1) * SPACING / 2;
+
+        for (int x = 0; x < GRID_SIZE; x++) {
+            for (int y = 0; y < GRID_SIZE; y++) {
+                for (int z = 0; z < GRID_SIZE; z++) {
+                    double px = offset + x * SPACING;
+                    double py = offset + y * SPACING;
+                    double pz = offset + z * SPACING;
+
+                    Color color = new Color(
+                            100 + random.nextInt(155),
+                            100 + random.nextInt(155),
+                            100 + random.nextInt(155)
+                    );
+                    LineAppearance appearance = new LineAppearance(1.5, color);
+
+                    Point3D center = new Point3D(px, py, pz);
+                    Point3D p1 = new Point3D(center.x - CUBE_SIZE, center.y - CUBE_SIZE, center.z - CUBE_SIZE);
+                    Point3D p2 = new Point3D(center.x + CUBE_SIZE, center.y + CUBE_SIZE, center.z + CUBE_SIZE);
+
+                    WireframeBox cube = new WireframeBox(p1, p2, appearance);
+                    shapes.addShape(cube);
+                    cubes.add(cube);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void teardown(ShapeCollection shapes) {
+        for (Object cube : cubes) {
+            shapes.getShapes().remove(cube);
+        }
+        cubes.clear();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/package-info.java
new file mode 100644 (file)
index 0000000..41d5c8b
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Graphics benchmark components for measuring the Sixth 3D engine's rendering performance.
+ * 
+ * <p>The benchmark suite includes:</p>
+ * <ul>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.examples.benchmark.GraphicsBenchmark} - Main benchmark runner</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.examples.benchmark.BenchmarkTest} - Interface for test implementations</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.examples.benchmark.SolidCubesTest} - Solid-color cube rendering test</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.examples.benchmark.TexturedCubesTest} - Textured cube rendering test</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.examples.benchmark.WireframeCubesTest} - Wireframe cube rendering test</li>
+ * </ul>
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.benchmark;
\ No newline at end of file
index 64689e0..ea856e6 100644 (file)
@@ -6,9 +6,15 @@
 
 package eu.svjatoslav.sixth.e3d.examples.launcher;
 
-import eu.svjatoslav.sixth.e3d.examples.*;
-import eu.svjatoslav.sixth.e3d.examples.galaxy_demo.PointCloudDemo;
+import eu.svjatoslav.sixth.e3d.examples.GraphDemo;
+import eu.svjatoslav.sixth.e3d.examples.OctreeDemo;
+import eu.svjatoslav.sixth.e3d.examples.RandomPolygonsDemo;
+import eu.svjatoslav.sixth.e3d.examples.RainingNumbersDemo;
 import eu.svjatoslav.sixth.e3d.examples.ShadedShapesDemo;
+import eu.svjatoslav.sixth.e3d.examples.TextEditorDemo;
+import eu.svjatoslav.sixth.e3d.examples.TextEditorDemo2;
+import eu.svjatoslav.sixth.e3d.examples.benchmark.GraphicsBenchmark;
+import eu.svjatoslav.sixth.e3d.examples.galaxy_demo.PointCloudDemo;
 
 import javax.swing.*;
 import java.awt.event.ActionEvent;