unit srBuffer;

{$mode ObjFPC}{$H+}

interface

uses
 sysutils,
 spirv,
 ginodes,
 srNode,
 srType,
 srTypes,
 srLayout,
 srDecorate,
 srConfig;

type
 String2=String[2];

 TsrBuffer=class;

 TsrField=class;

 TsrFieldValue=(frNotFit,frIdent,frVectorAsValue,frValueInVector,frValueInArray);

 TFieldFetchValue=record
  fValue:TsrFieldValue;
  pField:TsrField;
 end;

 TFieldEnumCb=procedure(pField:TsrField) of object;
 TsrField=class
  type
   TFieldTree=specialize TNodeTreeClass<TsrField>;
  var
   pLeft,pRight:TsrField;
   //----

   pBuffer:TsrBuffer;
   pParent:TsrField;
   //
   key:PtrUint;  //offset
   //
   FFSize :PtrUint; //full size of struct/array (cached)
   stride :PtrUint; //value size/stride in array
   count  :PtrUint; //count in array

   FCount:PtrUint;     //field count
   Fdtype:TsrDataType; //field type

   sType,vType:TsrType;

   FID:Integer; //alloc late

   //

   FList:TFieldTree;

  class function c(n1,n2:PPtrUint):Integer; static;
  property  offset:PtrUint read key write key;
  function  Cross(o,s:PtrUint):Boolean;
  function  Find_be(o:PtrUint):TsrField;
  function  Find_le(o:PtrUint):TsrField;
  function  Find_ls(o:PtrUint):TsrField;
  function  First:TsrField;
  function  Last:TsrField;
  function  Next(p:TsrField):TsrField;
  function  Prev(p:TsrField):TsrField;
  function  Fetch(o:PtrUint):TsrField;
  Procedure ClearSize;
  function  FindIntersect(_offset,_size:PtrUint):TsrField;
  function  FetchValue(_offset,_size:PtrUint;_dtype:TsrDataType;_weak:Boolean):TFieldFetchValue;
  function  FetchArray(_offset,_size,_stride:PtrUint):TFieldFetchValue;
  function  FetchRuntimeArray(_offset,_stride:PtrUint):TFieldFetchValue;
  function  IsStructUsedRuntimeArray:Boolean;
  function  IsStructNotUsed:Boolean;
  function  IsTop:Boolean;
  function  GetStructDecorate:DWORD;
  procedure UpdateSize;
  function  Size:PtrUint;
  procedure FillNode(o,s:PtrUint);
  function  FillSpace:Integer;
 end;

 TsrBufferType=(btStorageBuffer,btUniformBuffer,btPushConstant,btWorkgroup,btPrivate);

 PsrBufferKey=^TsrBufferKey;
 TsrBufferKey=packed record
  pLayout:TsrDataLayout;
  AliasId:PtrInt;
 end;

 TsrBuffer=class(TsrDescriptor)
  pLeft,pRight:TsrBuffer;
  //----
  //
  align_offset:DWORD;

  FDList:TDependenceNodeList;

  bType:TsrBufferType;

  key:TsrBufferKey;

  FTop:TsrField;

  pNextAlias:TsrBuffer; // alias cache

  tRef:TsrNode; //bitcast cache

  //
  Procedure add_read(src:TsrNode);         override;
  Procedure rem_read(src:TsrNode);         override;
  //
  function  _GetStorageName:RawByteString; override;
  //
  class function c(n1,n2:PsrBufferKey):Integer; static;
  //
  property  pLayout:TsrDataLayout read key.pLayout;
  property  AliasId:PtrInt        read key.AliasId;
  //
  Function  is_export_used:Boolean;
  Procedure Init(); inline;
  Procedure AddDep(t:TsrNode);
  Procedure RemDep(t:TsrNode);
  function  _chain_read:DWORD;
  function  chain_read :DWORD;
  function  _chain_write:DWORD;
  function  chain_write:DWORD;
  function  GetStorageName:RawByteString;
  function  GetTypeStr:PChar;
  function  GetFlagsStr:String2;
  function  GetStructName:RawByteString;
  function  GetSize:PtrUint;
  procedure EnumAllField(cb:TFieldEnumCb);
  procedure ShiftOffset(Offset:PtrUint);
  //
  procedure AllocSourceExtension2(var Writer:TseWriter); override;
 end;

 ntBuffer=TsrBuffer;

 PsrBufferList=^TsrBufferList;
 TsrBufferList=object
  type
   TNodeTree=specialize TNodeTreeClass<TsrBuffer>;
  var
   FEmit:TCustomEmit;
   FTree:TNodeTree;
   FPushConstant:TsrBuffer;
  procedure Init(Emit:TCustomEmit); inline;
  function  Fetch(s:TsrDataLayout;n:PtrInt;GLC,SLC:Boolean):TsrBuffer;
  function  NextAlias(buf:TsrBuffer;GLC,SLC:Boolean):TsrBuffer;
  Function  First:TsrBuffer;
  Function  Next(node:TsrBuffer):TsrBuffer;
  procedure EnumAllField(cb:TFieldEnumCb);
  procedure OnFillSpace(node:TsrField);
  procedure FillSpace;
  procedure OnAllocID(pField:TsrField);
  procedure AllocID;
  procedure AllocBinding(Var FBinding:Integer);
  function  FindUserDataBuf:TsrBuffer;
  procedure ApplyBufferType;
  procedure AlignOffset(node:TsrBuffer;offset:PtrUint);
  procedure AlignOffset;
  procedure OnAllocTypeBinding(pField:TsrField);
  procedure AllocTypeBinding;
  procedure AllocName;
 end;

