[Fix] (Tessellation): bound the pass-through control-stage cache and stop baking "draw nothing" for levels GL clamps

This commit is contained in:
Swung0x48
2026-08-27 03:18:14 -04:00
parent 2635fe84b6
commit 31252cf0da
7 changed files with 104 additions and 52 deletions
@@ -12,6 +12,10 @@
#include "ShaderCompiler.h"
#include <format>
#include <cmath>
#include "SpirvPasses/EliminateFloatEqualsZeroPass.h"
#include "SpirvPasses/FlattenInterfaceStructPass.h"
#include "SpirvPasses/RenameSamplerFunctionParameterPass.h"
@@ -64,6 +68,30 @@
namespace MobileGL {
namespace MG_Util {
namespace ShaderTranspiler {
// Above every plausible GL_MAX_TESS_GEN_LEVEL (the GL core minimum is 64), so it lands on the
// same clamped result the device's own maximum would. +inf has to reach the tessellator as
// "as finely as possible", not as "discard".
static constexpr const char* kClampedHighTessLevelLiteral = "65536.0";
String TessellationLevelLiteral(Float value) {
// GL leaves a NaN level unspecified; 0.0 is the safe reading, and unlike "nan" it compiles.
if (std::isnan(value)) return "0.0";
// -inf is <= 0 and discards the patch, exactly like 0.0. +inf clamps to the maximum.
if (std::isinf(value)) return value > 0.0f ? kClampedHighTessLevelLiteral : "0.0";
// Shortest round-trip, not a fixed six decimals: "{:.6f}" renders every level below ~5e-7
// as "0.000000", which turns a positive level GL would clamp to 1 into a discarded patch.
String text = std::format("{}", value);
// ...but shortest round-trip spells an integral value as a bare digit sequence, which GLSL
// reads as an INT literal, so the decimal point has to be put back when nothing else marks
// the literal as floating point.
if (text.find('.') == String::npos && text.find('e') == String::npos &&
text.find('E') == String::npos) {
text += ".0";
}
return text;
}
// `env` is the compile-time backend snapshot; null means "resolve from the live
// backend", which is what the standalone/test entry points do. The pipeline always
// passes one, so a worker never reaches pActiveBackendObject through here.
@@ -18,6 +18,18 @@
namespace MobileGL {
namespace MG_Util {
namespace ShaderTranspiler {
// A GLSL float literal for a tessellation level, for the pass-through tessellation
// control stage both backends synthesize when a program has an evaluation stage and no
// control stage. Shared so the two generators cannot disagree about what a level means.
//
// GL 4.6 core 11.2.2 discards a patch only when a relevant OUTER level is <= 0; every
// other value is CLAMPED into [1, MAX_TESS_GEN_LEVEL]. So "draw nothing" is reserved
// for the values that really mean it, and everything else has to survive the trip
// through text: a shortest-round-trip spelling, because a fixed-decimal one flushes
// small positive levels to zero, and always with a '.' or an exponent, because a bare
// digit sequence is an INT literal and `gl_TessLevelOuter[0] = 1;` does not compile.
String TessellationLevelLiteral(Float value);
class ShaderCompiler {
public:
static Result<SharedPtr<glslang::TShader>> CompileShader(const ShaderAttrib& attrib);