diff --git a/core/src/mindustry/ClientLauncher.java b/core/src/mindustry/ClientLauncher.java index 5866c47a2c..91a8754457 100644 --- a/core/src/mindustry/ClientLauncher.java +++ b/core/src/mindustry/ClientLauncher.java @@ -54,12 +54,12 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform //debug GL information Log.info("[GL] Version: @", graphics.getGLVersion()); Log.info("[GL] Max texture size: @", maxTextureSize); - Log.info("[GL] Using @ context.", gl30 != null ? "OpenGL 3" : "OpenGL 2"); + Log.info("[GL] Using @ API.", gl30 != null ? "OpenGL 3" : "OpenGL 2"); if(GpuDetect.gpus.size > 0) Log.info("[GL] Detected GPU: @", GpuDetect.gpus.toString(", ")); - if(GpuDetect.isIntel) Log.warn("[GL] Intel GPU detected. Due to memory corruption issues, OpenGL 3 support has been disabled for Intel GPUs. See issue #11041."); + if(GpuDetect.hasIntel && !graphics.isGL30Available()) Log.warn("[GL] Intel GPU detected. Due to memory corruption issues, OpenGL 3 support has been disabled for Intel GPUs. See issue #11041."); - if(gl30 == null && !GpuDetect.isIntel) Log.warn("[GL] Your device or video drivers do not support OpenGL 3. This will cause performance issues."); + if(gl30 == null && !GpuDetect.hasIntel) Log.warn("[GL] Your device or video drivers do not support OpenGL 3. This will cause performance issues."); if(NvGpuInfo.hasMemoryInfo()) Log.info("[GL] Total available VRAM: @mb", NvGpuInfo.getMaxMemoryKB()/1024); diff --git a/core/src/mindustry/graphics/GpuDetect.java b/core/src/mindustry/graphics/GpuDetect.java index 8b3c56ea66..88ce08d8cd 100644 --- a/core/src/mindustry/graphics/GpuDetect.java +++ b/core/src/mindustry/graphics/GpuDetect.java @@ -9,7 +9,7 @@ import java.util.*; public class GpuDetect{ public static String rawGpuString = ""; public static Seq gpus = new Seq<>(); - public static boolean isIntel, isNvidia, isAMD; + public static boolean hasIntel, hasNvidia, hasAMD; public static void init(){ if(OS.isWindows){ @@ -17,9 +17,9 @@ public class GpuDetect{ rawGpuString = OS.exec("wmic", "path", "win32_VideoController", "get", "name"); gpus = Seq.with(rawGpuString.split("\n")).map(s -> s.trim()).removeAll(s -> s.isEmpty() || s.equalsIgnoreCase("name")); - isIntel = rawGpuString.toLowerCase(Locale.ROOT).contains("intel"); - isNvidia = rawGpuString.toLowerCase(Locale.ROOT).contains("nvidia"); - isAMD = rawGpuString.toLowerCase(Locale.ROOT).contains("amd") || rawGpuString.toLowerCase(Locale.ROOT).contains("radeon"); + hasIntel = rawGpuString.toLowerCase(Locale.ROOT).contains("intel"); + hasNvidia = rawGpuString.toLowerCase(Locale.ROOT).contains("nvidia"); + hasAMD = rawGpuString.toLowerCase(Locale.ROOT).contains("amd") || rawGpuString.toLowerCase(Locale.ROOT).contains("radeon"); }catch(Exception e){ Log.err(e); } diff --git a/core/src/mindustry/graphics/ParticleRenderer.java b/core/src/mindustry/graphics/ParticleRenderer.java new file mode 100644 index 0000000000..1d3beae510 --- /dev/null +++ b/core/src/mindustry/graphics/ParticleRenderer.java @@ -0,0 +1,233 @@ +package mindustry.graphics; + +import arc.*; +import arc.graphics.*; +import arc.graphics.gl.*; +import arc.math.*; +import arc.math.geom.*; +import arc.util.*; +import mindustry.*; + +import java.util.concurrent.*; + +/** WIP experimental point-sprite based particle renderer. */ +public class ParticleRenderer{ + static final boolean useAsync = true; + static final int maxParticles = 100_000, maxParticlesPerFrame = 25_000; + static final int particleSize = + 1 + //time + 1 + //total lifetime + 2 + //position xy + 2 + //velocity xy + 1 + //sizeFrom + 1 + //sizeTo + 1 //color + ; //TODO fade color? + + static final int particleVertexSize = + 2 + //xy + 1 + //size + 1 //color + ; + + static final float globalDrag = 0.05f; + + static final float cullPadding = 8f*3f; + + static final VertexAttribute[] attributes = {VertexAttribute.position, new VertexAttribute(1, "a_size"), VertexAttribute.color}; + static Shader shader; + + float[] data = new float[maxParticles * particleSize]; + volatile int count; + + float[] addBuffer = new float[maxParticlesPerFrame * particleSize]; //TODO should be smaller than data + int addCount; + + Mesh mesh = new Mesh(false, maxParticles, 0, attributes); + float[] vertexBuffer = new float[maxParticles * particleVertexSize]; + volatile int vertexBufferLength; + + @Nullable Future asyncTask; + + public int count(){ + return count; + } + + public void updateAndRender(){ + if(!Vars.state.isPaused()){ + update(); + } + + render(); + } + + public void update(){ + if(useAsync && asyncTask != null){ + try{ + asyncTask.get(); + }catch(Exception e){ + Log.err(e); + } + } + + //append added particles to the queue + int maxAdded = Math.min(maxParticles - count, addCount); + //if maxAdded is less than addCount, prioritize particles at the end of the array (most recent) + int addOffset = addCount - maxAdded; + + if(maxAdded > 0){ + System.arraycopy(addBuffer, addOffset * particleSize, data, count * particleSize, maxAdded * particleSize); + } + + count += maxAdded; + addCount = 0; + + //uses data calculated from previous frame + uploadMeshData(mesh); + + if(useAsync){ + asyncTask = Vars.mainExecutor.submit(this::updateAsync); + }else{ + updateAsync(); + } + } + + void updateAsync(){ + count = update(data, count, Time.delta); + buildVertices(); + } + + public void render(){ + if(shader == null) makeShader(); + + Gl.enable(Gl.programPointSize); + + shader.bind(); + shader.setUniformMatrix4("u_mat", Core.camera.mat); + shader.setUniformf("u_scaling", Core.graphics.getWidth() / Core.camera.width); + + mesh.render(shader, Gl.points); + } + + public void add(float x, float y, float lifetime, float vx, float vy, float sizeFrom, float sizeTo, float color){ + if(addCount * particleSize >= addBuffer.length || + //ignore particles added not in the camera viewport + //TODO fast-moving offscreen particles won't show up. + !Rect.contains( + x - Core.camera.width/2f - sizeFrom - cullPadding, + y - Core.camera.height/2f - sizeFrom - cullPadding, + Core.camera.width + sizeFrom*2f + cullPadding*2f, + Core.camera.height + sizeFrom*2f + cullPadding*2f, x, y)) return; + + float[] buf = addBuffer; + + int i = addCount * particleSize; + buf[i + 0] = 0f; + buf[i + 1] = lifetime; + buf[i + 2] = x; + buf[i + 3] = y; + buf[i + 4] = vx; + buf[i + 5] = vy; + buf[i + 6] = sizeFrom * 2f; + buf[i + 7] = sizeTo * 2f; + buf[i + 8] = color; + + addCount ++; + } + + void buildVertices(){ + //TODO: cull based on camera viewport + float[] data = this.data; + float count = this.count * particleSize; + + float[] vertices = vertexBuffer; + + int bufferIndex = 0; + + for(int i = 0; i < count; i += particleSize){ + float color = data[i + 8]; //TODO: colorTo + float size = Mathf.lerp(data[i + 6], data[i + 7], Mathf.clamp(data[i] / data[i + 1])); + + //xy + vertices[bufferIndex + 0] = data[i + 2]; + vertices[bufferIndex + 1] = data[i + 3]; + //size + vertices[bufferIndex + 2] = size; + //color + vertices[bufferIndex + 3] = color; + + bufferIndex += particleVertexSize; + } + + vertexBufferLength = bufferIndex; + } + + void uploadMeshData(Mesh mesh){ + var buffer = mesh.getVerticesBuffer(); + + buffer.position(0); + buffer.limit(vertexBufferLength); + buffer.put(vertexBuffer, 0, vertexBufferLength); + buffer.position(0); + } + + static int update(float[] data, int count, float delta){ + int head = count * particleSize; + + float dragValue = Math.max(1f - globalDrag * delta, 0f); + + for(int i = 0; i < head; i += particleSize){ + data[i] += delta; + if(data[i] >= data[i + 1]){ + if(head > particleSize){ + //swap head + System.arraycopy(data, head - particleSize, data, i, particleSize); + } + head -= particleSize; + i -= particleSize; + }else{ + //velocity + data[i + 2] += data[i + 4] * delta; + data[i + 3] += data[i + 5] * delta; + data[i + 4] *= dragValue; + data[i + 5] *= dragValue; + } + } + + return head / particleSize; + } + + static void makeShader(){ + shader = new Shader( + """ + uniform mat4 u_mat; + uniform float u_scaling; + + attribute vec4 a_position; + attribute float a_size; + attribute vec4 a_color; + + varying vec4 v_color; + + void main(){ + v_color = a_color; + + gl_Position = u_mat * a_position; + gl_PointSize = a_size * u_scaling; + } + """, + """ + varying lowp vec4 v_color; + + #define RAD1 0.43 + #define RAD2 0.5 + + + void main(){ + vec2 delta = gl_PointCoord - vec2(0.5); + gl_FragColor = vec4(v_color.rgb, v_color.a * (1.0-smoothstep(RAD1*RAD1, RAD2*RAD2, delta.x * delta.x + delta.y * delta.y))); + } + """ + ); + } +} diff --git a/desktop/src/mindustry/desktop/DesktopLauncher.java b/desktop/src/mindustry/desktop/DesktopLauncher.java index 62420c9156..ca97a0bf27 100644 --- a/desktop/src/mindustry/desktop/DesktopLauncher.java +++ b/desktop/src/mindustry/desktop/DesktopLauncher.java @@ -52,7 +52,8 @@ public class DesktopLauncher extends ClientLauncher{ height = 700; //on Windows, Intel drivers might be buggy with OpenGL 3.x, so only use 2.x. See https://github.com/Anuken/Mindustry/issues/11041 - if(GpuDetect.isIntel){ + if(GpuDetect.hasIntel && (!GpuDetect.hasAMD || !GpuDetect.hasNvidia)){ + allowGl30 = false; coreProfile = false; glVersions = new int[][]{{2, 1}, {2, 0}}; }else if(OS.isMac){ diff --git a/gradle.properties b/gradle.properties index 816af52630..5aabc69edc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,4 +26,4 @@ org.gradle.caching=true org.gradle.internal.http.socketTimeout=100000 org.gradle.internal.http.connectionTimeout=100000 android.enableR8.fullMode=false -archash=bf0cbe10da +archash=390d5e8665