Const
 MAX_BUF_SIZE=QWORD(High(DWORD)+1);

operator := (i:TObject):TsrField; inline;

implementation

operator := (i:TObject):TsrField; inline;
begin
 Result:=TsrField(Pointer(i)); //typecast hack
end;

Procedure TsrBuffer.add_read(src:TsrNode);
begin
 inherited;
 if src.IsType(ntChain) then
 begin
  AddDep(src);
 end;
end;

Procedure TsrBuffer.rem_read(src:TsrNode);
begin
 inherited;
 if src.IsType(ntChain) then
 begin
  RemDep(src);
 end;
end;

function TsrBuffer._GetStorageName:RawByteString;
begin
 Result:=GetStorageName;
end;

//---

class function TsrField.c(n1,n2:PPtrUint):Integer;
begin
 Result:=ord(n1^>n2^)-ord(n1^<n2^);
end;

function TsrField.Cross(o,s:PtrUint):Boolean;
begin
 Result:=((o>=offset) and (o<(offset+size))) or
         ((offset>=o) and (offset<(o+s)));
end;

function TsrField.Find_be(o:PtrUint):TsrField;
begin
 Result:=FList.Find_be(@o);
end;

function TsrField.Find_le(o:PtrUint):TsrField;
begin
 Result:=FList.Find_le(@o);
end;

function TsrField.Find_ls(o:PtrUint):TsrField;
begin
 Result:=FList.Find_ls(@o);
end;

function TsrField.First:TsrField;
begin
 Result:=FList.Min;
end;

function TsrField.Last:TsrField;
begin
 Result:=FList.Max;
end;

function TsrField.Next(p:TsrField):TsrField;
begin
 Result:=FList.Next(p);
end;

function TsrField.Prev(p:TsrField):TsrField;
begin
 Result:=FList.Prev(p);
end;

function TsrField.Fetch(o:PtrUint):TsrField;
begin
 Result:=FList.Find(@o);
 if (Result=nil) then
 begin
  Assert(pBuffer.Emit<>nil);
  Result:=pBuffer.Emit.specialize New<TsrField>;
  Result.pBuffer:=pBuffer;
  Result.pParent:=Self;
  Result.offset :=o;
  Result.FID    :=-1;
  FList.Insert(Result);
  //inc field count
  Inc(FCount);
  //clear parent size
  ClearSize;
 end;
end;

Procedure TsrField.ClearSize;
var
 node:TsrField;
begin
 node:=Self;
 while (node<>nil) do
 begin
  //update only struct
  if (node.Fdtype<>dtTypeStruct) then Exit;
  //dont clear max size
  if (node.FFSize=MAX_BUF_SIZE) then Exit;
  //
  node.FFSize:=0;
  //
  node:=node.pParent;
 end;
end;

function TsrField.FindIntersect(_offset,_size:PtrUint):TsrField;
var
 node:TsrField;
begin
 Result:=nil;
 node:=Find_ls(_offset+_size);
 //
 while (node<>nil) do
 begin
  if node.Cross(_offset,_size) then
  begin
   Exit(node);
  end;
  //
  node:=Prev(node);
 end;
end;

function TsrField.FetchValue(_offset,_size:PtrUint;_dtype:TsrDataType;_weak:Boolean):TFieldFetchValue;
var
 node:TsrField;
 _stride:PtrUint;
