unit srBuffer; {$mode ObjFPC}{$H+} interface uses sysutils, spirv, ginodes, srNode, srType, srTypes, srReg, srVariable, srLayout, srDecorate, srConfig; type ntBuffer=class(ntDescriptor) class Procedure add_read (node,src:PsrNode); override; class Procedure rem_read (node,src:PsrNode); override; // class Function pwrite_count (node:PsrNode):PDWORD; override; class function GetStorageName(node:PsrNode):RawByteString; override; end; PsrBuffer=^TsrBuffer; PsrField=^TsrField; TsrFieldValue=(frNotFit,frIdent,frVectorAsValue,frValueInVector,frValueInArray); TFieldFetchValue=record fValue:TsrFieldValue; pField:PsrField; end; TFieldEnumCb=procedure(pField:PsrField) of object; TsrField=object type TFieldFetch=specialize TNodeFetch; var pLeft,pRight:PsrField; //---- pBuffer:PsrBuffer; pParent:PsrField; offset:PtrUint; size:PtrUint; stride:PtrUint; FCount:PtrUint; dtype:TsrDataType; sType,pType:PsrType; FID:Integer; //alloc late // FList:TFieldFetch; function c(n1,n2:PsrField):Integer; static; function Cross(o,s:PtrUint):Boolean; function Find_be(o:PtrUint):PsrField; function Find_le(o:PtrUint):PsrField; function First:PsrField; function Last:PsrField; function Next(p:PsrField):PsrField; function Prev(p:PsrField):PsrField; function Fetch(o:PtrUint):PsrField; function FetchValue(_offset,_size:PtrUint;_dtype:TsrDataType):TFieldFetchValue; function FetchRuntimeArray(_offset,_stride:PtrUint):TFieldFetchValue; function IsStructUsedRuntimeArray:Boolean; function IsStructNotUsed:Boolean; function IsTop:Boolean; function GetStructDecorate:DWORD; procedure UpdateSize; function GetSize:PtrUint; procedure FillNode(o,s:PtrUint); function FillSpace:Integer; end; TsrBufferType=(btStorageBuffer,btUniformBuffer,btPushConstant); TsrBuffer=packed object(TsrDescriptor) pLeft,pRight:PsrBuffer; //---- fwrite_count:DWORD; // align_offset:DWORD; FEmit:TCustomEmit; FDList:TRegDNodeList; bType:TsrBufferType; key:packed record pLayout:PsrDataLayout; AliasId:PtrInt; end; FTop:TsrField; function c(n1,n2:PsrBuffer):Integer; static; Procedure Init(Emit:TCustomEmit); inline; Procedure AddDep(t:PsrNode); Procedure RemDep(t:PsrNode); function chain_read :DWORD; function chain_write:DWORD; function GetStorageName:RawByteString; function GetTypeChar:Char; function GetString:RawByteString; function GetStructName:RawByteString; function GetSize:PtrUint; procedure EnumAllField(cb:TFieldEnumCb); procedure ShiftOffset(Offset:PtrUint); end; PsrBufferList=^TsrBufferList; TsrBufferList=object type TNodeFetch=specialize TNodeFetch; var FEmit:TCustomEmit; FNTree:TNodeFetch; FPushConstant:PsrBuffer; procedure Init(Emit:TCustomEmit); inline; function Fetch(s:PsrDataLayout;n:PtrInt):PsrBuffer; function NextAlias(buf:PsrBuffer):PsrBuffer; Function First:PsrBuffer; Function Next(node:PsrBuffer):PsrBuffer; procedure EnumAllField(cb:TFieldEnumCb); procedure OnFillSpace(node:PsrField); procedure FillSpace; procedure OnAllocID(pField:PsrField); procedure AllocID; procedure AllocBinding(Var FBinding:Integer); procedure AllocSourceExtension; function FindUserDataBuf:PsrBuffer; procedure ApplyBufferType; procedure AlignOffset(node:PsrBuffer;offset:PtrUint); procedure AlignOffset; procedure OnAllocTypeBinding(pField:PsrField); procedure AllocTypeBinding; procedure AllocName; end; implementation class Procedure ntBuffer.add_read(node,src:PsrNode); begin inherited; if src^.IsType(ntChain) then begin PsrBuffer(node)^.AddDep(src); end; end; class Procedure ntBuffer.rem_read(node,src:PsrNode); begin inherited; if src^.IsType(ntChain) then begin PsrBuffer(node)^.RemDep(src); end; end; // class Function ntBuffer.pwrite_count(node:PsrNode):PDWORD; begin Result:=@PsrBuffer(node)^.fwrite_count; end; class function ntBuffer.GetStorageName(node:PsrNode):RawByteString; begin Result:=PsrBuffer(node)^.GetStorageName; end; //--- function TsrField.c(n1,n2:PsrField):Integer; begin Result:=Integer(n1^.offset>n2^.offset)-Integer(n1^.offset=offset) and (o=o) and (offsetnil); Result:=pBuffer^.FEmit.Alloc(SizeOf(TsrField)); Result^.pBuffer:=pBuffer; Result^.pParent:=@Self; Result^.offset :=o; Result^.FID :=-1; FList.Insert(Result); Inc(FCount); end; end; function TsrField.FetchValue(_offset,_size:PtrUint;_dtype:TsrDataType):TFieldFetchValue; var node:PsrField; _stride:PtrUint; begin Result:=Default(TFieldFetchValue); _stride:=0; if _dtype.isVector then begin _stride:=_dtype.Child.BitSize div 8; end; Assert(_size=(_dtype.BitSize div 8)); node:=Find_le(_offset); if (node<>nil) then begin if not node^.Cross(_offset,_size) then begin node:=Next(node); if (node<>nil) then begin if not node^.Cross(_offset,_size) then begin node:=nil; end; end; end; end; if (node=nil) then begin //new node:=Fetch(_offset); node^.size :=_size; node^.stride:=_stride; node^.dtype :=_dtype; Result.fValue:=frIdent; Result.pField:=node; end else Case node^.dtype 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; Result.fValue:=frNotFit; end; else begin if node^.dtype.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; end; function TsrField.FetchRuntimeArray(_offset,_stride:PtrUint):TFieldFetchValue; var node:PsrField; begin Result:=Default(TFieldFetchValue); node:=Find_be(_offset); //RA only last item if (node<>nil) then begin if (node^.offset>_offset) or (node^.dtype<>dtTypeRuntimeArray) or (node^.stride<>_stride) then begin Result.fValue:=frNotFit; Result.pField:=nil; Exit; end; end; node:=Find_le(_offset); if (node=nil) then begin //new node:=Fetch(_offset); node^.size :=High(PtrUint)-_offset; node^.stride :=_stride; node^.dtype :=dtTypeRuntimeArray; Result.fValue:=frValueInArray; Result.pField:=node; end else if (node^.stride=_stride) and (node^.dtype =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:PsrField; begin Result:=False; if (dtype=dtTypeStruct) then begin node:=FList.Max; if (node<>nil) then begin size:=node^.offset+node^.size; end; Result:=(size=High(PtrUint)); end; end; function TsrField.IsStructNotUsed:Boolean; var child:PsrField; 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 (dtype=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:PsrField; begin node:=FList.Max; if (node<>nil) then begin size:=node^.offset+node^.size; end; end; function TsrField.GetSize:PtrUint; begin UpdateSize; Result:=size; end; procedure TsrField.FillNode(o,s:PtrUint); procedure _Pad(p,v:PtrUint;_dtype:TsrDataType); //inline; var node:PsrField; begin if (o mod p<>0) and (s>=v) then begin node:=Fetch(o); Assert(node^.dtype=dtUnknow,'WTF'); node^.size:=v; node^.dtype:=_dtype; o:=o+v; s:=s-v; end; end; procedure _Fill(v:PtrUint;_dtype:TsrDataType); //inline; var count:PtrUint; node:PsrField; begin count:=s div v; While (count<>0) do begin node:=Fetch(o); Assert(node^.dtype=dtUnknow,'WTF'); node^.size:=v; node^.dtype:=_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:PsrField; Foffset,Fsize:PtrUint; begin Result:=0; pNode:=First; if (pNode=nil) then Exit; Foffset:=0; While (pNode<>nil) do begin if (pNode^.dtype=dtUnknow) then begin Case pNode^.size of 1:pNode^.dtype:=dtUint8; 2:pNode^.dtype:=dtHalf16; 4:pNode^.dtype:=dtFloat32; 8:pNode^.dtype:=dtVec2f; 16:pNode^.dtype:=dtVec4f; else Assert(false); end; end; if (Foffset0) and (dtype in [dtTypeArray,dtTypeRuntimeArray]) then begin pNode:=FList.Max; if (pNode<>nil) then begin Foffset:=pNode^.offset+pNode^.size; Assert(Foffset<=stride); if (Foffsetn2^.key.pLayout)-Integer(n1^.key.pLayout0) then Exit; //second CastNum Result:=Integer(n1^.key.AliasId>n2^.key.AliasId)-Integer(n1^.key.AliasIdnil) do begin if (node^.pNode=t) then begin if (_prev=nil) then begin FDList.pHead:=node^.pNext; end else begin _prev^.pNext:=node^.pNext; end; pRegsStory:=FEmit.GetRegsStory; pRegsStory^.FreeDep(node); Exit; end; _prev:=node; node:=node^.pNext; end; Assert(false,'not found!'); end; function TsrBuffer.chain_read:DWORD; var node:PRegDNode; begin Result:=0; node:=FDList.pHead; While (node<>nil) do begin if node^.pNode^.IsType(ntChain) then begin Result:=Result+PsrChain(node^.pNode)^.read_count; end; node:=node^.pNext; end; end; function TsrBuffer.chain_write:DWORD; var node:PRegDNode; begin Result:=0; node:=FDList.pHead; While (node<>nil) do begin if node^.pNode^.IsType(ntChain) then begin Result:=Result+PsrChain(node^.pNode)^.write_count; end; node:=node^.pNext; end; end; function TsrBuffer.GetStorageName:RawByteString; begin Result:=''; Case bType of btStorageBuffer:Result:='sBuf'+IntToStr(FBinding); btUniformBuffer:Result:='uBuf'+IntToStr(FBinding); btPushConstant :Result:='cBuf'; end; end; function TsrBuffer.GetTypeChar:Char; begin Result:=#0; Case bType of btStorageBuffer:Result:='S'; btUniformBuffer:Result:='U'; btPushConstant :Result:='P'; end; end; function TsrBuffer.GetString:RawByteString; var PID:DWORD; begin PID:=0; if (key.pLayout<>nil) then begin PID:=key.pLayout^.FID; end; Result:='B'+GetTypeChar+ ';PID='+HexStr(PID,8)+ ';BND='+HexStr(FBinding,8)+ ';LEN='+HexStr(GetSize,8)+ ';OFS='+HexStr(align_offset,8); end; function TsrBuffer.GetStructName:RawByteString; begin Result:='TD'+HexStr(FBinding,8); end; function TsrBuffer.GetSize:PtrUint; begin Result:=FTop.GetSize; end; procedure TsrBuffer.EnumAllField(cb:TFieldEnumCb); var curr,node:PsrField; 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:PsrField; begin if (Offset=0) then Exit; node:=FTop.Last; While (node<>nil) do begin if (node^.offset+node^.size=High(PtrUint)) then begin node^.size:=node^.size-Offset; end; node^.offset:=node^.offset+Offset; node:=FTop.Prev(node); end; end; procedure TsrBufferList.Init(Emit:TCustomEmit); inline; begin FEmit:=Emit; end; function TsrBufferList.Fetch(s:PsrDataLayout;n:PtrInt):PsrBuffer; var node:TsrBuffer; begin node:=Default(TsrBuffer); node.Init(FEmit); node.key.pLayout:=s; node.key.AliasId:=n; Result:=FNTree.Find(@node); if (Result=nil) then begin Result:=FEmit.Alloc(SizeOf(TsrBuffer)); Move(node,Result^,SizeOf(TsrBuffer)); Result^.FTop.pBuffer:=Result; // Result^.InitVar(FEmit); // FNTree.Insert(Result); end; end; function TsrBufferList.NextAlias(buf:PsrBuffer):PsrBuffer; begin Result:=nil; if (buf=nil) then Exit; Result:=Fetch(buf^.key.pLayout,buf^.key.AliasId+1); end; Function TsrBufferList.First:PsrBuffer; begin Result:=FNTree.Min; end; Function TsrBufferList.Next(node:PsrBuffer):PsrBuffer; begin Result:=FNTree.Next(node); end; procedure TsrBufferList.EnumAllField(cb:TFieldEnumCb); var node:PsrBuffer; 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:PsrField); begin node^.FillSpace; end; procedure TsrBufferList.FillSpace; begin EnumAllField(@OnFillSpace); end; procedure TsrBufferList.OnAllocID(pField:PsrField); var node:PsrField; ID:Integer; begin ID:=0; node:=pField^.First; While (node<>nil) do begin if pField^.dtype.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:PsrDecorateList; node:PsrBuffer; pVar:PsrVariable; begin pConfig:=FEmit.GetConfig; pDecorateList:=FEmit.GetDecorateList; node:=First; While (node<>nil) do begin pVar:=node^.pVar; if (pVar<>nil) and node^.IsUsed then if (node^.bType<>btPushConstant) then if (node^.FBinding=-1) then //alloc begin pDecorateList^.OpDecorate(pVar,Decoration.Binding,FBinding); pDecorateList^.OpDecorate(pVar,Decoration.DescriptorSet,pConfig^.DescriptorSet); node^.FBinding:=FBinding; Inc(FBinding); end; node:=Next(node); end; end; procedure TsrBufferList.AllocSourceExtension; var pDebugInfoList:PsrDebugInfoList; node:PsrBuffer; pVar:PsrVariable; begin pDebugInfoList:=FEmit.GetDebugInfoList; node:=First; While (node<>nil) do begin pVar:=node^.pVar; if (pVar<>nil) and node^.IsUsed then begin pDebugInfoList^.OpSource(node^.GetString); end; node:=Next(node); end; end; function TsrBufferList.FindUserDataBuf:PsrBuffer; var node:PsrBuffer; begin Result:=nil; node:=First; While (node<>nil) do begin if node^.IsUsed then begin if node^.key.pLayout^.IsUserData then begin Exit(node); end; end; node:=Next(node); end; end; procedure TsrBufferList.ApplyBufferType; var pConfig:PsrConfig; node:PsrBuffer; fchain_write:DWORD; begin pConfig:=FEmit.GetConfig; node:=FindUserDataBuf; if (node<>nil) and (FPushConstant=nil) then if (node^.write_count=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 (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 if pConfig^.CanUseStorageBufferClass then begin node^.FStorage:=StorageClass.StorageBuffer; end else begin node^.FStorage:=StorageClass.Uniform; 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:PsrBuffer;offset:PtrUint); var P:Pointer; begin P:=node^.key.pLayout^.GetData; offset:=AlignShift(P,offset); node^.align_offset:=offset; node^.ShiftOffset(offset); end; procedure TsrBufferList.AlignOffset; var pConfig:PsrConfig; node:PsrBuffer; begin pConfig:=FEmit.GetConfig; node:=First; While (node<>nil) do begin if node^.IsUsed 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:PsrField); var pDecorateList:PsrDecorateList; node:PsrField; 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:PsrDecorateList; node:PsrBuffer; begin EnumAllField(@OnAllocTypeBinding); // pDecorateList:=FEmit.GetDecorateList; // node:=First; While (node<>nil) do begin if node^.IsUsed and (node^.pVar<>nil) then 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; node:=Next(node); end; end; procedure TsrBufferList.AllocName; var FDebugInfo:PsrDebugInfoList; node:PsrBuffer; begin FDebugInfo:=FEmit.GetDebugInfoList; node:=First; While (node<>nil) do begin if node^.IsUsed and (node^.FTop.pType<>nil) then begin FDebugInfo^.OpName(node^.FTop.pType,node^.GetStructName); end; node:=Next(node); end; end; end.