From a1ee3ca3823b521bbd397b8224341c3307597f36 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Sep 2025 17:54:13 +0000 Subject: [PATCH 1/2] Initial plan From 37d60b22a26a97460800c115cf6fc12389cc02e6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Sep 2025 18:01:15 +0000 Subject: [PATCH 2/2] Add GPU info API to renderer abstraction Co-authored-by: riccardobl <4943530+riccardobl@users.noreply.github.com> --- .../main/java/com/jme3/renderer/GpuInfo.java | 180 ++++++++++++++++++ .../main/java/com/jme3/renderer/Renderer.java | 7 + .../com/jme3/renderer/opengl/GLRenderer.java | 24 ++- .../java/com/jme3/system/NullRenderer.java | 6 + .../java/com/jme3/renderer/GpuInfoTest.java | 179 +++++++++++++++++ 5 files changed, 389 insertions(+), 7 deletions(-) create mode 100644 jme3-core/src/main/java/com/jme3/renderer/GpuInfo.java create mode 100644 jme3-core/src/test/java/com/jme3/renderer/GpuInfoTest.java diff --git a/jme3-core/src/main/java/com/jme3/renderer/GpuInfo.java b/jme3-core/src/main/java/com/jme3/renderer/GpuInfo.java new file mode 100644 index 0000000000..5814be6264 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/GpuInfo.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer; + +/** + * Holds information about the GPU hardware and drivers. + * This information can be used to detect device capabilities + * and adjust performance settings accordingly. + * + * @author jMonkeyEngine + */ +public class GpuInfo { + + private final String vendor; + private final String renderer; + private final String version; + private final String glslVersion; + private final String profile; + + /** + * Creates a new GpuInfo instance. + * + * @param vendor The GPU vendor (e.g., "NVIDIA Corporation", "AMD", "Intel") + * @param renderer The GPU renderer string (e.g., "GeForce GTX 1080") + * @param version The OpenGL version string + * @param glslVersion The GLSL version string + * @param profile The OpenGL profile ("Core" or "Compatibility") + */ + public GpuInfo(String vendor, String renderer, String version, String glslVersion, String profile) { + this.vendor = vendor != null ? vendor : "Unknown"; + this.renderer = renderer != null ? renderer : "Unknown"; + this.version = version != null ? version : "Unknown"; + this.glslVersion = glslVersion != null ? glslVersion : "Unknown"; + this.profile = profile != null ? profile : "Unknown"; + } + + /** + * Gets the GPU vendor name. + * + * @return The vendor name (e.g., "NVIDIA Corporation", "AMD", "Intel") + */ + public String getVendor() { + return vendor; + } + + /** + * Gets the GPU renderer name. + * + * @return The renderer name (e.g., "GeForce GTX 1080", "Radeon RX 580") + */ + public String getRenderer() { + return renderer; + } + + /** + * Gets the OpenGL version string. + * + * @return The OpenGL version + */ + public String getVersion() { + return version; + } + + /** + * Gets the GLSL (OpenGL Shading Language) version string. + * + * @return The GLSL version + */ + public String getGlslVersion() { + return glslVersion; + } + + /** + * Gets the OpenGL profile being used. + * + * @return "Core", "Compatibility", or "Unknown" + */ + public String getProfile() { + return profile; + } + + /** + * Checks if this appears to be a low-end GPU based on vendor and renderer strings. + * This is a heuristic method that may not be 100% accurate. + * + * @return true if this appears to be a low-end GPU + */ + public boolean isLowEndGpu() { + String lowerVendor = vendor.toLowerCase(); + String lowerRenderer = renderer.toLowerCase(); + + // Intel integrated graphics (most are considered low-end for gaming) + if (lowerVendor.contains("intel") && + (lowerRenderer.contains("hd graphics") || + lowerRenderer.contains("uhd graphics") || + lowerRenderer.contains("iris") || + lowerRenderer.contains("integrated"))) { + return true; + } + + // Software rendering + if (lowerRenderer.contains("software") || + lowerRenderer.contains("llvmpipe") || + lowerRenderer.contains("mesa")) { + return true; + } + + // Very old or low-end discrete GPUs + if (lowerRenderer.contains("geforce") && + (lowerRenderer.contains("gt ") || lowerRenderer.contains("mx "))) { + return true; + } + + return false; + } + + @Override + public String toString() { + return "GpuInfo{" + + "vendor='" + vendor + '\'' + + ", renderer='" + renderer + '\'' + + ", version='" + version + '\'' + + ", glslVersion='" + glslVersion + '\'' + + ", profile='" + profile + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + GpuInfo gpuInfo = (GpuInfo) o; + + if (!vendor.equals(gpuInfo.vendor)) return false; + if (!renderer.equals(gpuInfo.renderer)) return false; + if (!version.equals(gpuInfo.version)) return false; + if (!glslVersion.equals(gpuInfo.glslVersion)) return false; + return profile.equals(gpuInfo.profile); + } + + @Override + public int hashCode() { + int result = vendor.hashCode(); + result = 31 * result + renderer.hashCode(); + result = 31 * result + version.hashCode(); + result = 31 * result + glslVersion.hashCode(); + result = 31 * result + profile.hashCode(); + return result; + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java index acd0d599e1..403979f156 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java @@ -77,6 +77,13 @@ public interface Renderer { */ public EnumMap getLimits(); + /** + * Gets information about the GPU hardware and drivers. + * + * @return The GPU information, or null if not available + */ + public GpuInfo getGpuInfo(); + /** * Copies the render statistics. * diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java index 09a560d4a6..3944113752 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java @@ -98,6 +98,7 @@ public final class GLRenderer implements Renderer { private final NativeObjectManager objManager = new NativeObjectManager(); private final EnumSet caps = EnumSet.noneOf(Caps.class); private final EnumMap limits = new EnumMap<>(Limits.class); + private GpuInfo gpuInfo = null; private FrameBuffer mainFbOverride = null; private int defaultFBO = 0; @@ -177,6 +178,11 @@ public EnumMap getLimits() { return limits; } + @Override + public GpuInfo getGpuInfo() { + return gpuInfo; + } + private HashSet loadExtensions() { HashSet extensionSet = new HashSet<>(64); if (caps.contains(Caps.OpenGL30)) { @@ -629,6 +635,16 @@ private void loadCapabilitiesCommon() { caps.add(Caps.GLDebug); } + // Gather GPU information + String vendor = gl.glGetString(GL.GL_VENDOR); + String renderer = gl.glGetString(GL.GL_RENDERER); + String version = gl.glGetString(GL.GL_VERSION); + String glslVersion = gl.glGetString(GL.GL_SHADING_LANGUAGE_VERSION); + String profile = caps.contains(Caps.CoreProfile) ? "Core" : "Compatibility"; + + // Store GPU info for API access + gpuInfo = new GpuInfo(vendor, renderer, version, glslVersion, profile); + // Print context information logger.log(Level.INFO, "OpenGL Renderer Information\n" + " * Vendor: {0}\n" + @@ -636,13 +652,7 @@ private void loadCapabilitiesCommon() { " * OpenGL Version: {2}\n" + " * GLSL Version: {3}\n" + " * Profile: {4}", - new Object[]{ - gl.glGetString(GL.GL_VENDOR), - gl.glGetString(GL.GL_RENDERER), - gl.glGetString(GL.GL_VERSION), - gl.glGetString(GL.GL_SHADING_LANGUAGE_VERSION), - caps.contains(Caps.CoreProfile) ? "Core" : "Compatibility" - }); + new Object[]{vendor, renderer, version, glslVersion, profile}); // Print capabilities (if fine logging is enabled) if (logger.isLoggable(Level.FINE)) { diff --git a/jme3-core/src/main/java/com/jme3/system/NullRenderer.java b/jme3-core/src/main/java/com/jme3/system/NullRenderer.java index 28eb6b5231..4d9ff42503 100644 --- a/jme3-core/src/main/java/com/jme3/system/NullRenderer.java +++ b/jme3-core/src/main/java/com/jme3/system/NullRenderer.java @@ -36,6 +36,7 @@ import com.jme3.math.ColorRGBA; import com.jme3.math.Matrix4f; import com.jme3.renderer.Caps; +import com.jme3.renderer.GpuInfo; import com.jme3.renderer.Limits; import com.jme3.renderer.Renderer; import com.jme3.renderer.Statistics; @@ -77,6 +78,11 @@ public EnumSet getCaps() { return caps; } + @Override + public GpuInfo getGpuInfo() { + return null; // No GPU info available for null renderer + } + @Override public Statistics getStatistics() { return stats; diff --git a/jme3-core/src/test/java/com/jme3/renderer/GpuInfoTest.java b/jme3-core/src/test/java/com/jme3/renderer/GpuInfoTest.java new file mode 100644 index 0000000000..00b980f905 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/renderer/GpuInfoTest.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2025 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer; + +import com.jme3.system.NullRenderer; +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests for GPU information functionality. + * + * @author jMonkeyEngine + */ +public class GpuInfoTest { + + /** + * Test basic GpuInfo construction and getter methods. + */ + @Test + public void testGpuInfoConstruction() { + GpuInfo gpuInfo = new GpuInfo("NVIDIA Corporation", "GeForce GTX 1080", + "4.6.0 NVIDIA 456.71", "4.60 NVIDIA", "Core"); + + Assert.assertEquals("NVIDIA Corporation", gpuInfo.getVendor()); + Assert.assertEquals("GeForce GTX 1080", gpuInfo.getRenderer()); + Assert.assertEquals("4.6.0 NVIDIA 456.71", gpuInfo.getVersion()); + Assert.assertEquals("4.60 NVIDIA", gpuInfo.getGlslVersion()); + Assert.assertEquals("Core", gpuInfo.getProfile()); + } + + /** + * Test GpuInfo construction with null values. + */ + @Test + public void testGpuInfoWithNulls() { + GpuInfo gpuInfo = new GpuInfo(null, null, null, null, null); + + Assert.assertEquals("Unknown", gpuInfo.getVendor()); + Assert.assertEquals("Unknown", gpuInfo.getRenderer()); + Assert.assertEquals("Unknown", gpuInfo.getVersion()); + Assert.assertEquals("Unknown", gpuInfo.getGlslVersion()); + Assert.assertEquals("Unknown", gpuInfo.getProfile()); + } + + /** + * Test low-end GPU detection for Intel integrated graphics. + */ + @Test + public void testLowEndDetectionIntel() { + GpuInfo intel1 = new GpuInfo("Intel", "Intel(R) HD Graphics 4000", + "4.0.0", "4.00", "Compatibility"); + Assert.assertTrue("Intel HD Graphics should be detected as low-end", intel1.isLowEndGpu()); + + GpuInfo intel2 = new GpuInfo("Intel Corporation", "Intel(R) UHD Graphics 620", + "4.5.0", "4.50", "Core"); + Assert.assertTrue("Intel UHD Graphics should be detected as low-end", intel2.isLowEndGpu()); + + GpuInfo intel3 = new GpuInfo("Intel", "Intel(R) Iris(TM) Graphics 6100", + "4.3.0", "4.30", "Core"); + Assert.assertTrue("Intel Iris Graphics should be detected as low-end", intel3.isLowEndGpu()); + } + + /** + * Test low-end GPU detection for software rendering. + */ + @Test + public void testLowEndDetectionSoftware() { + GpuInfo software1 = new GpuInfo("VMware, Inc.", "llvmpipe (LLVM 10.0.0, 256 bits)", + "3.1 Mesa 20.0.0", "3.10", "Core"); + Assert.assertTrue("Software rendering should be detected as low-end", software1.isLowEndGpu()); + + GpuInfo software2 = new GpuInfo("Microsoft Corporation", "GDI Generic", + "1.1.0", "1.10", "Compatibility"); + Assert.assertTrue("Generic software renderer should be detected as low-end", software2.isLowEndGpu()); + } + + /** + * Test low-end GPU detection for NVIDIA GT series. + */ + @Test + public void testLowEndDetectionNvidiaGT() { + GpuInfo nvidiaGT = new GpuInfo("NVIDIA Corporation", "GeForce GT 730", + "4.6.0 NVIDIA 456.71", "4.60 NVIDIA", "Core"); + Assert.assertTrue("NVIDIA GT series should be detected as low-end", nvidiaGT.isLowEndGpu()); + + GpuInfo nvidiaMX = new GpuInfo("NVIDIA Corporation", "GeForce MX150", + "4.6.0 NVIDIA 456.71", "4.60 NVIDIA", "Core"); + Assert.assertTrue("NVIDIA MX series should be detected as low-end", nvidiaMX.isLowEndGpu()); + } + + /** + * Test that high-end GPUs are not detected as low-end. + */ + @Test + public void testHighEndDetection() { + GpuInfo nvidiaRTX = new GpuInfo("NVIDIA Corporation", "GeForce RTX 3080", + "4.6.0 NVIDIA 456.71", "4.60 NVIDIA", "Core"); + Assert.assertFalse("NVIDIA RTX should not be detected as low-end", nvidiaRTX.isLowEndGpu()); + + GpuInfo amdRX = new GpuInfo("Advanced Micro Devices, Inc.", "AMD Radeon RX 6800 XT", + "4.6.0", "4.60", "Core"); + Assert.assertFalse("AMD RX should not be detected as low-end", amdRX.isLowEndGpu()); + + GpuInfo nvidiaGTX = new GpuInfo("NVIDIA Corporation", "GeForce GTX 1080", + "4.6.0 NVIDIA 456.71", "4.60 NVIDIA", "Core"); + Assert.assertFalse("NVIDIA GTX (non-GT) should not be detected as low-end", nvidiaGTX.isLowEndGpu()); + } + + /** + * Test equals and hashCode methods. + */ + @Test + public void testEqualsAndHashCode() { + GpuInfo gpu1 = new GpuInfo("NVIDIA Corporation", "GeForce GTX 1080", + "4.6.0 NVIDIA 456.71", "4.60 NVIDIA", "Core"); + GpuInfo gpu2 = new GpuInfo("NVIDIA Corporation", "GeForce GTX 1080", + "4.6.0 NVIDIA 456.71", "4.60 NVIDIA", "Core"); + GpuInfo gpu3 = new GpuInfo("AMD", "Radeon RX 580", + "4.6.0", "4.60", "Core"); + + Assert.assertEquals("Same GPU info should be equal", gpu1, gpu2); + Assert.assertEquals("Same GPU info should have same hash code", gpu1.hashCode(), gpu2.hashCode()); + Assert.assertNotEquals("Different GPU info should not be equal", gpu1, gpu3); + } + + /** + * Test toString method. + */ + @Test + public void testToString() { + GpuInfo gpuInfo = new GpuInfo("NVIDIA Corporation", "GeForce GTX 1080", + "4.6.0 NVIDIA 456.71", "4.60 NVIDIA", "Core"); + String str = gpuInfo.toString(); + + Assert.assertTrue("toString should contain vendor", str.contains("NVIDIA Corporation")); + Assert.assertTrue("toString should contain renderer", str.contains("GeForce GTX 1080")); + Assert.assertTrue("toString should contain version", str.contains("4.6.0 NVIDIA 456.71")); + Assert.assertTrue("toString should contain GLSL version", str.contains("4.60 NVIDIA")); + Assert.assertTrue("toString should contain profile", str.contains("Core")); + } + + /** + * Test NullRenderer returns null for GPU info. + */ + @Test + public void testNullRendererGpuInfo() { + NullRenderer nullRenderer = new NullRenderer(); + Assert.assertNull("NullRenderer should return null GPU info", nullRenderer.getGpuInfo()); + } +} \ No newline at end of file