begin
 Result:=Default(TFieldFetchValue);

 if _dtype.isVector then
 begin
  _stride:=_dtype.Child.BitSize div 8;
 end else
 begin
  _stride:=_dtype.BitSize div 8;
 end;

 Assert(_size=(_dtype.BitSize div 8));

 //find intersec
 node:=FindIntersect(_offset,_size);

 if (node=nil) then
 begin
  //new
  node:=Fetch(_offset);
  node.FFSize:=_size;   //fixed size
  node.stride:=_stride;
  node.count :=1;
  node.Fdtype:=_dtype;
  //
  Result.fValue:=frIdent;
  Result.pField:=node;
 end else
 Case node.Fdtype of
  dtTypeArray:
   begin
    if (node.offset>_offset) then Exit; //ident or big than
    if ((node.offset+node.size)<(_offset+_size)) then Exit;
    if (node.stride<_size) then Exit;   //ident or min stride
    //
    Result.fValue:=frValueInArray;
    Result.pField:=node;
   end;
  dtTypeRuntimeArray:
   begin
    if (node.offset>_offset) then Exit; //ident or big than
    if (node.stride<_size) then Exit;   //ident or min stride
    //
    Result.fValue:=frValueInArray;
    Result.pField:=node;
   end;
  else
   begin
    if node.Fdtype.isVector then
    begin //ftVector
     if _dtype.isVector then
     begin
      if (node.offset=_offset) and
         (node.size  =_size)   and
         (node.stride=_stride) then
      begin
       Result.fValue:=frIdent; //ident
       Result.pField:=node;
      end;
     end else
     begin
      if (node.offset>_offset) then Exit; //ident or big than
      _offset:=_offset-node.offset;

      if (node.stride=_size) and
         (_offset mod node.stride=0) then
      begin
       Result.fValue:=frValueInVector; //down to vector
       Result.pField:=node;
      end;
     end;
    end else
    begin //ftValue
     if _dtype.isVector then
     begin
      if (node.offset=_offset) and
         (node.size  =_size)   then
      begin
       Result.fValue:=frVectorAsValue; //vector as value?
       Result.pField:=node;
      end;
     end else
     begin
      if (node.offset=_offset) and
         (node.size  =_size)   then
      begin
       Result.fValue:=frIdent; //ident
       Result.pField:=node;
      end;
     end;
    end;
   end;
 end;

 if not _weak then
 begin
  case Result.fValue of
   frIdent:
     begin
      if (node.Fdtype<>_dtype) then
      begin
       Result:=Default(TFieldFetchValue);
      end;
     end;
   frValueInVector:
     begin
      if (node.Fdtype.Child<>_dtype) then
      begin
       Result:=Default(TFieldFetchValue);
      end;
     end;
   else;
  end;
 end;
 //
end;

function TsrField.FetchArray(_offset,_size,_stride:PtrUint):TFieldFetchValue;
var
 node:TsrField;
 _count:PtrUint;

 max_old:PtrUint;
 max_new:PtrUint;
begin
 Result:=Default(TFieldFetchValue);

 Assert(_stride<>0);
 Assert(_size  <>0);

 _count:=_size div _stride;
 _size :=_count*_stride; //align

 Assert(_count<>0);

 //find intersec
 node:=FindIntersect(_offset,_size);

 if (node=nil) then
 begin
  //new
  node:=Fetch(_offset);
  node.FFSize:=_size; //fixed size
  node.stride:=_stride;
  node.count :=_count;
  node.Fdtype:=dtTypeArray;
  //
  Result.fValue:=frValueInArray;
  Result.pField:=node;
 end else
 Case node.Fdtype of
  dtTypeArray:
   begin
    if (node.offset>_offset) then Exit; //ident or big than
    if (node.stride<>_stride) then Exit; //ident stride
    //
    max_old:=node.offset+node.size;
    max_new:=_offset+_size;
    //
    if (max_old<max_new) then
    begin
     //expand?
     if FindIntersect(max_old,max_new-max_old)<>nil then
     begin
      //dont expand
      //not fit
      Exit;
     end;
     //
     _size:=max_new-node.offset;
     _size:=Align(_size,_stride); //align up
     _count:=_size div _stride;
     //
     node.FFSize:=_size; //fixed size
     node.count :=_count;
    end;
    //
    Result.fValue:=frValueInArray;
    Result.pField:=node;
   end;
  dtTypeRuntimeArray:
   begin
    if (node.offset>_offset) then Exit; //ident or big than
    if (node.stride<>_stride) then Exit; //ident stride
    //
    Result.fValue:=frValueInArray;
    Result.pField:=node;
   end;
  else;
 end;
end;

function TsrField.FetchRuntimeArray(_offset,_stride:PtrUint):TFieldFetchValue;
var
 node:TsrField;
