unit vHostBufferManager;

{$mode objfpc}{$H+}

interface

uses
 SysUtils,
 g23tree,
 Vulkan,
 vMemory,
 vBuffer,
 vDependence;

type
 TvHostBuffer=class(TvBuffer)
  FAddr:QWORD;
 end;

function FetchHostBuffer(cmd:TvDependenciesObject;
                         Addr:QWORD;
                         Size:TVkDeviceSize;
                         device_local:Boolean=False):TvHostBuffer;

implementation

uses
 kern_rwlock,
 kern_dmem;

type
 TvHostBufferKey=packed record
  FAddr  :QWORD;
  FBuffer:TvHostBuffer;
 end;

 TvAddrCompare=object
  function c(const a,b:TvHostBufferKey):Integer; static;
 end;

 _TvHostBufferSet=specialize T23treeSet<TvHostBufferKey,TvAddrCompare>;
 TvHostBufferSet=object(_TvHostBufferSet)
  lock:Pointer;
  Procedure Lock_wr;
  Procedure Unlock_wr;
 end;

var
 FHostBufferSet:TvHostBufferSet;


Procedure TvHostBufferSet.Lock_wr;
begin
 rw_wlock(lock);
end;

Procedure TvHostBufferSet.Unlock_wr;
begin
 rw_wunlock(lock);
end;

function TvAddrCompare.c(const a,b:TvHostBufferKey):Integer;
begin
 //1 FAddr
 Result:=Integer(a.FAddr>b.FAddr)-Integer(a.FAddr<b.FAddr);
 if (Result<>0) then Exit;
end;

var
 host_alignment:TVkDeviceSize=0;

function _fix_buf_size(var Addr:QWORD;var Size:TVkDeviceSize;usage:TVkFlags):TVkDeviceSize;
var
 mr:TVkMemoryRequirements;
begin
 if (host_alignment=0) then
 begin
  mr:=GetRequirements(false,Size,usage,@buf_ext);
  host_alignment:=mr.alignment;
 end;

 Result:=(Addr mod host_alignment);

 Addr:=Addr-Result;
 Size:=Size+Result;
end;

function _FindHostBuffer(Addr:QWORD;Size:TVkDeviceSize):TvHostBuffer;
label
 _repeat,
 _delete;
var
 It:TvHostBufferSet.Iterator;
 tmp:TvHostBufferKey;
 buf:TvHostBuffer;
 __end:QWORD;
begin
 Result:=nil;
 __end:=Addr+Size;

 _repeat:

 tmp:=Default(TvHostBufferKey);
 tmp.FAddr :=Addr;

 It:=FHostBufferSet.find(tmp);

 if (It.Item<>nil) then
 begin
  buf:=It.Item^.FBuffer;

  if buf.Hold(nil) then
  begin

   if ((buf.FAddr+buf.FSize)>=__end) then
   begin
    Exit(buf);
   end else
   begin
    buf.Drop(nil);
    //The search key matches but the size does not.
    goto _delete;
   end;

  end else
  begin
   _delete:
   //mem is deleted, free buf
   FHostBufferSet.erase(It);
   buf.Release(nil); //map ref
   buf:=nil;
   goto _repeat;
  end;

 end;

 tmp:=Default(TvHostBufferKey);
 tmp.FAddr :=__end;

 //[s|new|e] ->
 //      [s|old|e]

 It:=FHostBufferSet.find_ls(tmp);

 while (It.Item<>nil) do
 begin
  buf:=It.Item^.FBuffer;

  if buf.Hold(nil) then
  begin

   if (buf.FAddr<=Addr) and
      ((buf.FAddr+buf.FSize)>=__end) then
   begin
    Exit(buf);
   end;

   buf.Drop(nil);
  end else
  begin
   goto _delete;
  end;

  if not It.Prev then Break;
 end;

end;

const
 ALL_BUFFER_USAGE=
  ord(VK_BUFFER_USAGE_TRANSFER_SRC_BIT) or
  ord(VK_BUFFER_USAGE_TRANSFER_DST_BIT) or
  ord(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) or
  ord(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) or
  ord(VK_BUFFER_USAGE_INDEX_BUFFER_BIT) or
  ord(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) or
  ord(VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT);

function FetchHostBuffer(cmd:TvDependenciesObject;
                         Addr:QWORD;
                         Size:TVkDeviceSize;
                         device_local:Boolean=False):TvHostBuffer;
label
 _repeat;
var
 dmem_addr:QWORD;
 key:TvHostBufferKey;
 mem:TvPointer;
begin
 Result:=nil;
 Assert(Size<>0);

 dmem_addr:=QWORD(get_dmem_ptr(Pointer(Addr)));

 dmem_addr:=dmem_addr-_fix_buf_size(Addr,Size,ALL_BUFFER_USAGE);

 key:=Default(TvHostBufferKey);
 key.FAddr:=Addr;

 //
 FHostBufferSet.Lock_wr;
 //

 _repeat:

 key.FBuffer:=_FindHostBuffer(Addr,Size);

 //
 FHostBufferSet.Unlock_wr;
 //

 if (key.FBuffer=nil) then
 begin
  //create new

  mem:=MemManager.FetchHostMap(dmem_addr,Size,device_local);

  if (mem.FMemory=nil) then
  begin
   if device_local then
   begin
    mem:=MemManager.FetchHostMap(dmem_addr,Size,False);
   end;
  end;

  if (mem.FMemory=nil) then
  begin
   //try shrink?
   //ENOMEM
   Exit(nil);
  end;

  key.FBuffer:=TvHostBuffer.Create(Size,ALL_BUFFER_USAGE,@buf_ext);
  key.FBuffer.FAddr:=Addr;

  key.FBuffer.SetObjectName('HB_0x'+HexStr(Addr,10)+'..'+HexStr(Addr+Size,10));

  if (key.FBuffer.BindMem(mem)<>VK_SUCCESS) then
  begin
   //unknow error
   FreeAndNil(key.FBuffer);
   mem.Release; //release [FetchHostMap]
   //
   Exit(nil);
  end;

  //
  FHostBufferSet.Lock_wr;
  //

  if FHostBufferSet.Insert(key) then
  begin
   key.FBuffer.Acquire(nil); //map ref
  end else
  begin
   //collision?

   FreeAndNil(key.FBuffer);
   mem.Release; //release [FetchHostMap]

   //
   goto _repeat;
  end;

  key.FBuffer.Hold(nil); //analog ref in [_FindHostBuffer]
  mem.Release;           //release [FetchHostMap]

  //
  FHostBufferSet.Unlock_wr;
  //

  //create new
 end;

 Result:=key.FBuffer;

 //add dep
 cmd.RefTo(Result);

 if (Result<>nil) then
 begin
  Result.Drop(nil); //release [_FindHostBuffer]
 end;
end;


end.


