unit vRectGS;

{$mode ObjFPC}{$H+}

interface

uses
 Classes,

 Vulkan,

 vRegs2Vulkan,

 spirv,
 srNode,
 srType,
 srTypes,
 srReg,
 srVariable,
 srOutput,

 SprvEmit;

function CompileRectangleGeometryShader(var GPU_REGS:TGPU_REGS):TMemoryStream;

implementation

function interpolate(SprvEmit:TSprvEmit;
                     rtype   :TsrDataType;
                     barycentric,inputs:PPsrRegNode):TsrRegNode;
var
 tmp:array[0..2] of TsrRegNode;
 //
 i:Byte;
begin
 For i:=0 to 2 do
 begin
  tmp[i]:=SprvEmit.NewReg(rtype);
  //
  SprvEmit._Op2(SprvEmit.line,Op.OpVectorTimesScalar,tmp[i],inputs[i],barycentric[i]);
 end;
 //
 Result:=SprvEmit.OpFAddTo(tmp[0],tmp[1]);
 Result:=SprvEmit.OpFAddTo(Result,tmp[2]);
end;

type
 TINPUT_INFO=packed record
  LAYOUT_ID  :Byte;
  FLAT_SHADE :Boolean;
 end;

//source: [amdilc_rect_gs_compiler.c]
function CompileRectangleGeometryShader(var GPU_REGS:TGPU_REGS):TMemoryStream;
var
 INPUT_INFO :array[0..31] of TINPUT_INFO;
 INPUT_COUNT:Byte;
 //
 SprvEmit:TSprvEmit;
 //
 pVec4f      :TsrType;
 InputPos    :TsrVariable;
 OutputPos   :TsrVariable;
 InputParams :array[0..31] of TsrVariable;
 OutputParams:array[0..31] of TsrVariable;
 //
 pChain      :TsrNode;
 UintId      :array[0..3] of TsrRegNode;
 Positions   :array[0..3] of TsrRegNode;
 positionsX  :array[0..2] of TsrRegNode;
 positionsY  :array[0..2] of TsrRegNode;
 CoordEqualX :array[0..2] of TsrRegNode;
 CoordEqualY :array[0..2] of TsrRegNode;
 EdgeVertex  :array[0..2] of TsrRegNode;
 barycentric :array[0..2] of TsrRegNode;
 Parameters  :array[0..2] of TsrRegNode;
 xyEqual     :TsrRegNode;
 yxEqual     :TsrRegNode;
 pOneId      :TsrRegNode;
 pMinusOne   :TsrRegNode;
 pIndex      :TsrRegNode;
 pLoaded     :TsrRegNode;
 //
 i,a:Byte;