begin
 Result:=Default(TFieldFetchValue);

 node:=Find_be(_offset); //RA only last item
 if (node<>nil) then
 begin
  if (node.offset>_offset) or
     (node.Fdtype<>dtTypeRuntimeArray) or
     (node.stride<>_stride) then
  begin
   //not fit
   Exit;
  end;
 end;

 //find intersec
 node:=FindIntersect(_offset,MAX_BUF_SIZE-offset);

 if (node=nil) then
 begin
  //new
  node:=Fetch(_offset);
  node.FFSize:=MAX_BUF_SIZE; //fixed size
  node.stride:=_stride;
  node.count :=node.FFSize div _stride;
  node.Fdtype :=dtTypeRuntimeArray;
  //
  Result.fValue:=frValueInArray;
  Result.pField:=node;
 end else
 if (node.stride=_stride) and   //ident stride
    (node.Fdtype=dtTypeRuntimeArray) and
    (node.offset<=_offset) then //ident or big than
 begin
  Result.fValue:=frValueInArray;
  Result.pField:=node;
 end;
end;

function TsrField.IsStructUsedRuntimeArray:Boolean;
var
 node:TsrField;
begin
 Result:=False;
 if (Fdtype=dtTypeStruct) then
 begin
  node:=FList.Max;
  if (node<>nil) then
  begin
   Result:=(node.Fdtype=dtTypeRuntimeArray);
  end;
 end;
end;

function TsrField.IsStructNotUsed:Boolean;
var
 child:TsrField;
begin
 Result:=False;

 if IsTop then Exit;
 if (FCount>1) then Exit;

 child:=First;
 if (child=nil) then Exit;

 if (child.offset<>0) then Exit;
 if (child.size<>stride) then Exit;

 Result:=True;
end;

function TsrField.IsTop:Boolean;
begin
 Result:=(pParent=nil);
end;

function TsrField.GetStructDecorate:DWORD;
begin
 Result:=DWORD(-1); //dont use
 if IsTop then
 if (Fdtype=dtTypeStruct) then  //is struct
 begin

  if (pBuffer.FStorage<>StorageClass.StorageBuffer) and
     (pBuffer.bType=btStorageBuffer) then
  begin
   Result:=Decoration.BufferBlock;
  end else
  begin
   Result:=Decoration.Block;
  end;

 end;
end;

procedure TsrField.UpdateSize;
var
 node:TsrField;
begin
 if (FFSize<>0) then Exit;
 //
 if Fdtype.isVector then
 begin
  FFSize:=Fdtype.BitSize div 8
 end else
 if (Fdtype=dtTypeArray) then
 begin
  //array
  FFSize:=count*stride;
 end else
 if (Fdtype=dtTypeRuntimeArray) then
 begin
  //runtame array
  FFSize:=MAX_BUF_SIZE;
 end else
 begin
  //struct
  node:=FList.Max;
  if (node<>nil) then
  begin
   FFSize:=node.size;
   //check max
   if (FFSize=MAX_BUF_SIZE) then Exit;
   FFSize:=node.offset+FFSize;
  end;
 end;
end;

function TsrField.Size:PtrUint;
begin
 UpdateSize;
 Result:=FFSize;
end;

procedure TsrField.FillNode(o,s:PtrUint);

 procedure _Pad(p,v:PtrUint;_dtype:TsrDataType); //inline;
 var
  node:TsrField;
 begin
  if (o mod p<>0) and (s>=v) then
  begin
   node:=Fetch(o);
   Assert(node.Fdtype=dtUnknow,'WTF');
   node.FFSize:=v; //fixed size
   node.stride:=v;
   if _dtype.isVector then
   begin
    node.stride:=_dtype.Child.BitSize div 8;
   end;
   node.Fdtype:=_dtype;
   o:=o+v;
   s:=s-v;
  end;
 end;

 procedure _Fill(v:PtrUint;_dtype:TsrDataType); //inline;
 var
  count:PtrUint;
  node:TsrField;
 begin
  count:=s div v;
  While (count<>0) do
  begin
   node:=Fetch(o);
   Assert(node.Fdtype=dtUnknow,'WTF');
   node.FFSize:=v; //fixed size
   node.stride:=v;
   if _dtype.isVector then
   begin
    node.stride:=_dtype.Child.BitSize div 8;
   end;
   node.Fdtype:=_dtype;
   o:=o+v;
   s:=s-v;
   Dec(count);
  end;
 end;

begin
 if (s=0) then Exit;

 _Pad ( 2,1,dtUint8);
 _Pad ( 4,2,dtHalf16);
 _Pad ( 8,4,dtFloat32);
 _Pad (16,8,dtVec2f);

 _Fill(16,dtVec4f);
 _Fill(8 ,dtVec2f);
 _Fill(4 ,dtFloat32);
 _Fill(2 ,dtHalf16);
 _Fill(1 ,dtUint8);
end;

