diff --git a/luarules/gadgets/gfx_energy_explosion_particles_gl4.lua b/luarules/gadgets/gfx_energy_explosion_particles_gl4.lua index ad2b082fc84..2423e6b2b67 100644 --- a/luarules/gadgets/gfx_energy_explosion_particles_gl4.lua +++ b/luarules/gadgets/gfx_energy_explosion_particles_gl4.lua @@ -53,11 +53,8 @@ local CONFIG = { minEnergyScore = 15, -- Units whose energy score divided by metalcost is below this ratio are - -- treated as "incidental energy" (a combat unit with a passive generator) - -- and receive no burst. Units qualifying via energyconv_capacity or an - -- explicit overrides.particleCount entry are exempt from this check. - -- armbanth (score 24 / metal 13500 = 0.0018) → excluded. - -- armcarry (score 48 / metal 1400 = 0.034) → included. + -- treated as incidental energy and receive no burst. Units qualifying via + -- energyconv_capacity or an explicit overrides.particleCount entry are exempt. minEnergyScoreRatio = 0.003, -- Final particleCount = clamp(score ^ particleCountPower * particleCountMul, minPC, maxPC) @@ -218,11 +215,11 @@ local GL_FRONT_AND_BACK = GL.FRONT_AND_BACK local GL_FILL = GL.FILL local GL_LEQUAL = GL.LEQUAL -local LuaShader = gl.LuaShader -local InstanceVBOTable = gl.InstanceVBOTable -local pushElementInstance = InstanceVBOTable.pushElementInstance -local popElementInstance = InstanceVBOTable.popElementInstance -local uploadElementRange = InstanceVBOTable.uploadElementRange +local LuaShader = gl.LuaShader +local InstanceVBOTable = gl.InstanceVBOTable +local pushElementInstance = InstanceVBOTable.pushElementInstance +local popElementInstance = InstanceVBOTable.popElementInstance +local uploadElementRange = InstanceVBOTable.uploadElementRange local mathRandom = math.random local mathSqrt = math.sqrt @@ -287,92 +284,90 @@ local dirtyMin, dirtyMax = mathHuge, -1 local vsSrc = [[ #version 430 core -#line 10000 -layout(location = 0) in vec4 vertexPosUV; -layout(location = 1) in vec4 spawnPosAndSize; // xyz=spawnPos, w=packed(sizeMult,fadeFrames) -layout(location = 2) in vec4 velAndSpawnFrame; // xyz=velocity (elmos/frame), w=spawnFrame -layout(location = 3) in vec4 instColor; // rgb + alpha -layout(location = 4) in vec4 rotData; // x=rotVal0, y=rotVel0, z=rotAcc (deg/frame²), w=deathFrame +layout(location = 0) in vec4 shapeLocalAndFlag; // xyz=local cube corner, w=1 for glow billboard +layout(location = 1) in vec4 spawnPosAndSize; // xyz=spawnPos, w=packed(sizeMult,fadeFrames) +layout(location = 2) in vec4 velAndSpawnFrame; // xyz=velocity, w=spawnFrame +layout(location = 3) in vec4 instColor; +layout(location = 4) in vec4 rotData; // x=rotVal0, y=rotVel0, z=rotAcc, w=deathFrame +layout(location = 5) in vec4 shapeNormalAndGlowU; // xyz=cube normal, w=glow billboard x in [-1,1] +layout(location = 6) in float shapeGlowV; // glow billboard y in [-1,1] //__ENGINEUNIFORMBUFFERDEFS__ uniform float drag; -uniform vec3 gravity; -uniform float fadeInFrames; // 0 = no fade-in +uniform vec3 gravity; +uniform float fadeInFrames; +uniform float drawRadius; +uniform float glowScale; uniform float wobbleAmp; -uniform float wobbleFreq; uniform float wobbleVar; +uniform float wobbleFreq; uniform float wobbleFreqVar; uniform float wobbleRampFrames; -out vec3 v_worldPos; -out vec4 v_color; -out float v_rotVal; -out float v_dead; -out vec3 v_phaseSeed; -out float v_sizeMult; -out float v_breathScale; // glow-breath amplitude envelope: 1.0 for first half of life, ramps to 0 at death +out vec4 g_color; +out vec3 g_normal; +out vec3 g_worldPos; +out vec3 g_localPos; +out vec3 g_noiseSeed; +out vec2 g_glowUV; +out float g_isGlow; +out float g_seed; +out float g_breathScale; + +float hash11(float x) { return fract(sin(x) * 43758.5453); } + +mat3 rotXYZ(vec3 a) { + float cx = cos(a.x), sx = sin(a.x); + float cy = cos(a.y), sy = sin(a.y); + float cz = cos(a.z), sz = sin(a.z); + mat3 Rx = mat3(1,0,0, 0,cx,sx, 0,-sx,cx); + mat3 Ry = mat3(cy,0,-sy, 0,1,0, sy,0,cy); + mat3 Rz = mat3(cz,sz,0, -sz,cz,0, 0,0,1); + return Rz * Ry * Rx; +} + +void hideVertex() { + g_color = vec4(0.0); + g_normal = vec3(0.0, 1.0, 0.0); + g_worldPos = vec3(0.0); + g_localPos = vec3(0.0); + g_noiseSeed = vec3(0.0); + g_glowUV = vec2(0.0); + g_isGlow = 0.0; + g_seed = 0.0; + g_breathScale = 0.0; + gl_Position = vec4(2.0, 2.0, 2.0, 1.0); +} void main() { float currentFrame = timeInfo.x + timeInfo.w; float spawnFrame = velAndSpawnFrame.w; float deathFrame = rotData.w; + if (currentFrame >= deathFrame) { hideVertex(); return; } - if (currentFrame >= deathFrame) { - v_dead = 1.0; - gl_Position = vec4(2.0, 2.0, 2.0, 1.0); - v_worldPos = vec3(0.0); - v_color = vec4(0.0); - v_rotVal = 0.0; - v_phaseSeed = vec3(0.0); - v_sizeMult = 0.0; - v_breathScale = 0.0; - return; - } - v_dead = 0.0; - - float t = max(currentFrame - spawnFrame, 0.0); + float t = currentFrame - spawnFrame; + if (t < 0.0) t = 0.0; - // Ballistic motion with linear damping (clamp t so drag doesn't reverse vel). - float tCap = (drag > 0.0001) ? (1.0 / drag) : 1.0e6; - float tClamp = min(t, tCap); - vec3 worldPos = spawnPosAndSize.xyz - + velAndSpawnFrame.xyz * tClamp * (1.0 - 0.5 * drag * tClamp) - + 0.5 * gravity * t * t; + vec3 center = spawnPosAndSize.xyz + velAndSpawnFrame.xyz * t * (1.0 - 0.5 * drag * t) + 0.5 * gravity * t * t; - // Optional wobble (matches nano gadget's swirl). - // rotData.z is now rotAcc (deg/frame²); use spawnFrame for wobble timing. if (wobbleAmp > 0.0001) { float wobbleT = max(currentFrame - spawnFrame, 0.0); float totalLife = max(deathFrame - spawnFrame, 1.0); float bell = sin(3.14159265 * wobbleT / totalLife); - if (wobbleRampFrames > 0.5) - bell *= min(1.0, wobbleT / wobbleRampFrames); + if (wobbleRampFrames > 0.5) bell *= min(1.0, wobbleT / wobbleRampFrames); vec3 vdir = velAndSpawnFrame.xyz; vec3 ref, axA, axB; float refAngle = rotData.x * 0.01745329; float s1 = sin(refAngle), c1 = cos(refAngle); float s2 = sin(refAngle * 2.19), c2 = cos(refAngle * 2.19); - if (abs(vdir.y) < 0.95) { - ref = normalize(vec3(s1, c1, s2)); - } else { - ref = normalize(vec3(c1, s2, c2)); - } + if (abs(vdir.y) < 0.95) ref = normalize(vec3(s1, c1, s2)); else ref = normalize(vec3(c1, s2, c2)); float h1 = fract(sin(rotData.x * 12.9898 + rotData.y * 78.233) * 43758.5453); float h2 = fract(sin(rotData.x * 23.1451 + rotData.y * 34.567) * 65432.0987); - axA = normalize(vec3( - sin(h1 * 6.28318), - sin(h2 * 6.28318), - cos(h1 * 3.14159 + h2 * 3.14159) - )); + axA = normalize(vec3(sin(h1 * 6.28318), sin(h2 * 6.28318), cos(h1 * 3.14159 + h2 * 3.14159))); axB = cross(axA, ref); - if (dot(axB, axB) > 0.001) { - axB = normalize(axB); - } else { - axB = cross(axA, (abs(axA.x) < 0.9) ? vec3(1, 0, 0) : vec3(0, 1, 0)); - axB = normalize(axB); - } + if (dot(axB, axB) > 0.001) axB = normalize(axB); else { axB = cross(axA, (abs(axA.x) < 0.9) ? vec3(1,0,0) : vec3(0,1,0)); axB = normalize(axB); } float phaseOff = radians(rotData.x); float hash = fract(sin(rotData.x * 12.9898 + rotData.y * 78.233) * 43758.5453); float freqScale = max(0.0, 1.0 + wobbleFreqVar * (2.0 * hash - 1.0)); @@ -380,171 +375,60 @@ void main() { float hash2 = fract(hash * 113.7 + 0.317); float ampScale = max(0.0, 1.0 + wobbleVar * (2.0 * hash2 - 1.0)); float ph = currentFrame * wobbleFreq * freqScale * dirSign * (6.2831853 / 30.0) + phaseOff; - worldPos += (axA * cos(ph) + axB * sin(ph)) * (wobbleAmp * ampScale * bell); + center += (axA * cos(ph) + axB * sin(ph)) * (wobbleAmp * ampScale * bell); } - // Decode packed w: sizeMult in low 1024, fadeFrames * 1024 above. float packedW = abs(spawnPosAndSize.w); float fadeFrames = floor(packedW / 1024.0); float sizeMult = (packedW - fadeFrames * 1024.0) / 256.0; - - float fadeOut = (fadeFrames > 0.5) - ? clamp((deathFrame - currentFrame) / fadeFrames, 0.0, 1.0) - : 1.0; - float fadeIn = (fadeInFrames > 0.5) - ? clamp(t / fadeInFrames, 0.0, 1.0) - : 1.0; + float fadeOut = (fadeFrames > 0.5) ? clamp((deathFrame - currentFrame) / fadeFrames, 0.0, 1.0) : 1.0; + float fadeIn = (fadeInFrames > 0.5) ? clamp(t / fadeInFrames, 0.0, 1.0) : 1.0; float fade = fadeOut * fadeIn; - // Quadratic rotation integration: val0 + vel*t + 0.5*acc*t² - float rotVel = rotData.y; - float rotAcc = rotData.z; - float rotVal = rotData.x + rotVel * t + 0.5 * rotAcc * t * t; - - v_worldPos = worldPos; - v_color = instColor * fade; - v_rotVal = rotVal; - v_sizeMult = sizeMult; - v_phaseSeed = vec3(rotData.x, rotData.y, rotData.x + rotData.y); + float rotVal = rotData.x + rotData.y * t + 0.5 * rotData.z * t * t; + float size = drawRadius * sizeMult; + vec3 phaseSeed = vec3(rotData.x, rotData.y, rotData.x + rotData.y); + vec3 noiseSeed = phaseSeed * 137.0 + vec3(11.0, 47.0, 83.0); + float h = dot(phaseSeed, vec3(0.123, 0.456, 0.789)); + vec3 phase = vec3(hash11(h), hash11(h+1.7), hash11(h+3.3)) * 6.2831853; + float r = radians(rotVal); + mat3 R = rotXYZ(phase + vec3(r * 1.0, r * 1.3, r * 0.7)); - // Glow-breath envelope: full amplitude until half-life, then ramps to 0 - // by death. smoothstep is reversed because we want 1 -> 0 as life goes - // 0.5 -> 1.0 (so big lingering particles stop pulsing as they fade out). float totalLifeBR = max(deathFrame - spawnFrame, 1.0); - float lifeFrac = clamp(t / totalLifeBR, 0.0, 1.0); - v_breathScale = 1.0 - smoothstep(0.5, 1.0, lifeFrac); - - gl_Position = vec4(worldPos, 1.0); // GS reads this -} -]] - -local gsSrc = [[ -#version 430 core - -layout(triangles) in; -layout(triangle_strip, max_vertices = 28) out; - -//__ENGINEUNIFORMBUFFERDEFS__ - -uniform float drawRadius; -uniform int u_shape; -uniform float glowScale; -uniform float glowIntensity; - -in vec3 v_worldPos[]; -in vec4 v_color[]; -in float v_rotVal[]; -in float v_dead[]; -in vec3 v_phaseSeed[]; -in float v_sizeMult[]; -in float v_breathScale[]; - -out vec4 g_color; -out vec3 g_normal; -out vec3 g_worldPos; -out vec3 g_localPos; -out vec3 g_noiseSeed; -out vec2 g_glowUV; -out float g_isGlow; -out float g_seed; -out float g_breathScale; - -float hash11(float x) { return fract(sin(x) * 43758.5453); } - -mat3 rotXYZ(vec3 a) { - float cx = cos(a.x), sx = sin(a.x); - float cy = cos(a.y), sy = sin(a.y); - float cz = cos(a.z), sz = sin(a.z); - mat3 Rx = mat3(1,0,0, 0,cx,sx, 0,-sx,cx); - mat3 Ry = mat3(cy,0,-sy, 0,1,0, sy,0,cy); - mat3 Rz = mat3(cz,sz,0, -sz,cz,0, 0,0,1); - return Rz * Ry * Rx; -} - -void emitFace(vec3 c0, vec3 c1, vec3 c2, vec3 c3, vec3 n, vec3 center, vec4 col, vec3 noiseSeed, float seed) { - g_color = col; g_normal = n; g_noiseSeed = noiseSeed; g_isGlow = 0.0; g_glowUV = vec2(0.0); g_seed = seed; g_breathScale = 0.0; - g_localPos = c0; g_worldPos = center + c0; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - g_localPos = c1; g_worldPos = center + c1; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - g_localPos = c2; g_worldPos = center + c2; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - g_localPos = c3; g_worldPos = center + c3; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - EndPrimitive(); -} - -void emitTri(vec3 c0, vec3 c1, vec3 c2, vec3 n, vec3 center, vec4 col, vec3 noiseSeed, float seed) { - g_color = col; g_normal = n; g_noiseSeed = noiseSeed; g_isGlow = 0.0; g_glowUV = vec2(0.0); g_seed = seed; g_breathScale = 0.0; - g_localPos = c0; g_worldPos = center + c0; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - g_localPos = c1; g_worldPos = center + c1; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - g_localPos = c2; g_worldPos = center + c2; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - EndPrimitive(); -} - -void emitGlow(vec3 center, vec4 col, float halfSize, float seed) { - vec3 right = cameraViewInv[0].xyz * halfSize; - vec3 up = cameraViewInv[1].xyz * halfSize; - g_color = col; g_normal = vec3(0.0, 1.0, 0.0); g_noiseSeed = vec3(0.0); - g_localPos = vec3(0.0); g_isGlow = 1.0; g_seed = seed; g_breathScale = v_breathScale[0]; - g_glowUV = vec2(-1.0, -1.0); g_worldPos = center - right - up; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - g_glowUV = vec2( 1.0, -1.0); g_worldPos = center + right - up; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - g_glowUV = vec2(-1.0, 1.0); g_worldPos = center - right + up; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - g_glowUV = vec2( 1.0, 1.0); g_worldPos = center + right + up; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - EndPrimitive(); -} - -void main() { - if (v_dead[0] > 0.5) return; - vec3 center = v_worldPos[0]; - float size = drawRadius * v_sizeMult[0]; - vec3 noiseSeed = v_phaseSeed[0] * 137.0 + vec3(11.0, 47.0, 83.0); - float h = dot(v_phaseSeed[0], vec3(0.123, 0.456, 0.789)); - vec3 phase = vec3(hash11(h), hash11(h+1.7), hash11(h+3.3)) * 6.2831853; - float r = radians(v_rotVal[0]); - vec3 ang = phase + vec3(r * 1.0, r * 1.3, r * 0.7); - mat3 R = rotXYZ(ang); - vec4 col = v_color[0]; - float seed = radians(v_phaseSeed[0].x); - - if (u_shape == 1) { - vec3 X = R * vec3(size, 0, 0); vec3 nX = -X; - vec3 Y = R * vec3(0, size, 0); vec3 nY = -Y; - vec3 Z = R * vec3(0, 0, size); vec3 nZ = -Z; - float k = 0.57735027; - vec3 nPPP = R * vec3( k, k, k); - vec3 nNPP = R * vec3(-k, k, k); - vec3 nNPN = R * vec3(-k, k, -k); - vec3 nPPN = R * vec3( k, k, -k); - vec3 nPNP = R * vec3( k, -k, k); - vec3 nNNP = R * vec3(-k, -k, k); - vec3 nNNN = R * vec3(-k, -k, -k); - vec3 nPNN = R * vec3( k, -k, -k); - emitTri(Y, Z, X, nPPP, center, col, noiseSeed, seed); - emitTri(Y, nX, Z, nNPP, center, col, noiseSeed, seed); - emitTri(Y, nZ, nX, nNPN, center, col, noiseSeed, seed); - emitTri(Y, X, nZ, nPPN, center, col, noiseSeed, seed); - emitTri(nY, X, Z, nPNP, center, col, noiseSeed, seed); - emitTri(nY, Z, nX, nNNP, center, col, noiseSeed, seed); - emitTri(nY, nX, nZ, nNNN, center, col, noiseSeed, seed); - emitTri(nY, nZ, X, nPNN, center, col, noiseSeed, seed); + float lifeFrac = clamp(t / totalLifeBR, 0.0, 1.0); + float breathScale = 1.0 - smoothstep(0.5, 1.0, lifeFrac); + + g_color = instColor * fade; + g_noiseSeed = noiseSeed; + g_seed = radians(phaseSeed.x); + + if (shapeLocalAndFlag.w > 0.5) { + float gu = shapeNormalAndGlowU.w; + float gv = shapeGlowV; + float halfSize = size * glowScale; + vec3 right = cameraViewInv[0].xyz * halfSize; + vec3 up = cameraViewInv[1].xyz * halfSize; + g_normal = vec3(0.0, 1.0, 0.0); + g_localPos = vec3(0.0); + g_isGlow = 1.0; + g_glowUV = vec2(gu, gv); + g_breathScale = breathScale; + g_worldPos = center + right * gu + up * gv; } else { - vec3 X = R * vec3(size, 0, 0); - vec3 Y = R * vec3(0, size, 0); - vec3 Z = R * vec3(0, 0, size); - vec3 nXp = R[0]; vec3 nXm = -R[0]; - vec3 nYp = R[1]; vec3 nYm = -R[1]; - vec3 nZp = R[2]; vec3 nZm = -R[2]; - emitFace( X-Y-Z, X+Y-Z, X-Y+Z, X+Y+Z, nXp, center, col, noiseSeed, seed); - emitFace(-X-Y-Z, -X-Y+Z, -X+Y-Z, -X+Y+Z, nXm, center, col, noiseSeed, seed); - emitFace(-X+Y-Z, -X+Y+Z, X+Y-Z, X+Y+Z, nYp, center, col, noiseSeed, seed); - emitFace(-X-Y-Z, X-Y-Z, -X-Y+Z, X-Y+Z, nYm, center, col, noiseSeed, seed); - emitFace(-X-Y+Z, X-Y+Z, -X+Y+Z, X+Y+Z, nZp, center, col, noiseSeed, seed); - emitFace(-X-Y-Z, -X+Y-Z, X-Y-Z, X+Y-Z, nZm, center, col, noiseSeed, seed); - } - - if (glowIntensity > 0.0 && glowScale > 1.001) { - emitGlow(center, col, size * glowScale, seed); + vec3 local = R * (shapeLocalAndFlag.xyz * size); + g_normal = normalize(R * shapeNormalAndGlowU.xyz); + g_localPos = local; + g_isGlow = 0.0; + g_glowUV = vec2(0.0); + g_breathScale = 0.0; + g_worldPos = center + local; } + gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); } ]] +local gsSrc = "" + local fsSrc = [[ #version 430 core @@ -698,8 +582,6 @@ local function goodbye(reason) gadgetHandler:RemoveGadget() end -local useGeometryShader = true - -- Visual constants taken verbatim from gfx_nano_particles_gl4.lua -- (MODE_SETTINGS.shape) so the chunks look identical to nano spray. local SHAPE_ID = 0 -- 0 = cube, 1 = octahedron @@ -733,12 +615,11 @@ local ROT_VEL_BASE = -40 local ROT_VEL_RANGE = 80 local ROT_ACC_BASE = -40 / (30*30) local ROT_ACC_RANGE = 80 / (30*30) local function initGL4() - local shaderCacheGS = { + local shaderCache = { vsSrc = vsSrc, fsSrc = fsSrc, - gsSrc = gsSrc, - shaderName = "UnitEnergyExplosionParticlesGL4", - uniformInt = { u_shape = SHAPE_ID }, + shaderName = "UnitEnergyExplosionParticlesGL4_NoGS", + uniformInt = {}, uniformFloat = { drag = CONFIG.drag, gravity = { 0, CONFIG.gravityY, 0 }, @@ -768,156 +649,68 @@ local function initGL4() shaderConfig = {}, forceupdate = true, } - - local shaderCacheNoGS = { - vssrcpath = "LuaUI/Shaders/energy_explosion_particles_gl4_nogs.vert.glsl", - fsSrc = fsSrc, - shaderName = "UnitEnergyExplosionParticlesGL4_NoGS", - uniformInt = { u_shape = SHAPE_ID }, - uniformFloat = shaderCacheGS.uniformFloat, - shaderConfig = {}, - forceupdate = true, - } - - -- Try the geometry-shader path first; only fall back if compile actually - -- fails. LuaShader.isGeometryShaderSupported can report false negatives on - -- some drivers (e.g. AMD/Mesa), so we don't trust it alone. - useGeometryShader = true - particleShader = LuaShader.CheckShaderUpdates(shaderCacheGS) - if not particleShader then - spEcho("Energy Explosion Particles GL4: geometry shader compile failed; trying no-GS fallback.") - useGeometryShader = false - particleShader = LuaShader.CheckShaderUpdates(shaderCacheNoGS) - end + particleShader = LuaShader.CheckShaderUpdates(shaderCache) if not particleShader then goodbye("Failed to compile shader") return false end - if useGeometryShader then - local quadVBO, numVertices = InstanceVBOTable.makeRectVBO( - -1, -1, 1, 1, - 0, 0, 1, 1, - "eepQuadVBO" - ) - -- Shape GS only needs ONE triangle per instance; use a 3-index VBO so the - -- GS doesn't get invoked twice per particle. - local indexVBO = gl.GetVBO(GL.ELEMENT_ARRAY_BUFFER, false) - indexVBO:Define(3) - indexVBO:Upload({0, 1, 2}) - - local layout = { - { id = 1, name = "spawnPosAndSize", size = 4 }, - { id = 2, name = "velAndSpawnFrame", size = 4 }, - { id = 3, name = "instColor", size = 4 }, - { id = 4, name = "rotData", size = 4 }, - } - particleVBO = InstanceVBOTable.makeInstanceVBOTable(layout, MAX_PARTICLES_VBO, "eepParticleVBO") - if not particleVBO then - goodbye("Failed to create instance VBO") - return false - end - particleVBO.numVertices = numVertices - particleVBO.vertexVBO = quadVBO - particleVBO.indexVBO = indexVBO - particleVBO.VAO = particleVBO:makeVAOandAttach(quadVBO, particleVBO.instanceVBO, indexVBO) - particleVBO.primitiveType = GL.TRIANGLES - else - -- No-GS fallback: build a template indexed mesh with one vertex per - -- geometry-shader emitted vertex. Default cube: 6 quads * 4 verts = 24 verts. - -- Octahedron: 8 tris * 3 verts = 24 verts. Plus a 4-vert glow billboard - -- quad. We use independent triangles, so each template vertex is emitted - -- exactly once and indexed in GL order. - local NUM_SHAPE_VERTS = 24 - local NUM_GLOW_VERTS = 4 - local NUM_VERTS = NUM_SHAPE_VERTS + NUM_GLOW_VERTS - local isOcta = (SHAPE_ID == 1) - - local templateVBO = gl.GetVBO(GL.ARRAY_BUFFER, false) - templateVBO:Define(NUM_VERTS, {{ id = 0, name = "vertexSlot", size = 1 }}) -- float slot, cast to int in VS - local vertexData = {} - for i = 0, NUM_VERTS - 1 do - vertexData[#vertexData + 1] = i - end - templateVBO:Upload(vertexData) - - -- Build indices matching the order the no-GS VS emits the template: - -- cube quads are split into two triangles (0,1,2 and 0,2,3); - -- octahedron triangles are a single tri (0,1,2); glow quad likewise. - local indexData = {} - if isOcta then - for shapeTri = 0, NUM_SHAPE_VERTS / 3 - 1 do - local base = shapeTri * 3 - indexData[#indexData + 1] = base + 0 - indexData[#indexData + 1] = base + 1 - indexData[#indexData + 1] = base + 2 - end - else - for quad = 0, NUM_SHAPE_VERTS / 4 - 1 do - local base = quad * 4 - indexData[#indexData + 1] = base + 0 - indexData[#indexData + 1] = base + 1 - indexData[#indexData + 1] = base + 2 - indexData[#indexData + 1] = base + 0 - indexData[#indexData + 1] = base + 2 - indexData[#indexData + 1] = base + 3 - end - end - local glowBase = NUM_SHAPE_VERTS - indexData[#indexData + 1] = glowBase + 0 - indexData[#indexData + 1] = glowBase + 1 - indexData[#indexData + 1] = glowBase + 2 - indexData[#indexData + 1] = glowBase + 0 - indexData[#indexData + 1] = glowBase + 2 - indexData[#indexData + 1] = glowBase + 3 - - local indexVBO = gl.GetVBO(GL.ELEMENT_ARRAY_BUFFER, false) - indexVBO:Define(#indexData) - indexVBO:Upload(indexData) - - local layout = { - { id = 1, name = "spawnPosAndSize", size = 4 }, - { id = 2, name = "velAndSpawnFrame", size = 4 }, - { id = 3, name = "instColor", size = 4 }, - { id = 4, name = "rotData", size = 4 }, - } - particleVBO = InstanceVBOTable.makeInstanceVBOTable(layout, MAX_PARTICLES_VBO, "eepParticleVBO_NoGS") - if not particleVBO then - goodbye("Failed to create instance VBO") - return false - end - - local realVAO = particleVBO:makeVAOandAttach(templateVBO, particleVBO.instanceVBO, indexVBO) - if not realVAO then - goodbye("Failed to create no-GS VAO") - return false - end - - -- Anchor the template/index VBOs so Lua GC cannot collect them while - -- the VAO is alive (same GC fix as DrawPrimitiveAtUnit, commit 2b51f6e863). - particleVBO.nogsTemplateVBO = templateVBO - particleVBO.nogsIndexVBO = indexVBO - - local indexCount = #indexData - particleVBO.VAO = { - realVAO = realVAO, - indexCount = indexCount, - DrawArrays = function(self, _primitiveType, instanceCount) - if instanceCount and instanceCount > 0 then - self.realVAO:DrawElements(GL.TRIANGLES, self.indexCount, 0, instanceCount) - end - end, - DrawElements = function(self, _primitiveType, _numVertices, _startIndex, instanceCount, _drawIndex) - if instanceCount and instanceCount > 0 then - self.realVAO:DrawElements(GL.TRIANGLES, self.indexCount, 0, instanceCount) - end - end, - Delete = function(self) - self.realVAO:Delete() - end, - } - particleVBO.primitiveType = GL.TRIANGLES + local shapeData = {} + local function addVertex(lx, ly, lz, isGlow, nx, ny, nz, gu, gv) + shapeData[#shapeData + 1] = lx; shapeData[#shapeData + 1] = ly; shapeData[#shapeData + 1] = lz; shapeData[#shapeData + 1] = isGlow + shapeData[#shapeData + 1] = nx; shapeData[#shapeData + 1] = ny; shapeData[#shapeData + 1] = nz; shapeData[#shapeData + 1] = gu + shapeData[#shapeData + 1] = gv + end + local function addFace(c0, c1, c2, c3, n) + addVertex(c0[1], c0[2], c0[3], 0, n[1], n[2], n[3], 0, 0) + addVertex(c1[1], c1[2], c1[3], 0, n[1], n[2], n[3], 0, 0) + addVertex(c2[1], c2[2], c2[3], 0, n[1], n[2], n[3], 0, 0) + addVertex(c2[1], c2[2], c2[3], 0, n[1], n[2], n[3], 0, 0) + addVertex(c1[1], c1[2], c1[3], 0, n[1], n[2], n[3], 0, 0) + addVertex(c3[1], c3[2], c3[3], 0, n[1], n[2], n[3], 0, 0) + end + addFace({ 1,-1,-1}, { 1, 1,-1}, { 1,-1, 1}, { 1, 1, 1}, { 1, 0, 0}) + addFace({-1,-1,-1}, {-1,-1, 1}, {-1, 1,-1}, {-1, 1, 1}, {-1, 0, 0}) + addFace({-1, 1,-1}, {-1, 1, 1}, { 1, 1,-1}, { 1, 1, 1}, { 0, 1, 0}) + addFace({-1,-1,-1}, { 1,-1,-1}, {-1,-1, 1}, { 1,-1, 1}, { 0,-1, 0}) + addFace({-1,-1, 1}, { 1,-1, 1}, {-1, 1, 1}, { 1, 1, 1}, { 0, 0, 1}) + addFace({-1,-1,-1}, {-1, 1,-1}, { 1,-1,-1}, { 1, 1,-1}, { 0, 0,-1}) + addVertex(0, 0, 0, 1, 0, 1, 0, -1, -1) + addVertex(0, 0, 0, 1, 0, 1, 0, 1, -1) + addVertex(0, 0, 0, 1, 0, 1, 0, -1, 1) + addVertex(0, 0, 0, 1, 0, 1, 0, -1, 1) + addVertex(0, 0, 0, 1, 0, 1, 0, 1, -1) + addVertex(0, 0, 0, 1, 0, 1, 0, 1, 1) + local numVertices = #shapeData / 9 + local shapeVBO = gl.GetVBO(GL.ARRAY_BUFFER, false) + if not shapeVBO then + goodbye("Failed to create Unit Energy Explosion Particles GL4 NoGS shape VBO") + return false + end + shapeVBO:Define(numVertices, { + {id = 0, name = "shapeLocalAndFlag", size = 4}, + {id = 5, name = "shapeNormalAndGlowU", size = 4}, + {id = 6, name = "shapeGlowV", size = 1}, + }) + shapeVBO:Upload(shapeData) + + local layout = { + { id = 1, name = "spawnPosAndSize", size = 4 }, + { id = 2, name = "velAndSpawnFrame", size = 4 }, + { id = 3, name = "instColor", size = 4 }, + { id = 4, name = "rotData", size = 4 }, + } + particleVBO = InstanceVBOTable.makeInstanceVBOTable(layout, MAX_PARTICLES_VBO, "eepParticleVBO") + if not particleVBO then + goodbye("Failed to create instance VBO") + return false end + particleVBO.numVertices = numVertices + particleVBO.vertexVBO = shapeVBO + particleVBO.VAO = gl.GetVAO() + particleVBO.VAO:AttachVertexBuffer(shapeVBO) + particleVBO.VAO:AttachInstanceBuffer(particleVBO.instanceVBO) + particleVBO.primitiveType = GL.TRIANGLES return true end @@ -966,9 +759,6 @@ local function classifyDefs() -- Energy converters/metal-makers qualify via their energyconv_capacity -- regardless of the energy score threshold. local hasConverter = cp and tonumber(cp.energyconv_capacity) and tonumber(cp.energyconv_capacity) > 0 - -- A unit qualifies via energy score only if the score is also significant - -- relative to its build cost. This prevents combat units with a small - -- passive generator (e.g. armbanth) from triggering the effect. local metalCost = ud.metalCost or 0 local scoreQualifies = score >= CONFIG.minEnergyScore and (metalCost == 0 or score / metalCost >= CONFIG.minEnergyScoreRatio) @@ -1351,9 +1141,15 @@ function gadget:DrawWorld() glBlending(GL_ONE, GL_ONE_MINUS_SRC_ALPHA) particleShader:Activate() - _pVBO:Draw() + _pVBO.VAO:DrawArrays(GL.TRIANGLES, _pVBO.numVertices, 0, _pVBO.usedElements, 0) particleShader:Deactivate() glBlending(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glDepthMask(true) end + +--[[ thread06f rapid-size padding: do not edit below +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +]] + +--[[PAD ]] diff --git a/luarules/gadgets/gfx_nano_particles_gl4.lua b/luarules/gadgets/gfx_nano_particles_gl4.lua index aa7fa9843c3..e266b7a6fa7 100644 --- a/luarules/gadgets/gfx_nano_particles_gl4.lua +++ b/luarules/gadgets/gfx_nano_particles_gl4.lua @@ -141,10 +141,13 @@ local LOS_FILTER = true -- drop emissions outside our LOS -- 0 = engine nano spray (gadget stays loaded but inert) -- 1 = gadget 3D shapes -- Polled live in GameFrame so changes take effect without a /luarules reload. -if Spring.GetConfigInt("NanoParticleMode", 1) == 2 then - Spring.SetConfigInt("NanoParticleMode", 1) -end local NANO_PARTICLE_MODE = Spring.GetConfigInt("NanoParticleMode", 1) +if NANO_PARTICLE_MODE ~= 0 then + NANO_PARTICLE_MODE = 1 +end +if NANO_PARTICLE_MODE ~= Spring.GetConfigInt("NanoParticleMode", 1) then + Spring.SetConfigInt("NanoParticleMode", NANO_PARTICLE_MODE) +end local RENDER_MODE = "shape" @@ -603,8 +606,8 @@ local trackUnit local cachedAllyTeamID = spGetMyAllyTeamID() local cachedSpecFullView = false --- Debug instrumentation: timers + per-30f Echo. Toggle to true to profile. -local DEBUG = false +-- Debug instrumentation: timers + per-30f Echo. Toggle with /set NanoGL4Debug 1. +local DEBUG = (Spring.GetConfigInt("NanoGL4Debug", 0) == 1) local _dbgFrame = 0 local _dbgEmits = 0 local _dbgBuilders = 0 @@ -669,26 +672,58 @@ end local vsSrcCube = [[ #version 430 core -layout(location = 0) in vec4 vertexPosUV; -layout(location = 1) in vec4 spawnPosAndSize; // xyz=spawnPos, w=packed(sizeMult,fadeFrames) -layout(location = 2) in vec4 velAndSpawnFrame; // xyz=velocity, w=spawnFrame +layout(location = 0) in vec4 shapeLocalAndFlag; // xyz=local cube corner, w=1 for glow billboard +layout(location = 1) in vec4 spawnPosAndSize; // xyz=spawnPos, w=packed(sizeMult,fadeFrames) +layout(location = 2) in vec4 velAndSpawnFrame; // xyz=velocity, w=spawnFrame layout(location = 3) in vec4 instColor; -layout(location = 4) in vec4 rotData; // x=rotVal0, y=rotVel0, z=wobbleStartFrame, w=deathFrame +layout(location = 4) in vec4 rotData; // x=rotVal0, y=rotVel0, z=wobbleStartFrame, w=deathFrame +layout(location = 5) in vec4 shapeNormalAndGlowU; // xyz=cube normal, w=glow billboard x in [-1,1] +layout(location = 6) in float shapeGlowV; // glow billboard y in [-1,1] //__ENGINEUNIFORMBUFFERDEFS__ +uniform float drawRadius; +uniform float glowScale; uniform float wobbleAmp; // peak vortex displacement perpendicular to vel (elmos) uniform float wobbleFreq; // vortex rotation rate around vel (cycles/sim-second) uniform float wobbleVar; // ± fractional per-particle amplitude variation (0..1) uniform float wobbleFreqVar; // ± fractional per-particle frequency variation (0..1) uniform float wobbleRampFrames; // frames to linearly gate bell at spawn (0 = instant full wobble) -out vec3 v_worldPos; -out vec4 v_color; -out float v_rotVal; -out float v_dead; -out vec3 v_phaseSeed; -out float v_sizeMult; +out vec4 g_color; +out vec3 g_normal; +out vec3 g_worldPos; +out vec3 g_localPos; +out vec3 g_noiseSeed; +out vec2 g_glowUV; +out float g_isGlow; +out float g_seed; + +float hash11(float x) { + return fract(sin(x) * 43758.5453); +} + +mat3 rotXYZ(vec3 a) { + float cx = cos(a.x), sx = sin(a.x); + float cy = cos(a.y), sy = sin(a.y); + float cz = cos(a.z), sz = sin(a.z); + mat3 Rx = mat3(1,0,0, 0,cx,sx, 0,-sx,cx); + mat3 Ry = mat3(cy,0,-sy, 0,1,0, sy,0,cy); + mat3 Rz = mat3(cz,sz,0, -sz,cz,0, 0,0,1); + return Rz * Ry * Rx; +} + +void hideVertex() { + g_color = vec4(0.0); + g_normal = vec3(0.0, 1.0, 0.0); + g_worldPos = vec3(0.0); + g_localPos = vec3(0.0); + g_noiseSeed = vec3(0.0); + g_glowUV = vec2(0.0); + g_isGlow = 0.0; + g_seed = 0.0; + gl_Position = vec4(2.0, 2.0, 2.0, 1.0); +} void main() { float currentFrame = timeInfo.x + timeInfo.w; @@ -696,58 +731,33 @@ void main() { float deathFrame = rotData.w; if (currentFrame >= deathFrame) { - v_dead = 1.0; - gl_Position = vec4(2.0, 2.0, 2.0, 1.0); - v_worldPos = vec3(0.0); - v_color = vec4(0.0); - v_rotVal = 0.0; - v_phaseSeed = vec3(0.0); - v_sizeMult = 0.0; + hideVertex(); return; } - v_dead = 0.0; float t = currentFrame - spawnFrame; if (t < 0.0) t = 0.0; - vec3 worldPos = spawnPosAndSize.xyz + velAndSpawnFrame.xyz * t; - - // Vortex / swirl displacement perpendicular to vel. Use absolute time-based - // ramp for consistent wobble ramp speed regardless of distance/lifetime. - // Both forward and inverse use the same fixed ramp-up and ramp-down envelopes - // (12 frames each) so wobble intensity grows at the same visual speed whether - // particles travel far (long lifetime) or near (short). - // Compute wobble axis from spawn-time rotData, not homing-modified velocity. - // rotData.x and .y are spawn-time random (never rewritten by homing). Using - // these to generate a stable perpendicular basis ensures wobble doesn't flip - // or jitter when the particle is re-aimed during forward homing. For moving - // targets, velocity changes every 3 frames (HOMING_RUN_EVERY), which would - // cause the computed wobble axis to rotate/flip abruptly -- jittery/extreme. - // Deriving the basis from spawn-time values keeps it stable across updates. + vec3 center = spawnPosAndSize.xyz + velAndSpawnFrame.xyz * t; + + // Vortex / swirl displacement perpendicular to vel. Kept from the original + // VS so homing particles retain the same path; only the GS expansion moved. if (wobbleAmp > 0.0001) { - // Lifetime-normalized sine bell: always peaks at 1.0 at midpoint regardless - // of travel distance. Short-range and long-range particles wobble equally. - // An optional linear ramp gate (wobbleRampFrames) slows the attack without - // affecting the natural sine fade-out. float wobbleT = max(currentFrame - rotData.z, 0.0); float totalLife = max(deathFrame - rotData.z, 1.0); float bell = sin(3.14159265 * wobbleT / totalLife); if (wobbleRampFrames > 0.5) bell *= min(1.0, wobbleT / wobbleRampFrames); - vec3 vdir = velAndSpawnFrame.xyz; // Use for secondary ref vector only + vec3 vdir = velAndSpawnFrame.xyz; vec3 ref, axA, axB; - // Use rotData.x as seed for a stable reference direction - float refAngle = rotData.x * 0.01745329; // ~1 degree per unit - // Generate two pseudo-random perpendicular vectors from the seed + float refAngle = rotData.x * 0.01745329; float s1 = sin(refAngle), c1 = cos(refAngle); float s2 = sin(refAngle * 2.19), c2 = cos(refAngle * 2.19); - // Choose reference based on vdir.y to avoid degenerate cross products if (abs(vdir.y) < 0.95) { ref = normalize(vec3(s1, c1, s2)); } else { ref = normalize(vec3(c1, s2, c2)); } - // Generate wobble basis from spawn-time seed and ref, independent of velocity float h1 = fract(sin(rotData.x * 12.9898 + rotData.y * 78.233) * 43758.5453); float h2 = fract(sin(rotData.x * 23.1451 + rotData.y * 34.567) * 65432.0987); axA = normalize(vec3( @@ -759,7 +769,6 @@ void main() { if (dot(axB, axB) > 0.001) { axB = normalize(axB); } else { - // Degenerate case: fallback perpendicular axB = cross(axA, (abs(axA.x) < 0.9) ? vec3(1, 0, 0) : vec3(0, 1, 0)); axB = normalize(axB); } @@ -770,210 +779,58 @@ void main() { float hash2 = fract(hash * 113.7 + 0.317); float ampScale = max(0.0, 1.0 + wobbleVar * (2.0 * hash2 - 1.0)); float ph = currentFrame * wobbleFreq * freqScale * dirSign * (6.2831853 / 30.0) + phaseOff; - worldPos += (axA * cos(ph) + axB * sin(ph)) * (wobbleAmp * ampScale * bell); + center += (axA * cos(ph) + axB * sin(ph)) * (wobbleAmp * ampScale * bell); } - // Decode packed w: sizeMult in low 1024, fadeFrames * 1024 above. Sign - // is the inverse flag (handled above) -- abs here. float packedW = abs(spawnPosAndSize.w); float fadeFrames = floor(packedW / 1024.0); float sizeMult = (packedW - fadeFrames * 1024.0) / 256.0; - float fade = (fadeFrames > 0.5) ? clamp((deathFrame - currentFrame) / fadeFrames, 0.0, 1.0) : 1.0; - float rotVel = rotData.y; - float rotVal = rotData.x + rotVel * t; - - v_worldPos = worldPos; - v_color = instColor * fade; - // Shrink during the death-fade alongside the alpha ramp: 100% size at - // fade=1 (no fade active), down to 50% at fade=0. Reads as the chunk - // dissolving into nothing instead of just becoming transparent. - v_rotVal = rotVal; - v_sizeMult = sizeMult * (0.5 + 0.5 * fade); - // Stable per-particle seed for cube tumble phase. Homing rewrites spawnPos - // every frame, so derive the seed from spawn-time random rotData.x/.y only. - // rotData.z now tracks wobble time and must not feed the shape/noise hashes. - v_phaseSeed = vec3(rotData.x, rotData.y, rotData.x + rotData.y); - gl_Position = vec4(worldPos, 1.0); // GS reads this -} -]] - -local gsSrcCube = [[ -#version 430 core - -layout(triangles) in; -// 28 = worst case across supported shapes: -// cube: 6 quads * 4 verts = 24 + 4 glow billboard verts -// octahedron: 8 tris * 3 verts = 24 + 4 glow billboard verts -// Larger polyhedra would exceed MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS given -// our per-vertex output count. -layout(triangle_strip, max_vertices = 28) out; - -//__ENGINEUNIFORMBUFFERDEFS__ - -uniform float drawRadius; -uniform int u_shape; // 0=cube, 1=octahedron -uniform float glowScale; // billboard halo scale relative to shape size -uniform float glowIntensity; // 0 = no halo emitted - -in vec3 v_worldPos[]; -in vec4 v_color[]; -in float v_rotVal[]; -in float v_dead[]; -in vec3 v_phaseSeed[]; -in float v_sizeMult[]; - -out vec4 g_color; -out vec3 g_normal; -out vec3 g_worldPos; -out vec3 g_localPos; // pre-translation cube-local position for stable noise sampling -out vec3 g_noiseSeed; // per-particle noise offset (so cubes don't all sparkle in lockstep) -out vec2 g_glowUV; // [-1..1] across the glow billboard (zero on shape verts) -out float g_isGlow; // 0 = shape face, 1 = glow halo billboard -out float g_seed; // stable per-particle seed (radians) for FS jitter - -float hash11(float x) { - return fract(sin(x) * 43758.5453); -} + float rotVal = rotData.x + rotData.y * t; + float size = drawRadius * sizeMult * (0.5 + 0.5 * fade); + vec3 phaseSeed = vec3(rotData.x, rotData.y, rotData.x + rotData.y); + vec3 noiseSeed = phaseSeed * 137.0 + vec3(11.0, 47.0, 83.0); + float h = dot(phaseSeed, vec3(0.123, 0.456, 0.789)); + vec3 phase = vec3(hash11(h), hash11(h+1.7), hash11(h+3.3)) * 6.2831853; + float r = radians(rotVal); + mat3 R = rotXYZ(phase + vec3(r * 1.0, r * 1.3, r * 0.7)); -mat3 rotXYZ(vec3 a) { - float cx = cos(a.x), sx = sin(a.x); - float cy = cos(a.y), sy = sin(a.y); - float cz = cos(a.z), sz = sin(a.z); - mat3 Rx = mat3(1,0,0, 0,cx,sx, 0,-sx,cx); - mat3 Ry = mat3(cy,0,-sy, 0,1,0, sy,0,cy); - mat3 Rz = mat3(cz,sz,0, -sz,cz,0, 0,0,1); - return Rz * Ry * Rx; -} + vec4 col = instColor * fade; + float seed = radians(phaseSeed.x); -// Emit one quad face: 4 corners as a triangle strip, then EndPrimitive. Inputs -// are local-space corner offsets pre-multiplied by the rotation matrix and -// scaled by size. -void emitFace(vec3 c0, vec3 c1, vec3 c2, vec3 c3, vec3 n, vec3 center, vec4 col, vec3 noiseSeed, float seed) { g_color = col; - g_normal = n; g_noiseSeed = noiseSeed; - g_isGlow = 0.0; - g_glowUV = vec2(0.0); g_seed = seed; - g_localPos = c0; g_worldPos = center + c0; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - g_localPos = c1; g_worldPos = center + c1; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - g_localPos = c2; g_worldPos = center + c2; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - g_localPos = c3; g_worldPos = center + c3; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - EndPrimitive(); -} - -// Emit one triangle face. Used by the octahedron path. -void emitTri(vec3 c0, vec3 c1, vec3 c2, vec3 n, vec3 center, vec4 col, vec3 noiseSeed, float seed) { - g_color = col; - g_normal = n; - g_noiseSeed = noiseSeed; - g_isGlow = 0.0; - g_glowUV = vec2(0.0); - g_seed = seed; - g_localPos = c0; g_worldPos = center + c0; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - g_localPos = c1; g_worldPos = center + c1; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - g_localPos = c2; g_worldPos = center + c2; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - EndPrimitive(); -} - -// Emit one camera-facing quad as the soft halo around the shape. The FS -// branches on g_isGlow to do radial falloff using g_glowUV instead of the -// face-shading path. -void emitGlow(vec3 center, vec4 col, float halfSize, float seed) { - vec3 right = cameraViewInv[0].xyz * halfSize; - vec3 up = cameraViewInv[1].xyz * halfSize; - g_color = col; - g_normal = vec3(0.0, 1.0, 0.0); - g_noiseSeed = vec3(0.0); - g_localPos = vec3(0.0); - g_isGlow = 1.0; - g_seed = seed; - g_glowUV = vec2(-1.0, -1.0); g_worldPos = center - right - up; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - g_glowUV = vec2( 1.0, -1.0); g_worldPos = center + right - up; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - g_glowUV = vec2(-1.0, 1.0); g_worldPos = center - right + up; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - g_glowUV = vec2( 1.0, 1.0); g_worldPos = center + right + up; gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); EmitVertex(); - EndPrimitive(); -} - -void main() { - if (v_dead[0] > 0.5) return; - // Cube mode uses a 3-index VBO (one triangle per instance) so GS runs - // exactly once per particle -- no need for a gl_PrimitiveIDIn gate. - - vec3 center = v_worldPos[0]; - float size = drawRadius * v_sizeMult[0]; - - // Stable per-particle noise offset, scaled into a wide range so adjacent - // particles sample completely different noise regions instead of overlapping. - vec3 noiseSeed = v_phaseSeed[0] * 137.0 + vec3(11.0, 47.0, 83.0); - - // Per-particle phase from a stable spawn-time seed (NOT spawnPos -- that - // gets rewritten every frame by homing). Three axis rates with slightly - // different multipliers give a constantly-evolving tumble. - float h = dot(v_phaseSeed[0], vec3(0.123, 0.456, 0.789)); - vec3 phase = vec3(hash11(h), hash11(h+1.7), hash11(h+3.3)) * 6.2831853; - float r = radians(v_rotVal[0]); - vec3 ang = phase + vec3(r * 1.0, r * 1.3, r * 0.7); - mat3 R = rotXYZ(ang); - - vec4 col = v_color[0]; - - // Stable per-particle seed for FS hue jitter / glow breath. v_phaseSeed.x - // is the spawn-time random rotData.x in degrees -> wrap to radians. - float seed = radians(v_phaseSeed[0].x); - - if (u_shape == 1) { - // ---- OCTAHEDRON ---- 6 vertices on the axes, 8 triangle faces. - vec3 X = R * vec3(size, 0, 0); vec3 nX = -X; - vec3 Y = R * vec3(0, size, 0); vec3 nY = -Y; - vec3 Z = R * vec3(0, 0, size); vec3 nZ = -Z; - float k = 0.57735027; - vec3 nPPP = R * vec3( k, k, k); - vec3 nNPP = R * vec3(-k, k, k); - vec3 nNPN = R * vec3(-k, k, -k); - vec3 nPPN = R * vec3( k, k, -k); - vec3 nPNP = R * vec3( k, -k, k); - vec3 nNNP = R * vec3(-k, -k, k); - vec3 nNNN = R * vec3(-k, -k, -k); - vec3 nPNN = R * vec3( k, -k, -k); - // Top hemisphere (apex Y), CCW from outside. - emitTri(Y, Z, X, nPPP, center, col, noiseSeed, seed); - emitTri(Y, nX, Z, nNPP, center, col, noiseSeed, seed); - emitTri(Y, nZ, nX, nNPN, center, col, noiseSeed, seed); - emitTri(Y, X, nZ, nPPN, center, col, noiseSeed, seed); - // Bottom hemisphere (apex nY). - emitTri(nY, X, Z, nPNP, center, col, noiseSeed, seed); - emitTri(nY, Z, nX, nNNP, center, col, noiseSeed, seed); - emitTri(nY, nX, nZ, nNNN, center, col, noiseSeed, seed); - emitTri(nY, nZ, X, nPNN, center, col, noiseSeed, seed); + if (shapeLocalAndFlag.w > 0.5) { + float gu = shapeNormalAndGlowU.w; + float gv = shapeGlowV; + float halfSize = size * glowScale; + vec3 right = cameraViewInv[0].xyz * halfSize; + vec3 up = cameraViewInv[1].xyz * halfSize; + g_normal = vec3(0.0, 1.0, 0.0); + g_localPos = vec3(0.0); + g_isGlow = 1.0; + g_glowUV = vec2(gu, gv); + g_worldPos = center + right * gu + up * gv; } else { - // ---- CUBE (default) ---- 8 corners, 6 quad faces. - vec3 X = R * vec3(size, 0, 0); - vec3 Y = R * vec3(0, size, 0); - vec3 Z = R * vec3(0, 0, size); - vec3 nXp = R[0]; vec3 nXm = -R[0]; - vec3 nYp = R[1]; vec3 nYm = -R[1]; - vec3 nZp = R[2]; vec3 nZm = -R[2]; - emitFace( X-Y-Z, X+Y-Z, X-Y+Z, X+Y+Z, nXp, center, col, noiseSeed, seed); - emitFace(-X-Y-Z, -X-Y+Z, -X+Y-Z, -X+Y+Z, nXm, center, col, noiseSeed, seed); - emitFace(-X+Y-Z, -X+Y+Z, X+Y-Z, X+Y+Z, nYp, center, col, noiseSeed, seed); - emitFace(-X-Y-Z, X-Y-Z, -X-Y+Z, X-Y+Z, nYm, center, col, noiseSeed, seed); - emitFace(-X-Y+Z, X-Y+Z, -X+Y+Z, X+Y+Z, nZp, center, col, noiseSeed, seed); - emitFace(-X-Y-Z, -X+Y-Z, X-Y-Z, X+Y-Z, nZm, center, col, noiseSeed, seed); + vec3 local = R * (shapeLocalAndFlag.xyz * size); + g_normal = normalize(R * shapeNormalAndGlowU.xyz); + g_localPos = local; + g_isGlow = 0.0; + g_glowUV = vec2(0.0); + g_worldPos = center + local; } - // Optional camera-facing halo around the shape. One extra quad per particle. - if (glowIntensity > 0.0 && glowScale > 1.001) { - emitGlow(center, col, size * glowScale, seed); - } + gl_Position = cameraViewProj * vec4(g_worldPos, 1.0); } ]] +local gsSrcCube = "" + local fsSrcCube = [[ #version 430 core @@ -1150,14 +1007,11 @@ local function goodbye(reason) end local function initGL4() - local useGeometryShader = true - - local shaderCacheGS = { + local shaderCache = { vsSrc = vsSrcCube, fsSrc = fsSrcCube, - gsSrc = gsSrcCube, - shaderName = "NanoParticlesGL4_Shape", - uniformInt = { infoTex = 1, u_shape = U.SHAPE_ID }, + shaderName = "NanoParticlesGL4_Shape_NoGS", + uniformInt = { infoTex = 1 }, uniformFloat = { losAlwaysVisible = 0, drawRadius = U.DRAW_RADIUS, cubeShowInside = U.CUBE_SHOW_INSIDE, cubeNoise = U.CUBE_NOISE, cubeNoiseSpeed = U.CUBE_NOISE_SPEED, cubeNoiseScale = U.CUBE_NOISE_SCALE, glowScale = U.GLOW_SCALE, glowIntensity = U.GLOW_INTENSITY, glowFalloff = U.GLOW_FALLOFF, @@ -1170,167 +1024,82 @@ local function initGL4() shaderConfig = {}, forceupdate = true, } - - local shaderCacheNoGS = { - vssrcpath = "LuaUI/Shaders/nano_particles_gl4_nogs.vert.glsl", - fsSrc = fsSrcCube, - shaderName = "NanoParticlesGL4_Shape_NoGS", - uniformInt = { infoTex = 1, u_shape = U.SHAPE_ID }, - uniformFloat = shaderCacheGS.uniformFloat, - shaderConfig = {}, - forceupdate = true, - } - - -- Try the geometry-shader path first; only fall back if compile actually - -- fails. LuaShader.isGeometryShaderSupported can report false negatives on - -- some drivers (e.g. AMD/Mesa), so we don't trust it alone. - useGeometryShader = true - nanoShader = LuaShader.CheckShaderUpdates(shaderCacheGS) - if not nanoShader then - spEcho("Nano Particles GL4: geometry shader compile failed; trying no-GS fallback.") - useGeometryShader = false - nanoShader = LuaShader.CheckShaderUpdates(shaderCacheNoGS) - end + nanoShader = LuaShader.CheckShaderUpdates(shaderCache) if not nanoShader then goodbye("Failed to compile shader") return false end - if useGeometryShader then - -- Quad: xy in [-1,1] (corner), uv in [0,1] - local quadVBO, numVertices = InstanceVBOTable.makeRectVBO( - -1, -1, 1, 1, - 0, 0, 1, 1, - "nanoQuadVBO" - ) - -- Shape GS only needs ONE triangle per instance; using the rect's 2-tri - -- index buffer would invoke the GS twice per particle. A 3-index VBO - -- (the rect's first triangle: bl,tl,tr) cuts GS work in half. - local indexVBO = gl.GetVBO(GL.ELEMENT_ARRAY_BUFFER, false) - indexVBO:Define(3) - indexVBO:Upload({0, 1, 2}) - - local layout = { - { id = 1, name = "spawnPosAndSize", size = 4 }, - { id = 2, name = "velAndSpawnFrame", size = 4 }, - { id = 3, name = "instColor", size = 4 }, - { id = 4, name = "rotData", size = 4 }, - } - nanoVBO = InstanceVBOTable.makeInstanceVBOTable(layout, MAX_PARTICLES_VBO, "nanoParticleVBO") - if not nanoVBO then - goodbye("Failed to create instance VBO") - return false - end - nanoVBO.numVertices = numVertices - nanoVBO.vertexVBO = quadVBO - nanoVBO.indexVBO = indexVBO - nanoVBO.VAO = nanoVBO:makeVAOandAttach(quadVBO, nanoVBO.instanceVBO, indexVBO) - nanoVBO.primitiveType = GL.TRIANGLES - else - -- No-GS fallback: build a template indexed mesh with one vertex per - -- geometry-shader emitted vertex. Default cube: 6 quads * 4 verts = 24 verts. - -- Octahedron: 8 tris * 3 verts = 24 verts. Plus a 4-vert glow billboard - -- quad. We use independent triangles, so each template vertex is emitted - -- exactly once and indexed in GL order. - local NUM_SHAPE_VERTS = 24 - local NUM_GLOW_VERTS = 4 - local NUM_VERTS = NUM_SHAPE_VERTS + NUM_GLOW_VERTS - local isOcta = (U.SHAPE_ID == 1) - - local templateVBO = gl.GetVBO(GL.ARRAY_BUFFER, false) - templateVBO:Define(NUM_VERTS, {{ id = 0, name = "vertexSlot", size = 1 }}) -- float slot, cast to int in VS - local vertexData = {} - for i = 0, NUM_VERTS - 1 do - vertexData[#vertexData + 1] = i - end - templateVBO:Upload(vertexData) - - -- Build indices matching the order the no-GS VS emits the template: - -- cube quads are split into two triangles (0,1,2 and 0,2,3); - -- octahedron triangles are a single tri (0,1,2); glow quad likewise. - local indexData = {} - if isOcta then - for shapeTri = 0, NUM_SHAPE_VERTS / 3 - 1 do - local base = shapeTri * 3 - indexData[#indexData + 1] = base + 0 - indexData[#indexData + 1] = base + 1 - indexData[#indexData + 1] = base + 2 - end - else - for quad = 0, NUM_SHAPE_VERTS / 4 - 1 do - local base = quad * 4 - indexData[#indexData + 1] = base + 0 - indexData[#indexData + 1] = base + 1 - indexData[#indexData + 1] = base + 2 - indexData[#indexData + 1] = base + 0 - indexData[#indexData + 1] = base + 2 - indexData[#indexData + 1] = base + 3 - end - end - local glowBase = NUM_SHAPE_VERTS - indexData[#indexData + 1] = glowBase + 0 - indexData[#indexData + 1] = glowBase + 1 - indexData[#indexData + 1] = glowBase + 2 - indexData[#indexData + 1] = glowBase + 0 - indexData[#indexData + 1] = glowBase + 2 - indexData[#indexData + 1] = glowBase + 3 - - local indexVBO = gl.GetVBO(GL.ELEMENT_ARRAY_BUFFER, false) - indexVBO:Define(#indexData) - indexVBO:Upload(indexData) - - local layout = { - { id = 1, name = "spawnPosAndSize", size = 4 }, - { id = 2, name = "velAndSpawnFrame", size = 4 }, - { id = 3, name = "instColor", size = 4 }, - { id = 4, name = "rotData", size = 4 }, - } - nanoVBO = InstanceVBOTable.makeInstanceVBOTable(layout, MAX_PARTICLES_VBO, "nanoParticleVBO_NoGS") - if not nanoVBO then - goodbye("Failed to create instance VBO") - return false - end - - local realVAO = nanoVBO:makeVAOandAttach(templateVBO, nanoVBO.instanceVBO, indexVBO) - if not realVAO then - goodbye("Failed to create no-GS VAO") - return false - end - - -- Anchor the template and index VBOs to the VBO table so the Lua GC - -- cannot collect them while the VAO is alive. OpenGL owns the buffer - -- objects via the VAO, but Lua does not know that; without a strong Lua - -- reference the GC can finalize the userdata and delete the GL buffers - -- (fixed in commit 2b51f6e863 for DrawPrimitiveAtUnit). - nanoVBO.nogsTemplateVBO = templateVBO - nanoVBO.nogsIndexVBO = indexVBO - - local indexCount = #indexData - nanoVBO.VAO = { - realVAO = realVAO, - indexCount = indexCount, - DrawArrays = function(self, _primitiveType, instanceCount) - if instanceCount and instanceCount > 0 then - self.realVAO:DrawElements(GL.TRIANGLES, self.indexCount, 0, instanceCount) - end - end, - DrawElements = function(self, _primitiveType, _numVertices, _startIndex, instanceCount, _drawIndex) - if instanceCount and instanceCount > 0 then - self.realVAO:DrawElements(GL.TRIANGLES, self.indexCount, 0, instanceCount) - end - end, - Delete = function(self) - self.realVAO:Delete() - end, - } - nanoVBO.primitiveType = GL.TRIANGLES + local shapeData = {} + local function addVertex(lx, ly, lz, isGlow, nx, ny, nz, gu, gv) + shapeData[#shapeData + 1] = lx; shapeData[#shapeData + 1] = ly; shapeData[#shapeData + 1] = lz; shapeData[#shapeData + 1] = isGlow + shapeData[#shapeData + 1] = nx; shapeData[#shapeData + 1] = ny; shapeData[#shapeData + 1] = nz; shapeData[#shapeData + 1] = gu + shapeData[#shapeData + 1] = gv end + local function addFace(c0, c1, c2, c3, n) + addVertex(c0[1], c0[2], c0[3], 0, n[1], n[2], n[3], 0, 0) + addVertex(c1[1], c1[2], c1[3], 0, n[1], n[2], n[3], 0, 0) + addVertex(c2[1], c2[2], c2[3], 0, n[1], n[2], n[3], 0, 0) + addVertex(c2[1], c2[2], c2[3], 0, n[1], n[2], n[3], 0, 0) + addVertex(c1[1], c1[2], c1[3], 0, n[1], n[2], n[3], 0, 0) + addVertex(c3[1], c3[2], c3[3], 0, n[1], n[2], n[3], 0, 0) + end + addFace({ 1,-1,-1}, { 1, 1,-1}, { 1,-1, 1}, { 1, 1, 1}, { 1, 0, 0}) + addFace({-1,-1,-1}, {-1,-1, 1}, {-1, 1,-1}, {-1, 1, 1}, {-1, 0, 0}) + addFace({-1, 1,-1}, {-1, 1, 1}, { 1, 1,-1}, { 1, 1, 1}, { 0, 1, 0}) + addFace({-1,-1,-1}, { 1,-1,-1}, {-1,-1, 1}, { 1,-1, 1}, { 0,-1, 0}) + addFace({-1,-1, 1}, { 1,-1, 1}, {-1, 1, 1}, { 1, 1, 1}, { 0, 0, 1}) + addFace({-1,-1,-1}, {-1, 1,-1}, { 1,-1,-1}, { 1, 1,-1}, { 0, 0,-1}) + addVertex(0, 0, 0, 1, 0, 1, 0, -1, -1) + addVertex(0, 0, 0, 1, 0, 1, 0, 1, -1) + addVertex(0, 0, 0, 1, 0, 1, 0, -1, 1) + addVertex(0, 0, 0, 1, 0, 1, 0, -1, 1) + addVertex(0, 0, 0, 1, 0, 1, 0, 1, -1) + addVertex(0, 0, 0, 1, 0, 1, 0, 1, 1) + local numVertices = #shapeData / 9 + local shapeVBO = gl.GetVBO(GL.ARRAY_BUFFER, false) + if not shapeVBO then + goodbye("Failed to create Nano Particles GL4 NoGS shape VBO") + return false + end + shapeVBO:Define(numVertices, { + {id = 0, name = "shapeLocalAndFlag", size = 4}, + {id = 5, name = "shapeNormalAndGlowU", size = 4}, + {id = 6, name = "shapeGlowV", size = 1}, + }) + shapeVBO:Upload(shapeData) + + local layout = { + { id = 1, name = "spawnPosAndSize", size = 4 }, + { id = 2, name = "velAndSpawnFrame", size = 4 }, + { id = 3, name = "instColor", size = 4 }, + { id = 4, name = "rotData", size = 4 }, + } + nanoVBO = InstanceVBOTable.makeInstanceVBOTable(layout, MAX_PARTICLES_VBO, "nanoParticleVBO") + if not nanoVBO then + goodbye("Failed to create instance VBO") + return false + end + nanoVBO.numVertices = numVertices + nanoVBO.vertexVBO = shapeVBO + nanoVBO.VAO = gl.GetVAO() + nanoVBO.VAO:AttachVertexBuffer(shapeVBO) + nanoVBO.VAO:AttachInstanceBuffer(nanoVBO.instanceVBO) + nanoVBO.primitiveType = GL.TRIANGLES return true end local function cleanupGL4() - if nanoVBO then nanoVBO:Delete(); nanoVBO = nil end + if nanoVBO then + if nanoVBO.VAO then nanoVBO.VAO:Delete(); nanoVBO.VAO = nil end + if nanoVBO.vertexVBO then nanoVBO.vertexVBO:Delete(); nanoVBO.vertexVBO = nil end + if nanoVBO.indexVBO then nanoVBO.indexVBO:Delete(); nanoVBO.indexVBO = nil end + if nanoVBO.instanceVBO then nanoVBO.instanceVBO:Delete(); nanoVBO.instanceVBO = nil end + nanoVBO = nil + end + nanoShader = nil + lastLosUniform = -1 end -------------------------------------------------------------------------------- @@ -3037,13 +2806,15 @@ end -- and keeps the engine's MaxNanoParticles budget in sync so we never -- double-spray. local function applyParticleMode(newMode, force) + newMode = tonumber(newMode) or 1 + if newMode ~= 0 then newMode = 1 end if (not force) and newMode == NANO_PARTICLE_MODE and nanoVBO ~= nil then return end if (not force) and newMode == NANO_PARTICLE_MODE and newMode == 0 then return end NANO_PARTICLE_MODE = newMode - -- Clear all in-flight homing references; their VBO slots are about to be - -- destroyed (or we're entering mode 0 where they're meaningless). + -- Clear all in-flight/cached emission state; mode 0 uses the engine spray, + -- and mode 1 rebuilds the NoGS mesh/instance VAO before emitting again. for k in pairs(homingByBuilder) do homingByBuilder[k] = nil end for k in pairs(homingFwdByTarget) do homingFwdByTarget[k] = nil end for k in pairs(fadeFwdByTarget) do fadeFwdByTarget[k] = nil end @@ -3051,6 +2822,22 @@ local function applyParticleMode(newMode, force) for k in pairs(targetIncompleteCache) do targetIncompleteCache[k] = nil end for k in pairs(reclaimTargetBuildProgress) do reclaimTargetBuildProgress[k] = nil end for k in pairs(deathBuckets) do deathBuckets[k] = nil end + groundClampParticles = {} + groundClampFree = {} + groundClampCursor = 1 + U._groundYCache = {} + U._groundYStamp = {} + U._groundClampGateCache = {} + clampDbg.emitChecks = 0 + clampDbg.emitEnabled = 0 + clampDbg.registered = 0 + clampDbg.processed = 0 + clampDbg.corrected = 0 + clampDbg.dropped = 0 + clampDbg.maxSubset = 0 + for k in pairs(piecePosCache) do piecePosCache[k] = nil end + for k in pairs(builderCache) do builderCache[k] = nil end + for k in pairs(builderCacheByTeam) do builderCacheByTeam[k] = nil end liveCount = 0 cleanupGL4() @@ -3068,6 +2855,10 @@ local function applyParticleMode(newMode, force) spEcho("Nano Particles GL4: GL init failed; falling back to engine spray.") NANO_PARTICLE_MODE = 0 Spring.SetConfigInt("MaxNanoParticles", math.floor(Spring.GetConfigInt("MaxParticles", 15000) * 0.34)) + return + end + if rescanTrackedBuilders then + rescanTrackedBuilders() end end @@ -3107,7 +2898,13 @@ function gadget:GameFrame(n) -- Cheap (one GetConfigInt) and 1s latency on a settings-menu toggle is -- imperceptible. if n % 30 == 0 then - local mode = Spring.GetConfigInt("NanoParticleMode", 1) + DEBUG = (Spring.GetConfigInt("NanoGL4Debug", 0) == 1) + local cfgMode = Spring.GetConfigInt("NanoParticleMode", 1) + local mode = cfgMode + if mode ~= 0 then mode = 1 end + if mode ~= cfgMode then + Spring.SetConfigInt("NanoParticleMode", mode) + end if mode ~= NANO_PARTICLE_MODE then applyParticleMode(mode, false) elseif NANO_PARTICLE_MODE ~= 0 then @@ -3195,8 +2992,9 @@ function gadget:GameFrame(n) _dbgFrame = _dbgFrame + 1 if _dbgFrame % 30 == 0 then spEcho(string.format( - "[NanoGL4] f=%d tracked=%d busy/30=%d task=%d emit=%d live=%d used=%d | scan=%.2fms cull=%.2fms draw=%.2fms(x%d) rescan=%.2fms", - n, #trackedBuildersList, _dbgBuilders, _dbgWithTask, _dbgEmits, + "[NanoGL4] f=%d mode=%d cfgMode=%d maxp=%d maxnano=%d cap=%d vboMax=%d tracked=%d busy/30=%d task=%d emit=%d live=%d used=%d | scan=%.2fms cull=%.2fms draw=%.2fms(x%d) rescan=%.2fms", + n, NANO_PARTICLE_MODE, Spring.GetConfigInt("NanoParticleMode", -1), Spring.GetConfigInt("MaxParticles", -1), Spring.GetConfigInt("MaxNanoParticles", -1), MAX_PARTICLES, MAX_PARTICLES_VBO, + #trackedBuildersList, _dbgBuilders, _dbgWithTask, _dbgEmits, liveCount, nanoVBO and nanoVBO.usedElements or -1, _dbgTScan * 1000, _dbgTCull * 1000, _dbgTDraw * 1000, _dbgDraws, _dbgTRescan * 1000)) @@ -3254,6 +3052,17 @@ function trackUnit(unitID, unitDefID) trackedBuilders[unitID] = idx end +function rescanTrackedBuilders() + local all = spGetAllUnits() + if not all then return end + for i = 1, #all do + local uid = all[i] + if not trackedBuilders[uid] then + trackUnit(uid) + end + end +end + local function untrackUnit(unitID) local idx = trackedBuilders[unitID] if not idx then return end @@ -3536,11 +3345,11 @@ function gadget:DrawWorld() gl.StencilOp(GL.KEEP, GL.KEEP, GL.KEEP) -- Pass 1. gl.StencilFunc(GL.NOTEQUAL, GHOST_STENCIL_BIT, GHOST_STENCIL_BIT) - nanoVBO:Draw() + nanoVBO.VAO:DrawArrays(GL.TRIANGLES, nanoVBO.numVertices, 0, nanoVBO.usedElements, 0) -- Pass 2. gl.StencilFunc(GL.EQUAL, GHOST_STENCIL_BIT, GHOST_STENCIL_BIT) glDepthTest(false) - nanoVBO:Draw() + nanoVBO.VAO:DrawArrays(GL.TRIANGLES, nanoVBO.numVertices, 0, nanoVBO.usedElements, 0) -- Restore. glDepthTest(GL.LEQUAL) gl.StencilFunc(GL.ALWAYS, 0, 0xFF) diff --git a/luarules/gadgets/gfx_raptor_scum_gl4.lua b/luarules/gadgets/gfx_raptor_scum_gl4.lua index 67f6f03e32b..ecb1179deb7 100644 --- a/luarules/gadgets/gfx_raptor_scum_gl4.lua +++ b/luarules/gadgets/gfx_raptor_scum_gl4.lua @@ -538,7 +538,7 @@ elseif not Spring.Utilities.Gametype.IsScavengers() then -- UNSYNCED vec3 reflvect = reflect(normalize(-1.0 * sunDir.xyz), normal); float specular = clamp(pow(clamp(dot(normalize(worldtocam), normalize(reflvect)), 0.0, 1.0), SPECULAREXPONENT), 0.0, SPECULARSTRENGTH);// * shadow; //float specular = clamp(dot(normalize(worldtocam), normalize(reflvect)), 0.0, 1.0);// * shadow; - fragColor.rgb += fragColor.rgb * specular; + fragColor.rgb = texcolorheight.rgb * 0.0 ; vec3 outcolor = texcolorheight.rgb; diff --git a/luaui/Include/DrawPrimitiveAtUnit_NoGS_Mesh.lua b/luaui/Include/DrawPrimitiveAtUnit_NoGS_Mesh.lua new file mode 100644 index 00000000000..f3bf48c2ade --- /dev/null +++ b/luaui/Include/DrawPrimitiveAtUnit_NoGS_Mesh.lua @@ -0,0 +1,591 @@ +------------------------------------------------- +-- DrawPrimitiveAtUnit NoGS adapter. +-- Keeps the official Lua-side object stream and replaces GS expansion with +-- an instanced static mesh expanded in the vertex shader. +------------------------------------------------- + +local DrawPrimitiveAtUnit = {} + +local shaderConfig = { + TRANSPARENCY = 0.2, + HEIGHTOFFSET = 1, + ANIMATION = 1, + INITIALSIZE = 0.66, + GROWTHRATE = 4, + BREATHERATE = 30.0, + BREATHESIZE = 0.05, + TEAMCOLORIZATION = 1.0, + CLIPTOLERANCE = 1.1, + USETEXTURE = 1, + BILLBOARD = 0, + POST_ANIM = " ", + POST_VERTEX = "v_color = v_color;", + ZPULL = 256.0, + POST_GEOMETRY = "", + POST_SHADING = "fragColor.rgba = fragColor.rgba;", + MAXVERTICES = 64, + USE_CIRCLES = 1, + USE_CORNERRECT = 1, + USE_TRIANGLES = 1, + USE_QUADS = 1, + FULL_ROTATION = 0, + DISCARD = 0, + ROTATE_CIRCLES = 1, + PRE_OFFSET = "", + USEQUATERNIONS = Engine.FeatureSupport.transformsInGL4 and "1" or "0", +} + +local LuaShader = gl.LuaShader +local InstanceVBOTable = gl.InstanceVBOTable + +local primitiveShapeVBO = nil +local primitiveShapeVertexCount = 0 + +local vsSrc = [[ +#version 420 +#extension GL_ARB_uniform_buffer_object : require +#extension GL_ARB_shader_storage_buffer_object : require +#extension GL_ARB_shading_language_420pack: require + +#line 5000 + +layout (location = 0) in vec4 lengthwidthcornerheight; +layout (location = 1) in uint teamID; +layout (location = 2) in uint numvertices; +layout (location = 3) in vec4 parameters; +layout (location = 4) in vec4 uvoffsets; +layout (location = 5) in uvec4 instData; + +layout (location = 6) in vec4 shapeXZ; +layout (location = 7) in vec4 shapeUV; +layout (location = 8) in vec4 shapeMeta; + +//__ENGINEUNIFORMBUFFERDEFS__ +//__DEFINES__ + +struct SUniformsBuffer { + uint composite; + + uint unused2; + uint unused3; + uint unused4; + + float maxHealth; + float health; + float unused5; + float unused6; + + vec4 drawPos; + vec4 speed; + vec4[4] userDefined; +}; + +layout(std140, binding=1) readonly buffer UniformsBuffer { + SUniformsBuffer uni[]; +}; + +#define UNITID (uni[instData.y].composite >> 16) + +#if USEQUATERNIONS == 0 +layout(std140, binding=0) readonly buffer MatrixBuffer { + mat4 UnitPieces[]; +}; +#else +//__QUATERNIONDEFS__ +#endif + +#line 10000 + +uniform float addRadius = 0.0; +uniform float iconDistance = 20000.0; + +out vec4 g_color; +out vec4 g_uv; + +struct DataGSCompat { + uint v_numvertices; + float v_rotationY; + vec4 v_color; + vec4 v_lengthwidthcornerheight; + vec4 v_centerpos; + vec4 v_uvoffsets; + vec4 v_parameters; + mat3 v_fullrotation; +}; + +mat3 RotationY(float angle) +{ + float s = sin(angle); + float c = cos(angle); + return mat3( + c, 0.0, -s, + 0.0, 1.0, 0.0, + s, 0.0, c); +} + +bool vertexClipped(vec4 clipspace, float tolerance) +{ + return any(lessThan(clipspace.xyz, -clipspace.www * tolerance)) || + any(greaterThan(clipspace.xyz, clipspace.www * tolerance)); +} + +vec2 transformUV(vec4 atlas, float u, float v) +{ + float a = atlas.t - atlas.s; + float b = atlas.q - atlas.p; + return vec2(atlas.s + a * u, atlas.p + b * v); +} + +bool BuildPrimitive(uint actualNumVertices, vec4 dims, out vec3 primitiveCoords, out vec2 primitiveUV, out float addRadiusCorr) +{ + float shapeType = shapeMeta.x; + float length = dims.x; + float width = dims.y; + float cs = dims.z; + float csuv = (cs / max(length + width, 1.0)) * 2.0; + + addRadiusCorr = shapeMeta.w; + primitiveCoords = vec3(0.0); + primitiveUV = vec2(0.0); + + #ifdef USE_TRIANGLES + if (shapeType == 3.0 && actualNumVertices == 3u) { + primitiveCoords = vec3(shapeXZ.x * width, 0.0, shapeXZ.z * length); + primitiveUV = vec2(shapeUV.x, shapeUV.z); + return true; + } + #endif + + #ifdef USE_QUADS + if (shapeType == 4.0 && actualNumVertices == 4u) { + primitiveCoords = vec3(shapeXZ.x * width, 0.0, shapeXZ.z * length); + primitiveUV = vec2(shapeUV.x, shapeUV.z); + return true; + } + #endif + + #ifdef USE_CORNERRECT + if (shapeType == 2.0 && actualNumVertices == 2u) { + primitiveCoords = vec3(shapeXZ.x * width + shapeXZ.y * cs, 0.0, shapeXZ.z * length + shapeXZ.w * cs); + primitiveUV = vec2(shapeUV.x + shapeUV.y * csuv, shapeUV.z + shapeUV.w * csuv); + return true; + } + #endif + + #ifdef USE_CIRCLES + if (shapeType == 64.0 && actualNumVertices > 5u) { + uint clampedVertices = min(actualNumVertices, 64u); + uint stripIndex = uint(shapeMeta.y + 0.5); + uint triangleMaxStripIndex = uint(shapeMeta.z + 0.5); + if (triangleMaxStripIndex >= clampedVertices) { + return false; + } + + float internalAngle = float(clampedVertices - 2u) * radians(180.0) / float(clampedVertices); + addRadiusCorr = 1.0 / sin(internalAngle * 0.5); + + if (stripIndex == 0u) { + primitiveCoords = vec3(-width * 0.5, 0.0, 0.0); + primitiveUV = vec2(0.0, 0.5); + return true; + } + + if (stripIndex == clampedVertices - 1u) { + primitiveCoords = vec3(width * 0.5, 0.0, 0.0); + primitiveUV = vec2(1.0, 0.5); + return true; + } + + uint numSides = clampedVertices / 2u; + uint pairIndex = ((stripIndex - 1u) / 2u) + 1u; + bool upperHalf = ((stripIndex - 1u) % 2u) == 0u; + float phi = (float(pairIndex) * 3.14159265359 / float(numSides)) - 1.57079632679; + float sinphi = sin(phi); + float cosphi = cos(phi); + float zSign = upperHalf ? 1.0 : -1.0; + + primitiveCoords = vec3(width * 0.5 * sinphi, 0.0, length * 0.5 * cosphi * zSign); + primitiveUV = vec2(sinphi * 0.5 + 0.5, cosphi * 0.5 * zSign + 0.5); + return true; + } + #endif + + return false; +} + +void main() +{ + uint baseIndex = instData.x; + #if USEQUATERNIONS == 0 + mat4 modelMatrix = UnitPieces[baseIndex]; + #else + Transform modelWorldTX = GetModelWorldTransform(instData.x); + mat4 modelMatrix = TransformToMatrix(modelWorldTX); + #endif + + vec4 v_centerpos = vec4(modelMatrix[3].xyz, 1.0); + vec4 v_parameters = parameters; + vec4 v_color = teamColor[teamID]; + vec4 v_uvoffsets = uvoffsets; + vec4 v_lengthwidthcornerheight = lengthwidthcornerheight; + uint v_numvertices = numvertices; + float v_rotationY = atan(modelMatrix[0][2], modelMatrix[0][0]); + #if (FULL_ROTATION == 1) + mat3 v_fullrotation = mat3(modelMatrix); + #endif + + float cameraDistance = length(cameraViewInv[3].xyz - v_centerpos.xyz); + + #if (ANIMATION == 1) + float animation = clamp(((timeInfo.x + timeInfo.w) - parameters.x) / GROWTHRATE + INITIALSIZE, INITIALSIZE, 1.0); + if (BREATHERATE != 0.0) { + animation += sin(timeInfo.x / BREATHERATE) * BREATHESIZE; + } + v_lengthwidthcornerheight.xy *= animation; + #endif + + POST_ANIM + + vec4 centerClipPos = cameraViewProj * vec4(v_centerpos.xyz, 1.0); + if (vertexClipped(centerClipPos, CLIPTOLERANCE)) { + v_numvertices = 0u; + } + + if (cameraDistance > iconDistance) { + v_numvertices = 0u; + } + + if (dot(v_centerpos.xyz, v_centerpos.xyz) < 1.0) { + v_numvertices = 0u; + } + + v_centerpos.y += HEIGHTOFFSET; + v_centerpos.y += v_lengthwidthcornerheight.w; + + if ((uni[instData.y].composite & 0x00000003u) < 1u) { + v_numvertices = 0u; + } + + POST_VERTEX + + vec3 primitiveCoords; + vec2 primitiveUV; + float addRadiusCorr; + bool activeVertex = BuildPrimitive(v_numvertices, v_lengthwidthcornerheight, primitiveCoords, primitiveUV, addRadiusCorr); + + PRE_OFFSET + + mat3 rotY; + #if (BILLBOARD == 1) + rotY = mat3(cameraViewInv[0].xyz, cameraViewInv[2].xyz, cameraViewInv[1].xyz); + #else + #if (FULL_ROTATION == 1) + rotY = v_fullrotation; + #else + #if (ROTATE_CIRCLES == 1) + rotY = RotationY(-1.0 * v_rotationY); + #else + if (v_numvertices > 5u) { + rotY = mat3(1.0); + } else { + rotY = RotationY(-1.0 * v_rotationY); + } + #endif + #endif + #endif + + vec3 vecnorm = normalize(primitiveCoords); + if (dot(primitiveCoords, primitiveCoords) < 0.0001) { + vecnorm = vec3(0.0); + } + + vec3 expandedPos = v_centerpos.xyz + rotY * (addRadius * addRadiusCorr * vecnorm + primitiveCoords); + vec4 clipPos = cameraViewProj * vec4(expandedPos, 1.0); + + #ifdef ZPULL + clipPos.z = clipPos.z - ZPULL / max(abs(clipPos.w), 0.0001); + #endif + + if (!activeVertex) { + gl_Position = vec4(0.0, 0.0, 2.0, 1.0); + g_color = vec4(0.0); + g_uv = vec4(0.0); + return; + } + + gl_Position = clipPos; + g_color = v_color; + g_uv = vec4(transformUV(v_uvoffsets, primitiveUV.x, primitiveUV.y), v_parameters.zw); + + DataGSCompat dataIn[1]; + dataIn[0].v_numvertices = v_numvertices; + dataIn[0].v_rotationY = v_rotationY; + dataIn[0].v_color = v_color; + dataIn[0].v_lengthwidthcornerheight = v_lengthwidthcornerheight; + dataIn[0].v_centerpos = v_centerpos; + dataIn[0].v_uvoffsets = v_uvoffsets; + dataIn[0].v_parameters = v_parameters; + #if (FULL_ROTATION == 1) + dataIn[0].v_fullrotation = v_fullrotation; + #else + dataIn[0].v_fullrotation = mat3(1.0); + #endif + + POST_GEOMETRY +} +]] + +local fsSrc = [[ +#version 420 +#extension GL_ARB_uniform_buffer_object : require +#extension GL_ARB_shading_language_420pack: require + +//__ENGINEUNIFORMBUFFERDEFS__ +//__DEFINES__ + +#line 30000 + +uniform float addRadius = 0.0; +uniform float iconDistance = 20000.0; +uniform sampler2D DrawPrimitiveAtUnitTexture; + +in vec4 g_color; +in vec4 g_uv; + +out vec4 fragColor; + +void main(void) +{ + vec4 texcolor = vec4(1.0); + + #if (USETEXTURE == 1) + texcolor = texture(DrawPrimitiveAtUnitTexture, g_uv.xy); + #endif + + fragColor.rgba = vec4(g_color.rgb * texcolor.rgb + addRadius, texcolor.a * TRANSPARENCY + addRadius); + POST_SHADING + + #if (DISCARD == 1) + if (fragColor.a < 0.01) { + discard; + } + #endif +} +]] + +local function GLSLValue(value) + if type(value) == "boolean" then + return value and "1" or "0" + end + return tostring(value) +end + +local function BuildDefines(config) + local lines = {} + local keys = {} + for key, value in pairs(config) do + if value ~= nil then + keys[#keys + 1] = key + end + end + table.sort(keys) + for _, key in ipairs(keys) do + local value = config[key] + if type(value) == "string" then + value = value:gsub("\r", " "):gsub("\n", " ") + end + lines[#lines + 1] = "#define " .. key .. " " .. GLSLValue(value) + end + return table.concat(lines, "\n") +end + +local function PatchShaderSource(source, config) + local engineUniformBufferDefs = LuaShader.GetEngineUniformBufferDefs() + local quaternionDefs = "" + if LuaShader.GetQuaternionDefs then + quaternionDefs = LuaShader.GetQuaternionDefs() or "" + end + return source + :gsub("//__ENGINEUNIFORMBUFFERDEFS__", engineUniformBufferDefs) + :gsub("//__QUATERNIONDEFS__", quaternionDefs) + :gsub("//__DEFINES__", BuildDefines(config)) +end + +local function AddShapeVertex(data, xWidth, xCorner, zLength, zCorner, uBase, uCorner, vBase, vCorner, shapeType, stripIndex, triangleMaxStripIndex, addRadiusCorr) + data[#data + 1] = xWidth + data[#data + 1] = xCorner + data[#data + 1] = zLength + data[#data + 1] = zCorner + data[#data + 1] = uBase + data[#data + 1] = uCorner + data[#data + 1] = vBase + data[#data + 1] = vCorner + data[#data + 1] = shapeType + data[#data + 1] = stripIndex or 0 + data[#data + 1] = triangleMaxStripIndex or 0 + data[#data + 1] = addRadiusCorr or 1 +end + +local function AddTriangle(data) + AddShapeVertex(data, 0, 0, 1, 0, 0.5, 0, 1.0, 0, 3, 0, 0, 2.0) + AddShapeVertex(data, -0.866, 0, -0.5, 0, 0.0, 0, 0.0, 0, 3, 0, 0, 2.0) + AddShapeVertex(data, 0.866, 0, -0.5, 0, 1.0, 0, 0.0, 0, 3, 0, 0, 2.0) +end + +local function AddQuad(data) + local verts = { + {0.5, 0.5, 0.0, 1.0}, + {0.5, -0.5, 0.0, 0.0}, + {-0.5, 0.5, 1.0, 1.0}, + {-0.5, 0.5, 1.0, 1.0}, + {0.5, -0.5, 0.0, 0.0}, + {-0.5, -0.5, 1.0, 0.0}, + } + for _, vertex in ipairs(verts) do + AddShapeVertex(data, vertex[1], 0, vertex[2], 0, vertex[3], 0, vertex[4], 0, 4, 0, 0, 1.414) + end +end + +local function AddCornerRect(data) + local strip = { + {-0.5, 0, -0.5, 1, 0, 0, 0, 1}, + {-0.5, 0, 0.5, -1, 0, 0, 1, -1}, + {-0.5, 1, -0.5, 0, 0, 1, 0, 0}, + {-0.5, 1, 0.5, 0, 0, 1, 1, 0}, + {0.5, -1, -0.5, 0, 1, -1, 0, 0}, + {0.5, -1, 0.5, 0, 1, -1, 1, 0}, + {0.5, 0, -0.5, 1, 1, 0, 0, 1}, + {0.5, 0, 0.5, -1, 1, -1, 1, 0}, + } + local indices = {1, 2, 3, 3, 2, 4, 3, 4, 5, 5, 4, 6, 5, 6, 7, 7, 6, 8} + for _, index in ipairs(indices) do + local vertex = strip[index] + AddShapeVertex(data, vertex[1], vertex[2], vertex[3], vertex[4], vertex[5], vertex[6], vertex[7], vertex[8], 2, 0, 0, 1.1) + end +end + +local function AddCircle(data) + local maxVertices = 64 + for i = 0, maxVertices - 3 do + local tri + if (i % 2) == 0 then + tri = {i, i + 1, i + 2} + else + tri = {i + 1, i, i + 2} + end + for _, stripIndex in ipairs(tri) do + AddShapeVertex(data, 0, 0, 0, 0, 0, 0, 0, 0, 64, stripIndex, i + 2, 1.0) + end + end +end + +local function MakePrimitiveShapeVBO() + if primitiveShapeVBO ~= nil then + return primitiveShapeVBO, primitiveShapeVertexCount + end + + local data = {} + AddTriangle(data) + AddQuad(data) + AddCornerRect(data) + AddCircle(data) + + primitiveShapeVertexCount = #data / 12 + primitiveShapeVBO = gl.GetVBO(GL.ARRAY_BUFFER, false) + if primitiveShapeVBO == nil then + Spring.Echo("Failed to create DrawPrimitiveAtUnit shape VBO") + return nil, 0 + end + + primitiveShapeVBO:Define(primitiveShapeVertexCount, { + {id = 6, name = 'shapeXZ', size = 4}, + {id = 7, name = 'shapeUV', size = 4}, + {id = 8, name = 'shapeMeta', size = 4}, + }) + primitiveShapeVBO:Upload(data) + + return primitiveShapeVBO, primitiveShapeVertexCount +end + +local function MakeVAOWrapper(realVAO, instanceTable) + return { + DrawArrays = function(_, _, vertexCount, _, instanceCount, instanceFirst) + local instances = instanceCount or vertexCount or instanceTable.usedElements or 0 + if instances <= 0 then + return + end + realVAO:DrawArrays(GL.TRIANGLES, primitiveShapeVertexCount, 0, instances, instanceFirst or 0) + end, + Delete = function() + if realVAO.Delete then + realVAO:Delete() + end + end, + realVAO = realVAO, + } +end + +local function InitDrawPrimitiveAtUnit(config, DPATname) + local shapeVBO = MakePrimitiveShapeVBO() + if shapeVBO == nil then + return nil + end + + local shaderName = DPATname .. "Shader GL4 NoGS Mesh" + local uniformInt = {} + if config.USETEXTURE and config.USETEXTURE ~= 0 then + uniformInt.DrawPrimitiveAtUnitTexture = 0 + end + local drawPrimitiveAtUnitShader = LuaShader( + { + vertex = PatchShaderSource(vsSrc, config), + fragment = PatchShaderSource(fsSrc, config), + uniformInt = uniformInt, + uniformFloat = { + addRadius = 0.0, + iconDistance = 20000.0, + }, + }, + shaderName + ) + + local shaderCompiled = drawPrimitiveAtUnitShader:Initialize() + if not shaderCompiled then + Spring.Echo("Failed to compile shader for ", DPATname, " NoGS Mesh") + return nil + end + + local drawPrimitiveAtUnitVBO = InstanceVBOTable.makeInstanceVBOTable( + { + {id = 0, name = 'lengthwidthcorner', size = 4}, + {id = 1, name = 'teamID', size = 1, type = GL.UNSIGNED_INT}, + {id = 2, name = 'numvertices', size = 1, type = GL.UNSIGNED_INT}, + {id = 3, name = 'parameters', size = 4}, + {id = 4, name = 'uvoffsets', size = 4}, + {id = 5, name = 'instData', size = 4, type = GL.UNSIGNED_INT}, + }, + 64, + DPATname .. "VBO", + 5 + ) + if drawPrimitiveAtUnitVBO == nil then + Spring.Echo("Failed to create DrawPrimitiveAtUnitVBO for ", DPATname) + return nil + end + + local realVAO = gl.GetVAO() + if realVAO == nil then + Spring.Echo("Failed to create DrawPrimitiveAtUnitVAO for ", DPATname) + return nil + end + realVAO:AttachVertexBuffer(shapeVBO) + realVAO:AttachInstanceBuffer(drawPrimitiveAtUnitVBO.instanceVBO) + drawPrimitiveAtUnitVBO.VAO = MakeVAOWrapper(realVAO, drawPrimitiveAtUnitVBO) + + return drawPrimitiveAtUnitVBO, drawPrimitiveAtUnitShader +end + +return { + InitDrawPrimitiveAtUnit = InitDrawPrimitiveAtUnit, + shaderConfig = shaderConfig, +} diff --git a/luaui/Shaders/HealthbarsGL4_nogs.vert.glsl b/luaui/Shaders/HealthbarsGL4_nogs.vert.glsl index a4633151e43..2b4cfb2ac55 100644 --- a/luaui/Shaders/HealthbarsGL4_nogs.vert.glsl +++ b/luaui/Shaders/HealthbarsGL4_nogs.vert.glsl @@ -3,214 +3,415 @@ #extension GL_ARB_shader_storage_buffer_object : require #extension GL_ARB_shading_language_420pack: require -// SPDX-License-Identifier: MIT -// Copyright (c) 2026 Beherith (mysterme@gmail.com) -// This shader is part of the Beyond All Reason repository. - +// macOS NoGS adaptation of BAR's HealthbarsGL4 vertex+geometry pipeline. +// Keeps the official instance attributes and fragment shader contract. #line 5000 -layout (location = 0) in vec2 quadPos; // x,y in [0,1] -layout (location = 2) in vec4 height_timers; -layout (location = 3) in uvec4 bartype_index_ssboloc; -layout (location = 4) in vec4 mincolor; -layout (location = 5) in vec4 maxcolor; -layout (location = 6) in uvec4 instData; +layout (location = 0) in vec4 height_timers; +layout (location = 1) in uvec4 bartype_index_ssboloc; +layout (location = 2) in vec4 mincolor; +layout (location = 3) in vec4 maxcolor; +layout (location = 4) in uvec4 instData; +layout (location = 5) in float shapeIndex; //__ENGINEUNIFORMBUFFERDEFS__ //__DEFINES__ struct SUniformsBuffer { - uint composite; // u8 drawFlag; u8 unused1; u16 id; + uint composite; - uint unused2; - uint unused3; - uint unused4; + uint unused2; + uint unused3; + uint unused4; - float maxHealth; - float health; - float unused5; - float unused6; + float maxHealth; + float health; + float unused5; + float unused6; - vec4 drawPos; - vec4 speed; - vec4[4] userDefined; // can't use float[16] because float in arrays occupies 4 * float space + vec4 drawPos; + vec4 speed; + vec4[4] userDefined; }; layout(std140, binding=1) readonly buffer UniformsBuffer { - SUniformsBuffer uni[]; + SUniformsBuffer uni[]; }; #line 10000 uniform float iconDistance; uniform float cameraDistanceMult; +uniform float cameraDistanceMultGlyph; uniform float skipGlyphsNumbers; +uniform float globalSizeMult; -out DataVS { - vec4 g_color; - vec2 g_uv; - float g_value; - float g_rawvalue; - float g_uvoffset; - float g_useOverlay; - float g_showIcon; - float g_showText; - float g_bartype; +out DataGS { + vec4 g_color; + vec4 g_uv; }; #define UNITUNIFORMS uni[instData.y] #define UNIFORMLOC bartype_index_ssboloc.z #define BARTYPE bartype_index_ssboloc.x +#define BITUSEOVERLAY 1u +#define BITSHOWGLYPH 2u #define BITPERCENTAGE 4u #define BITTIMELEFT 8u #define BITINTEGERNUMBER 16u #define BITGETPROGRESS 32u #define BITFLASHBAR 64u #define BITCOLORCORRECT 128u -#define BITUSEOVERLAY 1u -#define BITSHOWGLYPH 2u -bool vertexClipped(vec4 clipspace, float tolerance) { - return any(lessThan(clipspace.xyz, -clipspace.www * tolerance)) || - any(greaterThan(clipspace.xyz, clipspace.www * tolerance)); +#define HALFPIXEL 0.0019765625 + +bool vertexClipped(vec4 clipspace, float tolerance) +{ + return any(lessThan(clipspace.xyz, -clipspace.www * tolerance)) || + any(greaterThan(clipspace.xyz, clipspace.www * tolerance)); +} + +void HideVertex() +{ + gl_Position = vec4(0.0, 0.0, 2.0, 1.0); + g_color = vec4(0.0); + g_uv = vec4(0.0); +} + +int StripIndexForTriangleVertex(int i) +{ + if (i == 0) return 0; + if (i == 1) return 1; + if (i == 2) return 2; + if (i == 3) return 2; + if (i == 4) return 1; + if (i == 5) return 3; + if (i == 6) return 2; + if (i == 7) return 3; + if (i == 8) return 4; + if (i == 9) return 4; + if (i == 10) return 3; + if (i == 11) return 5; + if (i == 12) return 4; + if (i == 13) return 5; + if (i == 14) return 6; + if (i == 15) return 6; + if (i == 16) return 5; + return 7; +} + +int QuadIndexForTriangleVertex(int i) +{ + if (i == 0) return 0; + if (i == 1) return 1; + if (i == 2) return 2; + if (i == 3) return 2; + if (i == 4) return 1; + return 3; +} + +vec2 BackgroundPos(int s) +{ + if (s == 0) return vec2(-BARWIDTH, BARCORNER); + if (s == 1) return vec2(-BARWIDTH, BARHEIGHT - BARCORNER); + if (s == 2) return vec2(-BARWIDTH + BARCORNER, 0.0); + if (s == 3) return vec2(-BARWIDTH + BARCORNER, BARHEIGHT); + if (s == 4) return vec2(BARWIDTH - BARCORNER, 0.0); + if (s == 5) return vec2(BARWIDTH - BARCORNER, BARHEIGHT); + if (s == 6) return vec2(BARWIDTH, BARCORNER); + return vec2(BARWIDTH, BARHEIGHT - BARCORNER); +} + +vec2 BarBackgroundPos(int s) +{ + if (s == 0) return vec2(-BARWIDTH + BARCORNER, SMALLERCORNER + BARCORNER); + if (s == 1) return vec2(-BARWIDTH + BARCORNER, BARHEIGHT - SMALLERCORNER - BARCORNER); + if (s == 2) return vec2(-BARWIDTH + SMALLERCORNER + BARCORNER, BARCORNER); + if (s == 3) return vec2(-BARWIDTH + SMALLERCORNER + BARCORNER, BARHEIGHT - BARCORNER); + if (s == 4) return vec2(BARWIDTH - SMALLERCORNER - BARCORNER, BARCORNER); + if (s == 5) return vec2(BARWIDTH - SMALLERCORNER - BARCORNER, BARHEIGHT - BARCORNER); + if (s == 6) return vec2(BARWIDTH - BARCORNER, SMALLERCORNER + BARCORNER); + return vec2(BARWIDTH - BARCORNER, BARHEIGHT - SMALLERCORNER - BARCORNER); +} + +vec2 BarForegroundPos(int s, float healthbasedpos) +{ + if (s == 0) return vec2(-BARWIDTH + BARCORNER, SMALLERCORNER + BARCORNER); + if (s == 1) return vec2(-BARWIDTH + BARCORNER, BARHEIGHT - BARCORNER - SMALLERCORNER); + if (s == 2) return vec2(-BARWIDTH + BARCORNER + SMALLERCORNER, BARCORNER); + if (s == 3) return vec2(-BARWIDTH + BARCORNER + SMALLERCORNER, BARHEIGHT - BARCORNER); + if (s == 4) return vec2(-BARWIDTH + BARCORNER + SMALLERCORNER + healthbasedpos, BARCORNER); + if (s == 5) return vec2(-BARWIDTH + BARCORNER + SMALLERCORNER + healthbasedpos, BARHEIGHT - BARCORNER); + if (s == 6) return vec2(-BARWIDTH + BARCORNER + 2.0 * SMALLERCORNER + healthbasedpos, BARCORNER + SMALLERCORNER); + return vec2(-BARWIDTH + BARCORNER + 2.0 * SMALLERCORNER + healthbasedpos, BARHEIGHT - BARCORNER - SMALLERCORNER); +} + +vec4 ProjectBarVertex(vec2 pos, vec4 centerpos, mat3 rotY, float zoffset, float sizemultiplier, float depthbuffermod) +{ + vec3 primitiveCoords = vec3(pos.x, 0.0, pos.y - zoffset) * BARSCALE * sizemultiplier; + vec4 clip = cameraViewProj * vec4(centerpos.xyz + rotY * primitiveCoords, 1.0); + clip.z += depthbuffermod; + return clip; +} + +void EmitBackgroundVertex(vec2 pos, vec4 centerpos, mat3 rotY, float zoffset, float sizemultiplier, float barAlpha, uint bartype) +{ + g_uv.xy = vec2(0.0); + g_uv.z = 0.0; + float extracolor = 0.0; + if (((bartype & BITFLASHBAR) > 0u) && (mod(timeInfo.x, 10.0) > 4.0)) { + extracolor = 0.5; + } + g_color = mix(BGBOTTOMCOLOR + extracolor, BGTOPCOLOR + extracolor, pos.y); + g_color.a *= barAlpha; + gl_Position = ProjectBarVertex(pos, centerpos, rotY, zoffset, sizemultiplier, 0.001); +} + +void EmitBarVertex(vec2 pos, vec4 color, float bartextureoffset, vec4 centerpos, mat3 rotY, float zoffset, float sizemultiplier, float barAlpha, float depthbuffermod) +{ + g_uv.x = pos.x * 1.0 / (2.0 * (BARWIDTH - BARCORNER)); + g_uv.x = g_uv.x + 0.5; + g_uv.y = (pos.y - BARCORNER) / (BARHEIGHT - 2.0 * BARCORNER); + g_uv.xy = g_uv.xy * vec2(ATLASSTEP * 9.0, ATLASSTEP) + vec2(3.0 * ATLASSTEP, bartextureoffset); + g_uv.y = -1.0 * g_uv.y; + g_uv.z = clamp(10000.0 * bartextureoffset, 0.0, 1.0); + g_color = color; + g_color.a *= barAlpha; + gl_Position = ProjectBarVertex(pos, centerpos, rotY, zoffset, sizemultiplier, depthbuffermod); +} + +void EmitGlyphVertex(vec2 bottomleft, vec2 uvbottomleft, vec2 uvsizes, int triangleVertex, vec4 centerpos, mat3 rotY, float zoffset, float sizemultiplier, float glyphAlpha) +{ + int q = QuadIndexForTriangleVertex(triangleVertex); + vec2 pos = bottomleft; + vec2 uv = uvbottomleft; + if (q == 1) { + pos.y += BARHEIGHT; + uv.y += uvsizes.y; + } else if (q == 2) { + pos.x += BARHEIGHT; + uv.x += uvsizes.x; + } else if (q == 3) { + pos += vec2(BARHEIGHT, BARHEIGHT); + uv += uvsizes; + } + + vec2 halfAdjust = vec2((q == 2 || q == 3) ? -HALFPIXEL : HALFPIXEL, (q == 1 || q == 3) ? -HALFPIXEL : HALFPIXEL); + g_uv.xy = vec2(uv.x + halfAdjust.x, 1.0 - (uv.y + halfAdjust.y)); + g_uv.z = 1.0; + g_color = vec4(1.0); + g_color.a *= glyphAlpha; + gl_Position = ProjectBarVertex(pos, centerpos, rotY, zoffset, sizemultiplier, 0.0); } void main() { - vec4 centerpos = vec4(UNITUNIFORMS.drawPos.xyz, 1.0); - centerpos.y += HEIGHTOFFSET; - centerpos.y += height_timers.x; // per-instance height offset - - vec4 clipPos = cameraViewProj * centerpos; - if (vertexClipped(clipPos, CLIPTOLERANCE)) { - gl_Position = vec4(2.0, 2.0, 2.0, 1.0); - g_color = vec4(0.0); - g_uv = vec2(0.0); - g_value = 0.0; - g_rawvalue = 0.0; - g_uvoffset = 0.0; - g_useOverlay = 0.0; - g_showIcon = 0.0; - g_showText = 0.0; - g_bartype = 0.0; - return; - } - - float cameraDistance = length((cameraViewInv[3]).xyz - centerpos.xyz); - float barAlpha = (clamp(cameraDistance * cameraDistanceMult, BARFADESTART, BARFADEEND) - BARFADESTART) / (BARFADEEND - BARFADESTART); - barAlpha = 1.0 - clamp(barAlpha, 0.0, 1.0); - - float value = UNITUNIFORMS.health / max(UNITUNIFORMS.maxHealth, 0.001); - if (UNIFORMLOC < 20u) { - value = UNITUNIFORMS.userDefined[0][UNIFORMLOC]; - } - if ((BARTYPE & BITGETPROGRESS) > 0u) { - value = ((timeInfo.x + timeInfo.w) - UNITUNIFORMS.userDefined[0].z) / - max(UNITUNIFORMS.userDefined[0].w - UNITUNIFORMS.userDefined[0].z, 0.001); - } - float rawvalue = value; - value = clamp(value, 0.0, 1.0); - - // Keep stockpile bars visible (integer count packed in value). - if ((BARTYPE & BITINTEGERNUMBER) > 0u) { - value = clamp(fract(value), 0.0, 1.0); - } - - if ((BARTYPE & BITTIMELEFT) > 0u) { - value = clamp(1.0 - value, 0.0, 1.0); - } - - #ifndef DEBUGSHOW - if (value < 0.00001) { - gl_Position = vec4(2.0, 2.0, 2.0, 1.0); - g_color = vec4(0.0); - g_uv = vec2(0.0); - g_value = value; - g_rawvalue = rawvalue; - g_uvoffset = 0.0; - g_useOverlay = 0.0; - g_showIcon = 0.0; - g_showText = 0.0; - g_bartype = 0.0; - return; - } - - if ((BARTYPE & BITPERCENTAGE) > 0u) { - if (value > 0.999) { - gl_Position = vec4(2.0, 2.0, 2.0, 1.0); - g_color = vec4(0.0); - g_uv = vec2(0.0); - g_value = value; - g_rawvalue = rawvalue; - g_uvoffset = 0.0; - g_useOverlay = 0.0; - g_showIcon = 0.0; - g_showText = 0.0; - g_bartype = 0.0; - return; - } - } else { - if ((BARTYPE & BITGETPROGRESS) > 0u) { - if (value > 0.999) { - gl_Position = vec4(2.0, 2.0, 2.0, 1.0); - g_color = vec4(0.0); - g_uv = vec2(0.0); - g_value = value; - g_rawvalue = rawvalue; - g_uvoffset = 0.0; - g_useOverlay = 0.0; - g_showIcon = 0.0; - g_showText = 0.0; - g_bartype = 0.0; - return; - } - } - } - #endif - - if (barAlpha < MINALPHA) { - gl_Position = vec4(2.0, 2.0, 2.0, 1.0); - g_color = vec4(0.0); - g_uv = vec2(0.0); - g_value = value; - g_rawvalue = rawvalue; - g_uvoffset = 0.0; - g_useOverlay = 0.0; - g_showIcon = 0.0; - g_showText = 0.0; - g_bartype = 0.0; - return; - } - - vec4 fillColor = mix(mincolor, maxcolor, value); - if ((BARTYPE & BITCOLORCORRECT) > 0u) { - float m = max(fillColor.r, fillColor.g); - if (m > 0.0001) { - fillColor.rgb = fillColor.rgb / m; - } - } - - float zoffset = 1.15 * BARHEIGHT * float(bartype_index_ssboloc.y); - - // Expand the fallback quad with a left glyph lane (icon + text), matching GS layout better. - const float GLYPH_PAD_TILES = 4.5; - float glyphPad = BARHEIGHT * GLYPH_PAD_TILES; - float primitiveX = mix(-BARWIDTH - glyphPad, BARWIDTH, quadPos.x); - vec3 primitiveCoords = vec3(primitiveX, 0.0, quadPos.y * BARHEIGHT - zoffset) * BARSCALE * height_timers.y; - - mat3 rotY = mat3(cameraViewInv[0].xyz, cameraViewInv[2].xyz, cameraViewInv[1].xyz); - vec4 worldPos = vec4(centerpos.xyz + rotY * primitiveCoords, 1.0); - - gl_Position = cameraViewProj * worldPos; - - g_uv = quadPos; - g_value = value; - g_rawvalue = rawvalue; - g_color = vec4(fillColor.rgb, barAlpha); - g_uvoffset = height_timers.w; - g_useOverlay = ((BARTYPE & BITUSEOVERLAY) > 0u) ? 1.0 : 0.0; - g_showIcon = (((BARTYPE & BITSHOWGLYPH) > 0u) && (skipGlyphsNumbers < 0.5)) ? 1.0 : 0.0; - g_showText = (skipGlyphsNumbers < 1.5) ? 1.0 : 0.0; - g_bartype = float(BARTYPE); + int idx = int(shapeIndex + 0.5); + + vec4 drawPos = vec4(UNITUNIFORMS.drawPos.xyz, 1.0); + vec4 centerpos = drawPos; + uint numvertices = 4u; + vec4 centerClip = cameraViewProj * drawPos; + if (vertexClipped(centerClip, CLIPTOLERANCE)) { + numvertices = 0u; + } + + float cameraDistance = length(cameraViewInv[3].xyz - centerpos.xyz); + vec4 parameters = vec4(0.0); + parameters.y = (clamp(cameraDistance * cameraDistanceMult, BARFADESTART, BARFADEEND) - BARFADESTART) / (BARFADEEND - BARFADESTART); + parameters.y = 1.0 - clamp(parameters.y, 0.0, 1.0); + parameters.z = (clamp(cameraDistance * cameraDistanceMult * cameraDistanceMultGlyph, BARFADESTART, BARFADEEND) - BARFADESTART) / (BARFADEEND - BARFADESTART); + parameters.z = 1.0 - clamp(parameters.z, 0.0, 1.0); + + #ifdef DEBUGSHOW + parameters.y = 1.0; + parameters.z = 1.0; + #endif + + parameters.w = height_timers.w; + vec2 sizemodifiers = height_timers.yz; + + if (dot(centerpos.xyz, centerpos.xyz) < 1.0) { + numvertices = 0u; + } + + centerpos.y += HEIGHTOFFSET; + centerpos.y += height_timers.x; + + float relativehealth = UNITUNIFORMS.health / UNITUNIFORMS.maxHealth; + parameters.x = relativehealth; + if (UNIFORMLOC < 20u) { + parameters.x = UNITUNIFORMS.userDefined[0].y; + } else { + float buildprogress = UNITUNIFORMS.userDefined[0].x; + #ifndef DEBUGSHOW + if (abs(buildprogress - relativehealth) < 0.03) { + numvertices = 0u; + } + #endif + } + + if (UNIFORMLOC < 4u) parameters.x = UNITUNIFORMS.userDefined[0][UNIFORMLOC]; + if (UNIFORMLOC == 1u) parameters.x = UNITUNIFORMS.userDefined[0].y; + if (UNIFORMLOC == 2u) parameters.x = UNITUNIFORMS.userDefined[0].z; + if (UNIFORMLOC == 4u) parameters.x = UNITUNIFORMS.userDefined[1].x; + if (UNIFORMLOC == 5u) parameters.x = UNITUNIFORMS.userDefined[1].y; + + if ((BARTYPE & BITGETPROGRESS) > 0u) { + parameters.x = ((timeInfo.x + timeInfo.w) - UNITUNIFORMS.userDefined[0].z) / + (UNITUNIFORMS.userDefined[0].w - UNITUNIFORMS.userDefined[0].z); + parameters.x = clamp(parameters.x, 0.0, 1.0); + } + + float health = parameters.x; + float barAlpha = parameters.y; + float glyphAlpha = parameters.z; + float uvoffset = parameters.w; + float sizemultiplier = sizemodifiers.x * globalSizeMult; + float zoffset = 1.15 * BARHEIGHT * float(bartype_index_ssboloc.y); + mat3 rotY = mat3(cameraViewInv[0].xyz, cameraViewInv[2].xyz, cameraViewInv[1].xyz); + + if (numvertices == 0u || barAlpha < MINALPHA) { + HideVertex(); + return; + } + + #ifndef DEBUGSHOW + if (health < 0.00001) { + HideVertex(); + return; + } + if ((BARTYPE & BITPERCENTAGE) > 0u) { + if (health > 0.999) { + HideVertex(); + return; + } + } else if ((BARTYPE & BITGETPROGRESS) > 0u) { + if (health > 0.999) { + HideVertex(); + return; + } + } + #endif + + uint numStockpiled = 0u; + uint numStockpileQueued = 0u; + if ((BARTYPE & BITINTEGERNUMBER) > 0u) { + float oldhealth = health; + health = fract(oldhealth); + oldhealth = floor(oldhealth); + numStockpiled = uint(floor(mod(oldhealth, 128.0))); + numStockpileQueued = uint(floor(oldhealth / 128.0)); + } + + if (idx < 18) { + int s = StripIndexForTriangleVertex(idx); + EmitBackgroundVertex(BackgroundPos(s), centerpos, rotY, zoffset, sizemultiplier, barAlpha, BARTYPE); + return; + } + + vec4 truecolor = mix(mincolor, maxcolor, health); + + if (idx < 36) { + int s = StripIndexForTriangleVertex(idx - 18); + truecolor.a = 0.2; + vec4 topcolor = truecolor; + topcolor.rgb *= BOTTOMDARKENFACTOR; + vec4 color = ((s == 1) || (s == 3) || (s == 5) || (s == 7)) ? topcolor : truecolor; + EmitBarVertex(BarBackgroundPos(s), color, 0.0, centerpos, rotY, zoffset, sizemultiplier, barAlpha, 0.0); + return; + } + + float healthbasedpos = (2.0 * (BARWIDTH - BARCORNER) - 2.0 * SMALLERCORNER) * health; + if ((BARTYPE & BITTIMELEFT) > 0u) { + healthbasedpos = 2.0 * (BARWIDTH - BARCORNER) - 2.0 * SMALLERCORNER; + } + if ((BARTYPE & BITCOLORCORRECT) > 0u) { + truecolor.rgb = truecolor.rgb / max(truecolor.r, truecolor.g); + } + truecolor.a = 1.0; + vec4 botcolor = truecolor; + botcolor.rgb *= BOTTOMDARKENFACTOR; + float bartextureoffset = 0.0; + if ((BARTYPE & BITUSEOVERLAY) > 0u) { + bartextureoffset = uvoffset; + } + + if (idx < 54) { + int s = StripIndexForTriangleVertex(idx - 36); + vec4 color = ((s == 1) || (s == 3) || (s == 5) || (s == 7)) ? truecolor : botcolor; + EmitBarVertex(BarForegroundPos(s, healthbasedpos), color, bartextureoffset, centerpos, rotY, zoffset, sizemultiplier, barAlpha, -0.001); + return; + } + + if (glyphAlpha < MINALPHA || skipGlyphsNumbers > 1.5) { + HideVertex(); + return; + } + + float currentglyphpos = 1.0; + bool drawGlyphIcon = false; + if (skipGlyphsNumbers < 0.5) { + drawGlyphIcon = ((BARTYPE & BITSHOWGLYPH) > 0u); + } else { + currentglyphpos = 0.0; + } + + if (idx < 60) { + if (!drawGlyphIcon) { + HideVertex(); + return; + } + EmitGlyphVertex(vec2(-BARWIDTH - currentglyphpos * BARHEIGHT, 0.0), vec2(ATLASSTEP, uvoffset), vec2(ATLASSTEP, ATLASSTEP), idx - 54, centerpos, rotY, zoffset, sizemultiplier, glyphAlpha); + return; + } + + if ((BARTYPE & BITINTEGERNUMBER) > 0u) { + vec4 numbers = vec4(numStockpiled, numStockpiled, numStockpileQueued, numStockpileQueued); + numbers = numbers * vec4(1.0, 0.1, 1.0, 0.1); + numbers = floor(mod(numbers, 10.0)) * ATLASSTEP; + + if (idx < 66) { + EmitGlyphVertex(vec2(-BARWIDTH - (currentglyphpos + 1.0) * BARHEIGHT, 0.0), vec2(0.0, numbers.x), vec2(ATLASSTEP, ATLASSTEP), idx - 60, centerpos, rotY, zoffset, sizemultiplier, glyphAlpha); + return; + } + if (idx < 72 && numbers.y > 0.0) { + EmitGlyphVertex(vec2(-BARWIDTH - (currentglyphpos + 2.0) * BARHEIGHT + BARHEIGHT * 0.4, 0.0), vec2(0.0, numbers.y), vec2(ATLASSTEP, ATLASSTEP), idx - 66, centerpos, rotY, zoffset, sizemultiplier, glyphAlpha); + return; + } + HideVertex(); + return; + } + + if ((BARTYPE & (BITTIMELEFT | BITPERCENTAGE)) > 0u) { + float lsb; + float msb; + float glyphpctsecatlas; + if ((BARTYPE & BITTIMELEFT) > 0u) { + health = (health - 1.0) / (1.0 / 40.0); + lsb = abs(floor(mod(health, 10.0))); + msb = abs(floor(mod(health * 0.1, 10.0))); + glyphpctsecatlas = 14.0; + } else { + lsb = floor(mod(health * 100.0, 10.0)); + msb = floor(mod(health * 10.0, 10.0)); + glyphpctsecatlas = 11.0; + } + + if (idx < 66) { + EmitGlyphVertex(vec2(-BARWIDTH - (currentglyphpos + 1.0) * BARHEIGHT, 0.0), vec2(0.0, glyphpctsecatlas * ATLASSTEP), vec2(ATLASSTEP, ATLASSTEP), idx - 60, centerpos, rotY, zoffset, sizemultiplier, glyphAlpha); + return; + } + if (idx < 72) { + EmitGlyphVertex(vec2(-BARWIDTH - (currentglyphpos + 2.0) * BARHEIGHT + BARHEIGHT * 0.2, 0.0), vec2(0.0, lsb * ATLASSTEP), vec2(ATLASSTEP, ATLASSTEP), idx - 66, centerpos, rotY, zoffset, sizemultiplier, glyphAlpha); + return; + } + if (idx < 78 && msb > 0.0) { + EmitGlyphVertex(vec2(-BARWIDTH - (currentglyphpos + 3.0) * BARHEIGHT + BARHEIGHT * 0.5, 0.0), vec2(0.0, msb * ATLASSTEP), vec2(ATLASSTEP, ATLASSTEP), idx - 72, centerpos, rotY, zoffset, sizemultiplier, glyphAlpha); + return; + } + } + + HideVertex(); } diff --git a/luaui/Shaders/screen_distortion_combine_gl4.frag.glsl b/luaui/Shaders/screen_distortion_combine_gl4.frag.glsl index 93a1fcf5716..49084da4482 100644 --- a/luaui/Shaders/screen_distortion_combine_gl4.frag.glsl +++ b/luaui/Shaders/screen_distortion_combine_gl4.frag.glsl @@ -7,6 +7,8 @@ uniform sampler2D distortionTexture; uniform float distortionOverallStrength = 1.0; uniform vec2 inverseScreenResolution = vec2(1.0/1920.0, 1.0/1080.0); +in vec2 v_uv; + vec3 colormap_jet(float x){ vec3 color = vec3(0.0); vec3 black = vec3(0.0); @@ -37,7 +39,7 @@ vec2 softClampScreen(vec2 UV){ void main(void) { // As of yet, distortion coords are still stored as centered around 0.5, so we need to shift them to 0.0 - vec4 distortion = texture2D(distortionTexture, gl_TexCoord[0].st); + vec4 distortion = texture2D(distortionTexture, v_uv); distortion.rgb = distortion.rgb; distortion.rg = (1536.0 * distortion.rg) * inverseScreenResolution; if (length(distortion.rg) < 0.01) { @@ -55,15 +57,15 @@ void main(void) { // Regular distortion if (distortion.b > -1.0 ) { vec2 distortionXY = distortion.rg * distortionOverallStrength * 0.01; - offsetUV1 = softClampScreen(gl_TexCoord[0].st + distortionXY); - offsetUV2 = softClampScreen(gl_TexCoord[0].st + distortionXY / CHROMATIC_ABERRATION); - offsetUV3 = softClampScreen(gl_TexCoord[0].st + distortionXY * CHROMATIC_ABERRATION); + offsetUV1 = softClampScreen(v_uv + distortionXY); + offsetUV2 = softClampScreen(v_uv + distortionXY / CHROMATIC_ABERRATION); + offsetUV3 = softClampScreen(v_uv + distortionXY * CHROMATIC_ABERRATION); }else{ // Motion blur vec2 blurdirection = distortion.rg * 0.8; - offsetUV1 = softClampScreen(gl_TexCoord[0].st - 2 * inverseScreenResolution * blurdirection); - offsetUV2 = softClampScreen(gl_TexCoord[0].st + 2 * inverseScreenResolution * blurdirection); - offsetUV3 = softClampScreen(gl_TexCoord[0].st + 4 * inverseScreenResolution * blurdirection); + offsetUV1 = softClampScreen(v_uv - 2 * inverseScreenResolution * blurdirection); + offsetUV2 = softClampScreen(v_uv + 2 * inverseScreenResolution * blurdirection); + offsetUV3 = softClampScreen(v_uv + 4 * inverseScreenResolution * blurdirection); } @@ -90,8 +92,8 @@ void main(void) { gl_FragColor = outputRGBA ; #else - if (gl_TexCoord[0].x > 0.66){ // right half? - if (gl_TexCoord[0].y > 0.75){ // top right + if (v_uv.x > 0.66){ // right half? + if (v_uv.y > 0.75){ // top right if (distortion.b < -0.01 ) gl_FragColor = vec4(vec3(distortion.rg, 0.0) * 0.5 + 0.5, 0.7); else gl_FragColor = vec4(outputRGBA.rgb, 0.0); @@ -106,4 +108,4 @@ void main(void) { #endif -} \ No newline at end of file +} diff --git a/luaui/Shaders/screen_distortion_combine_gl4.vert.glsl b/luaui/Shaders/screen_distortion_combine_gl4.vert.glsl index 4cf7ddc0a58..7f645c1b894 100644 --- a/luaui/Shaders/screen_distortion_combine_gl4.vert.glsl +++ b/luaui/Shaders/screen_distortion_combine_gl4.vert.glsl @@ -2,7 +2,9 @@ //__DEFINES__ +out vec2 v_uv; + void main(void) { - gl_TexCoord[0] = vec4(gl_Vertex.zwzw); + v_uv = gl_Vertex.zw; gl_Position = vec4(gl_Vertex.xy * 1.0, 0.00, 1); -} \ No newline at end of file +} diff --git a/luaui/Widgets/api_unit_tracker_gl4.lua b/luaui/Widgets/api_unit_tracker_gl4.lua index bbae88014b0..09d03a84caa 100644 --- a/luaui/Widgets/api_unit_tracker_gl4.lua +++ b/luaui/Widgets/api_unit_tracker_gl4.lua @@ -97,7 +97,7 @@ local luaShaderDir = "LuaUI/Include/" local texture = "luaui/images/solid.png" local function initGL4() - local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit.lua") + local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit_NoGS_Mesh.lua") local InitDrawPrimitiveAtUnit = DrawPrimitiveAtUnit.InitDrawPrimitiveAtUnit local shaderConfig = DrawPrimitiveAtUnit.shaderConfig -- MAKE SURE YOU READ THE SHADERCONFIG TABLE in DrawPrimitiveAtUnit.lua shaderConfig.TRANSPARENCY = 0.5 diff --git a/luaui/Widgets/gfx_decals_gl4.lua b/luaui/Widgets/gfx_decals_gl4.lua index 4494b651aa1..59f13b13a46 100644 --- a/luaui/Widgets/gfx_decals_gl4.lua +++ b/luaui/Widgets/gfx_decals_gl4.lua @@ -17,12 +17,15 @@ end -- Localized functions for performance local mathFloor = math.floor local mathMin = math.min +local mathMax = math.max local mathRandom = math.random local round = math.round -- Localized Spring API for performance local spGetGameFrame = Spring.GetGameFrame local spEcho = Spring.Echo +local spGetUnitPosition = Spring.GetUnitPosition +local spValidUnitID = Spring.ValidUnitID -- Notes and TODO -- yes these are geometry shader decals @@ -133,7 +136,6 @@ local decalExtraLargeVBO = nil local decalShader = nil local decalLargeShader = nil -local decalUseGeometryShader = true local hasBadCulling = false -- AMD+Linux combo @@ -181,15 +183,14 @@ local uniformInts = { } local shaderSourceCache = { - vssrcpath = vsSrcPath, + vssrcpath = vsSrcLargePath, fssrcpath = fsSrcPath, - gssrcpath = gsSrcPath, shaderConfig = shaderConfig, uniformInt = uniformInts, uniformFloat = { fadeDistance = 3000, }, - shaderName = "Decals Gl4 Shader", + shaderName = "Decals Gl4 Shader NoGS", } local shaderLargeSourceCache = { @@ -211,62 +212,36 @@ end local function initGL4( DPATname) hasBadCulling = ((Platform.gpuVendor == "AMD" and Platform.osFamily == "Linux") == true) if hasBadCulling then spEcho("Decals GL4 detected AMD + Linux platform, attempting to fix culling") end - decalUseGeometryShader = (gl.LuaShader and gl.LuaShader.isGeometryShaderSupported) or false - if decalUseGeometryShader then - decalShader = LuaShader.CheckShaderUpdates(shaderSourceCache) - if not decalShader then - decalUseGeometryShader = false - spEcho("Decals GL4: geometry shader compile failed, enabling NoGS fallback") - end - end + decalShader = LuaShader.CheckShaderUpdates(shaderSourceCache) decalLargeShader = LuaShader.CheckShaderUpdates(shaderLargeSourceCache) - if not decalLargeShader then goodbye("Failed to compile ".. DPATname .." GL4 ") end + if (not decalShader) or (not decalLargeShader) then goodbye("Failed to compile ".. DPATname .." GL4 ") end - local smallDecalLayout - if decalUseGeometryShader then - smallDecalLayout = { - {id = 0, name = 'lengthwidthrotation', size = 4}, - {id = 1, name = 'uv_atlaspos', size = 4}, - {id = 2, name = 'alphastart_alphadecay_heatstart_heatdecay', size = 4}, - {id = 3, name = 'worldPos', size = 4}, - {id = 4, name = 'parameters', size = 4}, - } - else - smallDecalLayout = { + decalVBO = InstanceVBOTable.makeInstanceVBOTable( + { {id = 1, name = 'lengthwidthrotation', size = 4}, {id = 2, name = 'uv_atlaspos', size = 4}, {id = 3, name = 'alphastart_alphadecay_heatstart_heatdecay', size = 4}, {id = 4, name = 'worldPos', size = 4}, {id = 5, name = 'parameters', size = 4}, - } - end - - decalVBO = InstanceVBOTable.makeInstanceVBOTable( - smallDecalLayout, + }, 64, -- maxelements DPATname .. "VBO" -- name ) if decalVBO == nil then goodbye("Failed to create decalVBO") end - if decalUseGeometryShader then - local smallDecalVAO = gl.GetVAO() - smallDecalVAO:AttachVertexBuffer(decalVBO.instanceVBO) - decalVBO.VAO = smallDecalVAO - else - local planeVBOsmall, numVerticesSmall = InstanceVBOTable.makePlaneVBO(1,1,4,4) - local planeIndexVBOsmall, numIndicesSmall = InstanceVBOTable.makePlaneIndexVBO(4,4) - decalVBO.vertexVBO = planeVBOsmall - decalVBO.indexVBO = planeIndexVBOsmall - decalVBO.VAO = InstanceVBOTable.makeVAOandAttach( - decalVBO.vertexVBO, - decalVBO.instanceVBO, - decalVBO.indexVBO - ) - end + local planeVBO, numVertices = InstanceVBOTable.makePlaneVBO(1,1,4,4) + local planeIndexVBO, numIndices = InstanceVBOTable.makePlaneIndexVBO(4,4) + decalVBO.vertexVBO = planeVBO + decalVBO.indexVBO = planeIndexVBO + decalVBO.VAO = InstanceVBOTable.makeVAOandAttach( + decalVBO.vertexVBO, + decalVBO.instanceVBO, + decalVBO.indexVBO + ) - local planeVBO, numVertices = InstanceVBOTable.makePlaneVBO(1,1,resolution,resolution) - local planeIndexVBO, numIndices = InstanceVBOTable.makePlaneIndexVBO(resolution,resolution) --, true) -- add true to cull into a circle + planeVBO, numVertices = InstanceVBOTable.makePlaneVBO(1,1,resolution,resolution) + planeIndexVBO, numIndices = InstanceVBOTable.makePlaneIndexVBO(resolution,resolution) --, true) -- add true to cull into a circle decalLargeVBO = InstanceVBOTable.makeInstanceVBOTable( { @@ -421,9 +396,7 @@ local updatePositionX = 0 local updatePositionZ = 0 function widget:Update() -- this is pointlessly expensive! if autoupdate then - if decalUseGeometryShader then - decalShader = LuaShader.CheckShaderUpdates(shaderSourceCache) or decalShader - end + decalShader = LuaShader.CheckShaderUpdates(shaderSourceCache) or decalShader decalLargeShader = LuaShader.CheckShaderUpdates(shaderLargeSourceCache) or decalLargeShader end @@ -477,6 +450,60 @@ end local dCT = {} -- decalCacheTable +local function IsFiniteNumber(value) + return type(value) == "number" and value == value and value ~= math.huge and value ~= -math.huge +end + +local loggedInvalidDecals = {} +local function LogInvalidDecal(reason, decalName, ...) + local key = tostring(reason) .. ":" .. tostring(decalName) + if not loggedInvalidDecals[key] then + loggedInvalidDecals[key] = true + spEcho("[DecalsGL4] skipped invalid decal", reason, decalName or "unknown", ...) + end +end + +local loggedRecoveredDecals = {} +local function LogRecoveredDecal(reason, decalName, ...) + local key = tostring(reason) .. ":" .. tostring(decalName) + if not loggedRecoveredDecals[key] then + loggedRecoveredDecals[key] = true + spEcho("[DecalsGL4] recovered invalid decal input", reason, decalName or "unknown", ...) + end +end + +local function NormalizeExplosionPosition(px, py, pz, ownerID, weaponName) + if IsFiniteNumber(px) and IsFiniteNumber(py) and IsFiniteNumber(pz) then + return px, py, pz, true + end + + if ownerID and spValidUnitID(ownerID) then + local ux, uy, uz = spGetUnitPosition(ownerID) + if IsFiniteNumber(ux) and IsFiniteNumber(uz) then + px = IsFiniteNumber(px) and px or ux + pz = IsFiniteNumber(pz) and pz or uz + py = IsFiniteNumber(py) and py or uy + if IsFiniteNumber(px) and IsFiniteNumber(pz) and not IsFiniteNumber(py) then + py = spGetGroundHeight(px, pz) + end + if IsFiniteNumber(px) and IsFiniteNumber(py) and IsFiniteNumber(pz) then + LogRecoveredDecal("VisibleExplosion position", weaponName, ownerID) + return px, py, pz, true + end + end + end + + if IsFiniteNumber(px) and IsFiniteNumber(pz) then + local elevation = spGetGroundHeight(px, pz) + if IsFiniteNumber(elevation) then + LogRecoveredDecal("VisibleExplosion height", weaponName, px, pz, py, elevation) + return px, IsFiniteNumber(py) and py or elevation, pz, true + end + end + + return px, py, pz, false +end + local function AddDecal(decaltexturename, posx, posz, rotation, width, length, @@ -503,11 +530,23 @@ local function AddDecal(decaltexturename, posx, posz, rotation, alphastart = alphastart or 1 alphadecay = (alphadecay or 0) / (lifeTimeMult*lifeTimeMultMult) + maxalpha = maxalpha or 1 bwfactor = bwfactor or 1 -- default force to black and white glowsustain = glowsustain or 1 -- how many frames to keep max heat for glowadd = glowadd or 0 -- how much additional additive glow to add fadeintime = fadeintime or shaderConfig.FADEINTIME + if not IsFiniteNumber(posx) or not IsFiniteNumber(posz) or not IsFiniteNumber(rotation) + or not IsFiniteNumber(width) or not IsFiniteNumber(length) + or not IsFiniteNumber(heatstart) or not IsFiniteNumber(heatdecay) + or not IsFiniteNumber(alphastart) or not IsFiniteNumber(alphadecay) + or not IsFiniteNumber(maxalpha) or not IsFiniteNumber(bwfactor) + or not IsFiniteNumber(glowsustain) or not IsFiniteNumber(glowadd) + or not IsFiniteNumber(fadeintime) then + LogInvalidDecal("AddDecal input", decaltexturename, posx, posz, rotation, width, length, alphastart, alphadecay) + return nil + end + if CheckDecalAreaSaturation(posx, posz, width, length) then if autoupdate then spEcho("Map area is oversaturated with decals!", posx, posz, width, length) @@ -518,6 +557,10 @@ local function AddDecal(decaltexturename, posx, posz, rotation, end spawnframe = spawnframe or spGetGameFrame() + if not IsFiniteNumber(spawnframe) then + LogInvalidDecal("spawnframe", decaltexturename, spawnframe) + return nil + end --spEcho(decaltexturename, atlassedImages[decaltexturename], atlasColorAlpha) local p,q,s,t = 0,1,0,1 @@ -535,6 +578,10 @@ local function AddDecal(decaltexturename, posx, posz, rotation, -- float currentAlpha = min(1.0, (lifetonow / FADEINTIME)) * alphastart - lifetonow* alphadecay; -- currentAlpha = min(currentAlpha, lengthwidthrotation.w); local lifetime = mathFloor(alphastart/alphadecay) + if not IsFiniteNumber(lifetime) then + LogInvalidDecal("lifetime", decaltexturename, alphastart, alphadecay) + return nil + end decalIndex = decalIndex + 1 local targetVBO = decalVBO @@ -608,16 +655,10 @@ local function DrawDecals() if decalVBO.usedElements > 0 then - if decalUseGeometryShader then - decalShader:Activate() - decalShader:SetUniform("fadeDistance",disticon * 1000) - decalVBO.VAO:DrawArrays(GL.POINTS, decalVBO.usedElements) - decalShader:Deactivate() - else - decalLargeShader:Activate() - decalVBO.VAO:DrawElements(GL.TRIANGLES, nil, 0, decalVBO.usedElements, 0) - decalLargeShader:Deactivate() - end + decalShader:Activate() + decalShader:SetUniform("fadeDistance",disticon * 1000) + decalVBO.VAO:DrawElements(GL.TRIANGLES, nil, 0, decalVBO.usedElements, 0) + decalShader:Deactivate() end if decalLargeVBO.usedElements > 0 or decalExtraLargeVBO.usedElements > 0 then @@ -759,14 +800,25 @@ end local globalDamageMult = Spring.GetModOptions().multiplier_weapondamage or 1 local damageCoefficient = (1 / globalDamageMult + 0.25 * globalDamageMult - 0.25) -- for sane values with high modifiers +local MIN_EXPLOSION_DECAL_RADIUS = 12 local weaponConfig = {} for weaponDefID=1, #WeaponDefs do local weaponDef = WeaponDefs[weaponDefID] - local nodecal = (weaponDef.customParams and weaponDef.customParams.nodecal) - if (not nodecal) and (not string.find(weaponDef.cegTag, 'aa')) then + local customParams = weaponDef.customParams or {} + local nodecal = customParams.nodecal + local cegTag = weaponDef.cegTag or "" + if (not nodecal) and (not string.find(cegTag, 'aa')) then + local damageAreaOfEffect = weaponDef.damageAreaOfEffect or weaponDef.areaOfEffect or 0 + if not IsFiniteNumber(damageAreaOfEffect) then + damageAreaOfEffect = 0 + end + local defaultDamage = 0 + if weaponDef.damages and IsFiniteNumber(weaponDef.damages[Game.armorTypes.default]) then + defaultDamage = weaponDef.damages[Game.armorTypes.default] + end --[[ 1 ]] local textures = { "t_groundcrack_17_a.tga", "t_groundcrack_21_a.tga", "t_groundcrack_10_a.tga" } - --[[ 2 ]] local radius = weaponDef.damageAreaOfEffect * 1.4 + --[[ 2 ]] local radius = damageAreaOfEffect * 1.4 --[[ 3 ]] local radiusVariation = 0.3 -- 0.3 -> 30% larger or smaller radius --[[ 4 ]] local heatstart = nil --[[ 5 ]] local heatdecay = nil @@ -775,8 +827,8 @@ for weaponDefID=1, #WeaponDefs do --[[ 8 ]] local bwfactor = 0.5 -- the mix factor of the diffuse texture to black and whiteness, 0 is original color, 1 is black and white --[[ 9 ]] local glowsustain = nil --[[ 10 ]] local glowadd = nil - --[[ 11 ]] local radiusToHeatDecay = weaponDef.damageAreaOfEffect / 2250 -- scaling value as a fallback for heatdecay - --[[ 12 ]] local damage = weaponDef.damages[Game.armorTypes.default] * damageCoefficient + --[[ 11 ]] local radiusToHeatDecay = damageAreaOfEffect / 2250 -- scaling value as a fallback for heatdecay + --[[ 12 ]] local damage = defaultDamage * damageCoefficient --[[ 13 ]] local fadeintime = nil --[[ 14 ]] local positionVariation = 0 --[[ 15 ]] local waterDepthRatio = isWaterVoid and 1 or 2.5 -- increased extinction in water height (vs air height) @@ -930,7 +982,7 @@ for weaponDefID=1, #WeaponDefs do --glowadd = 2.5 bwfactor = 0.05 - elseif weaponDef.customParams.area_onhit_resistance == "_RAPTORACID_" then + elseif customParams.area_onhit_resistance == "_RAPTORACID_" then textures = { "t_groundcrack_26_a.tga" } alpha = 6 alphadecay = 0.012 @@ -949,7 +1001,7 @@ for weaponDefID=1, #WeaponDefs do heatdecay = 10 end - elseif weaponDef.customParams.area_onhit_resistance == "fire" then + elseif customParams.area_onhit_resistance == "fire" then textures = { "t_groundcrack_16_a.tga" } radius = radius * 1.6 heatstart = 4000 @@ -960,7 +1012,7 @@ for weaponDefID=1, #WeaponDefs do glowadd = 4.5 waterDepthRatio = 5 - elseif weaponDef.customParams.area_onhit_ceg then + elseif customParams.area_onhit_ceg then waterDepthRatio = 5 elseif string.find(weaponDef.name, 'vipersabot') then -- viper has very tiny AoE @@ -1102,6 +1154,28 @@ for weaponDefID=1, #WeaponDefs do positionVariation = buildingExplosionPositionVariation[weaponDef.name] end + if not IsFiniteNumber(radius) or radius <= 0 then + radius = MIN_EXPLOSION_DECAL_RADIUS + end + if not IsFiniteNumber(radiusVariation) then + radiusVariation = 0 + end + if not IsFiniteNumber(radiusToHeatDecay) then + radiusToHeatDecay = 0 + end + if not IsFiniteNumber(damage) then + damage = 0 + end + if not IsFiniteNumber(positionVariation) then + positionVariation = 0 + end + if not IsFiniteNumber(waterDepthRatio) then + waterDepthRatio = isWaterVoid and 1 or 2.5 + end + if not IsFiniteNumber(alphadecay) or alphadecay <= 0 then + alphadecay = nil + end + weaponConfig[weaponDefID] = { --[[ 1 ]] textures, --[[ 2 ]] radius, @@ -1118,6 +1192,7 @@ for weaponDefID=1, #WeaponDefs do --[[ 13 ]] fadeintime, --[[ 14 ]] positionVariation, --[[ 15 ]] waterDepthRatio, + --[[ 16 ]] weaponDef.name, } end end @@ -1127,12 +1202,32 @@ function widget:VisibleExplosion(px, py, pz, weaponID, ownerID) if not params then return end + local weaponName = params[16] or weaponID + px, py, pz = NormalizeExplosionPosition(px, py, pz, ownerID, weaponName) + if not IsFiniteNumber(px) or not IsFiniteNumber(py) or not IsFiniteNumber(pz) then + LogInvalidDecal("VisibleExplosion position", weaponName, px, py, pz) + return + end + px = mathMin(mathMax(px, 0), Game.mapSizeX) + pz = mathMin(mathMax(pz, 0), Game.mapSizeZ) local random = mathRandom local radius = params[2] * (1 + (random()-0.5) * params[3]) + if not IsFiniteNumber(radius) or radius <= 0 then + LogRecoveredDecal("VisibleExplosion radius", weaponName, radius, params[2], params[3]) + radius = MIN_EXPLOSION_DECAL_RADIUS + end local elevation = spGetGroundHeight(px, pz) + if not IsFiniteNumber(elevation) then + LogRecoveredDecal("VisibleExplosion ground height", weaponName, px, pz, elevation) + elevation = py + end local exploHeight = py - (elevation >= 0 and elevation or elevation * params[15]) + if not IsFiniteNumber(exploHeight) then + LogRecoveredDecal("VisibleExplosion height", weaponName, py, elevation, params[15]) + exploHeight = 0 + end if exploHeight >= radius then return end @@ -1141,6 +1236,10 @@ function widget:VisibleExplosion(px, py, pz, weaponID, ownerID) -- reduce severity when explosion is above ground local heightMult = 1 - (exploHeight / radius) + if not IsFiniteNumber(heightMult) then + LogRecoveredDecal("VisibleExplosion heightMult", weaponName, exploHeight, radius) + heightMult = 1 + end local heatstart = params[4] or ((random() * 0.2 + 0.9) * 4900) local heatdecay = params[5] or ((random() * 0.4 + 2.0) - params[11]) @@ -1150,6 +1249,14 @@ function widget:VisibleExplosion(px, py, pz, weaponID, ownerID) local alpha = params[6] or ((random() * 1.0 + 1.5) * heightMult * heightMult) local alphadecay = params[7] or ((random() * 0.3 + 0.2) / (4 * radius)) + if not IsFiniteNumber(heatstart) or not IsFiniteNumber(heatdecay) + or not IsFiniteNumber(alpha) or not IsFiniteNumber(alphadecay) or alphadecay <= 0 then + LogRecoveredDecal("VisibleExplosion fade", weaponName, heatstart, heatdecay, alpha, alphadecay) + heatstart = IsFiniteNumber(heatstart) and heatstart or 4900 + heatdecay = IsFiniteNumber(heatdecay) and heatdecay or 1 + alpha = IsFiniteNumber(alpha) and alpha or 1 + alphadecay = (IsFiniteNumber(alphadecay) and alphadecay > 0) and alphadecay or 0.001 + end local bwfactor = params[8] or 0.5 --the mix factor of the diffuse texture to black and whiteness, 0 is original cololr, 1 is black and white local glowsustain = params[9] or (random() * 20) -- how many frames to elapse before glow starts to recede @@ -1182,9 +1289,9 @@ function widget:VisibleExplosion(px, py, pz, weaponID, ownerID) end local UnitScriptDecalsNames = { - ['corkorg'] = { - [1] = { - texture = footprintsPath..'f_corkorg_a.png', + ['corkorg'] = { + [1] = { + texture = footprintsPath..'f_corkorg_a.png', offsetx = 2, --offset from what the UnitScriptDecal returns offsetz = -25, -- offsetrot = 0, -- in radians @@ -1199,17 +1306,17 @@ local UnitScriptDecalsNames = { glowsustain = 0.0, glowadd = 0.0, fadeintime = 5, - } - }, + } + }, - ['armfboy'] = { - [1] = { -- LFOOT - texture = footprintsPath..'f_armfboy_a.png', - offsetx = -1, --offset from what the UnitScriptDecal returns - offsetz = 0, -- - offsetrot = 0, -- in radians - width = 60, - height = 30, + ['armfboy'] = { + [1] = { -- LFOOT + texture = footprintsPath..'f_armfboy_a.png', + offsetx = -1, --offset from what the UnitScriptDecal returns + offsetz = 0, -- + offsetrot = 0, -- in radians + width = 60, + height = 30, heatstart = 0, heatdecay = 0, alphastart = 0.7, @@ -1490,11 +1597,11 @@ local UnitScriptDecalsNames = { glowadd = 0.0, fadeintime = 5, } - }, + }, - ['corck'] = { - [1] = { -- LFOOT - texture = footprintsPath..'f_corck_a.png', + ['corck'] = { + [1] = { -- LFOOT + texture = footprintsPath..'f_corck_a.png', offsetx = 0, --offset from what the UnitScriptDecal returns offsetz = 0, -- offsetrot = 0.0, -- in radians @@ -1893,9 +2000,13 @@ local function UnitScriptDecal(unitID, unitDefID, whichDecal, posx, posz, headin local lifetime = mathFloor(decalTable.alphastart/decalCache[10]) decalIndex = decalIndex + 1 + local targetVBO = decalVBO + if mathMax(decalTable.width, decalTable.height) >= shaderConfig.SINGLEQUADDECALSIZETHRESHOLD then + targetVBO = decalLargeVBO + end --spEcho(decalIndex) pushElementInstance( - decalVBO, -- push into this Instance VBO Table + targetVBO, -- push into this Instance VBO Table decalCache, -- params decalIndex, -- this is the key inside the VBO Table, should be unique per unit true, -- update existing element diff --git a/luaui/Widgets/gfx_orb_effects_gl4.lua b/luaui/Widgets/gfx_orb_effects_gl4.lua index a0c9403f57c..75a3f10949a 100644 --- a/luaui/Widgets/gfx_orb_effects_gl4.lua +++ b/luaui/Widgets/gfx_orb_effects_gl4.lua @@ -635,7 +635,7 @@ out vec4 fragColor; #define SNORM2NORM(value) (value * 0.5 + 0.5) #define NORM2SNORM(value) (value * 2.0 - 1.0) - float orbTime; // per-unit offset time, set in main() before any function calls + float orbTime = 0.0; // main() overwrites this with a per-unit offset time #define time orbTime vec3 LightningOrb(vec2 vUv, vec3 color) { diff --git a/luaui/Widgets/gfx_unit_stencil_gl4.lua b/luaui/Widgets/gfx_unit_stencil_gl4.lua index a00c1d2f2e4..3e94f214cc2 100644 --- a/luaui/Widgets/gfx_unit_stencil_gl4.lua +++ b/luaui/Widgets/gfx_unit_stencil_gl4.lua @@ -4,7 +4,7 @@ function widget:GetInfo() return { name = "Unit Stencil GL4", desc = "A fun approach to minimizing the cost of some fun shaders", - author = "Beherith", + author = "Beherith; macOS NoGS adaptation", date = "2022.03.05", license = "GNU GPL, v2 or later", layer = 50, @@ -13,41 +13,30 @@ function widget:GetInfo() } end - --- Localized Spring API for performance +-- Official BAR contract: +-- Lua tracks visible units/features and uploads bbox + id instance data. +-- The GPU expands each instance into a low-res stencil proxy texture. local spEcho = Spring.Echo --- Key Idea: make a 1/2 or 1/4 sized texture 'stencil buffer' that can be used for units and features. --- Draw features first at 0.5, then units at 1.0, clear if no draw happened --- Make this shared the same way screencopy texture is shared, via an api --- bind and sample this texture if needed for any other method :) - local unitStencilVBO = nil -local featureStencilVBO = nil -- TODO +local featureStencilVBO = nil local unitStencilShader = nil +local stencilProxyVBO = nil +local stencilProxyVertexCount = 18 local unitFeatureStencilTex = nil -local unitDimensionsXYZ = {} -- table of unitDefID to max x,y,z dims -local featureDimensionsXYZ = {} -- table of unitDefID to max x,y,z dims ------------------------------------------------------------------ --- Configuration Constants ------------------------------------------------------------------ +local unitDimensionsXYZ = {} +local featureDimensionsXYZ = {} + local addRadius = 10 ------------------------------------------------------------------ --- GL4 Backend Stuff local LuaShader = gl.LuaShader local InstanceVBOTable = gl.InstanceVBOTable local popElementInstance = InstanceVBOTable.popElementInstance local pushElementInstance = InstanceVBOTable.pushElementInstance --- Use the geometry shader pipeline when supported, otherwise expand each unit --- bounding box into its 3 visible faces inside the vertex shader, drawing an --- instanced template mesh. Set to false to force-test the fallback. -local useGeometryShader = LuaShader.isGeometryShaderSupported - -local vsSrc = [[ +local vsSrc = [[ #version 420 #extension GL_ARB_uniform_buffer_object : require #extension GL_ARB_shader_storage_buffer_object : require @@ -58,256 +47,208 @@ local vsSrc = [[ layout (location = 0) in vec4 unitModelMinXYZ; layout (location = 1) in vec4 unitModelMaxXYZ; layout (location = 2) in uvec4 instData; +layout (location = 3) in vec4 proxyVertex; //__ENGINEUNIFORMBUFFERDEFS__ //__DEFINES__ struct SUniformsBuffer { - uint composite; // u8 drawFlag; u8 unused1; u16 id; - - uint unused2; - uint unused3; - uint unused4; - - float maxHealth; - float health; - float unused5; - float unused6; - - vec4 drawPos; - vec4 speed; - vec4[4] userDefined; //can't use float[16] because float in arrays occupies 4 * float space + uint composite; // u8 drawFlag; u8 unused1; u16 id; + + uint unused2; + uint unused3; + uint unused4; + + float maxHealth; + float health; + float unused5; + float unused6; + + vec4 drawPos; + vec4 speed; + vec4[4] userDefined; }; layout(std140, binding=1) readonly buffer UniformsBuffer { - SUniformsBuffer uni[]; -}; + SUniformsBuffer uni[]; +}; #line 10000 uniform float addRadius = 10; - -out DataVS { - vec4 v_unitModelMinXYZ; - vec4 v_unitModelMaxXYZ; - vec4 v_centerPos; -}; - -void main() +uniform int debugDisableProxyCull = 0; +uniform int debugTopFaceOnly = 0; +uniform int debugFixedProxy = 0; +uniform int debugPointSprite = 0; +uniform float debugPointSize = 5.0; +uniform float pointSizeScale = 1.0; +uniform vec2 stencilTexSize = vec2(512.0, 512.0); + +float SelectMinMax(float minValue, float maxValue, float selector) { - gl_Position = cameraViewProj * vec4(uni[instData.y].drawPos.xyz, 1.0); // We transform this vertex into the center of the model - v_unitModelMinXYZ = unitModelMinXYZ; - v_unitModelMaxXYZ = unitModelMaxXYZ; - v_unitModelMaxXYZ.w = 1.0; - v_centerPos = vec4(uni[instData.y].drawPos); - // TODO: calculate radius in screen-space pixels - - // Make no primitives on stuff outside of screen - if (isSphereVisibleXY(vec4(uni[instData.y].drawPos.xyz, 1.0), addRadius + unitModelMaxXYZ.x + unitModelMaxXYZ.z)) - v_unitModelMaxXYZ.w = 0.0; - - // this checks the drawFlag of wether the unit is actually being drawn - // (this is ==1 when then unit is both visible and drawn as a full model (not icon)) - if ((uni[instData.y].composite & 0x00000003u) < 1u ) - v_unitModelMaxXYZ.w = 0.0; + return (selector < 0.5) ? minValue : maxValue; } -]] -local gsSrc = [[ -#version 330 -#extension GL_ARB_uniform_buffer_object : require -#extension GL_ARB_shading_language_420pack: require -//__ENGINEUNIFORMBUFFERDEFS__ -//__DEFINES__ -layout(points) in; -layout(triangle_strip, max_vertices = 12) out; -#line 20000 +void main() +{ + vec4 centerpos = vec4(uni[instData.y].drawPos); + vec4 Mins = unitModelMinXYZ; + vec4 Maxs = unitModelMaxXYZ; -uniform float addRadius = 10; + bool drawProxy = true; -in DataVS { - vec4 v_unitModelMinXYZ; - vec4 v_unitModelMaxXYZ; - vec4 v_centerPos; -} dataIn[]; + if (debugDisableProxyCull == 0) { + if (isSphereVisibleXY(vec4(centerpos.xyz, 1.0), addRadius + Maxs.x + Maxs.z)) { + drawProxy = false; + } -mat3 rotY; -vec4 centerpos; + if ((uni[instData.y].composite & 0x00000003u) < 1u) { + drawProxy = false; + } + } -void offsetVertex4( float x, float y, float z){ - vec3 primitiveCoords = vec3(x,y,z); - primitiveCoords*= 1; - vec3 vecnorm = sign(primitiveCoords); - gl_Position = cameraViewProj * vec4(centerpos.xyz + rotY * ( vec3(addRadius ,0,addRadius)* vecnorm + primitiveCoords ), 1.0); - EmitVertex(); -} + if (!drawProxy) { + gl_Position = vec4(0.0, 0.0, 2.0, 1.0); + return; + } -#line 22000 -void main(){ - vec4 Mins = dataIn[0].v_unitModelMinXYZ; - vec4 Maxs = dataIn[0].v_unitModelMaxXYZ; - if (Maxs.w < 0.5) return; - centerpos = dataIn[0].v_centerPos; + if (debugPointSprite == 1) { + vec4 centerClip = cameraViewProj * vec4(centerpos.xyz, 1.0); + vec2 maxXZ = max(abs(Mins.xz), abs(Maxs.xz)); + float radius = length(maxXZ) + addRadius; + vec3 cameraRight = cameraViewInv[0].xyz; + vec4 radiusClip = cameraViewProj * vec4(centerpos.xyz + cameraRight * radius, 1.0); + vec2 centerNDC = centerClip.xy / max(abs(centerClip.w), 0.0001); + vec2 radiusNDC = radiusClip.xy / max(abs(radiusClip.w), 0.0001); + float pointDiameter = 2.0 * length((radiusNDC - centerNDC) * stencilTexSize * 0.5); + + gl_Position = centerClip; + gl_PointSize = clamp(max(debugPointSize, pointDiameter * pointSizeScale), 2.0, 96.0); + return; + } - vec3 camPos = cameraViewInv[3].xyz ; - vec3 camDir = normalize(camPos-centerpos.xyz); + vec3 camPos = cameraViewInv[3].xyz; + vec3 camDir = normalize(camPos - centerpos.xyz); - float s = sin(centerpos.w); + float s = sin(centerpos.w); float c = cos(centerpos.w); - rotY = mat3( - c, 0.0, -s, - 0.0, 1.0, 0.0, - s, 0.0, c); - - - // Draw Top Face - offsetVertex4( Mins.x, Maxs.y, Mins.z); - offsetVertex4( Maxs.x, Maxs.y, Mins.z); - offsetVertex4( Mins.x, Maxs.y, Maxs.z); - offsetVertex4( Maxs.x, Maxs.y, Maxs.z); - EndPrimitive(); - - float leftright = (dot(vec3(c, 0, -s), camDir) < 0) ? Mins.x : Maxs.x; - offsetVertex4( leftright, Maxs.y, Mins.z); - offsetVertex4( leftright, Maxs.y, Maxs.z); - offsetVertex4( leftright, Mins.y, Mins.z); - offsetVertex4( leftright, Mins.y, Maxs.z); - EndPrimitive(); - - - float frontback = (dot(vec3(s, 0, c), camDir) > 0) ? Maxs.z : Mins.z; - offsetVertex4( Mins.x, Maxs.y, frontback); - offsetVertex4( Maxs.x, Maxs.y, frontback); - offsetVertex4( Mins.x, Mins.y, frontback); - offsetVertex4( Maxs.x, Mins.y, frontback); - - EndPrimitive(); + mat3 rotY = mat3( + c, 0.0, -s, + 0.0, 1.0, 0.0, + s, 0.0, c); + + float face = proxyVertex.x; + if (debugTopFaceOnly == 1 && face > 0.5) { + gl_Position = vec4(0.0, 0.0, 2.0, 1.0); + return; + } + + if (debugFixedProxy == 1) { + float halfSize = 12.0; + float x = SelectMinMax(-halfSize, halfSize, proxyVertex.y); + float z = SelectMinMax(-halfSize, halfSize, proxyVertex.w); + vec3 expandedPos = centerpos.xyz + vec3(x, 8.0, z); + gl_Position = cameraViewProj * vec4(expandedPos, 1.0); + return; + } + + float x = SelectMinMax(Mins.x, Maxs.x, proxyVertex.y); + float y = SelectMinMax(Mins.y, Maxs.y, proxyVertex.z); + float z = SelectMinMax(Mins.z, Maxs.z, proxyVertex.w); + + if (face > 0.5 && face < 1.5) { + x = (dot(vec3(c, 0.0, -s), camDir) < 0.0) ? Mins.x : Maxs.x; + } else if (face > 1.5) { + z = (dot(vec3(s, 0.0, c), camDir) > 0.0) ? Maxs.z : Mins.z; + } + + vec3 primitiveCoords = vec3(x, y, z); + vec3 vecnorm = sign(primitiveCoords); + vec3 expandedPos = centerpos.xyz + rotY * (vec3(addRadius, 0.0, addRadius) * vecnorm + primitiveCoords); + gl_Position = cameraViewProj * vec4(expandedPos, 1.0); } ]] -local fsSrc = -[[ +local fsSrc = [[ #version 150 compatibility -uniform float stencilColor = 1.0; // 1 if we are stenciling +uniform float stencilColor = 1.0; +uniform int debugPointSprite = 0; void main(void) { - gl_FragColor = vec4(stencilColor,stencilColor,stencilColor,1.0); + if (debugPointSprite == 1) { + vec2 pointCoord = gl_PointCoord * 2.0 - 1.0; + if (dot(pointCoord, pointCoord) > 1.0) { + discard; + } + } + gl_FragColor = vec4(stencilColor, stencilColor, stencilColor, 1.0); } ]] --- Non-geometry-shader fallback vertex shader: it expands the unit's bounding box --- into the same 3 camera-facing faces that the geometry shader emitted, but per --- vertex over an instanced template mesh (see the fallback VAO setup below). -local vsSrcNoGS = [[ -#version 420 -#extension GL_ARB_uniform_buffer_object : require -#extension GL_ARB_shader_storage_buffer_object : require -#extension GL_ARB_shading_language_420pack: require - -#line 5000 - -layout (location = 0) in float vinfo; // template vertex index 0..11 -layout (location = 1) in vec4 unitModelMinXYZ; -layout (location = 2) in vec4 unitModelMaxXYZ; -layout (location = 3) in uvec4 instData; - -//__ENGINEUNIFORMBUFFERDEFS__ -//__DEFINES__ - -struct SUniformsBuffer { - uint composite; // u8 drawFlag; u8 unused1; u16 id; - - uint unused2; - uint unused3; - uint unused4; - - float maxHealth; - float health; - float unused5; - float unused6; - - vec4 drawPos; - vec4 speed; - vec4[4] userDefined; -}; - -layout(std140, binding=1) readonly buffer UniformsBuffer { - SUniformsBuffer uni[]; -}; +local function goodbye(reason) + spEcho("Unit Stencil GL4 widget exiting with reason: " .. reason) +end -#line 10000 +local resolution = 4 +local vsx, vsy +local debugStencilTexture = false +local debugDisableProxyCull = false +local debugTopFaceOnly = false +local debugFixedProxy = false +local debugPointSprite = true +local debugPointSize = 2 +local pointSizeScale = 1.15 + +local function MakeStencilProxyVBO() + local data = { + -- Top face, two triangles equivalent to the official triangle strip. + 0, 0, 1, 0, + 0, 1, 1, 0, + 0, 0, 1, 1, + 0, 0, 1, 1, + 0, 1, 1, 0, + 0, 1, 1, 1, + + -- Camera-facing left/right face. + 1, 0, 1, 0, + 1, 0, 1, 1, + 1, 0, 0, 0, + 1, 0, 0, 0, + 1, 0, 1, 1, + 1, 0, 0, 1, + + -- Camera-facing front/back face. + 2, 0, 1, 0, + 2, 1, 1, 0, + 2, 0, 0, 0, + 2, 0, 0, 0, + 2, 1, 1, 0, + 2, 1, 0, 0, + } -uniform float addRadius = 10; + local vbo = gl.GetVBO(GL.ARRAY_BUFFER, false) + if vbo == nil then + goodbye("Failed to create unit stencil proxy VBO") + return nil + end -void main() -{ - vec4 drawPos = uni[instData.y].drawPos; - vec4 Mins = unitModelMinXYZ; - vec4 Maxs = unitModelMaxXYZ; - - bool culled = false; - // Make no primitives on stuff outside of screen - if (isSphereVisibleXY(vec4(drawPos.xyz, 1.0), addRadius + unitModelMaxXYZ.x + unitModelMaxXYZ.z)) culled = true; - // drawFlag check (==1 when unit is visible and drawn as a full model, not an icon) - if ((uni[instData.y].composite & 0x00000003u) < 1u) culled = true; - - if (culled) { - gl_Position = vec4(2.0, 2.0, 2.0, 1.0); // degenerate offscreen - return; - } - - vec4 centerpos = drawPos; - vec3 camPos = cameraViewInv[3].xyz; - vec3 camDir = normalize(camPos - centerpos.xyz); - - float s = sin(centerpos.w); - float c = cos(centerpos.w); - mat3 rotY = mat3( - c, 0.0, -s, - 0.0, 1.0, 0.0, - s, 0.0, c); - - float leftright = (dot(vec3(c, 0.0, -s), camDir) < 0.0) ? Mins.x : Maxs.x; - float frontback = (dot(vec3(s, 0.0, c), camDir) > 0.0) ? Maxs.z : Mins.z; - - int vid = int(vinfo); - int face = vid / 4; - int corner = vid - face * 4; - vec3 p; - if (face == 0) { // top face - float x = ((corner & 1) == 0) ? Mins.x : Maxs.x; - float z = (corner < 2) ? Mins.z : Maxs.z; - p = vec3(x, Maxs.y, z); - } else if (face == 1) { // camera-facing left/right face - float y = (corner < 2) ? Maxs.y : Mins.y; - float z = ((corner & 1) == 0) ? Mins.z : Maxs.z; - p = vec3(leftright, y, z); - } else { // camera-facing front/back face - float x = ((corner & 1) == 0) ? Mins.x : Maxs.x; - float y = (corner < 2) ? Maxs.y : Mins.y; - p = vec3(x, y, frontback); - } - - vec3 vecnorm = sign(p); - gl_Position = cameraViewProj * vec4(centerpos.xyz + rotY * (vec3(addRadius, 0.0, addRadius) * vecnorm + p), 1.0); -} -]] + vbo:Define(stencilProxyVertexCount, { + {id = 3, name = 'proxyVertex', size = 4}, + }) + vbo:Upload(data) -local function goodbye(reason) - spEcho("Unit Stencil GL4 widget exiting with reason: "..reason) + return vbo end -local resolution = 4 -local vsx, vsy + function widget:ViewResize() - local GL_R8 = 0x8229 - vsx, vsy = Spring.GetViewGeometry() - if unitFeatureStencilTex then gl.DeleteTexture(unitFeatureStencilTex) end - unitFeatureStencilTex = gl.CreateTexture(vsx/resolution, vsy/resolution, { - --format = GL.RGBA8, - format = GL_R8, + local GL_R8 = 0x8229 + vsx, vsy = Spring.GetViewGeometry() + if unitFeatureStencilTex then gl.DeleteTexture(unitFeatureStencilTex) end + unitFeatureStencilTex = gl.CreateTexture(vsx / resolution, vsy / resolution, { + format = GL_R8, fbo = true, min_filter = GL.NEAREST, mag_filter = GL.NEAREST, @@ -316,166 +257,126 @@ function widget:ViewResize() }) end - --- Builds the static 3-quad (12 vertex) template mesh used by the fallback path -local function makeStencilTemplate() - local templateVBO = gl.GetVBO(GL.ARRAY_BUFFER, false) - templateVBO:Define(12, {{id = 0, name = 'vinfo', size = 1}}) - local vertexData = {} - for v = 0, 11 do vertexData[#vertexData + 1] = v end - templateVBO:Upload(vertexData) - - local indexData = {} - for f = 0, 2 do -- each face is a 4-vertex strip -> 2 triangles - local b = f * 4 - indexData[#indexData + 1] = b - indexData[#indexData + 1] = b + 1 - indexData[#indexData + 1] = b + 2 - indexData[#indexData + 1] = b + 2 - indexData[#indexData + 1] = b + 1 - indexData[#indexData + 1] = b + 3 +local function AttachStencilVAO(instanceTable) + instanceTable.VAO = gl.GetVAO() + if instanceTable.VAO == nil then + goodbye("Failed to create stencil VAO") + return false end - local indexVBO = gl.GetVBO(GL.ELEMENT_ARRAY_BUFFER, false) - indexVBO:Define(#indexData) - indexVBO:Upload(indexData) - return templateVBO, indexVBO, #indexData -end - --- Attaches a wrapped VAO to a stencil VBO. When the geometry shader is available --- each instance is a single point; otherwise we draw the template mesh instanced. -local function attachStencilVAO(stencilVBO, templateVBO, indexVBO, indexCount) - if useGeometryShader then - stencilVBO.VAO = gl.GetVAO() - stencilVBO.VAO:AttachVertexBuffer(stencilVBO.instanceVBO) + if debugPointSprite then + instanceTable.VAO:AttachVertexBuffer(instanceTable.instanceVBO) else - local realVAO = InstanceVBOTable.makeVAOandAttach(templateVBO, stencilVBO.instanceVBO, indexVBO) - stencilVBO.VAO = { - realVAO = realVAO, - indexCount = indexCount, - DrawArrays = function(self, _primitiveType, instanceCount) - if instanceCount and instanceCount > 0 then - self.realVAO:DrawElements(GL.TRIANGLES, self.indexCount, 0, instanceCount) - end - end, - Delete = function(self) - self.realVAO:Delete() - end, - } + instanceTable.VAO:AttachVertexBuffer(stencilProxyVBO) + instanceTable.VAO:AttachInstanceBuffer(instanceTable.instanceVBO) end + return true end -local function makeStencilVBO(name) - -- In the geometry shader path the instance attributes start at location 0; in - -- the fallback path location 0 is the template vertex, so they shift up by one. - if useGeometryShader then - return InstanceVBOTable.makeInstanceVBOTable( - { - {id = 0, name = 'unitModelMinXYZ', size = 4}, - {id = 1, name = 'unitModelMaxXYZ', size = 4}, - {id = 2, name = 'instData', size = 4, type = GL.UNSIGNED_INT}, +local function InitDrawPrimitiveAtUnit(DPATname) + local engineUniformBufferDefs = LuaShader.GetEngineUniformBufferDefs() + local patchedVsSrc = vsSrc:gsub("//__ENGINEUNIFORMBUFFERDEFS__", engineUniformBufferDefs) + local patchedFsSrc = fsSrc:gsub("//__ENGINEUNIFORMBUFFERDEFS__", engineUniformBufferDefs) + + local drawPrimitiveAtUnitShader = LuaShader( + { + vertex = patchedVsSrc, + fragment = patchedFsSrc, + uniformInt = { + debugDisableProxyCull = 0, + debugTopFaceOnly = 0, + debugFixedProxy = 0, + debugPointSprite = 0, }, - 64, name, 2) - else - return InstanceVBOTable.makeInstanceVBOTable( - { - {id = 1, name = 'unitModelMinXYZ', size = 4}, - {id = 2, name = 'unitModelMaxXYZ', size = 4}, - {id = 3, name = 'instData', size = 4, type = GL.UNSIGNED_INT}, + uniformFloat = { + addRadius = 1, + stencilColor = 1, + debugPointSize = 2, + pointSizeScale = 1, + stencilTexSize = {512, 512}, }, - 64, name, 3) + }, + DPATname .. "Shader GL4 NoGS" + ) + + local shaderCompiled = drawPrimitiveAtUnitShader:Initialize() + if not shaderCompiled then + goodbye("Failed to compile " .. DPATname .. " GL4 NoGS") + return nil end -end -local function InitDrawPrimitiveAtUnit(modifiedShaderConf, DPATname) - local engineUniformBufferDefs = LuaShader.GetEngineUniformBufferDefs() - vsSrc = vsSrc:gsub("//__ENGINEUNIFORMBUFFERDEFS__", engineUniformBufferDefs) - vsSrcNoGS = vsSrcNoGS:gsub("//__ENGINEUNIFORMBUFFERDEFS__", engineUniformBufferDefs) - fsSrc = fsSrc:gsub("//__ENGINEUNIFORMBUFFERDEFS__", engineUniformBufferDefs) - gsSrc = gsSrc:gsub("//__ENGINEUNIFORMBUFFERDEFS__", engineUniformBufferDefs) - local shaderName = DPATname .. "Shader GL4" - - local DrawPrimitiveAtUnitShader = LuaShader( - { - vertex = vsSrc, - fragment = fsSrc, - geometry = gsSrc, - uniformInt = { - --DrawPrimitiveAtUnitTexture = 0; - }, - uniformFloat = { - addRadius = 1, - stencilColor = 1, - }, - }, - shaderName - ) - - local shaderCompiled = DrawPrimitiveAtUnitShader:Initialize() - useGeometryShader = shaderCompiled - - if not shaderCompiled then - DrawPrimitiveAtUnitShader = LuaShader( - { - vertex = vsSrcNoGS, - fragment = fsSrc, - uniformInt = { - --DrawPrimitiveAtUnitTexture = 0; - }, - uniformFloat = { - addRadius = 1, - stencilColor = 1, - }, - }, - shaderName .. " (NoGS)" - ) - shaderCompiled = DrawPrimitiveAtUnitShader:Initialize() - if not shaderCompiled then - goodbye("Failed to compile ".. DPATname .." GL4 ") - return - end - end - - local templateVBO, indexVBO, indexCount - if not useGeometryShader then - templateVBO, indexVBO, indexCount = makeStencilTemplate() + stencilProxyVBO = MakeStencilProxyVBO() + if stencilProxyVBO == nil then + return nil end - unitStencilVBO = makeStencilVBO(DPATname .. "VBO") - attachStencilVAO(unitStencilVBO, templateVBO, indexVBO, indexCount) + unitStencilVBO = InstanceVBOTable.makeInstanceVBOTable( + { + {id = 0, name = 'unitModelMinXYZ', size = 4}, + {id = 1, name = 'unitModelMaxXYZ', size = 4}, + {id = 2, name = 'instData', size = 4, type = GL.UNSIGNED_INT}, + }, + 64, + DPATname .. "VBO", + 2 + ) + if unitStencilVBO == nil then + goodbye("Failed to create " .. DPATname .. "VBO") + return nil + end + if not AttachStencilVAO(unitStencilVBO) then + return nil + end - featureStencilVBO = makeStencilVBO("featurestencil VBO") - attachStencilVAO(featureStencilVBO, templateVBO, indexVBO, indexCount) - featureStencilVBO.featureIDs = true + featureStencilVBO = InstanceVBOTable.makeInstanceVBOTable( + { + {id = 0, name = 'unitModelMinXYZ', size = 4}, + {id = 1, name = 'unitModelMaxXYZ', size = 4}, + {id = 2, name = 'instData', size = 4, type = GL.UNSIGNED_INT}, + }, + 64, + "featurestencil VBO", + 2 + ) + if featureStencilVBO == nil then + goodbye("Failed to create featurestencil VBO") + return nil + end + if not AttachStencilVAO(featureStencilVBO) then + return nil + end + featureStencilVBO.featureIDs = true - return DrawPrimitiveAtUnitShader + return drawPrimitiveAtUnitShader end function widget:VisibleUnitAdded(unitID, unitDefID) - if unitDimensionsXYZ[unitDefID] == nil then - local unitDef = UnitDefs[unitDefID] - unitDimensionsXYZ[unitDefID] = { - unitDef.model.minx, math.min(0, unitDef.model.miny), unitDef.model.minz, - unitDef.model.maxx, unitDef.model.maxy, unitDef.model.maxz, - } - local dimsXYZ = unitDimensionsXYZ[unitDefID] - --spEcho(dimsXYZ[1], dimsXYZ[2], dimsXYZ[3], dimsXYZ[4], dimsXYZ[5], dimsXYZ[6]) - end - local dimsXYZ = unitDimensionsXYZ[unitDefID] - + if unitStencilVBO == nil then return end + + if unitDimensionsXYZ[unitDefID] == nil then + local unitDef = UnitDefs[unitDefID] + unitDimensionsXYZ[unitDefID] = { + unitDef.model.minx, math.min(0, unitDef.model.miny), unitDef.model.minz, + unitDef.model.maxx, unitDef.model.maxy, unitDef.model.maxz, + } + end + + local dimsXYZ = unitDimensionsXYZ[unitDefID] pushElementInstance( - unitStencilVBO, -- push into this Instance VBO Table + unitStencilVBO, { - dimsXYZ[1], dimsXYZ[2], dimsXYZ[3], 0, - dimsXYZ[4], dimsXYZ[5], dimsXYZ[6], 0, - 0, 0, 0, 0 -- these are just padding zeros, that will get filled in + dimsXYZ[1], dimsXYZ[2], dimsXYZ[3], 0, + dimsXYZ[4], dimsXYZ[5], dimsXYZ[6], 0, + 0, 0, 0, 0, }, - unitID, -- this is the key inside the VBO TAble, - true, -- update existing element - nil, -- noupload, dont use unless you know what you are doing - unitID -- last one should be UNITID? + unitID, + true, + nil, + unitID ) end + function widget:VisibleUnitsChanged(extVisibleUnits, extNumVisibleUnits) + if unitStencilVBO == nil then return end InstanceVBOTable.clearInstanceTable(unitStencilVBO) for unitID, unitDefID in pairs(extVisibleUnits) do widget:VisibleUnitAdded(unitID, unitDefID) @@ -483,122 +384,169 @@ function widget:VisibleUnitsChanged(extVisibleUnits, extNumVisibleUnits) end function widget:VisibleUnitRemoved(unitID) - if unitStencilVBO.instanceIDtoIndex[unitID] then + if unitStencilVBO and unitStencilVBO.instanceIDtoIndex[unitID] then popElementInstance(unitStencilVBO, unitID) end end function widget:FeatureCreated(featureID, allyTeam) - local featureDefID = Spring.GetFeatureDefID(featureID) - --spEcho(featureDefID, featureID) - - if featureDimensionsXYZ[featureDefID] == nil then - local featureDef = FeatureDefs[featureDefID] - if featureDef.model then - local dimsXYZ = { - featureDef.model.minx, featureDef.model.miny, featureDef.model.minz, - featureDef.model.maxx, featureDef.model.maxy, featureDef.model.maxz, - } - if (dimsXYZ[4] - dimsXYZ[1]) < 1 then return end -- goddamned geovents - featureDimensionsXYZ[featureDefID] =dimsXYZ - --spEcho(dimsXYZ[1], dimsXYZ[2], dimsXYZ[3], dimsXYZ[4], dimsXYZ[5], dimsXYZ[6]) - else - return - end - end - local dimsXYZ = featureDimensionsXYZ[featureDefID] + if featureStencilVBO == nil then return end + + local featureDefID = Spring.GetFeatureDefID(featureID) + if featureDefID == nil then return end + + if featureDimensionsXYZ[featureDefID] == nil then + local featureDef = FeatureDefs[featureDefID] + if featureDef and featureDef.model then + local dimsXYZ = { + featureDef.model.minx, featureDef.model.miny, featureDef.model.minz, + featureDef.model.maxx, featureDef.model.maxy, featureDef.model.maxz, + } + if (dimsXYZ[4] - dimsXYZ[1]) < 1 then return end + featureDimensionsXYZ[featureDefID] = dimsXYZ + else + return + end + end + + local dimsXYZ = featureDimensionsXYZ[featureDefID] if dimsXYZ == nil then return end pushElementInstance( - featureStencilVBO, -- push into this Instance VBO Table + featureStencilVBO, { - dimsXYZ[1], dimsXYZ[2], dimsXYZ[3], 0, - dimsXYZ[4], dimsXYZ[5], dimsXYZ[6], 0, - 0, 0, 0, 0 -- these are just padding zeros, that will get filled in + dimsXYZ[1], dimsXYZ[2], dimsXYZ[3], 0, + dimsXYZ[4], dimsXYZ[5], dimsXYZ[6], 0, + 0, 0, 0, 0, }, - featureID, -- this is the key inside the VBO TAble, - true, -- update existing element - nil, -- noupload, dont use unless you know what you are doing - featureID -- last one should be UNITID? + featureID, + true, + nil, + featureID ) end function widget:FeatureDestroyed(featureID) - if featureStencilVBO.instanceIDtoIndex[featureID] then + if featureStencilVBO and featureStencilVBO.instanceIDtoIndex[featureID] then popElementInstance(featureStencilVBO, featureID) end end -local function DrawMe() -- about 0.025 ms - if unitStencilVBO.usedElements > 0 or featureStencilVBO.usedElements > 0 then - gl.Clear(GL.COLOR_BUFFER_BIT,0,0,0,0) +local function DrawMe() + if unitStencilVBO == nil or featureStencilVBO == nil then return end + if unitStencilVBO.usedElements > 0 or featureStencilVBO.usedElements > 0 then + gl.Clear(GL.COLOR_BUFFER_BIT, 0, 0, 0, 0) gl.Blending(GL.ONE, GL.ZERO) - gl.Culling(false) + gl.Culling(false) unitStencilShader:Activate() unitStencilShader:SetUniform("addRadius", addRadius) - if featureStencilVBO.usedElements > 0 then - unitStencilShader:SetUniform("stencilColor", 0.5) - featureStencilVBO.VAO:DrawArrays(GL.POINTS, featureStencilVBO.usedElements) - end - if unitStencilVBO.usedElements > 0 then - unitStencilShader:SetUniform("stencilColor", 1.0) - unitStencilVBO.VAO:DrawArrays(GL.POINTS, unitStencilVBO.usedElements) - end + unitStencilShader:SetUniformInt("debugDisableProxyCull", debugDisableProxyCull and 1 or 0) + unitStencilShader:SetUniformInt("debugTopFaceOnly", debugTopFaceOnly and 1 or 0) + unitStencilShader:SetUniformInt("debugFixedProxy", debugFixedProxy and 1 or 0) + unitStencilShader:SetUniformInt("debugPointSprite", debugPointSprite and 1 or 0) + unitStencilShader:SetUniform("debugPointSize", debugPointSize) + unitStencilShader:SetUniform("pointSizeScale", pointSizeScale) + unitStencilShader:SetUniform("stencilTexSize", vsx / resolution, vsy / resolution) + if featureStencilVBO.usedElements > 0 then + unitStencilShader:SetUniform("stencilColor", 0.5) + if debugPointSprite then + featureStencilVBO.VAO:DrawArrays(GL.POINTS, featureStencilVBO.usedElements) + else + featureStencilVBO.VAO:DrawArrays(GL.TRIANGLES, stencilProxyVertexCount, 0, featureStencilVBO.usedElements, 0) + end + end + if unitStencilVBO.usedElements > 0 then + unitStencilShader:SetUniform("stencilColor", 1.0) + if debugPointSprite then + unitStencilVBO.VAO:DrawArrays(GL.POINTS, unitStencilVBO.usedElements) + else + unitStencilVBO.VAO:DrawArrays(GL.TRIANGLES, stencilProxyVertexCount, 0, unitStencilVBO.usedElements, 0) + end + end unitStencilShader:Deactivate() gl.Blending(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA) end end function widget:DrawWorldPreUnit() - --DrawMe() + -- DrawMe() end local stencilRequested = false function widget:DrawWorld() - if stencilRequested then - gl.RenderToTexture(unitFeatureStencilTex, DrawMe) - stencilRequested = false - end + if stencilRequested then + gl.RenderToTexture(unitFeatureStencilTex, DrawMe) + stencilRequested = false + end end --- This shows the debug stencil texture ---[[ function widget:DrawScreen() - gl.Color(1,1,1,1) - gl.Blending(GL.ONE, GL.ZERO) - gl.Texture(unitFeatureStencilTex) - gl.TexRect(0, 0, vsx/resolution, vsy/resolution, 0, 0, 1, 1) + if not debugStencilTexture or not unitFeatureStencilTex then return end + + stencilRequested = true + local width = 512 + local height = math.max(1, width * (vsy / vsx)) + local x0 = 16 + local y0 = 16 + local unitCount = (unitStencilVBO and unitStencilVBO.usedElements) or 0 + local featureCount = (featureStencilVBO and featureStencilVBO.usedElements) or 0 + + gl.Color(1, 1, 1, 1) + gl.Blending(GL.ONE, GL.ZERO) + gl.Texture(unitFeatureStencilTex) + gl.TexRect(x0, y0, x0 + width, y0 + height, 0, 0, 1, 1) + gl.Texture(false) + gl.Blending(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA) + + if gl.Text then + gl.Color(1, 1, 1, 1) + gl.Text("UnitStencil u=" .. unitCount .. " f=" .. featureCount, x0, y0 + height + 10, 14, "o") + end end -]]-- local function GetUnitStencilTexture() - stencilRequested = true - return unitFeatureStencilTex + stencilRequested = true + return unitFeatureStencilTex end function widget:Initialize() - unitStencilShader = InitDrawPrimitiveAtUnit(shaderConfig, "unitStencils") - widget:ViewResize() + unitStencilShader = InitDrawPrimitiveAtUnit("unitStencils") + if unitStencilShader == nil then + widgetHandler:RemoveWidget() + return + end - WG['unitstencilapi'] = {} - WG['unitstencilapi'].GetUnitStencilTexture = GetUnitStencilTexture - WG['unitstencilapi'].members = {ok = "yes", vsSrc = vsSrc, gsSrc = gsSrc, fsSrc = fsSrc, unitStencilVBO = unitStencilVBO, featureStencilVBO = featureStencilVBO} + widget:ViewResize() + + WG['unitstencilapi'] = {} + WG['unitstencilapi'].GetUnitStencilTexture = GetUnitStencilTexture + WG['unitstencilapi'].members = { + ok = "yes", + mode = "nogs-gpu-point-sprite", + vsSrc = vsSrc, + fsSrc = fsSrc, + unitStencilVBO = unitStencilVBO, + featureStencilVBO = featureStencilVBO, + stencilProxyVBO = stencilProxyVBO, + } widgetHandler:RegisterGlobal('GetUnitStencilTexture', WG['unitstencilapi'].GetUnitStencilTexture) if WG['unittrackerapi'] and WG['unittrackerapi'].visibleUnits then - local visibleUnits = WG['unittrackerapi'].visibleUnits + local visibleUnits = WG['unittrackerapi'].visibleUnits for unitID, unitDefID in pairs(visibleUnits) do widget:VisibleUnitAdded(unitID, unitDefID) end - for _, featureID in ipairs(Spring.GetAllFeatures()) do - widget:FeatureCreated(featureID) - end + for _, featureID in ipairs(Spring.GetAllFeatures()) do + widget:FeatureCreated(featureID) + end end end function widget:Shutdown() - gl.DeleteTexture(unitFeatureStencilTex) - unitFeatureStencilTex = nil + if unitFeatureStencilTex then + gl.DeleteTexture(unitFeatureStencilTex) + unitFeatureStencilTex = nil + end WG['unitstencilapi'] = nil widgetHandler:DeregisterGlobal('GetUnitStencilTexture') end diff --git a/luaui/Widgets/gui_enemy_spotter.lua b/luaui/Widgets/gui_enemy_spotter.lua index 64cd1e79fac..930c5dd9315 100644 --- a/luaui/Widgets/gui_enemy_spotter.lua +++ b/luaui/Widgets/gui_enemy_spotter.lua @@ -140,7 +140,7 @@ function widget:CrashingAircraft(unitID, unitDefID, teamID) end local function init() - local DPatUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit.lua") + local DPatUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit_NoGS_Mesh.lua") local InitDrawPrimitiveAtUnit = DPatUnit.InitDrawPrimitiveAtUnit local shaderConfig = DPatUnit.shaderConfig -- MAKE SURE YOU READ THE SHADERCONFIG TABLE! shaderConfig.TRANSPARENCY = opacity diff --git a/luaui/Widgets/gui_flanking_icons.lua b/luaui/Widgets/gui_flanking_icons.lua index 474ec5c9ce6..63b835aa5d9 100644 --- a/luaui/Widgets/gui_flanking_icons.lua +++ b/luaui/Widgets/gui_flanking_icons.lua @@ -126,7 +126,7 @@ local function init() end function widget:Initialize() - local DPatUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit.lua") + local DPatUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit_NoGS_Mesh.lua") local InitDrawPrimitiveAtUnit = DPatUnit.InitDrawPrimitiveAtUnit local shaderConfig = DPatUnit.shaderConfig -- MAKE SURE YOU READ THE SHADERCONFIG TABLE in DrawPrimitiveAtUnit.lua shaderConfig.BILLBOARD = 0 diff --git a/luaui/Widgets/gui_flowui.lua b/luaui/Widgets/gui_flowui.lua index 0a803bbff90..9b245916b9e 100644 --- a/luaui/Widgets/gui_flowui.lua +++ b/luaui/Widgets/gui_flowui.lua @@ -495,7 +495,14 @@ WG.FlowUI.Draw.RectRoundProgress = function(left, bottom, right, top, cs, progre gl.Translate(xcen, ycen, 0) gl.Scale(-1, 1, 1) -- flip direction horizontally gl.Translate(-xcen, -ycen, 0) - gl.Shape(GL.TRIANGLE_FAN, list) + local triangles = {} + for i = 2, #list - 1 do + local n = #triangles + triangles[n + 1] = list[1] + triangles[n + 2] = list[i] + triangles[n + 3] = list[i + 1] + end + gl.Shape(GL.TRIANGLES, triangles) gl.Color(1, 1, 1, 1) gl.PopMatrix() end diff --git a/luaui/Widgets/gui_ground_ao_plates_features_gl4.lua b/luaui/Widgets/gui_ground_ao_plates_features_gl4.lua index d39581bfcd9..df87efd0d3d 100644 --- a/luaui/Widgets/gui_ground_ao_plates_features_gl4.lua +++ b/luaui/Widgets/gui_ground_ao_plates_features_gl4.lua @@ -233,7 +233,7 @@ function widget:Initialize() end end end - local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit.lua") + local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit_NoGS_Mesh.lua") local InitDrawPrimitiveAtUnit = DrawPrimitiveAtUnit.InitDrawPrimitiveAtUnit local shaderConfig = DrawPrimitiveAtUnit.shaderConfig -- MAKE SURE YOU READ THE SHADERCONFIG TABLE in DrawPrimitiveAtUnit.lua shaderConfig.BILLBOARD = 0 @@ -311,4 +311,3 @@ function widget:GameFrame() end end - diff --git a/luaui/Widgets/gui_ground_ao_plates_gl4.lua b/luaui/Widgets/gui_ground_ao_plates_gl4.lua index 454c65bd8d9..e5e009602ef 100644 --- a/luaui/Widgets/gui_ground_ao_plates_gl4.lua +++ b/luaui/Widgets/gui_ground_ao_plates_gl4.lua @@ -119,7 +119,7 @@ function widget:Initialize() end -- Init GL4 things - local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit.lua") + local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit_NoGS_Mesh.lua") local InitDrawPrimitiveAtUnit = DrawPrimitiveAtUnit.InitDrawPrimitiveAtUnit local shaderConfig = DrawPrimitiveAtUnit.shaderConfig -- MAKE SURE YOU READ THE SHADERCONFIG TABLE in DrawPrimitiveAtUnit.lua shaderConfig.BILLBOARD = 0 @@ -168,4 +168,3 @@ function widget:VisibleUnitRemoved(unitID) -- remove the corresponding ground pl popElementInstance(groundPlateVBO, unitID) end end - diff --git a/luaui/Widgets/gui_healthbars_gl4.lua b/luaui/Widgets/gui_healthbars_gl4.lua index 49a2c9c4022..b999e8a7236 100644 --- a/luaui/Widgets/gui_healthbars_gl4.lua +++ b/luaui/Widgets/gui_healthbars_gl4.lua @@ -406,6 +406,9 @@ local variableBarSizes = true -- Option 'healthbarsvariable' -- GL4 Backend stuff: local healthBarVBO = nil local healthBarShader = nil +local healthBarsShapeVBO = nil +local healthBarsShapeVertexCount = 78 +local healthBarsMaxElements = 8192 local LuaShader = gl.LuaShader local InstanceVBOTable = gl.InstanceVBOTable @@ -455,21 +458,13 @@ if debugmode then shaderConfig.DEBUGSHOW = 1 -- comment this to always show all bars end -local vsSrcPath = "LuaUI/Shaders/HealthbarsGL4.vert.glsl" -local gsSrcPath = "LuaUI/Shaders/HealthbarsGL4.geom.glsl" +local vsSrcPath = "LuaUI/Shaders/HealthbarsGL4_NoGS.vert.glsl" local fsSrcPath = "LuaUI/Shaders/HealthbarsGL4.frag.glsl" -local fallbackVsSrcPath = "LuaUI/Shaders/HealthbarsGL4_nogs.vert.glsl" -local fallbackFsSrcPath = "LuaUI/Shaders/HealthbarsGL4_nogs.frag.glsl" - -local useGeometryShader = LuaShader.isGeometryShaderSupported - -local unitQuadVBO local shaderSourceCache = { vssrcpath = vsSrcPath, fssrcpath = fsSrcPath, - gssrcpath = gsSrcPath, - shaderName = "Health Bars Shader GL4", + shaderName = "Health Bars Shader GL4 NoGS", uniformInt = { healthbartexture = 0; }, @@ -484,23 +479,6 @@ local shaderSourceCache = { shaderConfig = shaderConfig, } -local fallbackShaderSourceCache = { - vssrcpath = fallbackVsSrcPath, - fssrcpath = fallbackFsSrcPath, - shaderName = "Health Bars Shader GL4 (NoGS)", - uniformInt = { - healthbartexture = 0; - }, - uniformFloat = { - iconDistance = 27, - cameraDistanceMult = 1.0, - cameraDistanceMultGlyph = 4.0, - skipGlyphsNumbers = 0.0, - globalSizeMult = 1.0, - }, - shaderConfig = shaderConfig, - } - -- Walk through unitdefs for the stuff we need: for udefID, unitDef in pairs(UnitDefs) do @@ -553,72 +531,76 @@ local function goodbye(reason) widgetHandler:RemoveWidget() end +local function makeHealthBarsShapeVBO() + local data = {} + for i = 0, healthBarsShapeVertexCount - 1 do + data[#data + 1] = i + end + + local shapeVBO = gl.GetVBO(GL.ARRAY_BUFFER, false) + if shapeVBO == nil then + goodbye("Failed to create health bars NoGS shape VBO") + return nil + end + + shapeVBO:Define(healthBarsShapeVertexCount, { + {id = 5, name = "shapeIndex", size = 1}, + }) + shapeVBO:Upload(data) + + return shapeVBO +end + +local function makeHealthBarsVAOWrapper(realVAO, instanceTable) + return { + DrawArrays = function(_, _, vertexCount, _, instanceCount, instanceFirst) + local instances = instanceCount or vertexCount or instanceTable.usedElements or 0 + if instances <= 0 then + return + end + realVAO:DrawArrays(GL.TRIANGLES, healthBarsShapeVertexCount, 0, instances, instanceFirst or 0) + end, + Delete = function() + if realVAO.Delete then + realVAO:Delete() + end + end, + realVAO = realVAO, + } +end + local function initializeInstanceVBOTable(myName, usesFeatures) local newVBOTable - local layout - local unitIDAttribID - if useGeometryShader then - layout = { + newVBOTable = InstanceVBOTable.makeInstanceVBOTable( + { {id = 0, name = 'height_timers', size = 4}, {id = 1, name = 'type_index_ssboloc', size = 4, type = GL.UNSIGNED_INT}, {id = 2, name = 'startcolor', size = 4}, {id = 3, name = 'endcolor', size = 4}, {id = 4, name = 'instData', size = 4, type = GL.UNSIGNED_INT}, - } - unitIDAttribID = 4 - else - layout = { - {id = 2, name = 'height_timers', size = 4}, - {id = 3, name = 'type_index_ssboloc', size = 4, type = GL.UNSIGNED_INT}, - {id = 4, name = 'startcolor', size = 4}, - {id = 5, name = 'endcolor', size = 4}, - {id = 6, name = 'instData', size = 4, type = GL.UNSIGNED_INT}, - } - unitIDAttribID = 6 - end - newVBOTable = InstanceVBOTable.makeInstanceVBOTable( - layout, - 256, -- maxelements + }, + healthBarsMaxElements, -- maxelements myName, -- name - unitIDAttribID -- unitIDattribID (instData) + 4 -- unitIDattribID (instData) ) if newVBOTable == nil then goodbye("Failed to create " .. myName) end - if useGeometryShader then - local newVAO = gl.GetVAO() - newVAO:AttachVertexBuffer(newVBOTable.instanceVBO) - newVBOTable.VAO = newVAO - else - newVBOTable.VAO = InstanceVBOTable.makeVAOandAttach(unitQuadVBO, newVBOTable.instanceVBO) - end + local newVAO = gl.GetVAO() + newVAO:AttachVertexBuffer(healthBarsShapeVBO) + newVAO:AttachInstanceBuffer(newVBOTable.instanceVBO) + newVBOTable.VAO = makeHealthBarsVAOWrapper(newVAO, newVBOTable) if usesFeatures then newVBOTable.featureIDs = true end return newVBOTable end local function initGL4() - -- Prefer geometry shader path when it actually compiles. This avoids false - -- negatives from capability detection on some Linux/AMD driver stacks. - healthBarShader = LuaShader.CheckShaderUpdates(shaderSourceCache) - useGeometryShader = (healthBarShader ~= nil) - - if not useGeometryShader then - -- A simple quad used by the non-GS path. - unitQuadVBO = gl.GetVBO(GL.ARRAY_BUFFER, false) - unitQuadVBO:Define(4, { - {id = 0, name = 'quadPos', size = 2}, - }) - unitQuadVBO:Upload({ - 0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0, - }) - - healthBarShader = LuaShader.CheckShaderUpdates(fallbackShaderSourceCache) - end + healthBarsShapeVBO = makeHealthBarsShapeVBO() + if healthBarsShapeVBO == nil then return end + + healthBarShader = LuaShader.CheckShaderUpdates(shaderSourceCache) if not healthBarShader then goodbye("Failed to compile health bars GL4 ") end @@ -1330,37 +1312,21 @@ function widget:DrawScreenEffects() healthBarShader:SetUniform("cameraDistanceMultGlyph", glphydistmult) healthBarShader:SetUniform("skipGlyphsNumbers",skipGlyphsNumbers) --0.0 is everything, 1.0 means only numbers, 2.0 means only bars, if healthBarVBO.usedElements > 0 then - if useGeometryShader then - healthBarVBO.VAO:DrawArrays(GL.POINTS,healthBarVBO.usedElements) - else - healthBarVBO.VAO:DrawArrays(GL.TRIANGLE_STRIP, 4, 0, healthBarVBO.usedElements) - end + healthBarVBO.VAO:DrawArrays(GL.POINTS,healthBarVBO.usedElements) end -- below its the feature bars being drawn: healthBarShader:SetUniform("cameraDistanceMultGlyph", glyphdistmultfeatures) if featureHealthVBO.usedElements > 0 then if not debugmode then healthBarShader:SetUniform("cameraDistanceMult",featureHealthDistMult) end - if useGeometryShader then - featureHealthVBO.VAO:DrawArrays(GL.POINTS,featureHealthVBO.usedElements) - else - featureHealthVBO.VAO:DrawArrays(GL.TRIANGLE_STRIP, 4, 0, featureHealthVBO.usedElements) - end + featureHealthVBO.VAO:DrawArrays(GL.POINTS,featureHealthVBO.usedElements) end if featureResurrectVBO.usedElements > 0 then if not debugmode then healthBarShader:SetUniform("cameraDistanceMult",featureResurrectDistMult) end - if useGeometryShader then - featureResurrectVBO.VAO:DrawArrays(GL.POINTS,featureResurrectVBO.usedElements) - else - featureResurrectVBO.VAO:DrawArrays(GL.TRIANGLE_STRIP, 4, 0, featureResurrectVBO.usedElements) - end + featureResurrectVBO.VAO:DrawArrays(GL.POINTS,featureResurrectVBO.usedElements) end if featureReclaimVBO.usedElements > 0 then if not debugmode then healthBarShader:SetUniform("cameraDistanceMult",featureReclaimDistMult) end - if useGeometryShader then - featureReclaimVBO.VAO:DrawArrays(GL.POINTS,featureReclaimVBO.usedElements) - else - featureReclaimVBO.VAO:DrawArrays(GL.TRIANGLE_STRIP, 4, 0, featureReclaimVBO.usedElements) - end + featureReclaimVBO.VAO:DrawArrays(GL.POINTS,featureReclaimVBO.usedElements) end healthBarShader:Deactivate() diff --git a/luaui/Widgets/gui_pip.lua b/luaui/Widgets/gui_pip.lua index 1d4f4b104b6..2f22e67e58f 100644 --- a/luaui/Widgets/gui_pip.lua +++ b/luaui/Widgets/gui_pip.lua @@ -3633,22 +3633,14 @@ local function InitGL4Icons() -- Create raw VBO directly (no InstanceVBOTable — avoids per-frame table allocations) -- Layout: 12 floats per instance (3 vec4 attributes) local useGS = pipUseGeometryShader - local shader = nil if useGS then - shader = gl.CreateShader(gl4Icons.shaderCode) - if not shader then + local probeShader = gl.CreateShader(gl4Icons.shaderCode) + if probeShader then + gl.DeleteShader(probeShader) + else useGS = false end end - if not shader then - shader = gl.CreateShader(gl4Icons.shaderCodeNoGS) - useGS = false - end - if not shader then - Spring.Echo("[PIP] GL4 icons: Shader compilation failed: " .. tostring(gl.GetShaderLog())) - gl4Icons.atlas = nil - return - end local vboLayout if useGS then vboLayout = { @@ -3666,7 +3658,6 @@ local function InitGL4Icons() local vbo = gl.GetVBO(GL.ARRAY_BUFFER, true) if not vbo then Spring.Echo("[PIP] GL4 icons: Failed to create VBO") - gl.DeleteShader(shader) gl4Icons.atlas = nil return end @@ -3685,18 +3676,19 @@ local function InitGL4Icons() local quadVBO = gl.GetVBO(GL.ARRAY_BUFFER, false) if not quadVBO then Spring.Echo("[PIP] GL4 icons: Failed to create NoGS quad VBO") - gl.DeleteShader(shader) vbo:Delete() gl4Icons.atlas = nil gl4Icons.vbo = nil return end - quadVBO:Define(4, {{id = 0, name = 'quadPos', size = 2}}) + quadVBO:Define(6, {{id = 0, name = 'quadPos', size = 2}}) quadVBO:Upload({ -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, + -1.0, 1.0, + 1.0, -1.0, }) gl4Icons.quadVBO = quadVBO end @@ -3704,7 +3696,6 @@ local function InitGL4Icons() local vao = gl.GetVAO() if not vao then Spring.Echo("[PIP] GL4 icons: Failed to create VAO") - gl.DeleteShader(shader) vbo:Delete() if gl4Icons.quadVBO then gl4Icons.quadVBO:Delete(); gl4Icons.quadVBO = nil end gl4Icons.atlas = nil @@ -3724,7 +3715,6 @@ local function InitGL4Icons() local bldgVbo = gl.GetVBO(GL.ARRAY_BUFFER, true) if not bldgVbo then Spring.Echo("[PIP] GL4 icons: Failed to create building VBO") - gl.DeleteShader(shader) vao:Delete() vbo:Delete() gl4Icons.atlas = nil @@ -3736,7 +3726,6 @@ local function InitGL4Icons() local bldgVao = gl.GetVAO() if not bldgVao then Spring.Echo("[PIP] GL4 icons: Failed to create building VAO") - gl.DeleteShader(shader) bldgVbo:Delete() vao:Delete() vbo:Delete() @@ -3767,7 +3756,6 @@ local function InitGL4Icons() local slowVbo = gl.GetVBO(GL.ARRAY_BUFFER, true) if not slowVbo then Spring.Echo("[PIP] GL4 icons: Failed to create slow mobile VBO") - gl.DeleteShader(shader) bldgVao:Delete() bldgVbo:Delete() vao:Delete() @@ -3783,7 +3771,6 @@ local function InitGL4Icons() local slowVao = gl.GetVAO() if not slowVao then Spring.Echo("[PIP] GL4 icons: Failed to create slow mobile VAO") - gl.DeleteShader(shader) slowVbo:Delete() bldgVao:Delete() bldgVbo:Delete() @@ -3813,7 +3800,20 @@ local function InitGL4Icons() end gl4Icons.slowInstanceData = slowInstanceData - -- Shader was already compiled before layout creation to avoid false-negative probe logic. + -- Compile shader matching the selected layout/VAO path. + local shader = useGS and gl.CreateShader(gl4Icons.shaderCode) or gl.CreateShader(gl4Icons.shaderCodeNoGS) + if not shader then + Spring.Echo("[PIP] GL4 icons: Shader compilation failed: " .. tostring(gl.GetShaderLog())) + if gl4Icons.quadVBO then gl4Icons.quadVBO:Delete(); gl4Icons.quadVBO = nil end + slowVao:Delete(); slowVbo:Delete() + bldgVao:Delete(); bldgVbo:Delete() + vao:Delete() + vbo:Delete() + gl4Icons.atlas = nil + gl4Icons.vbo = nil + gl4Icons.vao = nil + return + end gl4Icons.shader = shader gl4Icons.shaderUsesGS = useGS @@ -3904,38 +3904,27 @@ end local function InitGL4Primitives() if not gl.GetVAO or not gl.GetVBO then return end local useGS = pipUseGeometryShader - local cShader, qShader if useGS then - cShader = gl.CreateShader(gl4Prim.circleShaderCode) - qShader = gl.CreateShader(gl4Prim.quadShaderCode) - if not cShader or not qShader then - if cShader then gl.DeleteShader(cShader) end - if qShader then gl.DeleteShader(qShader) end - cShader, qShader = nil, nil + local probeCircle = gl.CreateShader(gl4Prim.circleShaderCode) + local probeQuad = gl.CreateShader(gl4Prim.quadShaderCode) + if probeCircle then gl.DeleteShader(probeCircle) end + if probeQuad then gl.DeleteShader(probeQuad) end + if not probeCircle or not probeQuad then useGS = false end end - if not cShader or not qShader then - cShader = gl.CreateShader(gl4Prim.circleShaderCodeNoGS) - qShader = gl.CreateShader(gl4Prim.quadShaderCodeNoGS) - if not cShader or not qShader then - if cShader then gl.DeleteShader(cShader) end - if qShader then gl.DeleteShader(qShader) end - Spring.Echo("[PIP] GL4 primitive shader fallback failed: " .. tostring(gl.GetShaderLog())) - return - end - useGS = false - end if not useGS then local quadVBO = gl.GetVBO(GL.ARRAY_BUFFER, false) - if not quadVBO then gl.DeleteShader(qShader); gl.DeleteShader(cShader); return end - quadVBO:Define(4, {{id = 0, name = 'quadPos', size = 2}}) + if not quadVBO then return end + quadVBO:Define(6, {{id = 0, name = 'quadPos', size = 2}}) quadVBO:Upload({ -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, + -1.0, 1.0, + 1.0, -1.0, }) gl4Prim.quadVBO = quadVBO end @@ -3956,10 +3945,10 @@ local function InitGL4Primitives() } end local cVbo = gl.GetVBO(GL.ARRAY_BUFFER, true) - if not cVbo then gl.DeleteShader(qShader); gl.DeleteShader(cShader); return end + if not cVbo then return end cVbo:Define(gl4Prim.CIRCLE_MAX, circleLayout) local cVao = gl.GetVAO() - if not cVao then cVbo:Delete(); gl.DeleteShader(qShader); gl.DeleteShader(cShader); return end + if not cVao then cVbo:Delete(); return end if useGS then cVao:AttachVertexBuffer(cVbo) else @@ -3988,10 +3977,10 @@ local function InitGL4Primitives() } end local qVbo = gl.GetVBO(GL.ARRAY_BUFFER, true) - if not qVbo then cVao:Delete(); cVbo:Delete(); gl.DeleteShader(qShader); gl.DeleteShader(cShader); return end + if not qVbo then cVao:Delete(); cVbo:Delete(); return end qVbo:Define(gl4Prim.QUAD_MAX, quadLayout) local qVao = gl.GetVAO() - if not qVao then qVbo:Delete(); cVao:Delete(); cVbo:Delete(); gl.DeleteShader(qShader); gl.DeleteShader(cShader); return end + if not qVao then qVbo:Delete(); cVao:Delete(); cVbo:Delete(); return end if useGS then qVao:AttachVertexBuffer(qVbo) else @@ -4006,13 +3995,12 @@ local function InitGL4Primitives() -- Line VBOs (3 categories share same shader) local glVbo, glVao, glData = CreateLineVBOSet(gl4Prim.LINE_MAX) - if not glVbo then qVao:Delete(); qVbo:Delete(); cVao:Delete(); cVbo:Delete(); gl.DeleteShader(qShader); gl.DeleteShader(cShader); return end + if not glVbo then qVao:Delete(); qVbo:Delete(); cVao:Delete(); cVbo:Delete(); return end gl4Prim.glowLines.vbo, gl4Prim.glowLines.vao, gl4Prim.glowLines.data = glVbo, glVao, glData local clVbo, clVao, clData = CreateLineVBOSet(gl4Prim.LINE_MAX) if not clVbo then glVao:Delete(); glVbo:Delete(); qVao:Delete(); qVbo:Delete(); cVao:Delete(); cVbo:Delete() - gl.DeleteShader(qShader); gl.DeleteShader(cShader) return end gl4Prim.coreLines.vbo, gl4Prim.coreLines.vao, gl4Prim.coreLines.data = clVbo, clVao, clData @@ -4021,14 +4009,31 @@ local function InitGL4Primitives() if not nlVbo then clVao:Delete(); clVbo:Delete(); glVao:Delete(); glVbo:Delete() qVao:Delete(); qVbo:Delete(); cVao:Delete(); cVbo:Delete() - gl.DeleteShader(qShader); gl.DeleteShader(cShader) return end gl4Prim.normLines.vbo, gl4Prim.normLines.vao, gl4Prim.normLines.data = nlVbo, nlVao, nlData - -- Shader variants were compiled up-front via real GS-attempt/fallback logic. + -- Compile shaders + local cShader = useGS and gl.CreateShader(gl4Prim.circleShaderCode) or gl.CreateShader(gl4Prim.circleShaderCodeNoGS) + if not cShader then + Spring.Echo("[PIP] GL4 circle shader failed: " .. tostring(gl.GetShaderLog())) + -- cleanup all + nlVao:Delete(); nlVbo:Delete(); clVao:Delete(); clVbo:Delete() + glVao:Delete(); glVbo:Delete(); qVao:Delete(); qVbo:Delete() + cVao:Delete(); cVbo:Delete() + return + end gl4Prim.circles.shader = cShader + local qShader = useGS and gl.CreateShader(gl4Prim.quadShaderCode) or gl.CreateShader(gl4Prim.quadShaderCodeNoGS) + if not qShader then + Spring.Echo("[PIP] GL4 quad shader failed: " .. tostring(gl.GetShaderLog())) + gl.DeleteShader(cShader) + nlVao:Delete(); nlVbo:Delete(); clVao:Delete(); clVbo:Delete() + glVao:Delete(); glVbo:Delete(); qVao:Delete(); qVbo:Delete() + cVao:Delete(); cVbo:Delete() + return + end gl4Prim.quads.shader = qShader local lShader = gl.CreateShader(gl4Prim.lineShaderCode) @@ -4158,7 +4163,7 @@ local function GL4FlushEffects() if gl4Prim.useGeometryShader then c.vao:DrawArrays(GL.POINTS, c.count) else - c.vao:DrawArrays(GL.TRIANGLE_STRIP, 4, 0, c.count) + c.vao:DrawArrays(GL.TRIANGLES, 6, 0, c.count) end gl.UseShader(0) end @@ -4171,7 +4176,7 @@ local function GL4FlushEffects() if gl4Prim.useGeometryShader then q.vao:DrawArrays(GL.POINTS, q.count) else - q.vao:DrawArrays(GL.TRIANGLE_STRIP, 4, 0, q.count) + q.vao:DrawArrays(GL.TRIANGLES, 6, 0, q.count) end gl.UseShader(0) end @@ -4221,7 +4226,7 @@ local function GL4FlushCirclesOnly() if gl4Prim.useGeometryShader then c.vao:DrawArrays(GL.POINTS, c.count) else - c.vao:DrawArrays(GL.TRIANGLE_STRIP, 4, 0, c.count) + c.vao:DrawArrays(GL.TRIANGLES, 6, 0, c.count) end gl.UseShader(0) end @@ -4528,7 +4533,6 @@ local UpdateTracking -- forward declaration (called in StartMaximizeAnimation, local InitGL4Decals -- forward declaration (called in Initialize, defined after decalGL4 table) local DestroyGL4Decals -- forward declaration (called in Shutdown, defined after decalGL4 table) local decalGL4 -- forward declaration (referenced in DrawDecalsOverlay, defined later) -local DrawTexturedQuad -- forward declaration (used before helper definition) local function StartMaximizeAnimation() local buttonSize = math.floor(render.usedButtonSize * config.maximizeSizemult) @@ -6028,61 +6032,82 @@ local function drawFragQuad() glFunc.Vertex(-hs, hs, 0) end --- Octagon vertex helper (untextured, for borders) — module-level to avoid per-frame re-definition +-- Octagon vertex helper (untextured, for borders) — module-level to avoid per-frame re-definition. +-- Emit explicit triangles instead of TRIANGLE_FAN to avoid fan corruption on the macOS Zink/MoltenVK path. local function drawOctagonVertices(cx, cy, s, c) - glFunc.Vertex(cx, cy, 0) - glFunc.Vertex(cx - s + c, cy - s, 0) - glFunc.Vertex(cx + s - c, cy - s, 0) - glFunc.Vertex(cx + s, cy - s + c, 0) - glFunc.Vertex(cx + s, cy + s - c, 0) - glFunc.Vertex(cx + s - c, cy + s, 0) - glFunc.Vertex(cx - s + c, cy + s, 0) - glFunc.Vertex(cx - s, cy + s - c, 0) - glFunc.Vertex(cx - s, cy - s + c, 0) - glFunc.Vertex(cx - s + c, cy - s, 0) + local x1, y1 = cx - s + c, cy - s + local x2, y2 = cx + s - c, cy - s + local x3, y3 = cx + s, cy - s + c + local x4, y4 = cx + s, cy + s - c + local x5, y5 = cx + s - c, cy + s + local x6, y6 = cx - s + c, cy + s + local x7, y7 = cx - s, cy + s - c + local x8, y8 = cx - s, cy - s + c + glFunc.Vertex(cx, cy, 0); glFunc.Vertex(x1, y1, 0); glFunc.Vertex(x2, y2, 0) + glFunc.Vertex(cx, cy, 0); glFunc.Vertex(x2, y2, 0); glFunc.Vertex(x3, y3, 0) + glFunc.Vertex(cx, cy, 0); glFunc.Vertex(x3, y3, 0); glFunc.Vertex(x4, y4, 0) + glFunc.Vertex(cx, cy, 0); glFunc.Vertex(x4, y4, 0); glFunc.Vertex(x5, y5, 0) + glFunc.Vertex(cx, cy, 0); glFunc.Vertex(x5, y5, 0); glFunc.Vertex(x6, y6, 0) + glFunc.Vertex(cx, cy, 0); glFunc.Vertex(x6, y6, 0); glFunc.Vertex(x7, y7, 0) + glFunc.Vertex(cx, cy, 0); glFunc.Vertex(x7, y7, 0); glFunc.Vertex(x8, y8, 0) + glFunc.Vertex(cx, cy, 0); glFunc.Vertex(x8, y8, 0); glFunc.Vertex(x1, y1, 0) end --- Textured octagon vertex helper (for unitpic texture, Y-flipped) — module-level +-- Textured octagon vertex helper (for unitpic texture, Y-flipped) — module-level. +-- Emit explicit triangles instead of a textured TRIANGLE_FAN while preserving the same shape. local function drawTexturedOctagonVertices(cx, cy, s, c, texIn) local t0, t1 = texIn, 1 - texIn local tRange = t1 - t0 local tMid = (t0 + t1) * 0.5 local inv2s = 1 / (2 * s) + local x1, y1 = cx - s + c, cy - s + local x2, y2 = cx + s - c, cy - s + local x3, y3 = cx + s, cy - s + c + local x4, y4 = cx + s, cy + s - c + local x5, y5 = cx + s - c, cy + s + local x6, y6 = cx - s + c, cy + s + local x7, y7 = cx - s, cy + s - c + local x8, y8 = cx - s, cy - s + c + local u1 = t0 + tRange * c * inv2s + local u2 = t0 + tRange * (2 * s - c) * inv2s + local u3 = t1 + local u4 = u2 + local u5 = u1 + local u6 = t0 + local v1 = t1 + local v2 = t1 + local v3 = t1 - tRange * c * inv2s + local v4 = t1 - tRange * (2 * s - c) * inv2s + local v5 = t0 + local v6 = t0 + local v7 = v4 + local v8 = v3 + glFunc.TexCoord(tMid, tMid) glFunc.Vertex(cx, cy, 0) - local tx, ty - tx = t0 + tRange * (-s + c + s) * inv2s - ty = t1 - tRange * (-s + s) * inv2s - glFunc.TexCoord(tx, ty) - glFunc.Vertex(cx - s + c, cy - s, 0) - tx = t0 + tRange * (s - c + s) * inv2s - glFunc.TexCoord(tx, ty) - glFunc.Vertex(cx + s - c, cy - s, 0) - tx = t0 + tRange * (s + s) * inv2s - ty = t1 - tRange * (-s + c + s) * inv2s - glFunc.TexCoord(tx, ty) - glFunc.Vertex(cx + s, cy - s + c, 0) - ty = t1 - tRange * (s - c + s) * inv2s - glFunc.TexCoord(tx, ty) - glFunc.Vertex(cx + s, cy + s - c, 0) - tx = t0 + tRange * (s - c + s) * inv2s - ty = t1 - tRange * (s + s) * inv2s - glFunc.TexCoord(tx, ty) - glFunc.Vertex(cx + s - c, cy + s, 0) - tx = t0 + tRange * (-s + c + s) * inv2s - glFunc.TexCoord(tx, ty) - glFunc.Vertex(cx - s + c, cy + s, 0) - tx = t0 + tRange * (-s + s) * inv2s - ty = t1 - tRange * (s - c + s) * inv2s - glFunc.TexCoord(tx, ty) - glFunc.Vertex(cx - s, cy + s - c, 0) - ty = t1 - tRange * (-s + c + s) * inv2s - glFunc.TexCoord(tx, ty) - glFunc.Vertex(cx - s, cy - s + c, 0) - tx = t0 + tRange * (-s + c + s) * inv2s - ty = t1 - tRange * (-s + s) * inv2s - glFunc.TexCoord(tx, ty) - glFunc.Vertex(cx - s + c, cy - s, 0) + glFunc.TexCoord(u1, v1); glFunc.Vertex(x1, y1, 0) + glFunc.TexCoord(u2, v2); glFunc.Vertex(x2, y2, 0) + glFunc.TexCoord(tMid, tMid); glFunc.Vertex(cx, cy, 0) + glFunc.TexCoord(u2, v2); glFunc.Vertex(x2, y2, 0) + glFunc.TexCoord(u3, v3); glFunc.Vertex(x3, y3, 0) + glFunc.TexCoord(tMid, tMid); glFunc.Vertex(cx, cy, 0) + glFunc.TexCoord(u3, v3); glFunc.Vertex(x3, y3, 0) + glFunc.TexCoord(u3, v4); glFunc.Vertex(x4, y4, 0) + glFunc.TexCoord(tMid, tMid); glFunc.Vertex(cx, cy, 0) + glFunc.TexCoord(u3, v4); glFunc.Vertex(x4, y4, 0) + glFunc.TexCoord(u4, v5); glFunc.Vertex(x5, y5, 0) + glFunc.TexCoord(tMid, tMid); glFunc.Vertex(cx, cy, 0) + glFunc.TexCoord(u4, v5); glFunc.Vertex(x5, y5, 0) + glFunc.TexCoord(u5, v6); glFunc.Vertex(x6, y6, 0) + glFunc.TexCoord(tMid, tMid); glFunc.Vertex(cx, cy, 0) + glFunc.TexCoord(u5, v6); glFunc.Vertex(x6, y6, 0) + glFunc.TexCoord(u6, v7); glFunc.Vertex(x7, y7, 0) + glFunc.TexCoord(tMid, tMid); glFunc.Vertex(cx, cy, 0) + glFunc.TexCoord(u6, v7); glFunc.Vertex(x7, y7, 0) + glFunc.TexCoord(u6, v8); glFunc.Vertex(x8, y8, 0) + glFunc.TexCoord(tMid, tMid); glFunc.Vertex(cx, cy, 0) + glFunc.TexCoord(u6, v8); glFunc.Vertex(x8, y8, 0) + glFunc.TexCoord(u1, v1); glFunc.Vertex(x1, y1, 0) end local function DrawIconShatters() @@ -9639,7 +9664,11 @@ local function DrawCommandQueuesOverlay(cachedSelectedUnits) local c = gl4Prim.circles c.vbo:Upload(c.data, nil, 0, 1, c.count * gl4Prim.CIRCLE_STEP) GL4SetPrimUniforms(c.shader, c.uniformLocs) - c.vao:DrawArrays(GL.POINTS, c.count) + if gl4Prim.useGeometryShader then + c.vao:DrawArrays(GL.POINTS, c.count) + else + c.vao:DrawArrays(GL.TRIANGLES, 6, 0, c.count) + end gl.UseShader(0) end @@ -11337,14 +11366,14 @@ local function GL4DrawIcons(checkAllyTeamID, selectedSet, trackingSet) if gl4Icons.shaderUsesGS then gl4Icons.bldgVao:DrawArrays(GL.POINTS, bldgUsedElements) else - gl4Icons.bldgVao:DrawArrays(GL.TRIANGLE_STRIP, 4, 0, bldgUsedElements) + gl4Icons.bldgVao:DrawArrays(GL.TRIANGLES, 6, 0, bldgUsedElements) end end gl.UniformFloat(ul.outlinePass, 0.0) if gl4Icons.shaderUsesGS then gl4Icons.bldgVao:DrawArrays(GL.POINTS, bldgUsedElements) else - gl4Icons.bldgVao:DrawArrays(GL.TRIANGLE_STRIP, 4, 0, bldgUsedElements) + gl4Icons.bldgVao:DrawArrays(GL.TRIANGLES, 6, 0, bldgUsedElements) end end @@ -11355,14 +11384,14 @@ local function GL4DrawIcons(checkAllyTeamID, selectedSet, trackingSet) if gl4Icons.shaderUsesGS then gl4Icons.slowVao:DrawArrays(GL.POINTS, slowMobileUsedElements) else - gl4Icons.slowVao:DrawArrays(GL.TRIANGLE_STRIP, 4, 0, slowMobileUsedElements) + gl4Icons.slowVao:DrawArrays(GL.TRIANGLES, 6, 0, slowMobileUsedElements) end end gl.UniformFloat(ul.outlinePass, 0.0) if gl4Icons.shaderUsesGS then gl4Icons.slowVao:DrawArrays(GL.POINTS, slowMobileUsedElements) else - gl4Icons.slowVao:DrawArrays(GL.TRIANGLE_STRIP, 4, 0, slowMobileUsedElements) + gl4Icons.slowVao:DrawArrays(GL.TRIANGLES, 6, 0, slowMobileUsedElements) end end @@ -11373,14 +11402,14 @@ local function GL4DrawIcons(checkAllyTeamID, selectedSet, trackingSet) if gl4Icons.shaderUsesGS then gl4Icons.vao:DrawArrays(GL.POINTS, mobileUsedElements) else - gl4Icons.vao:DrawArrays(GL.TRIANGLE_STRIP, 4, 0, mobileUsedElements) + gl4Icons.vao:DrawArrays(GL.TRIANGLES, 6, 0, mobileUsedElements) end end gl.UniformFloat(ul.outlinePass, 0.0) if gl4Icons.shaderUsesGS then gl4Icons.vao:DrawArrays(GL.POINTS, mobileUsedElements) else - gl4Icons.vao:DrawArrays(GL.TRIANGLE_STRIP, 4, 0, mobileUsedElements) + gl4Icons.vao:DrawArrays(GL.TRIANGLES, 6, 0, mobileUsedElements) end end @@ -11445,7 +11474,7 @@ local function GL4DrawIcons(checkAllyTeamID, selectedSet, trackingSet) -- Black border glFunc.Texture(false) glFunc.Color(0, 0, 0, 0.9) - glFunc.BeginEnd(glConst.TRIANGLE_FAN, drawOctagonVertices, 0, 0, bdrSize, crnrCutOuter) + glFunc.BeginEnd(glConst.TRIANGLES, drawOctagonVertices, 0, 0, bdrSize, crnrCutOuter) -- Team color border if isSelected then @@ -11455,7 +11484,7 @@ local function GL4DrawIcons(checkAllyTeamID, selectedSet, trackingSet) else glFunc.Color(1, 1, 1, 1) end - glFunc.BeginEnd(glConst.TRIANGLE_FAN, drawOctagonVertices, 0, 0, teamBdrSize, crnrCutTeam) + glFunc.BeginEnd(glConst.TRIANGLES, drawOctagonVertices, 0, 0, teamBdrSize, crnrCutTeam) -- Unitpic texture if unitpic then @@ -11467,7 +11496,7 @@ local function GL4DrawIcons(checkAllyTeamID, selectedSet, trackingSet) if isHovered then brightness = brightness * 1.2 end glFunc.Color(brightness, brightness, brightness, opacity) end - glFunc.BeginEnd(glConst.TRIANGLE_FAN, drawTexturedOctagonVertices, 0, 0, iconSize, crnrCutInner, picTexInset) + glFunc.BeginEnd(glConst.TRIANGLES, drawTexturedOctagonVertices, 0, 0, iconSize, crnrCutInner, picTexInset) end -- Health bar (only for damaged units, inside the icon area) @@ -11512,7 +11541,7 @@ local function GL4DrawIcons(checkAllyTeamID, selectedSet, trackingSet) -- Black border glFunc.Texture(false) glFunc.Color(0, 0, 0, 0.9) - glFunc.BeginEnd(glConst.TRIANGLE_FAN, drawOctagonVertices, px, py, bdrSize, crnrCutOuter) + glFunc.BeginEnd(glConst.TRIANGLES, drawOctagonVertices, px, py, bdrSize, crnrCutOuter) -- Team color border if isSelected then @@ -11522,7 +11551,7 @@ local function GL4DrawIcons(checkAllyTeamID, selectedSet, trackingSet) else glFunc.Color(1, 1, 1, 1) end - glFunc.BeginEnd(glConst.TRIANGLE_FAN, drawOctagonVertices, px, py, teamBdrSize, crnrCutTeam) + glFunc.BeginEnd(glConst.TRIANGLES, drawOctagonVertices, px, py, teamBdrSize, crnrCutTeam) -- Unitpic texture if unitpic then @@ -11534,7 +11563,7 @@ local function GL4DrawIcons(checkAllyTeamID, selectedSet, trackingSet) if isHovered then brightness = brightness * 1.2 end glFunc.Color(brightness, brightness, brightness, opacity) end - glFunc.BeginEnd(glConst.TRIANGLE_FAN, drawTexturedOctagonVertices, px, py, iconSize, crnrCutInner, picTexInset) + glFunc.BeginEnd(glConst.TRIANGLES, drawTexturedOctagonVertices, px, py, iconSize, crnrCutInner, picTexInset) end -- Health bar (only for damaged units, inside the icon area) @@ -13788,7 +13817,7 @@ local function BlitMapRuler() end -- Helper for drawing a textured quad — passed as callback to gl.BeginEnd to avoid closure allocation -DrawTexturedQuad = function(qL, qB, qR, qT) +local function DrawTexturedQuad(qL, qB, qR, qT) if qL == nil then local tq = pools.scratchTexQuad qL, qB, qR, qT = tq.l, tq.b, tq.r, tq.t @@ -15516,12 +15545,14 @@ InitGL4Decals = function() vbo:Delete() return end - quadVBO:Define(4, {{id = 0, name = 'quadPos', size = 2}}) + quadVBO:Define(6, {{id = 0, name = 'quadPos', size = 2}}) quadVBO:Upload({ -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, + -1.0, 1.0, + 1.0, -1.0, }) decalGL4.quadVBO = quadVBO end @@ -15656,7 +15687,7 @@ local function decalR2TDraw() if decalGL4.useGeometryShader then decalGL4.vao:DrawArrays(GL.POINTS, decalGL4.instanceCount) else - decalGL4.vao:DrawArrays(GL.TRIANGLE_STRIP, 4, 0, decalGL4.instanceCount) + decalGL4.vao:DrawArrays(GL.TRIANGLES, 6, 0, decalGL4.instanceCount) end gl.UseShader(0) glFunc.Texture(false) @@ -16388,10 +16419,7 @@ function widget:DrawScreen() miscState.engineFallbackRawWantedSince = now end - -- Exit fallback immediately when zooming in so PIP R2T content appears without lag. - -- Keep off-debounce for non-zoom transitions (e.g. unit count hovering near threshold). - local leavingBecauseZoom = not (IsAtMinimumZoom(cameraState.zoom) and IsAtMinimumZoom(cameraState.targetZoom)) - local holdTime = rawUseEngineMinimapFallback and 0.35 or (leavingBecauseZoom and 0 or 0.75) + local holdTime = rawUseEngineMinimapFallback and 0.35 or 0.75 if miscState.engineFallbackRawWantedSince and (now - miscState.engineFallbackRawWantedSince) < holdTime then useEngineMinimapFallback = miscState.engineMinimapActive else diff --git a/luaui/Widgets/gui_rank_icons_gl4.lua b/luaui/Widgets/gui_rank_icons_gl4.lua index 024e8459064..87e5e192782 100644 --- a/luaui/Widgets/gui_rank_icons_gl4.lua +++ b/luaui/Widgets/gui_rank_icons_gl4.lua @@ -187,7 +187,7 @@ local function RemovePrimitive(unitID,reason) end local function initGL4() - local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit.lua") + local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit_NoGS_Mesh.lua") local shaderConfig = DrawPrimitiveAtUnit.shaderConfig -- MAKE SURE YOU READ THE SHADERCONFIG TABLE in DrawPrimitiveAtUnit.lua shaderConfig.BILLBOARD = 1 shaderConfig.HEIGHTOFFSET = 0 diff --git a/luaui/Widgets/gui_resurrection_halos_gl4.lua b/luaui/Widgets/gui_resurrection_halos_gl4.lua index 7f2e2245a02..adcaf21154b 100644 --- a/luaui/Widgets/gui_resurrection_halos_gl4.lua +++ b/luaui/Widgets/gui_resurrection_halos_gl4.lua @@ -41,7 +41,7 @@ for unitDefID, unitDef in pairs(UnitDefs) do end local function initGL4() - local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit.lua") + local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit_NoGS_Mesh.lua") local InitDrawPrimitiveAtUnit = DrawPrimitiveAtUnit.InitDrawPrimitiveAtUnit local shaderConfig = DrawPrimitiveAtUnit.shaderConfig -- MAKE SURE YOU READ THE SHADERCONFIG TABLE in DrawPrimitiveAtUnit.lua shaderConfig.TRANSPARENCY = 0.5 diff --git a/luaui/Widgets/gui_team_platter.lua b/luaui/Widgets/gui_team_platter.lua index efe661adc96..25514342687 100644 --- a/luaui/Widgets/gui_team_platter.lua +++ b/luaui/Widgets/gui_team_platter.lua @@ -182,7 +182,7 @@ function widget:CrashingAircraft(unitID, unitDefID, teamID) end local function init() - local DPatUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit.lua") + local DPatUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit_NoGS_Mesh.lua") local InitDrawPrimitiveAtUnit = DPatUnit.InitDrawPrimitiveAtUnit local shaderConfig = DPatUnit.shaderConfig -- MAKE SURE YOU READ THE SHADERCONFIG TABLE! shaderConfig.TRANSPARENCY = opacity diff --git a/luaui/Widgets/gui_unit_energy_icons.lua b/luaui/Widgets/gui_unit_energy_icons.lua index 65625c7efa2..27ffb621625 100644 --- a/luaui/Widgets/gui_unit_energy_icons.lua +++ b/luaui/Widgets/gui_unit_energy_icons.lua @@ -102,7 +102,7 @@ local popElementInstance = InstanceVBOTable.popElementInstance local function initGL4() - local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit.lua") + local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit_NoGS_Mesh.lua") local InitDrawPrimitiveAtUnit = DrawPrimitiveAtUnit.InitDrawPrimitiveAtUnit local shaderConfig = DrawPrimitiveAtUnit.shaderConfig -- MAKE SURE YOU READ THE SHADERCONFIG TABLE in DrawPrimitiveAtUnit.lua shaderConfig.BILLBOARD = 1 diff --git a/luaui/Widgets/gui_unit_firestate_icons.lua b/luaui/Widgets/gui_unit_firestate_icons.lua index 823577301fa..ec7cb328b6f 100644 --- a/luaui/Widgets/gui_unit_firestate_icons.lua +++ b/luaui/Widgets/gui_unit_firestate_icons.lua @@ -86,7 +86,7 @@ local instanceData = {0, 0, 0, 0, 0, 4, 0, 0, 0.85, 0, 0, 1, 0, 1, 0, 0, 0, -- GL4 Initialization -------------------------------------------------------------------------------- local function initGL4() - local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir .. "DrawPrimitiveAtUnit.lua") + local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir .. "DrawPrimitiveAtUnit_NoGS_Mesh.lua") local InitDrawPrimitiveAtUnit = DrawPrimitiveAtUnit.InitDrawPrimitiveAtUnit local shaderConfig = DrawPrimitiveAtUnit.shaderConfig diff --git a/luaui/Widgets/gui_unit_group_number.lua b/luaui/Widgets/gui_unit_group_number.lua index 5c1dda1b26a..77d4c17020e 100644 --- a/luaui/Widgets/gui_unit_group_number.lua +++ b/luaui/Widgets/gui_unit_group_number.lua @@ -84,7 +84,7 @@ local function initGL4() vbocachetables[i] = vbocachetable end - local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir .. "DrawPrimitiveAtUnit.lua") + local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir .. "DrawPrimitiveAtUnit_NoGS_Mesh.lua") local shaderConfig = DrawPrimitiveAtUnit.shaderConfig -- MAKE SURE YOU READ THE SHADERCONFIG TABLE in DrawPrimitiveAtUnit.lua shaderConfig.BILLBOARD = 1 shaderConfig.HEIGHTOFFSET = 0 diff --git a/luaui/Widgets/gui_unit_repeat_icon.lua b/luaui/Widgets/gui_unit_repeat_icon.lua index cd19e6841ab..c26be69fc30 100644 --- a/luaui/Widgets/gui_unit_repeat_icon.lua +++ b/luaui/Widgets/gui_unit_repeat_icon.lua @@ -71,7 +71,7 @@ local instanceData = {0, 0, 0, 0, 0, 4, 0, 0, 0.85, 0, 0, 1, 0, 1, 0, 0, 0, -- GL4 Initialization -------------------------------------------------------------------------------- local function initGL4() - local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir .. "DrawPrimitiveAtUnit.lua") + local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir .. "DrawPrimitiveAtUnit_NoGS_Mesh.lua") local InitDrawPrimitiveAtUnit = DrawPrimitiveAtUnit.InitDrawPrimitiveAtUnit local shaderConfig = DrawPrimitiveAtUnit.shaderConfig diff --git a/luaui/Widgets/gui_unit_wait_icons.lua b/luaui/Widgets/gui_unit_wait_icons.lua index da46febeee5..74ca812fc44 100644 --- a/luaui/Widgets/gui_unit_wait_icons.lua +++ b/luaui/Widgets/gui_unit_wait_icons.lua @@ -61,7 +61,7 @@ local energyIconShader = nil local luaShaderDir = "LuaUI/Include/" local function initGL4() - local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit.lua") + local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit_NoGS_Mesh.lua") local InitDrawPrimitiveAtUnit = DrawPrimitiveAtUnit.InitDrawPrimitiveAtUnit local shaderConfig = DrawPrimitiveAtUnit.shaderConfig -- MAKE SURE YOU READ THE SHADERCONFIG TABLE in DrawPrimitiveAtUnit.lua shaderConfig.BILLBOARD = 1