mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
1230 lines
49 KiB
C++
1230 lines
49 KiB
C++
// MobileGL - MobileGL/MG_Benchmark/Transpile/TranspileProfile.cpp
|
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
|
|
// Per-stage stopwatch for the DirectGLES program-build path.
|
|
//
|
|
// The chain a program goes through is GLSL text -> ShaderSourceProcessor -> glslang parse ->
|
|
// glslang link + mapIO -> GlslangToSpv -> SanitizeAndOptimizeBinary -> SPIRV-Reflect
|
|
// (global-UBO routing) -> a run of separate ShaderCompiler::...ForEssl optimizer round trips
|
|
// and Declares*/Probe* module parses -> SPIRV-Cross -> ESSL text passes -> the driver.
|
|
//
|
|
// Every one of those ...ForEssl entry points builds its own spvtools::Optimizer and calls
|
|
// Run(), i.e. a full SPIR-V parse + IR build + re-serialize per call, and every Declares*
|
|
// probe is another full BuildModule. This program measures each of them separately, plus a
|
|
// zero-pass Optimizer::Run and a bare BuildModule on the same binary, so the FIXED
|
|
// parse/serialize overhead can be separated from the work the passes actually do. It also
|
|
// runs the same pass set merged onto ONE Optimizer, which is the saving a merged chain
|
|
// would realise.
|
|
//
|
|
// Measurement only: it links MobileGL_s and drives the public ShaderCompiler API, so it
|
|
// needs no GL context and no driver. The driver's own glCompileShader/glLinkProgram is
|
|
// therefore NOT included here - see the report for how to measure that half on device.
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <functional>
|
|
#include <set>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "Init.h"
|
|
|
|
#include <MG_State/GLState/ProgramState/ShaderStage.h>
|
|
#include <MG_Util/ShaderTranspiler/CompileEnv.h>
|
|
#include <MG_Util/ShaderTranspiler/ShaderCompiler.h>
|
|
#include <MG_Util/ShaderTranspiler/ShaderSourceProcessor.h>
|
|
#include <MG_Util/ShaderTranspiler/SpvcSession.h>
|
|
#include <MG_Util/ShaderTranspiler/Types.h>
|
|
|
|
#include <MG_Util/ShaderTranspiler/SpirvPasses/ClampMultisampleFetchPass.h>
|
|
#include <MG_Util/ShaderTranspiler/SpirvPasses/Lower1DArrayImagesPass.h>
|
|
#include <MG_Util/ShaderTranspiler/SpirvPasses/PrivateToEntryLocalPass.h>
|
|
#include <MG_Util/ShaderTranspiler/SpirvPasses/EmulateNoPerspectivePass.h>
|
|
#include <MG_Util/ShaderTranspiler/SpirvPasses/LowerDrawParametersPass.h>
|
|
#include <MG_Util/ShaderTranspiler/SpirvPasses/LowerViewportIndexPass.h>
|
|
#include <MG_Util/ShaderTranspiler/SpirvPasses/NormalizeRectCoordinatesPass.h>
|
|
#include <MG_Util/ShaderTranspiler/SpirvPasses/SplitArrayVertexInputsPass.h>
|
|
#include <MG_Util/ShaderTranspiler/SpirvPasses/StripUboMemberRelaxedPrecisionPass.h>
|
|
|
|
#include "glslang/Include/PoolAlloc.h"
|
|
#include "glslang/MachineIndependent/Initialize.h"
|
|
|
|
#include "source/opt/build_module.h"
|
|
#include "source/opt/ir_context.h"
|
|
#include "spirv-tools/optimizer.hpp"
|
|
|
|
namespace MobileGL::MG_Util::ShaderTranspiler {
|
|
// Defined in ShaderCompiler.cpp at namespace scope, with no header declaration; the
|
|
// resource table every glslang parse is handed.
|
|
TBuiltInResource BuildTBuiltInResource(const CompileEnv* env);
|
|
} // namespace MobileGL::MG_Util::ShaderTranspiler
|
|
|
|
using namespace MobileGL;
|
|
using namespace MobileGL::MG_Util::ShaderTranspiler;
|
|
using Clock = std::chrono::steady_clock;
|
|
|
|
namespace {
|
|
|
|
// ---------------------------------------------------------------- timing
|
|
|
|
struct Stat {
|
|
double medianUs = 0.0;
|
|
double p10Us = 0.0;
|
|
double p90Us = 0.0;
|
|
double minUs = 0.0;
|
|
int reps = 0;
|
|
};
|
|
|
|
// Keeps a measured result observable so the optimizer cannot delete the work.
|
|
volatile unsigned long long g_sink = 0;
|
|
template <typename T>
|
|
void benchmarkSink(T value) {
|
|
g_sink += static_cast<unsigned long long>(value);
|
|
}
|
|
|
|
Stat Summarize(std::vector<double>& samples) {
|
|
std::sort(samples.begin(), samples.end());
|
|
Stat s;
|
|
s.reps = static_cast<int>(samples.size());
|
|
s.minUs = samples.front();
|
|
s.medianUs = samples[samples.size() / 2];
|
|
s.p10Us = samples[static_cast<size_t>(samples.size() * 0.10)];
|
|
s.p90Us = samples[static_cast<size_t>(samples.size() * 0.90)];
|
|
return s;
|
|
}
|
|
|
|
// Runs `body` until at least kMinReps samples exist AND kBudgetMs has elapsed, capped at
|
|
// kMaxReps. Three untimed warm-up calls first, so allocator growth and first-touch page
|
|
// faults land outside the sample set.
|
|
template <typename F>
|
|
Stat Measure(F&& body, const double budgetMs = 250.0, const int minReps = 15,
|
|
const int maxReps = 2000) {
|
|
for (int i = 0; i < 3; ++i) body();
|
|
|
|
std::vector<double> samples;
|
|
samples.reserve(64);
|
|
const auto start = Clock::now();
|
|
while (true) {
|
|
const auto t0 = Clock::now();
|
|
body();
|
|
const auto t1 = Clock::now();
|
|
samples.push_back(std::chrono::duration<double, std::micro>(t1 - t0).count());
|
|
if (static_cast<int>(samples.size()) >= maxReps) break;
|
|
const double elapsedMs = std::chrono::duration<double, std::milli>(t1 - start).count();
|
|
if (static_cast<int>(samples.size()) >= minReps && elapsedMs >= budgetMs) break;
|
|
}
|
|
return Summarize(samples);
|
|
}
|
|
|
|
// Same loop, but `body` returns the microseconds of the part that should be timed - for
|
|
// stages whose set-up (a fresh glslang parse, a fresh link) has to happen per iteration
|
|
// and must NOT be counted.
|
|
template <typename F>
|
|
Stat MeasureInner(F&& body, const double budgetMs = 250.0, const int minReps = 15,
|
|
const int maxReps = 2000) {
|
|
for (int i = 0; i < 3; ++i) (void)body();
|
|
|
|
std::vector<double> samples;
|
|
samples.reserve(64);
|
|
const auto start = Clock::now();
|
|
while (true) {
|
|
samples.push_back(body());
|
|
if (static_cast<int>(samples.size()) >= maxReps) break;
|
|
const double elapsedMs =
|
|
std::chrono::duration<double, std::milli>(Clock::now() - start).count();
|
|
if (static_cast<int>(samples.size()) >= minReps && elapsedMs >= budgetMs) break;
|
|
}
|
|
return Summarize(samples);
|
|
}
|
|
|
|
struct Row {
|
|
std::string corpus;
|
|
std::string stage; // "VS" / "FS" / "program"
|
|
std::string what;
|
|
std::string kind; // roundtrip / probe / frontend / crosscompile / baseline / merged
|
|
size_t moduleWords = 0;
|
|
Stat stat;
|
|
};
|
|
|
|
std::vector<Row> g_rows;
|
|
|
|
void Emit(const std::string& corpus, const std::string& stage, const std::string& what,
|
|
const std::string& kind, size_t words, const Stat& s) {
|
|
g_rows.push_back(Row{corpus, stage, what, kind, words, s});
|
|
std::printf("%-14s %-8s %-40s %-13s %7zu %10.1f %10.1f %10.1f %6d\n", corpus.c_str(),
|
|
stage.c_str(), what.c_str(), kind.c_str(), words, s.medianUs, s.p10Us,
|
|
s.p90Us, s.reps);
|
|
std::fflush(stdout);
|
|
}
|
|
|
|
// ---------------------------------------------------------------- corpora
|
|
|
|
// (a) The pair already in MG_Benchmark/Program/ProgramBench.cpp.
|
|
const char* kTinyVs = R"(#version 460
|
|
|
|
layout (location = 0) in vec4 Position;
|
|
in float fIn4;
|
|
in float fIn2;
|
|
in float fIn5;
|
|
in float fIn6;
|
|
in float fIn1;
|
|
in float fIn3;
|
|
|
|
layout(location = 0) uniform mat4 ProjMat;
|
|
layout(location = 10) uniform mat3 TestMat3;
|
|
layout(location = 20) uniform mat2 TestMat2;
|
|
uniform vec2 InSize;
|
|
uniform vec2 OutSize;
|
|
|
|
out vec2 texCoord;
|
|
out vec2 oneTexel;
|
|
|
|
void main(){
|
|
vec4 outPos = ProjMat * vec4(Position.xy, 0.0, 1.0);
|
|
gl_Position = vec4(outPos.xy, 0.2, 1.0);
|
|
|
|
vec2 dummy2 = TestMat2[0];
|
|
vec3 dummy3 = TestMat3[0];
|
|
|
|
oneTexel = (1.0 * (fIn1 * fIn2 * fIn3 * fIn4 * fIn5 * fIn6)) / InSize;
|
|
|
|
texCoord = Position.xy / OutSize;
|
|
})";
|
|
|
|
const char* kTinyFs = R"(#version 460
|
|
|
|
uniform sampler2D InSampler;
|
|
|
|
in vec2 texCoord;
|
|
in vec2 oneTexel;
|
|
|
|
uniform vec2 InSize;
|
|
|
|
layout(location = 1) uniform vec3 Gray;
|
|
uniform vec3 RedMatrix;
|
|
uniform vec3 GreenMatrix0;
|
|
uniform vec3 BlueMatrix;
|
|
uniform vec3 Offset;
|
|
uniform vec3 ColorScale;
|
|
layout(location = 6) uniform float Saturation;
|
|
uniform int AQuickFoxJumpsOverALazyDog;
|
|
uniform int intVal;
|
|
|
|
out vec4 fragColor;
|
|
|
|
void main() {
|
|
vec4 InTexel = texture(InSampler, texCoord);
|
|
|
|
float RedValue = dot(InTexel.rgb, RedMatrix);
|
|
float GreenValue = dot(InTexel.rgb, GreenMatrix0);
|
|
float BlueValue = dot(InTexel.rgb, BlueMatrix);
|
|
vec3 OutColor = vec3(RedValue, GreenValue, BlueValue);
|
|
|
|
OutColor = (OutColor * ColorScale) + Offset;
|
|
|
|
float Luma = dot(OutColor, Gray);
|
|
vec3 Chroma = OutColor - Luma;
|
|
OutColor = (Chroma * Saturation) + Luma;
|
|
|
|
fragColor = vec4(OutColor, float(intVal));
|
|
})";
|
|
|
|
// (b) KHR-GL33.texture_swizzle.smoke_*, reproduced from the templates in
|
|
// external/openglcts/modules/gl/gl3cTextureSwizzleTests.cpp (SmokeTest::getVertexShader /
|
|
// getFragmentShader). One instantiation: source format usampler2D ("uint"/"u"), output
|
|
// format "uint", 2D target, access "texture", channel "r". prepareAndTestProgram builds
|
|
// both the fragment-tested and the vertex-tested pair, which is why both appear here;
|
|
// that is the pair the case links 2592 times.
|
|
const char* kCtsBlankVs = R"(#version 330 core
|
|
|
|
void main()
|
|
{
|
|
switch (gl_VertexID)
|
|
{
|
|
case 0: gl_Position = vec4(-1.0, 1.0, 0.0, 1.0); break;
|
|
case 1: gl_Position = vec4( 1.0, 1.0, 0.0, 1.0); break;
|
|
case 2: gl_Position = vec4(-1.0,-1.0, 0.0, 1.0); break;
|
|
case 3: gl_Position = vec4( 1.0,-1.0, 0.0, 1.0); break;
|
|
}
|
|
}
|
|
)";
|
|
|
|
const char* kCtsTestFs = R"(#version 330 core
|
|
|
|
uniform usampler2D sampler;
|
|
|
|
out uint out_color;
|
|
|
|
void main()
|
|
{
|
|
uint result = texture(sampler, vec2(0, 0)).r;
|
|
|
|
out_color = result;
|
|
}
|
|
)";
|
|
|
|
const char* kCtsTestVs = R"(#version 330 core
|
|
|
|
uniform usampler2D sampler;
|
|
|
|
flat out uint result;
|
|
|
|
void main()
|
|
{
|
|
result = texture(sampler, vec2(0, 0)).r;
|
|
|
|
switch (gl_VertexID)
|
|
{
|
|
case 0: gl_Position = vec4(-1.0, 1.0, 0.0, 1.0); break;
|
|
case 1: gl_Position = vec4( 1.0, 1.0, 0.0, 1.0); break;
|
|
case 2: gl_Position = vec4(-1.0,-1.0, 0.0, 1.0); break;
|
|
case 3: gl_Position = vec4( 1.0,-1.0, 0.0, 1.0); break;
|
|
}
|
|
}
|
|
)";
|
|
|
|
const char* kCtsBlankFs = R"(#version 330 core
|
|
|
|
flat in uint result;
|
|
|
|
out uint out_color;
|
|
|
|
void main()
|
|
{
|
|
out_color = result;
|
|
}
|
|
)";
|
|
|
|
// (c) A large, realistic deferred-lighting fragment shader in the shape Iris shader packs
|
|
// reach the transpiler in: a flat wall of default-block uniforms (so the global-UBO
|
|
// packing and the OpName traffic are representative), several samplers, helper functions,
|
|
// a shadow-filter loop, PBR, fog, and tonemapping. Synthesized rather than copied so the
|
|
// benchmark carries no third-party shader-pack source.
|
|
const char* kBigVs = R"(#version 330 core
|
|
|
|
in vec3 vaPosition;
|
|
in vec4 vaColor;
|
|
in vec2 vaUV0;
|
|
in ivec2 vaUV2;
|
|
in vec3 vaNormal;
|
|
in vec4 at_tangent;
|
|
in vec2 mc_Entity;
|
|
in vec2 mc_midTexCoord;
|
|
|
|
uniform mat4 modelViewMatrix;
|
|
uniform mat4 modelViewMatrixInverse;
|
|
uniform mat4 projectionMatrix;
|
|
uniform mat4 projectionMatrixInverse;
|
|
uniform mat4 gbufferModelView;
|
|
uniform mat4 gbufferModelViewInverse;
|
|
uniform mat4 gbufferProjection;
|
|
uniform mat4 gbufferProjectionInverse;
|
|
uniform mat4 shadowModelView;
|
|
uniform mat4 shadowProjection;
|
|
uniform mat4 textureMatrix;
|
|
uniform vec3 cameraPosition;
|
|
uniform vec3 previousCameraPosition;
|
|
uniform vec3 chunkOffset;
|
|
uniform float frameTimeCounter;
|
|
uniform float rainStrength;
|
|
uniform float viewWidth;
|
|
uniform float viewHeight;
|
|
uniform int worldTime;
|
|
uniform int frameCounter;
|
|
|
|
out vec4 vColor;
|
|
out vec2 vTexCoord;
|
|
out vec2 vLightCoord;
|
|
out vec3 vNormal;
|
|
out vec3 vTangent;
|
|
out vec3 vViewPos;
|
|
out vec3 vWorldPos;
|
|
out vec3 vShadowPos;
|
|
out float vBlockId;
|
|
out float vFogDepth;
|
|
|
|
vec3 WavePosition(vec3 worldPos, float id, float t) {
|
|
if (id < 0.5) return worldPos;
|
|
float phase = worldPos.x * 0.35 + worldPos.z * 0.27 + t * 1.7;
|
|
float amp = 0.045 * (1.0 + rainStrength);
|
|
worldPos.x += sin(phase) * amp;
|
|
worldPos.z += cos(phase * 1.13) * amp;
|
|
worldPos.y += sin(phase * 0.71) * amp * 0.5;
|
|
return worldPos;
|
|
}
|
|
|
|
void main() {
|
|
vec3 localPos = vaPosition + chunkOffset;
|
|
vec3 worldPos = localPos + cameraPosition;
|
|
worldPos = WavePosition(worldPos, mc_Entity.x, frameTimeCounter);
|
|
localPos = worldPos - cameraPosition;
|
|
|
|
vec4 viewPos = modelViewMatrix * vec4(localPos, 1.0);
|
|
gl_Position = projectionMatrix * viewPos;
|
|
|
|
vColor = vaColor;
|
|
vTexCoord = (textureMatrix * vec4(vaUV0, 0.0, 1.0)).xy;
|
|
vLightCoord = clamp((vec2(vaUV2) - 8.0) / 240.0, 0.0, 1.0);
|
|
vNormal = normalize(mat3(modelViewMatrix) * vaNormal);
|
|
vTangent = normalize(mat3(modelViewMatrix) * at_tangent.xyz);
|
|
vViewPos = viewPos.xyz;
|
|
vWorldPos = worldPos;
|
|
vBlockId = mc_Entity.x;
|
|
vFogDepth = length(viewPos.xyz);
|
|
|
|
vec4 shadowView = shadowModelView * vec4(localPos, 1.0);
|
|
vec4 shadowClip = shadowProjection * shadowView;
|
|
vShadowPos = shadowClip.xyz / max(shadowClip.w, 1e-5) * 0.5 + 0.5;
|
|
}
|
|
)";
|
|
|
|
const char* kBigFs = R"(#version 330 core
|
|
|
|
uniform sampler2D gtexture;
|
|
uniform sampler2D lightmap;
|
|
uniform sampler2D normals;
|
|
uniform sampler2D specular;
|
|
uniform sampler2D noisetex;
|
|
uniform sampler2D depthtex0;
|
|
uniform sampler2D depthtex1;
|
|
uniform sampler2D colortex0;
|
|
uniform sampler2D colortex1;
|
|
uniform sampler2D colortex2;
|
|
uniform sampler2D colortex3;
|
|
uniform sampler2D colortex4;
|
|
uniform sampler2DShadow shadowtex0;
|
|
uniform sampler2DShadow shadowtex1;
|
|
uniform sampler2D shadowcolor0;
|
|
|
|
uniform mat4 gbufferModelView;
|
|
uniform mat4 gbufferModelViewInverse;
|
|
uniform mat4 gbufferProjection;
|
|
uniform mat4 gbufferProjectionInverse;
|
|
uniform mat4 gbufferPreviousModelView;
|
|
uniform mat4 gbufferPreviousProjection;
|
|
uniform mat4 shadowModelView;
|
|
uniform mat4 shadowModelViewInverse;
|
|
uniform mat4 shadowProjection;
|
|
uniform mat4 shadowProjectionInverse;
|
|
|
|
uniform vec3 cameraPosition;
|
|
uniform vec3 previousCameraPosition;
|
|
uniform vec3 sunPosition;
|
|
uniform vec3 moonPosition;
|
|
uniform vec3 shadowLightPosition;
|
|
uniform vec3 upPosition;
|
|
uniform vec3 fogColor;
|
|
uniform vec3 skyColor;
|
|
uniform vec4 entityColor;
|
|
|
|
uniform float frameTimeCounter;
|
|
uniform float frameTime;
|
|
uniform float sunAngle;
|
|
uniform float shadowAngle;
|
|
uniform float rainStrength;
|
|
uniform float wetness;
|
|
uniform float aspectRatio;
|
|
uniform float viewWidth;
|
|
uniform float viewHeight;
|
|
uniform float near;
|
|
uniform float far;
|
|
uniform float nightVision;
|
|
uniform float blindness;
|
|
uniform float darknessFactor;
|
|
uniform float screenBrightness;
|
|
uniform float eyeAltitude;
|
|
uniform float centerDepthSmooth;
|
|
uniform float playerMood;
|
|
|
|
uniform int worldTime;
|
|
uniform int worldDay;
|
|
uniform int moonPhase;
|
|
uniform int frameCounter;
|
|
uniform int heldItemId;
|
|
uniform int heldBlockLightValue;
|
|
uniform int isEyeInWater;
|
|
uniform int hideGUI;
|
|
uniform ivec2 eyeBrightness;
|
|
uniform ivec2 eyeBrightnessSmooth;
|
|
|
|
in vec4 vColor;
|
|
in vec2 vTexCoord;
|
|
in vec2 vLightCoord;
|
|
in vec3 vNormal;
|
|
in vec3 vTangent;
|
|
in vec3 vViewPos;
|
|
in vec3 vWorldPos;
|
|
in vec3 vShadowPos;
|
|
in float vBlockId;
|
|
in float vFogDepth;
|
|
|
|
layout(location = 0) out vec4 outColor0;
|
|
layout(location = 1) out vec4 outColor1;
|
|
layout(location = 2) out vec4 outColor2;
|
|
layout(location = 3) out vec4 outColor3;
|
|
|
|
const float PI = 3.14159265358979323846;
|
|
const float SHADOW_BIAS = 0.0009;
|
|
const int SHADOW_SAMPLES = 12;
|
|
|
|
const vec2 kPoisson[12] = vec2[12](
|
|
vec2(-0.3260, -0.4058), vec2( 0.7912, -0.0421), vec2(-0.1958, -0.9018),
|
|
vec2(-0.2354, 0.5259), vec2( 0.4479, 0.5734), vec2( 0.9036, 0.4162),
|
|
vec2(-0.7935, -0.5960), vec2(-0.0344, 0.0468), vec2(-0.9174, 0.2495),
|
|
vec2( 0.4443, -0.7563), vec2(-0.5551, -0.0998), vec2( 0.1770, 0.9294)
|
|
);
|
|
|
|
float Luminance(vec3 c) {
|
|
return dot(c, vec3(0.2125, 0.7154, 0.0721));
|
|
}
|
|
|
|
float LinearizeDepth(float d) {
|
|
return (2.0 * near * far) / (far + near - (d * 2.0 - 1.0) * (far - near));
|
|
}
|
|
|
|
vec3 ScreenToView(vec3 screenPos) {
|
|
vec4 ndc = vec4(screenPos * 2.0 - 1.0, 1.0);
|
|
vec4 view = gbufferProjectionInverse * ndc;
|
|
return view.xyz / view.w;
|
|
}
|
|
|
|
vec3 ViewToWorld(vec3 viewPos) {
|
|
return (gbufferModelViewInverse * vec4(viewPos, 1.0)).xyz + cameraPosition;
|
|
}
|
|
|
|
float Hash12(vec2 p) {
|
|
vec3 p3 = fract(vec3(p.xyx) * 0.1031);
|
|
p3 += dot(p3, p3.yzx + 33.33);
|
|
return fract((p3.x + p3.y) * p3.z);
|
|
}
|
|
|
|
mat2 RotationFromAngle(float a) {
|
|
float s = sin(a);
|
|
float c = cos(a);
|
|
return mat2(c, -s, s, c);
|
|
}
|
|
|
|
float DistributionGGX(vec3 n, vec3 h, float roughness) {
|
|
float a = roughness * roughness;
|
|
float a2 = a * a;
|
|
float ndoth = max(dot(n, h), 0.0);
|
|
float denom = ndoth * ndoth * (a2 - 1.0) + 1.0;
|
|
return a2 / max(PI * denom * denom, 1e-6);
|
|
}
|
|
|
|
float GeometrySchlick(float ndotv, float roughness) {
|
|
float r = roughness + 1.0;
|
|
float k = (r * r) / 8.0;
|
|
return ndotv / (ndotv * (1.0 - k) + k);
|
|
}
|
|
|
|
float GeometrySmith(vec3 n, vec3 v, vec3 l, float roughness) {
|
|
return GeometrySchlick(max(dot(n, v), 0.0), roughness) *
|
|
GeometrySchlick(max(dot(n, l), 0.0), roughness);
|
|
}
|
|
|
|
vec3 FresnelSchlick(float cosTheta, vec3 f0) {
|
|
return f0 + (1.0 - f0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
|
|
}
|
|
|
|
float SampleShadow(vec3 shadowPos, float ndotl) {
|
|
if (shadowPos.x < 0.0 || shadowPos.x > 1.0 || shadowPos.y < 0.0 || shadowPos.y > 1.0) {
|
|
return 1.0;
|
|
}
|
|
float bias = SHADOW_BIAS * (1.0 + (1.0 - ndotl) * 3.0);
|
|
float angle = Hash12(gl_FragCoord.xy + float(frameCounter)) * PI * 2.0;
|
|
mat2 rot = RotationFromAngle(angle);
|
|
float radius = 1.4 / 2048.0 * (1.0 + wetness);
|
|
float sum = 0.0;
|
|
for (int i = 0; i < SHADOW_SAMPLES; ++i) {
|
|
vec2 offset = rot * kPoisson[i] * radius;
|
|
sum += texture(shadowtex1, vec3(shadowPos.xy + offset, shadowPos.z - bias));
|
|
}
|
|
return sum / float(SHADOW_SAMPLES);
|
|
}
|
|
|
|
vec3 SkyLight(vec3 normal, float lightLevel) {
|
|
vec3 up = normalize(mat3(gbufferModelViewInverse) * upPosition);
|
|
float hemi = clamp(dot(normal, up) * 0.5 + 0.5, 0.0, 1.0);
|
|
vec3 sky = mix(fogColor, skyColor, hemi);
|
|
return sky * lightLevel * (1.0 - rainStrength * 0.6);
|
|
}
|
|
|
|
vec3 BlockLight(float lightLevel) {
|
|
vec3 warm = vec3(1.0, 0.62, 0.28);
|
|
float shaped = pow(lightLevel, 2.2);
|
|
return warm * shaped * (1.0 + float(heldBlockLightValue) * 0.01);
|
|
}
|
|
|
|
vec3 ApplyFog(vec3 color, float dist, vec3 viewDir) {
|
|
float density = 0.0016 * (1.0 + rainStrength * 2.0 + float(isEyeInWater) * 12.0);
|
|
float amount = 1.0 - exp(-dist * density);
|
|
vec3 tint = mix(fogColor, skyColor, clamp(viewDir.y * 0.5 + 0.5, 0.0, 1.0));
|
|
return mix(color, tint, clamp(amount, 0.0, 1.0));
|
|
}
|
|
|
|
vec3 Tonemap(vec3 c) {
|
|
c *= 1.6;
|
|
vec3 a = c * (c + 0.0245786) - 0.000090537;
|
|
vec3 b = c * (0.983729 * c + 0.4329510) + 0.238081;
|
|
c = a / b;
|
|
return pow(clamp(c, 0.0, 1.0), vec3(1.0 / 2.2));
|
|
}
|
|
|
|
void main() {
|
|
vec4 albedo = texture(gtexture, vTexCoord) * vColor;
|
|
if (albedo.a < 0.1) discard;
|
|
|
|
vec4 normalTex = texture(normals, vTexCoord);
|
|
vec4 specTex = texture(specular, vTexCoord);
|
|
|
|
vec3 n = normalize(vNormal);
|
|
vec3 t = normalize(vTangent);
|
|
vec3 b = cross(n, t);
|
|
mat3 tbn = mat3(t, b, n);
|
|
vec3 tangentNormal = normalTex.xyz * 2.0 - 1.0;
|
|
tangentNormal.z = sqrt(max(1.0 - dot(tangentNormal.xy, tangentNormal.xy), 0.0));
|
|
vec3 normal = normalize(tbn * tangentNormal);
|
|
|
|
float roughness = clamp(1.0 - specTex.r, 0.02, 1.0);
|
|
float metallic = clamp(specTex.g, 0.0, 1.0);
|
|
float emissive = clamp(specTex.b, 0.0, 1.0);
|
|
float porosity = clamp(specTex.a, 0.0, 1.0);
|
|
|
|
vec3 viewDir = normalize(-vViewPos);
|
|
vec3 lightDir = normalize(shadowLightPosition);
|
|
vec3 halfDir = normalize(viewDir + lightDir);
|
|
|
|
float ndotl = max(dot(normal, lightDir), 0.0);
|
|
float shadow = SampleShadow(vShadowPos, ndotl);
|
|
|
|
vec3 f0 = mix(vec3(0.04), albedo.rgb, metallic);
|
|
float ndf = DistributionGGX(normal, halfDir, roughness);
|
|
float geo = GeometrySmith(normal, viewDir, lightDir, roughness);
|
|
vec3 fres = FresnelSchlick(max(dot(halfDir, viewDir), 0.0), f0);
|
|
|
|
vec3 kd = (vec3(1.0) - fres) * (1.0 - metallic);
|
|
vec3 spec = (ndf * geo * fres) /
|
|
max(4.0 * max(dot(normal, viewDir), 0.0) * ndotl, 1e-4);
|
|
|
|
vec3 sunColor = mix(vec3(1.0, 0.92, 0.80), vec3(0.32, 0.42, 0.66), rainStrength);
|
|
vec3 direct = (kd * albedo.rgb / PI + spec) * sunColor * ndotl * shadow;
|
|
|
|
vec3 ambient = SkyLight(mat3(gbufferModelViewInverse) * normal, vLightCoord.y) * albedo.rgb;
|
|
vec3 blockLit = BlockLight(vLightCoord.x) * albedo.rgb;
|
|
vec3 emissiveLit = albedo.rgb * emissive * 4.0;
|
|
|
|
vec3 color = direct + ambient + blockLit + emissiveLit;
|
|
|
|
float wetMix = clamp(wetness * (1.0 - porosity) * vLightCoord.y, 0.0, 1.0);
|
|
color = mix(color, color * 0.72, wetMix);
|
|
|
|
color *= 1.0 - clamp(blindness + darknessFactor * 0.5, 0.0, 1.0);
|
|
color += vec3(nightVision) * 0.06 * Luminance(albedo.rgb);
|
|
|
|
vec3 worldViewDir = normalize(mat3(gbufferModelViewInverse) * -viewDir);
|
|
color = ApplyFog(color, vFogDepth, worldViewDir);
|
|
color = Tonemap(color);
|
|
|
|
outColor0 = vec4(color, albedo.a);
|
|
outColor1 = vec4(normal * 0.5 + 0.5, 1.0);
|
|
outColor2 = vec4(roughness, metallic, emissive, 1.0);
|
|
outColor3 = vec4(vLightCoord, vBlockId / 255.0, shadow);
|
|
}
|
|
)";
|
|
|
|
// ------------------------------------------------------- shared helpers
|
|
|
|
spvtools::MessageConsumer SilentConsumer() {
|
|
return [](spv_message_level_t, const char*, const spv_position_t&, const char*) {};
|
|
}
|
|
|
|
struct BuiltProgram {
|
|
std::vector<GLenum> types;
|
|
std::vector<std::vector<unsigned>> raw; // straight out of GlslangToSpv
|
|
std::vector<std::vector<unsigned>> sanitized; // after SanitizeAndOptimizeBinary
|
|
};
|
|
|
|
// Fresh parse of both stages. glslang mutates a TShader during link/mapIO, so anything
|
|
// that needs to be timed repeatedly has to rebuild these each iteration.
|
|
std::vector<SharedPtr<glslang::TShader>> ParseBoth(const std::string& vsPre,
|
|
const std::string& fsPre,
|
|
const CompileEnv& env) {
|
|
std::vector<SharedPtr<glslang::TShader>> out;
|
|
for (const auto& [type, src] :
|
|
{std::pair<GLenum, const std::string*>{GL_VERTEX_SHADER, &vsPre},
|
|
std::pair<GLenum, const std::string*>{GL_FRAGMENT_SHADER, &fsPre}}) {
|
|
ShaderAttrib attrib{.shaderType = type, .sourceStr = *src, .flags = 0, .env = &env};
|
|
auto r = ShaderCompiler::CompileShader(attrib);
|
|
if (!r) {
|
|
std::fprintf(stderr, "PARSE FAILED (%s):\n%s\n",
|
|
type == GL_VERTEX_SHADER ? "VS" : "FS", r.error().log.c_str());
|
|
std::abort();
|
|
}
|
|
out.push_back(r.value());
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// ------------------------------------------------------------ the sweep
|
|
|
|
void ProfileCorpus(const std::string& name, const char* vsSrc, const char* fsSrc) {
|
|
const CompileEnv& env = *GetDefaultCompileEnv();
|
|
|
|
// ---- stage 0: ShaderSourceProcessor (pure String -> String) ----
|
|
{
|
|
Stat s = Measure([&] {
|
|
String src = vsSrc;
|
|
PreprocessShaderSource(ShaderStage::Vertex, src, env);
|
|
benchmarkSink(src.size());
|
|
});
|
|
Emit(name, "VS", "ShaderSourceProcessor.Preprocess", "frontend", 0, s);
|
|
}
|
|
{
|
|
Stat s = Measure([&] {
|
|
String src = fsSrc;
|
|
PreprocessShaderSource(ShaderStage::Fragment, src, env);
|
|
benchmarkSink(src.size());
|
|
});
|
|
Emit(name, "FS", "ShaderSourceProcessor.Preprocess", "frontend", 0, s);
|
|
}
|
|
|
|
String vsPre = vsSrc;
|
|
PreprocessShaderSource(ShaderStage::Vertex, vsPre, env);
|
|
String fsPre = fsSrc;
|
|
PreprocessShaderSource(ShaderStage::Fragment, fsPre, env);
|
|
|
|
// ---- stage 1: glslang parse ----
|
|
{
|
|
Stat s = Measure([&] {
|
|
ShaderAttrib a{.shaderType = GL_VERTEX_SHADER, .sourceStr = vsPre, .flags = 0, .env = &env};
|
|
auto r = ShaderCompiler::CompileShader(a);
|
|
benchmarkSink(r.has_value());
|
|
});
|
|
Emit(name, "VS", "glslang.parse", "frontend", 0, s);
|
|
}
|
|
{
|
|
Stat s = Measure([&] {
|
|
ShaderAttrib a{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = fsPre, .flags = 0, .env = &env};
|
|
auto r = ShaderCompiler::CompileShader(a);
|
|
benchmarkSink(r.has_value());
|
|
});
|
|
Emit(name, "FS", "glslang.parse", "frontend", 0, s);
|
|
}
|
|
|
|
// ---- stage 2: glslang link + mapIO (fresh parses each iteration, untimed) ----
|
|
{
|
|
Stat inner = MeasureInner([&] {
|
|
auto shaders = ParseBoth(vsPre, fsPre, env);
|
|
const auto t0 = Clock::now();
|
|
ProgramAttrib pa{.shaders = shaders};
|
|
auto p = ShaderCompiler::LinkProgram(pa);
|
|
const auto t1 = Clock::now();
|
|
benchmarkSink(p.has_value());
|
|
return std::chrono::duration<double, std::micro>(t1 - t0).count();
|
|
});
|
|
Emit(name, "program", "glslang.link+mapIO", "frontend", 0, inner);
|
|
}
|
|
|
|
// ---- stage 3: GlslangToSpv ----
|
|
{
|
|
Stat inner = MeasureInner([&] {
|
|
auto shaders = ParseBoth(vsPre, fsPre, env);
|
|
ProgramAttrib pa{.shaders = shaders};
|
|
auto p = ShaderCompiler::LinkProgram(pa);
|
|
std::vector<GLenum> types{GL_VERTEX_SHADER, GL_FRAGMENT_SHADER};
|
|
ProgramBinaryAttrib ba{.shaderTypes = types, .program = *p.value()};
|
|
const auto t0 = Clock::now();
|
|
auto bin = ShaderCompiler::GetSpirvBinaryFromProgram(ba);
|
|
const auto t1 = Clock::now();
|
|
benchmarkSink(bin.has_value());
|
|
return std::chrono::duration<double, std::micro>(t1 - t0).count();
|
|
});
|
|
Emit(name, "program", "glslang.GlslangToSpv (both stages)", "frontend", 0, inner);
|
|
}
|
|
|
|
// ---- build the artefacts every later measurement runs against ----
|
|
BuiltProgram built;
|
|
{
|
|
auto shaders = ParseBoth(vsPre, fsPre, env);
|
|
ProgramAttrib pa{.shaders = shaders};
|
|
auto p = ShaderCompiler::LinkProgram(pa);
|
|
if (!p) {
|
|
std::fprintf(stderr, "LINK FAILED: %s\n", p.error().log.c_str());
|
|
std::abort();
|
|
}
|
|
built.types = {GL_VERTEX_SHADER, GL_FRAGMENT_SHADER};
|
|
ProgramBinaryAttrib ba{.shaderTypes = built.types, .program = *p.value()};
|
|
auto bin = ShaderCompiler::GetSpirvBinaryFromProgram(ba);
|
|
built.raw = bin.value();
|
|
built.sanitized = built.raw;
|
|
for (auto& m : built.sanitized) {
|
|
Vector<uint32_t> out;
|
|
if (!ShaderCompiler::SanitizeAndOptimizeBinary(m, out, true, false)) {
|
|
std::fprintf(stderr, "SANITIZE FAILED\n");
|
|
std::abort();
|
|
}
|
|
m = out;
|
|
}
|
|
}
|
|
|
|
// ---- stage 4: SanitizeAndOptimizeBinary, per stage ----
|
|
for (size_t i = 0; i < built.raw.size(); ++i) {
|
|
const char* stage = i == 0 ? "VS" : "FS";
|
|
const Vector<Uint32>& in = built.raw[i];
|
|
Stat s = Measure([&] {
|
|
Vector<uint32_t> out;
|
|
ShaderCompiler::SanitizeAndOptimizeBinary(in, out, true, false);
|
|
benchmarkSink(out.size());
|
|
});
|
|
Emit(name, stage, "SanitizeAndOptimizeBinary (11 passes)", "roundtrip", in.size(), s);
|
|
|
|
Stat sv = Measure([&] {
|
|
Vector<uint32_t> out;
|
|
ShaderCompiler::SanitizeAndOptimizeBinary(in, out, true, true);
|
|
benchmarkSink(out.size());
|
|
});
|
|
Emit(name, stage, "SanitizeAndOptimizeBinary + spirv-val", "roundtrip", in.size(), sv);
|
|
|
|
// Which of the eleven passes the time goes to: the two optimizing ones
|
|
// (PrivateToEntryLocal feeding AggressiveDCE) against the nine legality ones.
|
|
Stat pd = Measure([&] {
|
|
spvtools::Optimizer opt(SPV_ENV_VULKAN_1_1);
|
|
opt.SetMessageConsumer(SilentConsumer());
|
|
opt.RegisterPass(PrivateToEntryLocalPass::CreatePrivateToEntryLocalPass());
|
|
spvtools::OptimizerOptions o;
|
|
o.set_run_validator(false);
|
|
Vector<uint32_t> out;
|
|
opt.Run(in.data(), in.size(), &out, o);
|
|
benchmarkSink(out.size());
|
|
});
|
|
Emit(name, stage, " Sanitize part: PrivateToEntryLocal only", "roundtrip", in.size(), pd);
|
|
|
|
Stat ad = Measure([&] {
|
|
spvtools::Optimizer opt(SPV_ENV_VULKAN_1_1);
|
|
opt.SetMessageConsumer(SilentConsumer());
|
|
opt.RegisterPass(spvtools::CreateAggressiveDCEPass(false));
|
|
spvtools::OptimizerOptions o;
|
|
o.set_run_validator(false);
|
|
Vector<uint32_t> out;
|
|
opt.Run(in.data(), in.size(), &out, o);
|
|
benchmarkSink(out.size());
|
|
});
|
|
Emit(name, stage, " Sanitize part: AggressiveDCE only", "roundtrip", in.size(), ad);
|
|
}
|
|
|
|
// Everything below runs on the SANITIZED module, which is what the DirectGLES
|
|
// backend actually receives (ProgramSpirvTask stores the optimized binary).
|
|
for (size_t i = 0; i < built.sanitized.size(); ++i) {
|
|
const bool isVs = (i == 0);
|
|
const char* stage = isVs ? "VS" : "FS";
|
|
const Vector<Uint32>& mod = built.sanitized[i];
|
|
const size_t words = mod.size();
|
|
|
|
// ---- the two fixed-cost baselines ----
|
|
{
|
|
Stat s = Measure([&] {
|
|
spvtools::Optimizer opt(SPV_ENV_VULKAN_1_1);
|
|
spvtools::OptimizerOptions o;
|
|
o.set_run_validator(false);
|
|
opt.SetMessageConsumer(SilentConsumer());
|
|
Vector<uint32_t> out;
|
|
opt.Run(mod.data(), mod.size(), &out, o);
|
|
benchmarkSink(out.size());
|
|
});
|
|
Emit(name, stage, "BASELINE Optimizer::Run, ZERO passes", "baseline", words, s);
|
|
}
|
|
{
|
|
Stat s = Measure([&] {
|
|
auto ctx = spvtools::BuildModule(SPV_ENV_VULKAN_1_1, SilentConsumer(),
|
|
mod.data(), mod.size());
|
|
benchmarkSink(ctx != nullptr);
|
|
});
|
|
Emit(name, stage, "BASELINE BuildModule only (parse, no emit)", "baseline", words, s);
|
|
}
|
|
|
|
// ---- the gate probes ----
|
|
{
|
|
Stat s = Measure([&] {
|
|
auto f = ShaderCompiler::ProbeSpirvGateFeatures(mod);
|
|
benchmarkSink(f.WritesViewportIndexOutput || f.DeclaresMultisampledImage);
|
|
});
|
|
Emit(name, stage, "ProbeSpirvGateFeatures (merged 2 gates)", "probe", words, s);
|
|
}
|
|
{
|
|
Stat s = Measure([&] {
|
|
benchmarkSink(ShaderCompiler::DeclaresViewportIndexBuiltin(mod));
|
|
});
|
|
Emit(name, stage, "DeclaresViewportIndexBuiltin", "probe", words, s);
|
|
}
|
|
{
|
|
Stat s = Measure([&] {
|
|
benchmarkSink(ShaderCompiler::DeclaresMultisampledImage(mod));
|
|
});
|
|
Emit(name, stage, "DeclaresMultisampledImage", "probe", words, s);
|
|
}
|
|
{
|
|
Stat s = Measure([&] {
|
|
benchmarkSink(ShaderCompiler::DeclaresFormatlessStorageImage(mod));
|
|
});
|
|
Emit(name, stage, "DeclaresFormatlessStorageImage", "probe", words, s);
|
|
}
|
|
{
|
|
Stat s = Measure([&] {
|
|
benchmarkSink(ShaderCompiler::ModuleDeclaresBufferTextureSampler(mod));
|
|
});
|
|
Emit(name, stage, "ModuleDeclaresBufferTextureSampler", "probe", words, s);
|
|
}
|
|
{
|
|
// Early-outs on every module that declares no 1D-array storage image, i.e.
|
|
// InspectBinary's BuildModule + a full copy of the binary.
|
|
Stat s = Measure([&] {
|
|
Vector<uint32_t> out;
|
|
ShaderCompiler::Lower1DArrayImagesForEssl(mod, out, false);
|
|
benchmarkSink(out.size());
|
|
});
|
|
Emit(name, stage, "Lower1DArrayImagesForEssl (gate-only path)", "probe", words, s);
|
|
}
|
|
if (!isVs) {
|
|
// Same shape: BinaryHasDynamicOutputIndexing's BuildModule + a copy.
|
|
Stat s = Measure([&] {
|
|
Vector<uint32_t> out;
|
|
ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(mod, out, false);
|
|
benchmarkSink(out.size());
|
|
});
|
|
Emit(name, stage, "LegalizeFragmentOutputIndexingForEssl (gate-only)", "probe", words, s);
|
|
}
|
|
|
|
// ---- the individual optimizer round trips ----
|
|
if (isVs) {
|
|
Stat s = Measure([&] {
|
|
Vector<uint32_t> out;
|
|
ShaderCompiler::LowerDrawParametersForEssl(mod, out, false);
|
|
benchmarkSink(out.size());
|
|
});
|
|
Emit(name, stage, "LowerDrawParametersForEssl", "roundtrip", words, s);
|
|
|
|
Stat s2 = Measure([&] {
|
|
Vector<uint32_t> out;
|
|
ShaderCompiler::SplitArrayVertexInputsForEssl(mod, out, false);
|
|
benchmarkSink(out.size());
|
|
});
|
|
Emit(name, stage, "SplitArrayVertexInputsForEssl", "roundtrip", words, s2);
|
|
|
|
Vector<uint32_t> split;
|
|
ShaderCompiler::SplitArrayVertexInputsForEssl(mod, split, false);
|
|
Stat s3 = Measure([&] { benchmarkSink(split != mod); }, 100.0);
|
|
Emit(name, stage, " (its 'did anything change' vector compare)", "roundtrip", words, s3);
|
|
} else {
|
|
// A pass whose Process() acquires the def-use manager BEFORE asking whether it
|
|
// has anything to do, run on a stage that can never contain its builtins. The
|
|
// delta over the zero-pass baseline is the cost of the analysis alone.
|
|
Stat s = Measure([&] {
|
|
Vector<uint32_t> out;
|
|
ShaderCompiler::LowerDrawParametersForEssl(mod, out, false);
|
|
benchmarkSink(out.size());
|
|
});
|
|
Emit(name, stage, "LowerDrawParametersForEssl (no-op stage: def-use cost)",
|
|
"roundtrip", words, s);
|
|
}
|
|
{
|
|
Stat s = Measure([&] {
|
|
Vector<uint32_t> out;
|
|
ShaderCompiler::StripUboMemberRelaxedPrecisionForEssl(mod, out, false);
|
|
benchmarkSink(out.size());
|
|
});
|
|
Emit(name, stage, "StripUboMemberRelaxedPrecisionForEssl", "roundtrip", words, s);
|
|
}
|
|
{
|
|
Stat s = Measure([&] {
|
|
Vector<uint32_t> out;
|
|
ShaderCompiler::LowerRectImages(mod, out, false);
|
|
benchmarkSink(out.size());
|
|
});
|
|
Emit(name, stage, "LowerRectImages", "roundtrip", words, s);
|
|
}
|
|
{
|
|
// Armed on Adreno (integer multisample really is 1 while 4 is advertised).
|
|
Stat s = Measure([&] {
|
|
Vector<uint32_t> out;
|
|
ShaderCompiler::ClampMultisampleFetchesForEssl(mod, out, 4, 1, 1, 4, false);
|
|
benchmarkSink(out.size());
|
|
});
|
|
Emit(name, stage, "ClampMultisampleFetchesForEssl (if armed)", "roundtrip", words, s);
|
|
}
|
|
{
|
|
// Armed on a driver without GL_OES_viewport_array (Mali).
|
|
Stat s = Measure([&] {
|
|
Vector<uint32_t> out;
|
|
ShaderCompiler::LowerViewportIndexForEssl(mod, out, false);
|
|
benchmarkSink(out.size());
|
|
});
|
|
Emit(name, stage, "LowerViewportIndexForEssl (if armed)", "roundtrip", words, s);
|
|
}
|
|
{
|
|
// Armed on a driver without GL_NV_shader_noperspective_interpolation.
|
|
Stat s = Measure([&] {
|
|
Vector<uint32_t> out;
|
|
ShaderCompiler::EmulateNoPerspectiveForEssl(mod, out, false);
|
|
benchmarkSink(out.size());
|
|
});
|
|
Emit(name, stage, "EmulateNoPerspectiveForEssl (if armed)", "roundtrip", words, s);
|
|
}
|
|
|
|
// ---- SPIRV-Reflect (global-UBO routing) and SPIRV-Cross ----
|
|
{
|
|
Stat s = Measure([&] {
|
|
SpvcSession session(mod, SessionUsageBit::Reflection);
|
|
benchmarkSink(session.ParseMetaData());
|
|
});
|
|
Emit(name, stage, "SPIRV-Reflect ParseMetaData (UBO routing)", "crosscompile", words, s);
|
|
}
|
|
{
|
|
Stat s = Measure([&] {
|
|
SpvcSession session(mod, SessionUsageBit::Transpile);
|
|
spvc_compiler_options options;
|
|
session.CreateOptions(&options);
|
|
spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, 320);
|
|
spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ES, SPVC_TRUE);
|
|
spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS,
|
|
SPVC_FALSE);
|
|
session.SetOptions(options);
|
|
const char* result = nullptr;
|
|
session.Compile(&result);
|
|
benchmarkSink(result != nullptr ? std::strlen(result) : 0);
|
|
});
|
|
Emit(name, stage, "SPIRV-Cross parse+emit ESSL", "crosscompile", words, s);
|
|
}
|
|
|
|
// ---- the whole backend chain, as Managers.cpp runs it on an Adreno-class
|
|
// driver: viewport gate OFF, multisample-clamp gate ON, noperspective
|
|
// supported, no XFB, no format-less image, no storage-block override.
|
|
{
|
|
Stat s = Measure([&] {
|
|
const Vector<Uint32>* eff = &mod;
|
|
Vector<uint32_t> a, b, c, d, e, f;
|
|
if (isVs && ShaderCompiler::LowerDrawParametersForEssl(*eff, a, false) && !a.empty())
|
|
eff = &a;
|
|
auto gates = ShaderCompiler::ProbeSpirvGateFeatures(*eff);
|
|
if (gates.DeclaresMultisampledImage &&
|
|
ShaderCompiler::ClampMultisampleFetchesForEssl(*eff, b, 4, 1, 1, 4, false) &&
|
|
!b.empty())
|
|
eff = &b;
|
|
if (isVs && ShaderCompiler::SplitArrayVertexInputsForEssl(*eff, c, false) &&
|
|
!c.empty() && c != *eff)
|
|
eff = &c;
|
|
if (ShaderCompiler::StripUboMemberRelaxedPrecisionForEssl(*eff, d, false) && !d.empty())
|
|
eff = &d;
|
|
if (ShaderCompiler::LowerRectImages(*eff, e, false) && !e.empty()) eff = &e;
|
|
if (ShaderCompiler::Lower1DArrayImagesForEssl(*eff, f, false) && !f.empty()) eff = &f;
|
|
Vector<uint32_t> g;
|
|
if (!isVs && ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(*eff, g, false) &&
|
|
!g.empty())
|
|
eff = &g;
|
|
benchmarkSink(eff->size());
|
|
});
|
|
Emit(name, stage, "TOTAL current SPIR-V chain (backend, Adreno)", "chain", words, s);
|
|
}
|
|
|
|
// ---- the same work, merged onto ONE Optimizer ----
|
|
{
|
|
Stat s = Measure([&] {
|
|
spvtools::Optimizer opt(SPV_ENV_VULKAN_1_1);
|
|
opt.SetMessageConsumer(SilentConsumer());
|
|
if (isVs) {
|
|
opt.RegisterPass(LowerDrawParametersPass::CreateLowerDrawParametersPass());
|
|
opt.RegisterPass(SplitArrayVertexInputsPass::CreateSplitArrayVertexInputsPass());
|
|
}
|
|
opt.RegisterPass(ClampMultisampleFetchPass::CreateClampMultisampleFetchPass(4, 1, 1, 4));
|
|
opt.RegisterPass(
|
|
StripUboMemberRelaxedPrecisionPass::CreateStripUboMemberRelaxedPrecisionPass());
|
|
opt.RegisterPass(NormalizeRectCoordinatesPass::CreateNormalizeRectCoordinatesPass());
|
|
spvtools::OptimizerOptions o;
|
|
o.set_run_validator(false);
|
|
Vector<uint32_t> out;
|
|
opt.Run(mod.data(), mod.size(), &out, o);
|
|
benchmarkSink(out.size());
|
|
});
|
|
Emit(name, stage, "TOTAL merged SPIR-V chain (one Run)", "merged", words, s);
|
|
}
|
|
// Same, plus the 1D-array-image lowering registered as an ordinary pass instead of
|
|
// being reached through its own BuildModule gate.
|
|
{
|
|
Stat s = Measure([&] {
|
|
spvtools::Optimizer opt(SPV_ENV_VULKAN_1_1);
|
|
opt.SetMessageConsumer(SilentConsumer());
|
|
if (isVs) {
|
|
opt.RegisterPass(LowerDrawParametersPass::CreateLowerDrawParametersPass());
|
|
opt.RegisterPass(SplitArrayVertexInputsPass::CreateSplitArrayVertexInputsPass());
|
|
}
|
|
opt.RegisterPass(ClampMultisampleFetchPass::CreateClampMultisampleFetchPass(4, 1, 1, 4));
|
|
opt.RegisterPass(
|
|
StripUboMemberRelaxedPrecisionPass::CreateStripUboMemberRelaxedPrecisionPass());
|
|
opt.RegisterPass(NormalizeRectCoordinatesPass::CreateNormalizeRectCoordinatesPass());
|
|
opt.RegisterPass(Lower1DArrayImagesPass::CreateLower1DArrayImagesPass());
|
|
spvtools::OptimizerOptions o;
|
|
o.set_run_validator(false);
|
|
Vector<uint32_t> out;
|
|
opt.Run(mod.data(), mod.size(), &out, o);
|
|
benchmarkSink(out.size());
|
|
});
|
|
Emit(name, stage, "TOTAL merged + 1D-array pass (one Run)", "merged", words, s);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char** argv) {
|
|
// "--floor-loop" parses the same trivial shader forever, so an external sampler can
|
|
// attribute the source-independent part of a glslang parse.
|
|
const bool floorLoop = argc > 1 && std::strcmp(argv[1], "--floor-loop") == 0;
|
|
|
|
// Times the first glslang parse of the process separately: glslang builds its built-in
|
|
// symbol tables lazily, under a process-wide lock, on the first parse of each
|
|
// (version, spvVersion, profile, source) combination.
|
|
MobileGL::Initialize();
|
|
|
|
const CompileEnv& env = *GetDefaultCompileEnv();
|
|
{
|
|
const auto t0 = Clock::now();
|
|
String src = kCtsTestFs;
|
|
PreprocessShaderSource(ShaderStage::Fragment, src, env);
|
|
ShaderAttrib a{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = src, .flags = 0, .env = &env};
|
|
auto r = ShaderCompiler::CompileShader(a);
|
|
const auto t1 = Clock::now();
|
|
std::printf("# glslang FIRST parse of the process (cold built-in tables): %.1f us (ok=%d)\n",
|
|
std::chrono::duration<double, std::micro>(t1 - t0).count(), r.has_value() ? 1 : 0);
|
|
}
|
|
ShaderCompiler::PrewarmBuiltins();
|
|
|
|
if (floorLoop) {
|
|
static const char* kEmpty330 = "#version 330 core\nvoid main() {}\n";
|
|
String pre = kEmpty330;
|
|
PreprocessShaderSource(ShaderStage::Vertex, pre, env);
|
|
std::printf("# floor loop; sample me\n");
|
|
std::fflush(stdout);
|
|
for (;;) {
|
|
ShaderAttrib a{
|
|
.shaderType = GL_VERTEX_SHADER, .sourceStr = pre, .flags = 0, .env = &env};
|
|
auto r = ShaderCompiler::CompileShader(a);
|
|
benchmarkSink(r.has_value());
|
|
}
|
|
}
|
|
|
|
std::printf("\n%-14s %-8s %-40s %-13s %7s %10s %10s %10s %6s\n", "corpus", "stage", "what",
|
|
"kind", "words", "median_us", "p10_us", "p90_us", "reps");
|
|
std::printf("%s\n", std::string(120, '-').c_str());
|
|
|
|
// The per-parse FLOOR. glslang caches its version/profile built-in symbol table across
|
|
// parses, but AddContextSpecificSymbols() - the resource-dependent half, gl_MaxVertexAttribs
|
|
// and friends, generated from the TBuiltInResource we hand it - is rebuilt on every single
|
|
// TShader::parse (ShaderLang.cpp: it is called unconditionally after the cached table is
|
|
// adopted). An empty shader measures exactly that, with no user code to confound it.
|
|
{
|
|
static const char* kEmpty330 = "#version 330 core\nvoid main() {}\n";
|
|
static const char* kEmpty460 = "#version 460\nvoid main() {}\n";
|
|
for (const auto& [label, src] : {std::pair<const char*, const char*>{"330 core", kEmpty330},
|
|
std::pair<const char*, const char*>{"460", kEmpty460}}) {
|
|
String pre = src;
|
|
PreprocessShaderSource(ShaderStage::Vertex, pre, env);
|
|
Stat s = Measure([&] {
|
|
ShaderAttrib a{
|
|
.shaderType = GL_VERTEX_SHADER, .sourceStr = pre, .flags = 0, .env = &env};
|
|
auto r = ShaderCompiler::CompileShader(a);
|
|
benchmarkSink(r.has_value());
|
|
});
|
|
Emit("floor", "VS", std::string("glslang.parse of empty main(), #version ") + label,
|
|
"frontend", 0, s);
|
|
}
|
|
}
|
|
|
|
// What that floor is made of. glslang caches the version/profile built-in symbol table
|
|
// (SharedSymbolTables) but calls AddContextSpecificSymbols() unconditionally on every
|
|
// TShader::parse (ShaderLang.cpp), and that constructs a TBuiltIns, GENERATES the
|
|
// resource-dependent built-in declarations as GLSL text from the TBuiltInResource, and
|
|
// then PARSES that text into a fresh symbol-table level. Only the generate half can be
|
|
// timed from outside; the rest of the floor is the re-parse of the text it produced.
|
|
{
|
|
const TBuiltInResource resources = BuildTBuiltInResource(&env);
|
|
glslang::SpvVersion spvVersion;
|
|
spvVersion.spv = 0x00010300;
|
|
spvVersion.vulkan = 100;
|
|
glslang::TPoolAllocator pool;
|
|
glslang::SetThreadPoolAllocator(&pool);
|
|
size_t commonBytes = 0;
|
|
size_t stageBytes = 0;
|
|
Stat s = MeasureInner(
|
|
[&]() -> double {
|
|
pool.push();
|
|
const auto t0 = Clock::now();
|
|
glslang::TBuiltIns builtIns;
|
|
builtIns.initialize(resources, 330, ECoreProfile, spvVersion, EShLangVertex);
|
|
const auto t1 = Clock::now();
|
|
commonBytes = builtIns.getCommonString().size();
|
|
stageBytes = builtIns.getStageString(EShLangVertex).size();
|
|
const double us = std::chrono::duration<double, std::micro>(t1 - t0).count();
|
|
pool.pop();
|
|
return us;
|
|
},
|
|
150.0, 15, 400);
|
|
glslang::SetThreadPoolAllocator(nullptr);
|
|
Emit("floor", "VS", " of which: TBuiltIns::initialize (generate only)", "frontend", 0, s);
|
|
std::printf("# generated built-in declaration text: common %zu bytes, vertex stage %zu bytes\n",
|
|
commonBytes, stageBytes);
|
|
}
|
|
|
|
// Splitting the floor further: how much of it is object set-up that never touches the
|
|
// source, and how much is MobileGL's particular glslang configuration rather than a parse
|
|
// as such.
|
|
{
|
|
static const char* kEmpty330 = "#version 330 core\nvoid main() {}\n";
|
|
const TBuiltInResource resources = BuildTBuiltInResource(&env);
|
|
|
|
Stat ctor = Measure([&] {
|
|
auto sh = MakeShared<glslang::TShader>(EShLangVertex);
|
|
const char* src[] = {kEmpty330};
|
|
sh->setStrings(src, 1);
|
|
sh->setNanMinMaxClamp(true);
|
|
sh->setInvertY(true);
|
|
sh->setPreamble("#undef VULKAN\n");
|
|
sh->setEnvInput(glslang::EShSourceGlsl, EShLangVertex, glslang::EShClientVulkan, 450);
|
|
sh->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_1);
|
|
sh->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3);
|
|
sh->setEnvInputVulkanRulesRelaxed();
|
|
sh->setAutoMapLocations(true);
|
|
sh->setAutoMapBindings(true);
|
|
sh->setGlobalUniformBlockName(GLOBAL_UBO_NAME);
|
|
benchmarkSink(sh != nullptr);
|
|
});
|
|
Emit("floor", "VS", " TShader ctor+configure, NO parse()", "frontend", 0, ctor);
|
|
|
|
Stat bare = Measure([&] {
|
|
auto sh = MakeShared<glslang::TShader>(EShLangVertex);
|
|
const char* src[] = {kEmpty330};
|
|
sh->setStrings(src, 1);
|
|
TBuiltInResource r = resources;
|
|
benchmarkSink(sh->parse(&r, 330, ECoreProfile, false, true, EShMsgDefault));
|
|
});
|
|
Emit("floor", "VS", " parse() with NO MobileGL env configuration", "frontend", 0, bare);
|
|
}
|
|
|
|
std::printf("\n%-14s %-8s %-40s %-13s %7s %10s %10s %10s %6s\n", "corpus", "stage", "what",
|
|
"kind", "words", "median_us", "p10_us", "p90_us", "reps");
|
|
std::printf("%s\n", std::string(120, '-').c_str());
|
|
|
|
ProfileCorpus("tiny", kTinyVs, kTinyFs);
|
|
ProfileCorpus("cts_fs_tested", kCtsBlankVs, kCtsTestFs);
|
|
ProfileCorpus("cts_vs_tested", kCtsTestVs, kCtsBlankFs);
|
|
ProfileCorpus("iris_large", kBigVs, kBigFs);
|
|
|
|
return 0;
|
|
}
|