function TsrField.FillSpace:Integer;
var
 pNode:TsrField;
 Foffset,Fsize:PtrUint;
begin
 Result:=0;
 pNode:=First;
 if (pNode=nil) then Exit;

 Foffset:=0;
 While (pNode<>nil) do
 begin

  if (pNode.Fdtype=dtUnknow) then
  begin
   Case pNode.size of
     1:pNode.Fdtype:=dtUint8;
     2:pNode.Fdtype:=dtHalf16;
     4:pNode.Fdtype:=dtFloat32;
     8:pNode.Fdtype:=dtVec2f;
    16:pNode.Fdtype:=dtVec4f;
    else
     Assert(false);
   end;
  end;

  if (Foffset<pNode.offset) then
  begin
   Fsize:=pNode.offset-Foffset;
   FillNode(Foffset,Fsize);
   Inc(Result);
  end;
  Foffset:=pNode.offset+pNode.size;

  pNode:=Next(pNode);
 end;

 if (stride<>0) and (Fdtype in [dtTypeArray,dtTypeRuntimeArray]) then
 begin
  pNode:=FList.Max;

  if (pNode<>nil) then
  begin
   Foffset:=pNode.offset+pNode.size;
   Assert(Foffset<=stride);
   if (Foffset<stride) then
   begin
    Fsize:=stride-Foffset;
    FillNode(Foffset,Fsize);
    Inc(Result);
   end;
  end;
 end;
end;

//--

class function TsrBuffer.c(n1,n2:PsrBufferKey):Integer;
begin
 //first pLayout
 Result:=ord(n1^.pLayout.Order>n2^.pLayout.Order)-ord(n1^.pLayout.Order<n2^.pLayout.Order);
 if (Result<>0) then Exit;
 //second AliasId
 Result:=ord(n1^.AliasId>n2^.AliasId)-ord(n1^.AliasId<n2^.AliasId);
end;

Function TsrBuffer.is_export_used:Boolean;
begin
 Result:=IsUsed;
 if Result then
 begin
  if (pVar=nil) then Exit(false);
  Result:=pVar.IsUsed;
 end;
end;

Procedure TsrBuffer.Init(); inline;
begin
 bType   :=btStorageBuffer;
 FStorage:=StorageClass.Uniform;
 FBinding:=-1;

 FTop:=Emit.specialize New<TsrField>;

 FTop.FID    :=-1;
 FTop.Fdtype :=dtTypeStruct;
 FTop.pBuffer:=Self;
end;

Procedure TsrBuffer.AddDep(t:TsrNode);
var
 node:TDependenceNode;
begin
 if (t=nil) or (Self=nil) then Exit;

 node:=NewDependence;
 node.pNode:=t;

 FDList.Push_head(node);
end;

Procedure TsrBuffer.RemDep(t:TsrNode);
var
 node:TDependenceNode;
begin
 if (t=nil) or (Self=nil) then Exit;
 node:=FDList.pHead;
 While (node<>nil) do
 begin
  if (node.pNode=t) then
  begin
   FDList.Remove(node);

   FreeDependence(node);

   Exit;
  end;
  node:=node.pNext;
 end;
 Assert(false,'not found!');
end;

function TsrBuffer._chain_read:DWORD;
var
 node:TDependenceNode;
begin
 Result:=0;
 node:=FDList.pHead;
 While (node<>nil) do
 begin
  if node.pNode.IsType(ntChain) then
  begin
   Result:=Result+node.pNode.read_count;
  end;
  node:=node.pNext;
 end;
end;

function TsrBuffer.chain_read:DWORD;
var
 node:TsrBuffer;
begin
 Result:=0;
 if Flags.Bitcast then
 begin
  node:=Self;
  while (node<>nil) do
  begin
   Result:=Result+node._chain_read;
   node:=node.pNextAlias;
  end;
 end else
 begin
  Result:=_chain_read;
 end;
end;

function TsrBuffer._chain_write:DWORD;
var
 node:TDependenceNode;
begin
 Result:=0;
 node:=FDList.pHead;
 While (node<>nil) do
 begin
  if node.pNode.IsType(ntChain) then
  begin
   Result:=Result+node.pNode.write_count;
  end;
  node:=node.pNext;
 end;
end;

function TsrBuffer.chain_write:DWORD;
var
 node:TsrBuffer;
begin
 Result:=0;
 if Flags.Bitcast then
 begin
  node:=Self;
  while (node<>nil) do
  begin
   Result:=Result+node._chain_write;
   node:=node.pNextAlias;
  end;
 end else
 begin
  Result:=_chain_write;
 end;
end;

