Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

warns/ignores precision qualifier #108

Open
danielchasehooper opened this issue Oct 17, 2023 · 10 comments
Open

warns/ignores precision qualifier #108

danielchasehooper opened this issue Oct 17, 2023 · 10 comments

Comments

@danielchasehooper
Copy link

compiling the shader below warns:
warning: '' : all default precisions are highp; use precision statements to quiet warning, e.g.:

which seems wrong because we are using precision statements

compiled with sokol-shdc -l glsl330:metal_macos:glsl300es -i precision.glsl -o out.h

 
@vs blur_vs
in vec2 aVertexPosition;
uniform vs_uniforms {
    mat4 uProjectionMatrix;
    vec4 uv_window;
    vec2 offset1, offset2;
};
out vec2 uv_l2, uv_l1, uv_c, uv_h1, uv_h2;

void main(void) {
  vec2 uv = mix(uv_window.xy, uv_window.zw, aVertexPosition);
  uv_l2 = uv - offset2;
  uv_l1 = uv - offset1;
  uv_c  = uv;
  uv_h1 = uv + offset1;
  uv_h2 = uv + offset2;

  gl_Position =  vec4((uProjectionMatrix*vec4(aVertexPosition,0,1.0)).xy, 0,1.0);
}
@end

@fs blur_fs
precision highp float;
uniform texture2D tex;
uniform sampler blur_sampler;
uniform fs_uniforms {
    vec3 weight;
    vec4 uv_window;
};
in vec2 uv_l2, uv_l1, uv_c, uv_h1, uv_h2;
out vec4 FragColor;

void main(void) {
  lowp float s_l2,s_l1,s_c,s_h1,s_h2; // warning: '' : all default precisions are highp; use precision statements to quiet warning, e.g.:
  s_l2 = texture(sampler2D(tex, blur_sampler), uv_l2).a;
  s_l1 = texture(sampler2D(tex, blur_sampler), uv_l1).a;
  s_c  = texture(sampler2D(tex, blur_sampler), uv_c).a;
  s_h1 = texture(sampler2D(tex, blur_sampler), uv_h1).a;
  s_h2 = texture(sampler2D(tex, blur_sampler), uv_h2).a;
  float col;
  col =  s_c * weight.x;
  col += (s_l1 + s_h1) * weight.y;
  col += (s_l2 + s_h2) * weight.z;
  FragColor = vec4(col,col,col,col);
}

@end

@program blur blur_vs blur_fs
@floooh
Copy link
Owner

floooh commented Oct 18, 2023

