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

Adding preliminary SpecConstant support #154

Open
wants to merge 47 commits into
base: main
Choose a base branch
from

Conversation

shangjiaxuan
Copy link

@shangjiaxuan shangjiaxuan commented Jul 28, 2022

Edited Description

Added specialization constant parsing and evaluation code (32 bit and 64 bit operands).

Features Added:

  1. Specialization constant parsing code, mainly taken from godot/godot with permission (see Implement specialization constants godotengine/godot#50325).
  2. Specialization constant OpSpecConstantOp and OpSpecConstantComposite simple AST evaluation (no control flow, functions, temporary variables (registers), etc) with operands of subset available for vulkan (no kernel execution mode (sycl) specific features).
  3. Parsing of compute shader spec-constant execution modes (local_size_n, local_size_n_id in glsl and numthreads in hlsl) with support for builtin WorkGroupSize when last checked is widely used in compilers like glslc and was in preparation for deprecation for a long time (since spv1.5).
  4. Evaluation contexts that can have multiple instances, each with different spec-constant value states.
  5. These features come with tests and the executable module gives text output of spec-constants and evaluated values. YAML output not changed.
  6. Extra helper function to use with vulkan that fills VkSpecializationInfo with evaluation-internal data (user still need to allocate VkSpecializationMapEntry).

Sample Reflection

Code taken and adapted from spirv-cross (in tests/spec_constant/test_orig.glsl):

#version 450

layout(local_size_x_id = 8) in;

layout(constant_id = 0) const bool TRUE = true;
layout(constant_id = 1) const bool FALSE = false;
layout(constant_id = 2) const int SONE = 1;
layout(constant_id = 3) const int STWO = 2;
layout(constant_id = 4) const int SNEG_TWO = -2;
layout(constant_id = 5) const uint UONE = 1;
layout(constant_id = 6) const uint UTWO = 2;
layout(constant_id = 7) const int SNEG_THREE = -3;

const uint IADD = SONE + STWO + UONE + UTWO; // 6
const uint ISUB = UTWO - SNEG_TWO;               // 4
const uint IMUL = UTWO * UTWO;               // 4
const uint UDIV = UTWO / UTWO;               // 1
const int SDIV = STWO / SNEG_THREE;            // 0
const int SREM = STWO % SNEG_THREE;          // 2, instruction changed in patch...
const int SMOD = STWO % SNEG_THREE;          // -1
const uint UMOD = IADD % IMUL;               // 2

const uint LSHL = IADD << (ISUB - 3);              // 12
const uint RSHL = IADD >> (ISUB - 3);              // 3
const int RSHA = (-int(IADD)) >> (1-SDIV);         // -3

const bool IEQ = IADD == (ISUB - 3);               // false
const bool INEQ = IADD != (ISUB - 3);              // true
const bool ULT = IADD < (ISUB - 3);                // false
const bool ULE = IADD <= (ISUB - 3);               // false
const bool UGT = IADD > (ISUB - 3);                // true
const bool UGE = IADD >= (ISUB - 3);               // true

const bool SLT = SMOD < SREM;                // true
const bool SLE = SMOD <= SREM;               // true
const bool SGT = SMOD > SREM;                // false
const bool SGE = SMOD >= SREM;               // false

const bool LOR = IEQ || SLT;                 // true
const bool LAND = IEQ && SLT;                // false
const bool LNOT = !LOR;                      // false

const uint AND = IADD & IADD;                // 6
const uint OR = IADD | (ISUB - 3);                 // 7
const uint XOR = IADD ^ IADD;                // 0
const uint NOT = ~XOR;                       // UINT_MAX

const bool LEQ = LAND == LNOT;               // true
const bool LNEQ = LAND != LNOT;              // false

const uint SEL = IEQ ? IADD : (ISUB - 3);          // 1

#define DUMMY_SSBO(name, bind, size) layout(std430, set = 0, binding = bind) buffer SSBO_##name { float val[size]; float dummy; } name

// Normalize all sizes to 1 element so that the default offsets in glslang matches up with what we should be computing.
// If we do it right, we should get no layout(offset = N) expressions.
DUMMY_SSBO(IAdd, 0, IADD - 5);
DUMMY_SSBO(ISub, 1, ISUB - 3);
DUMMY_SSBO(IMul, 2, IMUL - 3);
DUMMY_SSBO(UDiv, 3, UDIV);
DUMMY_SSBO(SDiv, 4, SDIV + 1);
DUMMY_SSBO(SRem, 5, SREM - 1);
DUMMY_SSBO(SMod, 6, SMOD + 2);
DUMMY_SSBO(UMod, 7, UMOD - 1);
DUMMY_SSBO(LShl, 8, LSHL - 11);
DUMMY_SSBO(RShl, 9, RSHL - 2);
DUMMY_SSBO(RSha, 10, RSHA + 4);
DUMMY_SSBO(IEq, 11, IEQ ? 2 : 1);
DUMMY_SSBO(INeq, 12, INEQ ? 1 : 2);
DUMMY_SSBO(Ult, 13, ULT ? 2 : 1);
DUMMY_SSBO(Ule, 14, ULE ? 2 : 1);
DUMMY_SSBO(Ugt, 15, UGT ? 1 : 2);
DUMMY_SSBO(Uge, 16, UGE ? 1 : 2);
DUMMY_SSBO(Slt, 17, SLT ? 1 : 2);
DUMMY_SSBO(Sle, 18, SLE ? 1 : 2);
DUMMY_SSBO(Sgt, 19, SGT ? 2 : 1);
DUMMY_SSBO(Sge, 20, SGE ? 2 : 1);
DUMMY_SSBO(Lor, 21, LOR ? 1 : 2);
DUMMY_SSBO(Land, 22, LAND ? 2 : 1);
DUMMY_SSBO(Lnot, 23, LNOT ? 2 : 1);
DUMMY_SSBO(And, 24, AND - 5);
DUMMY_SSBO(Or, 25, OR - 6);
DUMMY_SSBO(Xor, 26, XOR + 1);
DUMMY_SSBO(Not, 27, NOT - 0xfffffffeu);
DUMMY_SSBO(Leq, 28, LEQ ? 1 : 2);
DUMMY_SSBO(Lneq, 29, LNEQ ? 2 : 1);
DUMMY_SSBO(Sel, 30, SEL);

void main()
{
	IAdd.val[0] = 0.0;
	ISub.val[0] = 0.0;
	IMul.val[0] = 0.0;
	UDiv.val[0] = 0.0;
	SDiv.val[0] = 0.0;
	SRem.val[0] = 0.0;
	SMod.val[0] = 0.0;
	UMod.val[0] = 0.0;
	LShl.val[0] = 0.0;
	RShl.val[0] = 0.0;
	RSha.val[0] = 0.0;
	IEq.val[0] = 0.0;
	INeq.val[0] = 0.0;
	Ult.val[0] = 0.0;
	Ule.val[0] = 0.0;
	Ugt.val[0] = 0.0;
	Uge.val[0] = 0.0;
	Slt.val[0] = 0.0;
	Sle.val[0] = 0.0;
	Sgt.val[0] = 0.0;
	Sge.val[0] = 0.0;
	Lor.val[0] = 0.0;
	Land.val[0] = 0.0;
	Lnot.val[0] = 0.0;
	And.val[0] = 0.0;
	Or.val[0] = 0.0;
	Xor.val[0] = 0.0;
	Not.val[0] = 0.0;
	Leq.val[0] = 0.0;
	Lneq.val[0] = 0.0;
	Sel.val[0] = 0.0;
}

Patch (in tests/spec_constant/test_32bit.spv.dis.patch):

10c10,11
<                OpExecutionMode %main LocalSize 1 1 1
---
>                ; should ignore this if working with WorkGroupSize builtin
>                OpExecutionMode %main LocalSize 2 3 4
425c426
<        %SREM = OpSpecConstantOp %int SMod %STWO %SNEG_THREE
---
>        %SREM = OpSpecConstantOp %int SRem %STWO %SNEG_THREE
601c602,603
<         %262 = OpSpecConstant %uint 1
---
>         ; default value should be respected, even if no compiler write things other than 1
>         %262 = OpSpecConstant %uint 4

output:

generator       : Khronos SPIR-V Tools Assembler
source lang     : GLSL
source lang ver : 450
source file     :
entry point     : main (stage=CS)
local size      : (4, 1, 1)


  Sepecialization constants: 9

    0:
      spirv id   : 34
      constant id: 2
      name       : SONE
      type       : signed 32 bit integer
      default    : 1
    1:
      spirv id   : 35
      constant id: 3
      name       : STWO
      type       : signed 32 bit integer
      default    : 2
    2:
      spirv id   : 36
      constant id: 5
      name       : UONE
      type       : unsigned 32 bit integer
      default    : 1
    3:
      spirv id   : 37
      constant id: 6
      name       : UTWO
      type       : unsigned 32 bit integer
      default    : 2
    4:
      spirv id   : 40
      constant id: 4
      name       : SNEG_TWO
      type       : signed 32 bit integer
      default    : -2
    5:
      spirv id   : 47
      constant id: 7
      name       : SNEG_THREE
      type       : signed 32 bit integer
      default    : -3
    6:
      spirv id   : 135
      constant id: 8
      name       :
      type       : unsigned 32 bit integer
      default    : 4
    7:
      spirv id   : 102
      constant id: 0
      name       : TRUE
      type       : boolean
      default    : 1
    8:
      spirv id   : 103
      constant id: 1
      name       : FALSE
      type       : boolean
      default    : 0


  Descriptor bindings: 31

    Binding 0.0
      spirv id : 3
      set      : 0
      binding  : 0
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : IAdd (SSBO_IAdd)
          // size = 0, padded size = 0
          struct SSBO_IAdd {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } IAdd;


    Binding 0.1
      spirv id : 4
      set      : 0
      binding  : 1
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : ISub (SSBO_ISub)
          // size = 0, padded size = 0
          struct SSBO_ISub {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } ISub;


    Binding 0.2
      spirv id : 5
      set      : 0
      binding  : 2
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : IMul (SSBO_IMul)
          // size = 0, padded size = 0
          struct SSBO_IMul {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } IMul;


    Binding 0.3
      spirv id : 6
      set      : 0
      binding  : 3
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : UDiv (SSBO_UDiv)
          // size = 0, padded size = 0
          struct SSBO_UDiv {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } UDiv;


    Binding 0.4
      spirv id : 7
      set      : 0
      binding  : 4
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : SDiv (SSBO_SDiv)
          // size = 0, padded size = 0
          struct SSBO_SDiv {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } SDiv;


    Binding 0.5
      spirv id : 8
      set      : 0
      binding  : 5
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : SRem (SSBO_SRem)
          // size = 0, padded size = 0
          struct SSBO_SRem {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } SRem;


    Binding 0.6
      spirv id : 9
      set      : 0
      binding  : 6
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : SMod (SSBO_SMod)
          // size = 0, padded size = 0
          struct SSBO_SMod {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } SMod;


    Binding 0.7
      spirv id : 10
      set      : 0
      binding  : 7
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : UMod (SSBO_UMod)
          // size = 0, padded size = 0
          struct SSBO_UMod {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } UMod;


    Binding 0.8
      spirv id : 11
      set      : 0
      binding  : 8
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : LShl (SSBO_LShl)
          // size = 0, padded size = 0
          struct SSBO_LShl {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } LShl;


    Binding 0.9
      spirv id : 12
      set      : 0
      binding  : 9
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : RShl (SSBO_RShl)
          // size = 0, padded size = 0
          struct SSBO_RShl {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } RShl;


    Binding 0.10
      spirv id : 13
      set      : 0
      binding  : 10
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : RSha (SSBO_RSha)
          // size = 0, padded size = 0
          struct SSBO_RSha {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } RSha;


    Binding 0.11
      spirv id : 14
      set      : 0
      binding  : 11
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : IEq (SSBO_IEq)
          // size = 0, padded size = 0
          struct SSBO_IEq {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } IEq;


    Binding 0.12
      spirv id : 15
      set      : 0
      binding  : 12
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : INeq (SSBO_INeq)
          // size = 0, padded size = 0
          struct SSBO_INeq {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } INeq;


    Binding 0.13
      spirv id : 16
      set      : 0
      binding  : 13
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Ult (SSBO_Ult)
          // size = 0, padded size = 0
          struct SSBO_Ult {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Ult;


    Binding 0.14
      spirv id : 17
      set      : 0
      binding  : 14
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Ule (SSBO_Ule)
          // size = 0, padded size = 0
          struct SSBO_Ule {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Ule;


    Binding 0.15
      spirv id : 18
      set      : 0
      binding  : 15
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Ugt (SSBO_Ugt)
          // size = 0, padded size = 0
          struct SSBO_Ugt {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Ugt;


    Binding 0.16
      spirv id : 19
      set      : 0
      binding  : 16
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Uge (SSBO_Uge)
          // size = 0, padded size = 0
          struct SSBO_Uge {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Uge;


    Binding 0.17
      spirv id : 20
      set      : 0
      binding  : 17
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Slt (SSBO_Slt)
          // size = 0, padded size = 0
          struct SSBO_Slt {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Slt;


    Binding 0.18
      spirv id : 21
      set      : 0
      binding  : 18
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Sle (SSBO_Sle)
          // size = 0, padded size = 0
          struct SSBO_Sle {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Sle;


    Binding 0.19
      spirv id : 22
      set      : 0
      binding  : 19
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Sgt (SSBO_Sgt)
          // size = 0, padded size = 0
          struct SSBO_Sgt {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Sgt;


    Binding 0.20
      spirv id : 23
      set      : 0
      binding  : 20
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Sge (SSBO_Sge)
          // size = 0, padded size = 0
          struct SSBO_Sge {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Sge;


    Binding 0.21
      spirv id : 24
      set      : 0
      binding  : 21
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Lor (SSBO_Lor)
          // size = 0, padded size = 0
          struct SSBO_Lor {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Lor;


    Binding 0.22
      spirv id : 25
      set      : 0
      binding  : 22
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Land (SSBO_Land)
          // size = 0, padded size = 0
          struct SSBO_Land {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Land;


    Binding 0.23
      spirv id : 26
      set      : 0
      binding  : 23
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Lnot (SSBO_Lnot)
          // size = 0, padded size = 0
          struct SSBO_Lnot {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Lnot;


    Binding 0.24
      spirv id : 27
      set      : 0
      binding  : 24
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : And (SSBO_And)
          // size = 0, padded size = 0
          struct SSBO_And {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } And;


    Binding 0.25
      spirv id : 28
      set      : 0
      binding  : 25
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Or (SSBO_Or)
          // size = 0, padded size = 0
          struct SSBO_Or {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Or;


    Binding 0.26
      spirv id : 29
      set      : 0
      binding  : 26
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Xor (SSBO_Xor)
          // size = 0, padded size = 0
          struct SSBO_Xor {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Xor;


    Binding 0.27
      spirv id : 30
      set      : 0
      binding  : 27
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Not (SSBO_Not)
          // size = 0, padded size = 0
          struct SSBO_Not {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Not;


    Binding 0.28
      spirv id : 31
      set      : 0
      binding  : 28
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Leq (SSBO_Leq)
          // size = 0, padded size = 0
          struct SSBO_Leq {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Leq;


    Binding 0.29
      spirv id : 32
      set      : 0
      binding  : 29
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Lneq (SSBO_Lneq)
          // size = 0, padded size = 0
          struct SSBO_Lneq {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Lneq;


    Binding 0.30
      spirv id : 33
      set      : 0
      binding  : 30
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Sel (SSBO_Sel)
          // size = 0, padded size = 0
          struct SSBO_Sel {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Sel;

Note that since the compiler uses WorkGroupSize builtin, OpExecutionMode changes are overridden completely by the builtin instruction, instead of using the patched (2, 3, 4) value from OpExecutionMode.

Notes on ABI

Recent changes moved the parsed localsize mode flag to internal field, so this pr should be ABI-compatible with binaries built using older headers (aka from godot. Since SpvReflectShaderModule has new members specialization_constant_count and specialization_constants, on-stack or struct member SpvReflectShaderModules are not ABI-compatible with main reporsitory.) now.

Notes on Arithmetic Operations.

As per glsl specification, glsl does not guarantee (a/b)*b + (a%b)==a for integer types. When using glslc, when last checked, a%b yields OpSMod instead of OpSRem. This means the following signed integer operations gives:

3/(-2)=-1
3%(-2)=-1

instead of

3/(-2)=-1
3%(-2)=1

This is because remainder is defined as having same sign as dividend, while modulus is defined as having same sign as divisor. Modulus operator % is currently defined in c/c++ as taking remainder (same for most similar operations like fmod).

This is important since the test case required special treatment in patching the assembly as in tests/spec_constant/test_32bit.spv.dis.patch

Original Description

Used code form godot/godot as starting point. See #121

Next steps: 64 bit types need 2 words as operand, SpecConstantOp need implementation for evaluating spec constant (spirv-cross has an implementation for uint KhronosGroup/SPIRV-Cross#1463). Also maybe composite type evaluation support?

@CLAassistant
Copy link

CLAassistant commented Jul 28, 2022

CLA assistant check
All committers have signed the CLA.

@shangjiaxuan
Copy link
Author

shangjiaxuan commented Jul 28, 2022

CLA may need contributers to godotengine/godot#50325. (Godot is licensed under MIT license, so maybe the agreement is compatible? Not a law expert...)

@shangjiaxuan
Copy link
Author

Related issue #121 (From godot, where starting of current pull is based on)

shangjiaxuan and others added 2 commits July 28, 2022 19:20
Used code form godot/godot as starting point. See KhronosGroup#121

Next steps: 64 bit types need 2 words as operand, `SpecConstantOp` need implementation for evaluating spec constant (spirv-cross has an implementation for uint KhronosGroup/SPIRV-Cross#1463). Also maybe composite type evaluation support?

Co-Authored-By: Juan Linietsky <[email protected]>
Next step would be the constant evaluator
Keeping everything in one is difficult, also there might be better ways to do this (especially with the variant types).

Modified some of the evaluation code for spec-constant array size, but not finished.
`memcpy` is required for strict aliasing rule. union member offset in c++ requires c++14 to guarantee (though c style code should always work on reasonable compilers.)
@shangjiaxuan
Copy link
Author

There may be better ways to evaluate the values (e.g. using some jit compiler that compiles to cpu native code), not sure if all the switch is easily maintainable (and if if it's possible through some #defines). Will add test cases for maintenance.

@shangjiaxuan
Copy link
Author

shangjiaxuan commented Jul 29, 2022

Currently using the following test from spirv-cross will result in correct specialized array size.

#version 460

layout(constant_id = 6) const uint UTWO = 2;

const uint IMUL = UTWO * UTWO;               // 4

layout(std430, set = 0, binding = 2)buffer SSBO_IMul{ float val[IMUL - 3]; float dummy; } IMul;

layout(local_size_x_id = 1, local_size_y_id = 2, local_size_z_id = 3) in;
void main(void)
{}

related spirv assembly:

               OpDecorate %UTWO SpecId 6
               OpDecorate %_arr_float_11 ArrayStride 4
               OpMemberDecorate %SSBO_IMul 0 Offset 0
               OpMemberDecorate %SSBO_IMul 1 Offset 4
               OpDecorate %SSBO_IMul Block
               OpDecorate %IMul DescriptorSet 0
               OpDecorate %IMul Binding 2
       %uint = OpTypeInt 32 0
       %UTWO = OpSpecConstant %uint 2
       %IMUL = OpSpecConstantOp %uint IMul %UTWO %UTWO
      %float = OpTypeFloat 32
     %uint_3 = OpConstant %uint 3
         %11 = OpSpecConstantOp %uint ISub %IMUL %uint_3
%_arr_float_11 = OpTypeArray %float %11
  %SSBO_IMul = OpTypeStruct %_arr_float_11 %float
%_ptr_StorageBuffer_SSBO_IMul = OpTypePointer StorageBuffer %SSBO_IMul
       %IMul = OpVariable %_ptr_StorageBuffer_SSBO_IMul StorageBuffer

spirv-reflect output:

Descriptor bindings: 1

    Binding 0.2
      spirv id : 15
      set      : 0
      binding  : 2
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : false
      name     : IMul (SSBO_IMul)
          // size = 0, padded size = 0
          struct SSBO_IMul {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4 UNUSED
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } IMul;

Changing constant value should work, but not tested.
Maybe storing a specconstantop dictionary can make most duplicate result evaluation go away. That may be included (compute spec ops after user specify a current value , or lazy init?)?

Still WIP.

Vector composition and logical (boolean) ops left...
@shangjiaxuan
Copy link
Author

Moved much of recurring code into #defines, also scalar integer arithmetic should be working now. Boolean and vector types still unimplemented.

Code too lengthy, difficult to maintain...
Operations done, but compositing and de-compositing vectors not included.
16 bit floating point quantization and kernel ops not implemented.
@shangjiaxuan
Copy link
Author

Scalar constants should work now. Tested with following glsl from spirv-cross:

#version 450

layout(local_size_x = 1) in;

layout(constant_id = 0) const bool TRUE = true;
layout(constant_id = 1) const bool FALSE = false;
layout(constant_id = 2) const int SONE = 1;
layout(constant_id = 3) const int STWO = 2;
layout(constant_id = 4) const int SNEG_TWO = -2;
layout(constant_id = 5) const uint UONE = 1;
layout(constant_id = 6) const uint UTWO = 2;
layout(constant_id = 7) const int SNEG_THREE = -3;

const uint IADD = SONE + STWO + UONE + UTWO; // 6
const uint ISUB = UTWO - SONE;               // 1
const uint IMUL = UTWO * UTWO;               // 4
const uint UDIV = UTWO / UTWO;               // 1
const int SDIV = STWO / SNEG_TWO;            // -1
//const int SREM = STWO % SNEG_THREE;          // 1
const int SREM = 1;
const int SMOD = STWO % SNEG_THREE;          // -1
const uint UMOD = IADD % IMUL;               // 2

const uint LSHL = IADD << ISUB;              // 12
const uint RSHL = IADD >> ISUB;              // 3
const int RSHA = (-int(IADD)) >> (-SDIV);         // -3

const bool IEQ = IADD == ISUB;               // false
const bool INEQ = IADD != ISUB;              // true
const bool ULT = IADD < ISUB;                // false
const bool ULE = IADD <= ISUB;               // false
const bool UGT = IADD > ISUB;                // true
const bool UGE = IADD >= ISUB;               // true

const bool SLT = SMOD < SREM;                // true
const bool SLE = SMOD <= SREM;               // true
const bool SGT = SMOD > SREM;                // false
const bool SGE = SMOD >= SREM;               // false

const bool LOR = IEQ || SLT;                 // true
const bool LAND = IEQ && SLT;                // false
const bool LNOT = !LOR;                      // false

const uint AND = IADD & IADD;                // 6
const uint OR = IADD | ISUB;                 // 7
const uint XOR = IADD ^ IADD;                // 0
const uint NOT = ~XOR;                       // UINT_MAX

const bool LEQ = LAND == LNOT;               // true
const bool LNEQ = LAND != LNOT;              // false

const uint SEL = IEQ ? IADD : ISUB;          // 1

#define DUMMY_SSBO(name, bind, size) layout(std430, set = 0, binding = bind) buffer SSBO_##name { float val[size]; float dummy; } name

// Normalize all sizes to 1 element so that the default offsets in glslang matches up with what we should be computing.
// If we do it right, we should get no layout(offset = N) expressions.
DUMMY_SSBO(IAdd, 0, IADD - 5);
DUMMY_SSBO(ISub, 1, ISUB);
DUMMY_SSBO(IMul, 2, IMUL - 3);
DUMMY_SSBO(UDiv, 3, UDIV);
DUMMY_SSBO(SDiv, 4, SDIV + 2);
DUMMY_SSBO(SRem, 5, SREM);
DUMMY_SSBO(SMod, 6, SMOD + 2);
DUMMY_SSBO(UMod, 7, UMOD - 1);
DUMMY_SSBO(LShl, 8, LSHL - 11);
DUMMY_SSBO(RShl, 9, RSHL - 2);
DUMMY_SSBO(RSha, 10, RSHA + 4);
DUMMY_SSBO(IEq, 11, IEQ ? 2 : 1);
DUMMY_SSBO(INeq, 12, INEQ ? 1 : 2);
DUMMY_SSBO(Ult, 13, ULT ? 2 : 1);
DUMMY_SSBO(Ule, 14, ULE ? 2 : 1);
DUMMY_SSBO(Ugt, 15, UGT ? 1 : 2);
DUMMY_SSBO(Uge, 16, UGE ? 1 : 2);
DUMMY_SSBO(Slt, 17, SLT ? 1 : 2);
DUMMY_SSBO(Sle, 18, SLE ? 1 : 2);
DUMMY_SSBO(Sgt, 19, SGT ? 2 : 1);
DUMMY_SSBO(Sge, 20, SGE ? 2 : 1);
DUMMY_SSBO(Lor, 21, LOR ? 1 : 2);
DUMMY_SSBO(Land, 22, LAND ? 2 : 1);
DUMMY_SSBO(Lnot, 23, LNOT ? 2 : 1);
DUMMY_SSBO(And, 24, AND - 5);
DUMMY_SSBO(Or, 24, OR - 6);
DUMMY_SSBO(Xor, 24, XOR + 1);
DUMMY_SSBO(Not, 25, NOT - 0xfffffffeu);
DUMMY_SSBO(Leq, 26, LEQ ? 1 : 2);
DUMMY_SSBO(Lneq, 27, LNEQ ? 2 : 1);
DUMMY_SSBO(Sel, 28, SEL);

void main()
{
	IAdd.val[0] = 0.0;
	ISub.val[0] = 0.0;
	IMul.val[0] = 0.0;
	UDiv.val[0] = 0.0;
	SDiv.val[0] = 0.0;
	SRem.val[0] = 0.0;
	SMod.val[0] = 0.0;
	UMod.val[0] = 0.0;
	LShl.val[0] = 0.0;
	RShl.val[0] = 0.0;
	RSha.val[0] = 0.0;
	IEq.val[0] = 0.0;
	INeq.val[0] = 0.0;
	Ult.val[0] = 0.0;
	Ule.val[0] = 0.0;
	Ugt.val[0] = 0.0;
	Uge.val[0] = 0.0;
	Slt.val[0] = 0.0;
	Sle.val[0] = 0.0;
	Sgt.val[0] = 0.0;
	Sge.val[0] = 0.0;
	Lor.val[0] = 0.0;
	Land.val[0] = 0.0;
	Lnot.val[0] = 0.0;
	And.val[0] = 0.0;
	Or.val[0] = 0.0;
	Xor.val[0] = 0.0;
	Not.val[0] = 0.0;
	Leq.val[0] = 0.0;
	Lneq.val[0] = 0.0;
	Sel.val[0] = 0.0;
}

Reflection output:

generator       : Google Shaderc over Glslang
source lang     : GLSL
source lang ver : 450
source file     :
entry point     : main (stage=CS)
local size      : (1, 1, 1)


  Sepecialization constants: 8

    0:
      spirv id   : 8
      constant id: 2
      name       : SONE
      type       : signed 32 bit integer
      default    : 1
    1:
      spirv id   : 9
      constant id: 3
      name       : STWO
      type       : signed 32 bit integer
      default    : 2
    2:
      spirv id   : 14
      constant id: 5
      name       : UONE
      type       : unsigned 32 bit integer
      default    : 1
    3:
      spirv id   : 16
      constant id: 6
      name       : UTWO
      type       : unsigned 32 bit integer
      default    : 2
    4:
      spirv id   : 49
      constant id: 4
      name       : SNEG_TWO
      type       : signed 32 bit integer
      default    : -2
    5:
      spirv id   : 64
      constant id: 7
      name       : SNEG_THREE
      type       : signed 32 bit integer
      default    : -3
    6:
      spirv id   : 251
      constant id: 0
      name       : TRUE
      type       : boolean
      default    : 1
    7:
      spirv id   : 252
      constant id: 1
      name       : FALSE
      type       : boolean
      default    : 0


  Descriptor bindings: 31

    Binding 0.0
      spirv id : 23
      set      : 0
      binding  : 0
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : IAdd (SSBO_IAdd)
          // size = 0, padded size = 0
          struct SSBO_IAdd {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } IAdd;


    Binding 0.1
      spirv id : 33
      set      : 0
      binding  : 1
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : ISub (SSBO_ISub)
          // size = 0, padded size = 0
          struct SSBO_ISub {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } ISub;


    Binding 0.2
      spirv id : 41
      set      : 0
      binding  : 2
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : IMul (SSBO_IMul)
          // size = 0, padded size = 0
          struct SSBO_IMul {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } IMul;


    Binding 0.3
      spirv id : 47
      set      : 0
      binding  : 3
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : UDiv (SSBO_UDiv)
          // size = 0, padded size = 0
          struct SSBO_UDiv {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } UDiv;


    Binding 0.4
      spirv id : 56
      set      : 0
      binding  : 4
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : SDiv (SSBO_SDiv)
          // size = 0, padded size = 0
          struct SSBO_SDiv {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } SDiv;


    Binding 0.5
      spirv id : 62
      set      : 0
      binding  : 5
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : SRem (SSBO_SRem)
          // size = 0, padded size = 0
          struct SSBO_SRem {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } SRem;


    Binding 0.6
      spirv id : 70
      set      : 0
      binding  : 6
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : SMod (SSBO_SMod)
          // size = 0, padded size = 0
          struct SSBO_SMod {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } SMod;


    Binding 0.7
      spirv id : 77
      set      : 0
      binding  : 7
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : UMod (SSBO_UMod)
          // size = 0, padded size = 0
          struct SSBO_UMod {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } UMod;


    Binding 0.8
      spirv id : 85
      set      : 0
      binding  : 8
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : LShl (SSBO_LShl)
          // size = 0, padded size = 0
          struct SSBO_LShl {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } LShl;


    Binding 0.9
      spirv id : 93
      set      : 0
      binding  : 9
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : RShl (SSBO_RShl)
          // size = 0, padded size = 0
          struct SSBO_RShl {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } RShl;


    Binding 0.10
      spirv id : 104
      set      : 0
      binding  : 10
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : RSha (SSBO_RSha)
          // size = 0, padded size = 0
          struct SSBO_RSha {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } RSha;


    Binding 0.11
      spirv id : 113
      set      : 0
      binding  : 11
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : IEq (SSBO_IEq)
          // size = 0, padded size = 0
          struct SSBO_IEq {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } IEq;


    Binding 0.12
      spirv id : 120
      set      : 0
      binding  : 12
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : INeq (SSBO_INeq)
          // size = 0, padded size = 0
          struct SSBO_INeq {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } INeq;


    Binding 0.13
      spirv id : 127
      set      : 0
      binding  : 13
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Ult (SSBO_Ult)
          // size = 0, padded size = 0
          struct SSBO_Ult {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Ult;


    Binding 0.14
      spirv id : 134
      set      : 0
      binding  : 14
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Ule (SSBO_Ule)
          // size = 0, padded size = 0
          struct SSBO_Ule {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Ule;


    Binding 0.15
      spirv id : 141
      set      : 0
      binding  : 15
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Ugt (SSBO_Ugt)
          // size = 0, padded size = 0
          struct SSBO_Ugt {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Ugt;


    Binding 0.16
      spirv id : 148
      set      : 0
      binding  : 16
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Uge (SSBO_Uge)
          // size = 0, padded size = 0
          struct SSBO_Uge {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Uge;


    Binding 0.17
      spirv id : 155
      set      : 0
      binding  : 17
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Slt (SSBO_Slt)
          // size = 0, padded size = 0
          struct SSBO_Slt {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Slt;


    Binding 0.18
      spirv id : 162
      set      : 0
      binding  : 18
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Sle (SSBO_Sle)
          // size = 0, padded size = 0
          struct SSBO_Sle {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Sle;


    Binding 0.19
      spirv id : 169
      set      : 0
      binding  : 19
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Sgt (SSBO_Sgt)
          // size = 0, padded size = 0
          struct SSBO_Sgt {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Sgt;


    Binding 0.20
      spirv id : 176
      set      : 0
      binding  : 20
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Sge (SSBO_Sge)
          // size = 0, padded size = 0
          struct SSBO_Sge {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Sge;


    Binding 0.21
      spirv id : 183
      set      : 0
      binding  : 21
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Lor (SSBO_Lor)
          // size = 0, padded size = 0
          struct SSBO_Lor {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Lor;


    Binding 0.22
      spirv id : 190
      set      : 0
      binding  : 22
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Land (SSBO_Land)
          // size = 0, padded size = 0
          struct SSBO_Land {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Land;


    Binding 0.23
      spirv id : 197
      set      : 0
      binding  : 23
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Lnot (SSBO_Lnot)
          // size = 0, padded size = 0
          struct SSBO_Lnot {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Lnot;


    Binding 0.24
      spirv id : 204
      set      : 0
      binding  : 24
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : And (SSBO_And)
          // size = 0, padded size = 0
          struct SSBO_And {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } And;


    Binding 0.24
      spirv id : 212
      set      : 0
      binding  : 24
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Or (SSBO_Or)
          // size = 0, padded size = 0
          struct SSBO_Or {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Or;


    Binding 0.24
      spirv id : 219
      set      : 0
      binding  : 24
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Xor (SSBO_Xor)
          // size = 0, padded size = 0
          struct SSBO_Xor {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Xor;


    Binding 0.25
      spirv id : 227
      set      : 0
      binding  : 25
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Not (SSBO_Not)
          // size = 0, padded size = 0
          struct SSBO_Not {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Not;


    Binding 0.26
      spirv id : 234
      set      : 0
      binding  : 26
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Leq (SSBO_Leq)
          // size = 0, padded size = 0
          struct SSBO_Leq {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Leq;


    Binding 0.27
      spirv id : 241
      set      : 0
      binding  : 27
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Lneq (SSBO_Lneq)
          // size = 0, padded size = 0
          struct SSBO_Lneq {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Lneq;


    Binding 0.28
      spirv id : 247
      set      : 0
      binding  : 28
      type     : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER (UAV)
      count    : 1
      accessed : true
      name     : Sel (SSBO_Sel)
          // size = 0, padded size = 0
          struct SSBO_Sel {
              float val[1]; // abs offset = 0, rel offset = 0, size = 4, padded size = 4, array stride = 4
              float dummy;  // abs offset = 4, rel offset = 4, size = 4, padded size = 4 UNUSED
          } Sel;

@shangjiaxuan
Copy link
Author

Next steps would be cleaning up clutter, removing dependency on parser and better evaluation.
Copy needed node info into a internal array, each result id have a evaluation state flag, current value, type pointe, and list of related spec constants.

Test and much work is needed to remove dependency on parser. (Need to store constant expressions in a separate representation. Potentially also removing redundant evaluation)
Remove dependency on parser. Still, FindType needs the module pointer...
@shangjiaxuan
Copy link
Author

Implementation is now done, but the API needs clearing up.

@shangjiaxuan shangjiaxuan marked this pull request as ready for review February 13, 2023 09:55
@zbendefy
Copy link
Contributor

zbendefy commented Apr 3, 2023

Hi! I just wanted to comment that after integrating it into a custom project, I find it very useful. I'm just hoping for approval and a merge so I can build on this as part of the vulkan sdk. :)

@chaoticbob
Copy link
Contributor

In addition to the feedback I left:

  • The giant macros need to become functions. The macros as they are cannot be debugged.
  • The code format still needs work, it should follow the existing formatting with alignment and naming convention.
  • Copy on returns should be avoided, the client code needs to make the allocation.
  • There are few comments that are not clear on what exactly is happennig.
  • Comments should indicate a clear behavior even if that behavior is undefined.

common/output_stream.cpp Outdated Show resolved Hide resolved
common/output_stream.cpp Outdated Show resolved Hide resolved
common/output_stream.cpp Outdated Show resolved Hide resolved
spirv_reflect.h Outdated Show resolved Hide resolved
spirv_reflect.h Outdated Show resolved Hide resolved
spirv_reflect.h Show resolved Hide resolved
spirv_reflect.h Outdated Show resolved Hide resolved
spirv_reflect.h Show resolved Hide resolved
spirv_reflect.h Outdated Show resolved Hide resolved
spirv_reflect.h Outdated Show resolved Hide resolved
Updated `SpvReflectEntryPoint` to have non-conflicting struct alignment.
Formatting fix.
A merge error occurred in the last commit. Fixed in this one.
Now localsize flag is not part of entry point struct.
@muraj
Copy link

muraj commented Oct 27, 2023

Any update on this? I would like to play with these in some of my shaders in order to dynamically change the work group size. Thanks!

@zbendefy
Copy link
Contributor

zbendefy commented Dec 8, 2023

Is there anything I can help out regarding this? :) I would really like to use spec constants via SPIRV-Reflect. I'm using shangjiaxuan's initial commit instead of the main branch, and its very useful.

@spencer-lunarg
Copy link
Contributor

spencer-lunarg commented Dec 25, 2023

@muraj @zbendefy thanks for poking, I want to help move this forward, but something I am lacking are the use cases.

It would be helpful to have a "what is expected" vs "what it currently does" to better understand what part of spec constant support people are after

edit: I see the issues now

@muraj
Copy link

muraj commented Jan 4, 2024

Sorry for the delay. Honestly, for my initial use case, just not barfing when specialization constants for the local group size are used would be fine. When I first tried it, it failed to parse if any spec constant syntax was involved in the variable. I had written up in full detail what happened somewhere.

Eventually I would like to retrieve more information about the variable definition for the local group size dimensions so I can consume any spirv given to modify the local group size and JIT at dispatch time to fit the local group size requested, caching more frequently used compilations for reuse later.

At the moment I'm just hard coding the specialization id, but I still need spirv-reflect to be able to parse the spirv in order to retrieve information about the entry point, etc.

for (uint32_t j = 0; j < p_module->entry_point_count; ++j) {
if (p_module->entry_points[j].spirv_execution_model == SpvExecutionModelKernel ||
p_module->entry_points[j].spirv_execution_model == SpvExecutionModelGLCompute) {
p_module->_internal->entry_point_mode_flags[j] = 4;
Copy link

@mbechard mbechard Feb 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this code block doing? Why is it overwriting the x value of the local size with the result_id that WorkGroupSize is held in?

Copy link
Author

@shangjiaxuan shangjiaxuan Feb 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the code is written, specialization constants for workgroup size (aka local_size_id_n), as per spirv 1.5 specification, the compiler have two options for generating code. One is using OpExecutionModeId, and referencing resultid of three constant Ops. The other is using WorkGroupSize builtin, which takes a uvec3 argument resultid and overwrites the OpExecutionMode Op. When the code is written, glslc and other compilers are relying on the second (builtin) mode for generating code in glsl. The second mode used to be deprecated in spv1.5, but later it seems the deprecation plan was moved to 1.6, and then deprecation got removed if I remember correctly.

Reference:
https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html

Screenshot_20240229-074453

Screenshot_20240229-074419

Screenshot_20240229-074447

Copy link
Author

@shangjiaxuan shangjiaxuan Feb 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a reference of spv asm disassembled from glslc generated code that uses the builtin, see the sample test code included in pr:

tests/spec_constant/test_orig.spv.dis

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the version of this PR I'm using is out of date, but in my case if a GLSL shader is using a spec constant, and is using the gl_WorkGroup variable, but the spec constant is not use to set the local_size, this code block still gets used and you end up with the incorrect value for local_size.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the version of this PR I'm using is out of date, but in my case if a GLSL shader is using a spec constant, and is using the gl_WorkGroup variable, but the spec constant is not use to set the local_size, this code block still gets used and you end up with the incorrect value for local_size.

Please give me sample glsl code and compiler options to compile it. Or you can give me the disassembled code. This can help clarify your problem and help me locate it.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm not sure I follow. In this case I want to get the values (16, 32, 1), since that is the actual local_size values. Where will those values reside when it's in this mode?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use spvReflectEvaluateResult to evaluate the value of localsize.x resultid, for the current specialization constants in the evaluator (default values if you don't set them) to get the current uvec3.

Copy link

@mbechard mbechard Mar 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I can see how that'll result in me getting the correct answer. However, specialization constant's aren't used for the workgroup size in this example. The are hardcoded constants. It seems to me it should only enter into this mode if a specialization constant is actually used to set the workgroup size.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also baffles me why the compiler generates the builtin instruction. The older version compiler didn't do this if I remember correctly.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm currently compiled again GLSLang 302a663a38, so it's not the most up to date version.
Another comment, I would think the reflection should put this result_id somewhere else, and try to resolve the correct local size in to the local_size variable. Re-using 'x' like this is confusing, and in general the user of reflection just wants the results, so asking them to use spvReflectEvaluateResult seems like an un-needed step.

A comment about the ABI (not directed at @shangjiaxuan), I don't understand why it would be requested that this tool which is only 2 files, should have a locked ABI. It's not completed enough to start hampering the interface with a locked ABI at this point.

@RandomShaper
Copy link
Contributor

It would be helpful to have a "what is expected" vs "what it currently does" to better understand what part of spec constant support people are after

For what it's worth, I'll let you know why we need this in Godot.

  • The API-agnostic layer of our rendering infrastructure use spec constant reflection for validation; namely, when it's asked to create a pipeline with some overrides for SC defaults, it validates the provided values are of the right type.
  • Our Direct3D 12 renderer implementation still uses SPIR-V as an input for shaders. That means we have to emulate spec constants. We do that by patching DXIL resulting from transpilation. The matter is that knowing what the default values are allows us to optimize the patching process whenever a pipeline is created, by skipping what would otherwise be redundant operations.
  • We're exposing shader reflection, including spec constants with their default values to userland. That means user scripts (mostly tools, I guess) can use such data however they see fit.

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

Successfully merging this pull request may close these issues.

9 participants