Skip to content

Shader compilation failure under MoltenVK with polar scalers

STR:

  • Build libplacebo 32837eb1 on macOS with Vulkan support via MoltenVK.
  • Play a file using a polar scaler (e.g. EWA Jinc).

Observe error (log truncated to critical components):

Spent 49.106 ms translating SPIR-V
[mvk-error] VK_ERROR_INITIALIZATION_FAILED: Shader library compile failed (Error code 3):
program_source:101:28: error: zero-length arrays are not permitted in C++
    threadgroup float _250[_247];
                           ^~~~
program_source:102:28: error: zero-length arrays are not permitted in C++
    threadgroup float _262[_259];
                           ^~~~
.
[mvk-error] VK_ERROR_INVALID_SHADER_NV: Compute shader function could not be compiled into pipeline. See previous logged error.
vk_recreate_pipelines(vk, pass, has_spec, NULL, pipe): VK_ERROR_INVALID_SHADER_NV (../src/vulkan/gpu_pass.c:615)
compute shader source:
[  1] #version 450

[ 33] layout(constant_id=0) const int _iw_12_4 = 0; 
[ 34] layout(constant_id=1) const int _ih_13_4 = 0; 

[ 59] shared float _in_11_40[_ih_13_4 * _iw_12_4]; 
[ 60] shared float _in_11_41[_ih_13_4 * _iw_12_4]; 

Note that _in_11_40 is being declared as an array of size 0 * 0, which ultimately makes it through to the MSL generated and compiled by MoltenVK; MSL doesn't support setting array sizes based on function constants (the Metal equivalent of specialization constants). See https://github.com/KhronosGroup/Vulkan-Portability/issues/10 for details.

This comes from this code in src/shaders/sampling.c:

        ident_t iw_c = sh_const(sh, (struct pl_shader_const) {
            .type = PL_VAR_SINT,
            .compile_time = true,
            .name ="iw",
            .data = &iw,
        });

        ident_t ih_c = sh_const(sh, (struct pl_shader_const) {
            .type = PL_VAR_SINT,
            .compile_time = true,
            .name = "ih",
            .data = &ih,
        });

Which calls sh_const in src/shaders.c, with the shader code ultimately being generated by this code in src/dispatch.c:

        const struct pl_shader_const *sc = &res->constants[i];
        ADD(pre, "layout(constant_id=%"PRIu32") const %s %s = 0; \n",
            pass_params->constants[i].id, types[sc->type], sc->name);

I'm currently working around this using this patch:

diff --git a/src/shaders.c b/src/shaders.c
index 4e684a5..a526399 100644
--- a/src/shaders.c
+++ b/src/shaders.c
@@ -258,7 +258,7 @@ ident_t sh_const(pl_shader sh, struct pl_shader_const sc)
     sc.name = sh_fresh(sh, sc.name);
 
     pl_gpu gpu = SH_GPU(sh);
-    if (gpu && gpu->limits.max_constants) {
+    if (gpu && gpu->limits.max_constants && 0) {
         sc.data = pl_memdup(SH_TMP(sh), sc.data, pl_var_type_size(sc.type));
         PL_ARRAY_APPEND(sh, sh->consts, sc);
         return (ident_t) sc.name;

It seems to me that the most appropriate solution would be:

  • Detect that the driver exposes VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME
  • Give pl_shader_const an extra arg indicating that the value will be used to compute an array size
  • If the driver has VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME and the array-size arg is set, skip the if (gpu && gpu->limits.max_constants) path and use the fallback one