function TsrBuffer.GetStorageName:RawByteString;
begin
 Result:='';

 if (pLayout<>nil) then
 begin
  case pLayout.key.rtype of
   rtLDS:
    begin
     Result:='sLds'+IntToStr(FBinding);
     Exit;
    end;
   rtGDS:
    begin
     Result:='sGds'+IntToStr(FBinding);
     Exit;
    end;
   else;
  end;
 end;

 Case bType of
  btStorageBuffer:Result:='sBuf'+IntToStr(FBinding);
  btUniformBuffer:Result:='uBuf'+IntToStr(FBinding);
  btPushConstant :Result:='cBuf';
 end;
end;

function TsrBuffer.GetTypeStr:PChar;
begin
 Result:=nil;
 Case bType of
  btStorageBuffer:Result:='+STR_BUF';
  btUniformBuffer:Result:='+UNF_BUF';
  btPushConstant :Result:='+PSH_CST';
 end;
end;

function TsrBuffer.GetFlagsStr:String2;
const
 _R:array[0..1] of AnsiChar='_R';
 _W:array[0..1] of AnsiChar='_W';
begin
 Result:=_R[ord(chain_read <>0)]+
         _W[ord(chain_write<>0)];
end;

function TsrBuffer.GetStructName:RawByteString;
begin
 Result:='TD'+HexStr(FBinding,8);
end;

function TsrBuffer.GetSize:PtrUint;
begin
 Result:=FTop.size;
end;

procedure TsrBuffer.EnumAllField(cb:TFieldEnumCb);
var
 curr,node:TsrField;
begin
 if (cb=nil) then Exit;
 curr:=FTop;
 node:=curr.First;
 repeat
  While (node<>nil) do
  begin
   if (node.First<>nil) then //child exist
   begin
    curr:=node;
    node:=curr.First;        //down
   end else
   begin
    cb(node);
    node:=curr.Next(node);
   end;
  end;
  cb(curr);
  node:=curr;
  curr:=curr.pParent; //up
  if (curr=nil) then Break;
  node:=curr.Next(node);
 until false;
end;

procedure TsrBuffer.ShiftOffset(Offset:PtrUint);
var
 node:TsrField;
begin
 if (Offset=0) then Exit;
 node:=FTop.Last;
 While (node<>nil) do
 begin
  node.offset:=node.offset+Offset;
  node:=FTop.Prev(node);
 end;
 //
 FTop.ClearSize;
 FTop.UpdateSize;
end;

procedure TsrBufferList.Init(Emit:TCustomEmit); inline;
begin
 FEmit:=Emit;
end;

function TsrBufferList.Fetch(s:TsrDataLayout;n:PtrInt;GLC,SLC:Boolean):TsrBuffer;
var
 key:TsrBufferKey;
begin
 key:=Default(TsrBufferKey);
 key.pLayout:=s;
 key.AliasId:=n;
 //
 Result:=FTree.Find(@key);
 if (Result=nil) then
 begin
  Result:=FEmit.specialize New<TsrBuffer>;
  Result.Init();
  Result.key:=key;
  //
  Result.InitVar();
  //
  FTree.Insert(Result);
  //
  s.FDescList.Push_tail(Result);
 end;
 //
 if GLC then
 begin
  Result.Flags.Coherent:=True;
 end;
 //
 if SLC then
 begin
  Result.Flags.Volatile:=True;
 end;
end;

function TsrBufferList.NextAlias(buf:TsrBuffer;GLC,SLC:Boolean):TsrBuffer;
begin
 Result:=nil;
 if (buf=nil) then Exit;

 Result:=buf.pNextAlias;
 if (Result=nil) then
 begin
  Result:=Fetch(buf.pLayout,buf.AliasId+1,GLC,SLC);
  //cache
  buf.pNextAlias:=Result;
 end;

 //mark
    buf.Flags.Aliased:=True;
 Result.Flags.Aliased:=True;
end;

Function TsrBufferList.First:TsrBuffer;
begin
 Result:=FTree.Min;
end;

Function TsrBufferList.Next(node:TsrBuffer):TsrBuffer;
begin
 Result:=FTree.Next(node);
end;

procedure TsrBufferList.EnumAllField(cb:TFieldEnumCb);
var
 node:TsrBuffer;
begin
 if (cb=nil) then Exit;
 node:=First;
 While (node<>nil) do
 begin
  if node.IsUsed then
  begin
   node.EnumAllField(cb);
  end;
  node:=Next(node);
 end;
end;

procedure TsrBufferList.OnFillSpace(node:TsrField);
begin
 node.FillSpace;
end;

procedure TsrBufferList.FillSpace;
begin
 EnumAllField(@OnFillSpace);
end;

procedure TsrBufferList.OnAllocID(pField:TsrField);
var
 node:TsrField;
 ID:Integer;