begin
 Result:=nil;

 INPUT_COUNT:=GPU_REGS.CX_REG^.SPI_PS_IN_CONTROL.NUM_INTERP;
 if (INPUT_COUNT<>0) then
 begin
  a:=0;
  for i:=0 to INPUT_COUNT-1 do
  //if not default
  if ((GPU_REGS.CX_REG^.SPI_PS_INPUT_CNTL[i].OFFSET shr 5)=0) then
  begin
   INPUT_INFO[a].LAYOUT_ID :=(GPU_REGS.CX_REG^.SPI_PS_INPUT_CNTL[i].OFFSET and 31);
   INPUT_INFO[a].FLAT_SHADE:=(GPU_REGS.CX_REG^.SPI_PS_INPUT_CNTL[i].FLAT_SHADE)<>0;
   //
   Inc(a);
  end;
  //update count
  INPUT_COUNT:=a;
 end;

 SprvEmit:=TSprvEmit.Create;

 SprvEmit.InitCustomGs();

 pVec4f   :=SprvEmit.TypeList.Fetch(dtVec4f);

 //postion i/o
 InputPos :=SprvEmit.AddPositionsInput(3);
 OutputPos:=SprvEmit.FetchOutput(etPos0,dtVec4f).pVar;

 //input param i/o
 if (INPUT_COUNT<>0) then
 begin
  for a:=0 to INPUT_COUNT-1 do
  begin
   InputParams [a]:=SprvEmit.AddParametersInput(INPUT_INFO[a].LAYOUT_ID,3);
   OutputParams[a]:=SprvEmit.FetchOutput(TpsslExportType(ord(etParam0)+INPUT_INFO[a].LAYOUT_ID),dtVec4f).pVar;
  end;
 end;

 //gen const id
 For i:=0 to 3 do
 begin
  UintId[i]:=SprvEmit.NewImm_i(dtUint32,i);
 end;

 //load positions
 For i:=0 to 2 do
 begin
  pChain:=SprvEmit.OpAccessChainTo(pVec4f,InputPos,UintId[i]);
  //
  Positions[i]:=SprvEmit.OpLoadTo(pVec4f,pChain);
 end;

 //extract
 For i:=0 to 2 do
 begin
  positionsX[i]:=SprvEmit.NewReg(dtFloat32);
  positionsY[i]:=SprvEmit.NewReg(dtFloat32);
  //
  SprvEmit.OpExtract(SprvEmit.line,positionsX[i],Positions[i],0);
  SprvEmit.OpExtract(SprvEmit.line,positionsY[i],Positions[i],1);
 end;

 //compare
 For i:=0 to 2 do
 begin
  CoordEqualX[i]:=SprvEmit.NewReg(dtBool);
  CoordEqualY[i]:=SprvEmit.NewReg(dtBool);
  //
  SprvEmit._Op2(SprvEmit.line,Op.OpFOrdEqual,CoordEqualX[i],positionsX[i],positionsX[(i + 1) mod 3]);
  SprvEmit._Op2(SprvEmit.line,Op.OpFOrdEqual,CoordEqualY[i],positionsY[i],positionsY[(i + 1) mod 3]);
 end;

 pOneId   :=SprvEmit.NewImm_s(dtFloat32, 1);
 pMinusOne:=SprvEmit.NewImm_s(dtFloat32,-1);

 //calc barycentric
 For i:=0 to 2 do
 begin
  xyEqual:=SprvEmit.OpAndTo(CoordEqualX[i], CoordEqualY[(i + 2) mod 3]);
  yxEqual:=SprvEmit.OpAndTo(CoordEqualY[i], CoordEqualX[(i + 2) mod 3]);
  //
  EdgeVertex[i]:=SprvEmit.OpOrTo(xyEqual,yxEqual);
  //cond,src_true,src_false
  barycentric[i]:=SprvEmit.OpSelectTo(EdgeVertex[i],pMinusOne,pOneId);
 end;

 //calc last pos
 Positions[3]:=interpolate(SprvEmit,dtVec4f,@barycentric,@Positions);

 //select first index
 //cond,src_true,src_false
 pIndex:=SprvEmit.OpSelectTo(EdgeVertex[1], UintId[1], UintId[0]);
 pIndex:=SprvEmit.OpSelectTo(EdgeVertex[2], UintId[2], pIndex   );

 //Send vertex by index
 For i:=0 to 2 do
 begin
  pChain :=SprvEmit.OpAccessChainTo(pVec4f,InputPos,pIndex);
  pLoaded:=SprvEmit.OpLoadTo(pVec4f,pChain);
  SprvEmit.OpStore(SprvEmit.line,OutputPos,pLoaded);

  //emit input attr
  if (INPUT_COUNT<>0) then
  begin
   for a:=0 to INPUT_COUNT-1 do
   begin
    if INPUT_INFO[a].FLAT_SHADE then
    begin
     //get first
     pChain:=SprvEmit.OpAccessChainTo(pVec4f,InputParams[a],UintId[0]);
    end else
    begin
     pChain:=SprvEmit.OpAccessChainTo(pVec4f,InputParams[a],pIndex);
    end;

    pLoaded:=SprvEmit.OpLoadTo(pVec4f,pChain);
    SprvEmit.OpStore(SprvEmit.line,OutputParams[a],pLoaded);
   end;
  end;

  //end vertex
  SprvEmit.OpEmitVertex(SprvEmit.line);

  if (i<>2) then
  begin
   //next index
   pIndex:=SprvEmit.OpIAddTo(pIndex,UintId[1]);
   pIndex:=SprvEmit.OpIModTo(pIndex,UintId[3]);
  end;
 end; //for

 //send last vertex
 SprvEmit.OpStore(SprvEmit.line,OutputPos,Positions[3]);

 //emit input attr
 if (INPUT_COUNT<>0) then
 begin
  for a:=0 to INPUT_COUNT-1 do
  begin
   if INPUT_INFO[a].FLAT_SHADE then
   begin
    //get first
    pChain :=SprvEmit.OpAccessChainTo(pVec4f,InputParams[a],UintId[0]);
    pLoaded:=SprvEmit.OpLoadTo(pVec4f,pChain);
   end else
   begin
    //load parameters
    For i:=0 to 2 do
    begin
     pChain:=SprvEmit.OpAccessChainTo(pVec4f,InputParams[a],UintId[i]);
     //
     Parameters[i]:=SprvEmit.OpLoadTo(pVec4f,pChain);
    end;

    //calc last parameter
    pLoaded:=interpolate(SprvEmit,dtVec4f,@barycentric,@Parameters);
   end;

   SprvEmit.OpStore(SprvEmit.line,OutputParams[a],pLoaded);
  end;
 end;

 //end vertex
 SprvEmit.OpEmitVertex(SprvEmit.line);

 //end prim
 SprvEmit.OpEndPrimitive(SprvEmit.line);

 //end function
 SprvEmit.OpReturn(SprvEmit.line);
 SprvEmit.OpFunctionEnd(SprvEmit.line);

 //ending stage
 SprvEmit.PostStage;
 SprvEmit.AllocStage;

 //SprvEmit.Print;

 Result:=TMemoryStream.Create;
 SprvEmit.SaveToStream(Result);

 SprvEmit.Free;
end;

end.