Hmm ok, I actually need to check how SPIRVCross assigns precision qualifiers and how much control we actually have over the process, e.g. in the regular texcube-sapp shader (which doesn't have any manual precision qualifiers) it looks like this (e.g. a global specifier which sets floats to mediump, but then specific highp specifiers in various places).

    #version 300 es
    precision mediump float;
    precision highp int;

    uniform highp sampler2D tex_smp;

    layout(location = 0) out highp vec4 frag_color;
    in highp vec2 uv;
    in highp vec4 color;

PS: apart from that, SPIRVCross allows to configure the default precision, but sokol-shdc currently doesn't expose that and uses the default settings (we could do this by extending the existing @glsl_options I guess:

https://github.com/KhronosGroup/SPIRV-Cross/blob/2de1265fca722929785d9acdec4ab728c47a0254/spirv_glsl.hpp#L184-L190

@floooh
Copy link
Owner

floooh commented Oct 18, 2023

Btw, here's a fragment shader code snippet from the mandelbulb sample (sdf-sapp, input shader code: https://github.com/floooh/sokol-samples/blob/master/sapp/sdf-sapp.glsl), I looked at this specifically, because the mediump precision would be too low for that shader.

highp float sd_mandelbulb(highp vec3 p, inout highp vec4 res_color)
    {
        highp vec3 w = p;
        highp float _49 = dot(p, p);
        highp float m = _49;
        highp vec4 trap = vec4(abs(p), _49);
        highp float dz = 1.0;
        for (int i = 0; i < 4; i++)
        {
            highp float _76 = m * m;
            dz = (8.0 * sqrt(((_76 * _76) * _76) * m)) * dz + 1.0;
            highp vec3 _653 = w;
            highp float _100 = _653.x * _653.x;
            highp float _104 = _100 * _100;
            highp float _112 = _653.y * _653.y;
            highp float _124 = _653.z * _653.z;
            highp float _128 = _124 * _124;
            highp float _132 = _653.x * _653.x + _124;

...looks like SPIRVCross forces anything to highp precision anyway, so putting any precision qualifiers into the input source code will probably be ignored.

Also, SPIRVCross actually allows to configure the global precision qualifier, but sokol-shdc currently doesn't expose that and instead uses the default settings (we could extend the existing @glsl_options tag for that though):

https://github.com/KhronosGroup/SPIRV-Cross/blob/2de1265fca722929785d9acdec4ab728c47a0254/spirv_glsl.hpp#L184-L190

Not sure how much the global precision mediump float; statement matters though if everything is explicitely forced to highp anyway.

@danielchasehooper
Copy link
Author

Being able to specify a reduced global precision to sokol-shdc wouldn't be that useful, because there are usually only some variables in a shader that can fit their values in a reduced precision range.

Are you saying that SPIRVCross doesn't support tracking per-variable precision qualifiers?

@floooh
Copy link
Owner

floooh commented Oct 19, 2023

Are you saying that SPIRVCross doesn't support tracking per-variable precision qualifiers?

I'm not sure actually, what it does seem to do is slap a highp to every local variable despite the default global specifier being mediump.

I'll need to check what happens when explicitly setting a local variable to mediump in the input source code (e.g. will SPIRVCross preserve that, or overwrite it to highp)

@floooh
Copy link
Owner

floooh commented Oct 23, 2023

I'm currently tinkering around with this, and the behaviour is "interesting".

First, that warning isn't actually coming out of SPIRVCross, but is from the first compile pass from GLSL to SPIRV which is handled by glslang (https://github.com/KhronosGroup/glslang), and I wasn't able to silence this warning in any way, it shows up as soon as local precision qualifiers on variables are used.

SPIRVCross on the other hand seems to behave correctly, and the rule seems to be like this:

  • SPIRVCross generally writes its own global precision mediump float or precision highp float, and this doesn't correspond with any global precision qualifiers in the input SPIRV but is instead configured in the SPIRVCross compiler API.
  • but SPIRVCross honors the input precision qualifier by annotating each individual value, but only if it differs from SPIRVCross's default precision which is configured in the SPIRVCross compiler API.

Meaning:

  • if you write precision highp float in the input shader, and SPIRVCross is configured to write a global precision highp float, then SPIRVCross will not write individual precision qualifiers (and the same for medium if both the input GLSL and the SPIRVCross default config agree on 'mediump')

  • SPIRVCross honors individual precision qualifiers if they differ from the default.

I suspect that the SPIRV spec doesn't have the concept of "global precision qualifiers" and only carries that information for each variable, and that's why SPIRVCross has to use a config option to assume a "default precision", and then annotates all variables that differ from the default precision.

...so apart from that warning in glslangValidator everything appears to work as expected (e.g. you can set a global precision qualifier at the top of an @fs shader snippet (the default is highp), and then annotate individual variables that differ from that precision. So in the default case (no global precision in the input GLSL) it only makes sense to annoate variables with mediump.

Next I'll need to check what to do about that warning (it's not only confusing, but also clipped)

@danielchasehooper
Copy link
Author

Apple says that 16 bit floats are twice as fast as 32 bit floats - I'd love to optimize some of our shaders with them but I'm unable to get sokol-shdc to emit the half type in its metal output. Am I missing something, or is there in fact a problem related to the interesting behavior you found?

for example, if you compile the below code with sokol-shdc -l metal_macos -i test.glsl -o out.h
none of the variables will be of type half or half2

@vs layer_vs

in mat2 aRotateScale;
in vec4 aColor; // premultiplied alpha
in vec2 aVertexPosition;
in vec2 aTranslate;
in vec2 aSize;
in float aCornerRadius;

out lowp vec4 color;
out lowp vec2 coord;
out lowp vec2 half_size;
out lowp float cornerRadius;

void main(void) {
    color = aColor;
    half_size = aSize/2.0;
    cornerRadius = aCornerRadius;
    gl_Position =  vec4(aRotateScale * aVertexPosition + aTranslate, 1, 1);
    coord = (aVertexPosition - 0.5) * aSize;
}
@end

@fs layer_fs

in lowp vec4 color; // must be premultiplied alpha
in lowp vec2 coord;
in lowp vec2 half_size;
in lowp float cornerRadius;

out vec4 FragColor;

lowp float round_rect_sdf(lowp vec2 p,lowp  vec2 rect_half_size,lowp  float radius){
    lowp vec2 d = abs(p) - rect_half_size + radius;
    return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - radius;
}

void main(void) {

    lowp float b = round_rect_sdf( coord, half_size, cornerRadius );

    lowp float alpha = (1.-clamp(b+1.5,0.,1.));
    FragColor = color * alpha;
}
@end

@program layer layer_vs layer_fs

@danielchasehooper
Copy link
Author

Found this: https://gist.github.com/zeux/c83001968e06fe0b789fa4bd513860c6

Unfortunately, SPIRV-Cross doesn't support RelaxedPrecision for Metal Shading Language. To get around that, you need to enable 16-bit type support when building SPIRV for use with SPIRV-Cross, which will result in the actual half-precision types being emitted

@floooh
Copy link
Owner

floooh commented Jan 24, 2024

Right. As far as I'm aware I'm not doing anything in sokol-shdc to prevent half precision. I would have recommended to check the SPIRVCross issue tracker and documentation though, such limitations are usually mentioned there.

The SPIRV compilation happens here:

static bool compile(EShLanguage stage, slang_t::type_t slang, const std::string& src, const input_t& inp, int snippet_index, spirv_t& out_spirv) {
const char* sources[1] = { src.c_str() };
const int sourcesLen[1] = { (int) src.length() };
const char* sourcesNames[1] = { inp.base_path.c_str() };
// compile GLSL vertex- or fragment-shader
glslang::TShader shader(stage);
// FIXME: add custom defines here: compiler.addProcess(...)
shader.setStringsWithLengthsAndNames(sources, sourcesLen, sourcesNames, 1);
shader.setEnvInput(glslang::EShSourceGlsl, stage, glslang::EShClientVulkan, 100);
shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_0);
shader.setEnvTarget(glslang::EshTargetSpv, glslang::EShTargetSpv_1_0);
// NOTE: where using AutoMapBinding here, but this will just throw all bindings
// into descriptor set null, which is not what we actually want.
// We'll fix up the bindings later before calling SPIRVCross.
shader.setAutoMapLocations(true);
shader.setAutoMapBindings(true);
bool parse_success = shader.parse(GetDefaultResources(), 100, false, EShMsgDefault);
infolog_to_errors(shader.getInfoLog(), inp, snippet_index, out_spirv.errors);
infolog_to_errors(shader.getInfoDebugLog(), inp, snippet_index, out_spirv.errors);
if (!parse_success) {
return false;
}
// "link" into a program
glslang::TProgram program;
program.addShader(&shader);
bool link_success = program.link(EShMsgDefault);
infolog_to_errors(program.getInfoLog(), inp, snippet_index, out_spirv.errors);
infolog_to_errors(program.getInfoDebugLog(), inp, snippet_index, out_spirv.errors);
if (!link_success) {
return false;
}
bool map_success = program.mapIO();
infolog_to_errors(program.getInfoLog(), inp, snippet_index, out_spirv.errors);
infolog_to_errors(program.getInfoDebugLog(), inp, snippet_index, out_spirv.errors);
if (!map_success) {
return false;
}
// translate intermediate representation to SPIRV
const glslang::TIntermediate* im = program.getIntermediate(stage);
assert(im);
spv::SpvBuildLogger spv_logger;
glslang::SpvOptions spv_options;
// disable the optimizer passes, we'll run our own after the translation
spv_options.generateDebugInfo = false;
spv_options.stripDebugInfo = false; // NOTE: don't set this to true as the info is needed for reflection!
spv_options.disableOptimizer = true;
spv_options.optimizeSize = true;
spv_options.disassemble = false;
spv_options.validate = false;
spv_options.emitNonSemanticShaderDebugInfo = false;
spv_options.emitNonSemanticShaderDebugSource = false;
out_spirv.blobs.push_back(spirv_blob_t(snippet_index));
out_spirv.blobs.back().source = src;
glslang::GlslangToSpv(*im, out_spirv.blobs.back().bytecode, &spv_logger, &spv_options);
std::string spirv_log = spv_logger.getAllMessages();
if (!spirv_log.empty()) {
// FIXME: need to parse string for errors and translate to errmsg_t objects?
// haven't seen a case yet where this generates log messages
fmt::print("{}", spirv_log);
}
// run optimizer passes
spirv_optimize(slang, out_spirv.blobs.back().bytecode);
return true;
}

...maybe there's an option on glslang::TShader to enable this 16-bit type support for GLSL input?

Hmm, on the other hand: https://gist.github.com/zeux/c83001968e06fe0b789fa4bd513860c6?permalink_comment_id=2942898#gistcomment-2942898

@floooh
Copy link
Owner

floooh commented Jan 24, 2024

(PS: I'm actually surprised that Vulkan-style GLSL didn't add a proper half type)

@staminajim
Copy link

I've found that putting:

#extension GL_EXT_shader_explicit_arithmetic_types_float16 : enable

in your shader code, and use any of the 16 bit types mentioned here:
https://github.com/KhronosGroup/GLSL/blob/main/extensions/ext/GL_EXT_shader_explicit_arithmetic_types.txt

(for example f16vec4 instead of vec4), then the emitted metal shader code uses half precision

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants