From 7e6574b0240cb9d2b8a9d8d224cd3d654d3583b1 Mon Sep 17 00:00:00 2001 From: Svjatoslav Agejenko Date: Sun, 15 Mar 2026 05:21:20 +0200 Subject: [PATCH] refactor(examples): extract GraphicsBenchmark into dedicated package 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 | 28 +- .../sixth/e3d/examples/GraphicsBenchmark.java | 460 ------------------ .../e3d/examples/benchmark/BenchmarkTest.java | 36 ++ .../examples/benchmark/GraphicsBenchmark.java | 221 +++++++++ .../examples/benchmark/SolidCubesTest.java | 75 +++ .../e3d/examples/benchmark/TestResult.java | 37 ++ .../e3d/examples/benchmark/TexturedCube.java | 76 +++ .../examples/benchmark/TexturedCubesTest.java | 66 +++ .../benchmark/WireframeCubesTest.java | 76 +++ .../e3d/examples/benchmark/package-info.java | 19 + .../launcher/ApplicationListPanel.java | 10 +- 11 files changed, 625 insertions(+), 479 deletions(-) delete mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/examples/GraphicsBenchmark.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/BenchmarkTest.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/GraphicsBenchmark.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/SolidCubesTest.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TestResult.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TexturedCube.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TexturedCubesTest.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/WireframeCubesTest.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/package-info.java diff --git a/doc/index.org b/doc/index.org index db97fed..2ad0fd7 100644 --- a/doc/index.org +++ b/doc/index.org @@ -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 index 0071d47..0000000 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/GraphicsBenchmark.java +++ /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. - * - *

The benchmark creates a 16x16x16 grid of cubes (4096 total) with the camera - * following a deterministic orbital path. Each test runs for 30 seconds.

- * - * @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 results = new ArrayList<>(); - private final List 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 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 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 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 index 0000000..13326c3 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/BenchmarkTest.java @@ -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 index 0000000..5d9a77d --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/GraphicsBenchmark.java @@ -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. + * + *

The benchmark creates a 16x16x16 grid of cubes (4096 total) with the camera + * following a deterministic orbital path. Each test runs for 30 seconds.

+ * + *

Available tests:

+ *
    + *
  • {@link SolidCubesTest} - Solid-color polygon rendering
  • + *
  • {@link TexturedCubesTest} - Textured polygon rendering
  • + *
  • {@link WireframeCubesTest} - Line rendering
  • + *
+ */ +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 results = new ArrayList<>(); + private final List 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 index 0000000..ca1a7dc --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/SolidCubesTest.java @@ -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 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 index 0000000..9c7eef2 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TestResult.java @@ -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 index 0000000..ecb883e --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TexturedCube.java @@ -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 index 0000000..15b99f8 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TexturedCubesTest.java @@ -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 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 index 0000000..c76a184 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/WireframeCubesTest.java @@ -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 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 index 0000000..41d5c8b --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/package-info.java @@ -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. + * + *

The benchmark suite includes:

+ *
    + *
  • {@link eu.svjatoslav.sixth.e3d.examples.benchmark.GraphicsBenchmark} - Main benchmark runner
  • + *
  • {@link eu.svjatoslav.sixth.e3d.examples.benchmark.BenchmarkTest} - Interface for test implementations
  • + *
  • {@link eu.svjatoslav.sixth.e3d.examples.benchmark.SolidCubesTest} - Solid-color cube rendering test
  • + *
  • {@link eu.svjatoslav.sixth.e3d.examples.benchmark.TexturedCubesTest} - Textured cube rendering test
  • + *
  • {@link eu.svjatoslav.sixth.e3d.examples.benchmark.WireframeCubesTest} - Wireframe cube rendering test
  • + *
+ */ + +package eu.svjatoslav.sixth.e3d.examples.benchmark; \ No newline at end of file diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java index 64689e0..ea856e6 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java @@ -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; -- 2.20.1