begin
 ID:=0;
 node:=pField.First;
 While (node<>nil) do
 begin
  if pField.Fdtype.IsVector then
  begin
   ID:=node.offset div pField.stride;
   node.FID:=ID;
  end else
  begin
   node.FID:=ID;
   Inc(ID);
  end;
  node:=pField.Next(node);
 end;
end;

procedure TsrBufferList.AllocID;
begin
 EnumAllField(@OnAllocID);
end;

procedure TsrBufferList.AllocBinding(Var FBinding:Integer);
var
 pConfig:PsrConfig;
 pDecorateList:TsrDecorateList;
 node:TsrBuffer;
 FHide:Integer;
begin
 pConfig:=FEmit.GetConfig;
 pDecorateList:=FEmit.GetDecorateList;
 //
 node:=First;
 While (node<>nil) do
 begin
  if node.is_export_used then
  if not (node.bType in [btPushConstant]) then
  if (node.FBinding=-1) then //alloc
  begin
   //
   if not (node.bType in [btWorkgroup,btPrivate]) then
   begin
    pDecorateList.OpDecorate(node.pVar,Decoration.Binding      ,FBinding);
    pDecorateList.OpDecorate(node.pVar,Decoration.DescriptorSet,pConfig^.DescriptorSet);
    //
    if (node.Flags.Coherent) then
    begin
     pDecorateList.OpDecorate(node.pVar,Decoration.Coherent,0);
    end;
    //
    if (node.Flags.Volatile) then
    begin
     pDecorateList.OpDecorate(node.pVar,Decoration.Volatile,0);
    end;
    //next bind id
    node.FBinding:=FBinding;
    Inc(FBinding);
   end; //
   //Aliased need for uniform/storage/workgroup
   if (node.Flags.Aliased) and (not node.Flags.Bitcast) then
   begin
    pDecorateList.OpDecorate(node.pVar,Decoration.Aliased,0);
   end;
  end;
  //
  node:=Next(node);
 end;
 //Alloc hide id
 FHide:=FBinding;
 node:=First;
 While (node<>nil) do
 begin
  if (node.FBinding=-1) then //alloc
  begin
   node.FBinding:=FHide;
   Inc(FHide);
  end;
  node:=Next(node);
 end;
end;

procedure TsrBuffer.AllocSourceExtension2(var Writer:TseWriter);
begin
 if is_export_used then
 if not (bType in [btWorkgroup,btPrivate]) then
 begin
  //start block
  Writer.Header(GetTypeStr);
  //
  Writer.IntOpt('BND',FBinding);
  if (GetSize<>MAX_BUF_SIZE) then
  begin
   Writer.HexOpt('LEN',GetSize);
  end;
  if (align_offset<>0) then
  begin
   Writer.HexOpt('OFS',align_offset);
  end;
  Writer.StrOpt('FLG',GetFlagsStr);
 end;
end;

function TsrBufferList.FindUserDataBuf:TsrBuffer;
var
 node:TsrBuffer;
begin
 Result:=nil;
 node:=First;
 While (node<>nil) do
 begin
  if node.IsUsed then
  begin
   if node.pLayout.IsUserData then
   begin
    Exit(node);
   end;
  end;
  node:=Next(node);
 end;
end;

procedure TsrBufferList.ApplyBufferType;
label
 _storage;
var
 pConfig:PsrConfig;
 node:TsrBuffer;
 fchain_write:DWORD;
begin
 pConfig:=FEmit.GetConfig;

 node:=FindUserDataBuf;
 if (node<>nil) and
    (FPushConstant=nil) then
 if (node.bType=btStorageBuffer) and
    (node.chain_write=0) and
    (node.GetSize<=pConfig^.maxPushConstantsSize) then
 begin
  node.bType   :=btPushConstant;
  node.FStorage:=StorageClass.PushConstant;
  FPushConstant:=node;
 end;

 node:=First;
 While (node<>nil) do
 begin
  if node.IsUsed and (node.bType=btStorageBuffer) then
  begin

   fchain_write:=node.chain_write;

   if node.pLayout.IsGlobalDataShare then
   begin
    //global buffer
    goto _storage;
    //
   end else
   if node.pLayout.IsLocalDataShare then
   begin
    //Workgroup? (btWorkgroup) (btPrivate)
    if (FEmit.GetExecutionModel=ExecutionModel.GLCompute) then
    begin
     node.bType   :=btWorkgroup;
     node.FStorage:=StorageClass.Workgroup;
    end else
    begin
     node.bType   :=btPrivate;
     node.FStorage:=StorageClass.Private_;
    end;
    //
   end else
   if (not pConfig^.UseOnlyUserdataPushConst) and
      (FPushConstant=nil) and
      (fchain_write=0) and
      (node.GetSize<=pConfig^.maxPushConstantsSize) then
   begin
    node.bType   :=btPushConstant;
    node.FStorage:=StorageClass.PushConstant;
    FPushConstant:=node;
   end else
   if (fchain_write=0) and
      (node.GetSize<=pConfig^.maxUniformBufferRange) then
   begin
    node.bType   :=btUniformBuffer;
    node.FStorage:=StorageClass.Uniform;
   end else
   begin
    _storage: //
    if pConfig^.CanUseStorageBufferClass then
    begin
     node.FStorage:=StorageClass.StorageBuffer;
    end else
    begin
     node.FStorage:=StorageClass.Uniform;
    end;
   end;

  end;
  node:=Next(node);
 end;
end;

function AlignShift(addr:Pointer;alignment:PtrUInt):PtrUInt; inline;
begin
 if (alignment>1) then
 begin
  Result:=(PtrUInt(addr) mod alignment);
 end else
 begin
  Result:=0;
 end;
end;

procedure TsrBufferList.AlignOffset(node:TsrBuffer;offset:PtrUint);
var
 P:Pointer;
begin
 P:=node.pLayout.GetData;
 offset:=AlignShift(P,offset);
 node.align_offset:=offset;
 node.ShiftOffset(offset);
end;

procedure TsrBufferList.AlignOffset;
var
 pConfig:PsrConfig;
 node:TsrBuffer;
begin
 pConfig:=FEmit.GetConfig;
 node:=First;
 While (node<>nil) do
 begin
  if node.IsUsed then //Dont use is_export_used in this stage
  if not node.pLayout.IsLocalDataShare then
  if not node.pLayout.IsGlobalDataShare then
  begin
   Case node.bType of
    btStorageBuffer:
      begin
       AlignOffset(node,pConfig^.minStorageBufferOffsetAlignment);
      end;
    btUniformBuffer:
      begin
       AlignOffset(node,pConfig^.minUniformBufferOffsetAlignment);
      end;
    btPushConstant:
      begin
       node.align_offset:=pConfig^.PushConstantsOffset;
       node.ShiftOffset(pConfig^.PushConstantsOffset);
      end;
   end;
  end;
  node:=Next(node);
 end;
end;

procedure TsrBufferList.OnAllocTypeBinding(pField:TsrField);
var
 pDecorateList:TsrDecorateList;
 node:TsrField;
 SD:DWORD;
begin
 if (pField.sType=nil) then Exit;
 if (pField.sType.dtype<>dtTypeStruct) then Exit;

 pDecorateList:=FEmit.GetDecorateList;
 SD:=pField.GetStructDecorate;
 if (SD<>DWORD(-1)) then
 begin
  pDecorateList.OpDecorate(pField.sType,SD,0);
 end;
 node:=pField.First;
 While (node<>nil) do
 begin
  pDecorateList.OpMember(pField.sType,node.FID,node.offset);
  node:=pField.Next(node);
 end;
end;

procedure TsrBufferList.AllocTypeBinding;
var
 pDecorateList:TsrDecorateList;
 pHeaderList  :TsrHeaderList;
 Config       :PsrConfig;
 node:TsrBuffer;
begin
 EnumAllField(@OnAllocTypeBinding);
 //
 pDecorateList:=FEmit.GetDecorateList;
 pHeaderList  :=FEmit.GetHeaderList;
 Config       :=FEmit.GetConfig;
 //
 node:=First;
 While (node<>nil) do
 begin
  if node.is_export_used then
  begin
   if (node.bType=btStorageBuffer) then
   begin
    if (node.chain_read=0) then
    begin
     pDecorateList.OpDecorate(node.pVar,Decoration.NonReadable,0);
    end;
    if (node.chain_write=0) then
    begin
     pDecorateList.OpDecorate(node.pVar,Decoration.NonWritable,0);
    end;
   end;
   //
   if (node.bType=btWorkgroup) then
   begin
    if Config^.IsSpv14 then
    begin
     pHeaderList.SPV_KHR_workgroup_memory_explicit_layout;
    end;
   end;
  end; //is_export_used
  //
  node:=Next(node);
 end;
end;

procedure TsrBufferList.AllocName;
var
 FDebugInfo:TsrDebugInfoList;
 node:TsrBuffer;
begin
 FDebugInfo:=FEmit.GetDebugInfoList;
 node:=First;
 While (node<>nil) do
 begin
  if node.IsUsed and (node.FTop.vType<>nil) then
  begin
   FDebugInfo.OpName(node.FTop.vType,node.GetStructName);
  end;
  node:=Next(node);
 end;
end;

end.

