unit vMemory;

{$mode objfpc}{$H+}

interface

uses
 sysutils,
 LFQueue,
 mqueue,
 vmparam,
 vm_mmap,
 Vulkan,
 vDevice,
 vDependence,
 atomic,
 kern_hazard_pointer;

type
 TvMemInfo=bitpacked record
  heap_index     :0..VK_MAX_MEMORY_HEAPS-1;
  mem_type       :0..VK_MAX_MEMORY_TYPES-1;
  device_local   :Boolean;
  device_coherent:Boolean;
  host_visible   :Boolean;
  host_coherent  :Boolean;
 end;

 pp_gpu_map_entry=^p_gpu_map_entry;
 p_gpu_map_entry=^gpu_map_entry;
 gpu_map_entry=packed record
  prev    :p_gpu_map_entry; // previous entry
  next    :p_gpu_map_entry; // next entry
  left    :p_gpu_map_entry; // left child in binary search tree
  right   :p_gpu_map_entry; // right child in binary search tree
  start   :TVkDeviceSize;   // start address
  __end   :TVkDeviceSize;   // end address
  adj_free:TVkDeviceSize;   // amount of adjacent free space
  max_free:TVkDeviceSize;   // max free space in subtree
  desc    :Pointer;
 end;

 p_gpu_map=^gpu_map;
 gpu_map=object
  header  :gpu_map_entry;   // List of entries
  root    :p_gpu_map_entry; // Root of a binary search tree
  size    :TVkDeviceSize;   // virtual size
  nentries:Integer;         // Number of entries
  property min_offset:TVkDeviceSize read header.start write header.start;
  property max_offset:TVkDeviceSize read header.__end write header.__end;
 end;

 TvDeviceMemory=class(TvDependenciesObject)
  entry   :TAILQ_ENTRY;
  //
  FHandle :TVkDeviceMemory;
  FSize   :TVkDeviceSize;
  FMemInfo:TvMemInfo;
  FMap    :gpu_map;
  //
  Constructor Create(Handle:TVkDeviceMemory;Size:TVkDeviceSize;mem_type:Byte;mem_info:PVkMemoryType);
  Destructor  Destroy; override;
  Procedure   remove_all;
  Procedure   Unmap;
  Procedure   Flush;
  function    Acquire(Sender:TObject):Boolean; override;
  function    Release(Sender:TObject):Boolean; override;
 end;

 TvHostMemory=class(TvDeviceMemory)
  FStart:QWORD;
  F__End:QWORD;
 end;

 TvPointer=packed object
  FMemory:TvDeviceMemory;
  FOffset:TVkDeviceSize;
  function Acquire:TvPointer;
  function Release:Boolean;
  function Hold:Boolean;
  function Drop:Boolean;
 end;

Const
 GRANULAR_DEV_BLOCK_SIZE=128*1024*1024;
 GRANULAR_MAP_BLOCK_SIZE= 16*1024*1024;

type
 PvHeap=^TvHeap;
 TvHeap=bitpacked record
  heap_size      :TVkDeviceSize;
  heap_index     :0..VK_MAX_MEMORY_HEAPS-1;
  def_mem_type   :0..VK_MAX_MEMORY_TYPES-1;
  device_local   :Boolean;
  device_coherent:Boolean;
  host_visible   :Boolean;
  host_coherent  :Boolean;
 end;

const
 V_PROP_DEVICE_LOCAL   =ord(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT       );
 V_PROP_HOST_VISIBLE   =ord(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT       );
 V_PROP_HOST_COHERENT  =ord(VK_MEMORY_PROPERTY_HOST_COHERENT_BIT      );
 V_PROP_HOST_CACHED    =ord(VK_MEMORY_PROPERTY_HOST_CACHED_BIT        );
 V_PROP_DEVICE_COHERENT=ord(VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD);
 V_PROP_DEVICE_UNCACHED=ord(VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD);
 //
 V_PROP_BEST_FIT       =$80000000;

type
 PQNode=^TQNode;
 TQNode=object
  next_:PQNode;
 end;

 PQUnmapNode=^TQUnmapNode;
 TQUnmapNode=object(TQNode)
  start:QWORD;
  __end:QWORD;
 end;

 TvMemManager=class
  public
   FProperties:TVkPhysicalDeviceMemoryProperties;

   FSparceMemoryTypes:TVkUInt32;

   FHeaps:array of TvHeap;

  private
   FDevs:TAILQ_HEAD; //TvDeviceMemory

   FBacked:TvDeviceMemory;

   FHosts:TAILQ_HEAD; //TvHostMemory

   FHosts_count:Integer;

   FUnmapQueue:TIntrusiveMPSCQueue;
  public

   Constructor Create;

   function    findMemoryType(Filter:TVkUInt32;prop:TVkMemoryPropertyFlags;start:Integer):Integer;
   procedure   LoadMemoryHeaps;
   procedure   PrintMemoryHeaps;
   procedure   PrintMemoryType(typeFilter:TVkUInt32);

   Function    FetchMemory(const mr:TVkMemoryRequirements;
                           prop:TVkMemoryPropertyFlags;
                           desc:Pointer):TvPointer;

   Function    FetchMemory(Size          :TVkDeviceSize;
                           Align         :TVkDeviceSize;
                           memoryTypeBits:TVkUInt32;
                           desc          :Pointer;
                           best_fit      :Boolean):TvPointer;

   Function    FreeMemory(P:TvPointer):Boolean;

  private

   Function    _AllocDevBlock(Size:TVkDeviceSize;mtindex:Byte):TvDeviceMemory;
   procedure   _FreeDevBlock(node:TvDeviceMemory);

   Function    _shrink_dev_block(max:TVkDeviceSize;heap_index:Byte):TVkDeviceSize;
   Function    _shrink_host_map (max:TVkDeviceSize;heap_index:Byte):TVkDeviceSize;
   Function    _shrink(max:TVkDeviceSize;mtindex:Byte;mode:Byte):TVkDeviceSize;

   procedure   _unmap_host_queue;
   procedure   _unmap_host(start,__end:QWORD);

  public

   procedure   unmap_host(start,__end:QWORD);

   Function    FetchHostMap(Addr,Size:TVkDeviceSize;mtindex:Byte):TvPointer;
   Function    FetchHostMap(Addr,Size:TVkDeviceSize;device_local:Boolean):TvPointer;

   Procedure   Flush;

   Procedure   _print_host;
   Procedure   _print_devs;
 end;

var
 MemManager:TvMemManager;

const
 buf_ext:TVkExternalMemoryBufferCreateInfo=(
  sType:VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO;
  pNext:nil;
  handleTypes:ord(VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT);
 );

function vkAllocMemory         (device:TVkDevice;Size:TVkDeviceSize;mtindex:TVkUInt32):TVkDeviceMemory;
function vkAllocHostMemory     (device:TVkDevice;Size:TVkDeviceSize;mtindex:TVkUInt32;addr:Pointer     ):TVkDeviceMemory;
function vkAllocDedicatedImage (device:TVkDevice;Size:TVkDeviceSize;mtindex:TVkUInt32;FHandle:TVkImage ):TVkDeviceMemory;
function vkAllocDedicatedBuffer(device:TVkDevice;Size:TVkDeviceSize;mtindex:TVkUInt32;FHandle:TVkBuffer):TVkDeviceMemory;

function GetHostMappedRequirements:TVkMemoryRequirements;
function GetSparceMemoryTypes:TVkUInt32;

function  GetMemoryBudget(var budget:TVkPhysicalDeviceMemoryBudgetPropertiesEXT):Boolean;
function  MemoryBudgetCanBeAlloc(heap_id:Byte;size:TVkDeviceSize):Boolean;
procedure PrintMemoryBudget;

implementation

uses
 subr_backtrace,
 kern_rwlock,
 kern_dmem;

var
 global_mem_lock:Pointer=nil;

function AlignUp(addr:PtrUInt;alignment:PtrUInt):PtrUInt; inline;
var
 tmp:PtrUInt;
begin
 tmp:=addr+PtrUInt(alignment-1);
 Result:=tmp-(tmp mod alignment);
end;

function AlignDw(addr:PtrUInt;alignment:PtrUInt):PtrUInt; inline;
begin
 Result:=addr-(addr mod alignment);
end;

function Max(a,b:Ptruint):Ptruint; inline;
begin
 if (a>b) then Result:=a else Result:=b;
end;

function Min(a,b:Ptruint):Ptruint; inline;
begin
 if (a<b) then Result:=a else Result:=b;
end;


//

const
 GPU_SUCCESS        =0;
 GPU_INVALID_ADDRESS=1;
 GPU_NO_SPACE       =2;

procedure gpu_map_init(map:p_gpu_map;min,max:TVkDeviceSize);
begin
 map^.header.next:=@map^.header;
 map^.header.prev:=@map^.header;
 map^.min_offset:=min;
 map^.max_offset:=max;
 map^.header.adj_free:=(max-min);
 map^.header.max_free:=(max-min);
 map^.root:=nil;
end;

function gpu_map_entry_create(map:p_gpu_map):p_gpu_map_entry;
var
 new_entry:p_gpu_map_entry;
begin
 new_entry:=AllocMem(SizeOf(gpu_map_entry));
 Assert((new_entry<>nil),'gpu_map_entry_create: kernel resources exhausted');
 Result:=new_entry;
end;

procedure gpu_map_entry_dispose(map:p_gpu_map;entry:p_gpu_map_entry); inline;
begin
 FreeMem(entry);
end;

procedure gpu_map_entry_set_max_free(entry:p_gpu_map_entry);
begin
 entry^.max_free:=entry^.adj_free;
 if (entry^.left<>nil) then
 if (entry^.left^.max_free>entry^.max_free) then
 begin
  entry^.max_free:=entry^.left^.max_free;
 end;
 if (entry^.right<>nil) then
 if (entry^.right^.max_free>entry^.max_free) then
 begin
  entry^.max_free:=entry^.right^.max_free;
 end;
end;

function gpu_map_entry_splay(addr:TVkDeviceSize;root:p_gpu_map_entry):p_gpu_map_entry;
var
 llist,rlist:p_gpu_map_entry;
 ltree,rtree:p_gpu_map_entry;
 y          :p_gpu_map_entry;
begin
 { Special case of empty tree. }
 if (root=nil) then Exit(root);

 {
  * Pass One: Splay down the tree until we find addr or a nil
  * pointer where addr would go.  llist and rlist are the two
  * sides in reverse order (bottom-up), with llist linked by
  * the right pointer and rlist linked by the left pointer in
  * the vm_map_entry.  Wait until Pass Two to set max_free on
  * the two spines.
  }
 llist:=nil;
 rlist:=nil;
 repeat
  { root is never nil in here. }
  if (addr<root^.start) then
  begin
   y:=root^.left;
   if (y=nil) then break;
   if (addr<y^.start) and (y^.left<>nil) then
   begin
    { Rotate right and put y on rlist. }
    root^.left:=y^.right;
    y^.right:=root;
    gpu_map_entry_set_max_free(root);
    root:=y^.left;
    y^.left:=rlist;
    rlist:=y;
   end else
   begin
    { Put root on rlist. }
    root^.left:=rlist;
    rlist:=root;
    root:=y;
   end;
  end else
  if (addr>=root^.__end) then
  begin
   y:=root^.right;
   if (y=nil) then break;
   if (addr>=y^.__end) and (y^.right<>nil) then
   begin
    { Rotate left and put y on llist. }
    root^.right:=y^.left;
    y^.left:=root;
    gpu_map_entry_set_max_free(root);
    root:=y^.right;
    y^.right:=llist;
    llist:=y;
   end else
   begin
    { Put root on llist. }
    root^.right:=llist;
    llist:=root;
    root:=y;
   end;
  end else
  begin
   break;
  end;
 until false;

 {
  * Pass Two: Walk back up the two spines, flip the pointers
  * and set max_free.  The subtrees of the root go at the
  * bottom of llist and rlist.
  }
 ltree:=root^.left;
 while (llist<>nil) do
 begin
  y:=llist^.right;
  llist^.right:=ltree;
  gpu_map_entry_set_max_free(llist);
  ltree:=llist;
  llist:=y;
 end;
 rtree:=root^.right;
 while (rlist<>nil) do
 begin
  y:=rlist^.left;
  rlist^.left:=rtree;
  gpu_map_entry_set_max_free(rlist);
  rtree:=rlist;
  rlist:=y;
 end;

 {
  * Final assembly: add ltree and rtree as subtrees of root.
  }
 root^.left:=ltree;
 root^.right:=rtree;
 gpu_map_entry_set_max_free(root);

 Result:=(root);
end;

procedure gpu_map_entry_link(
           map        :p_gpu_map;
           after_where:p_gpu_map_entry;
           entry      :p_gpu_map_entry);
var
 i:TVkDeviceSize;
begin
 Inc(map^.nentries);
 entry^.prev:=after_where;
 entry^.next:=after_where^.next;
 entry^.next^.prev:=entry;
 after_where^.next:=entry;

 if (after_where<>@map^.header) then
 begin
  if (after_where<>map^.root) then
  begin
   gpu_map_entry_splay(after_where^.start, map^.root);
  end;
  entry^.right:=after_where^.right;
  entry^.left:=after_where;
  after_where^.right:=nil;
  after_where^.adj_free:=entry^.start - after_where^.__end;
  gpu_map_entry_set_max_free(after_where);
 end else
 begin
  entry^.right:=map^.root;
  entry^.left:=nil;
 end;
 if (entry^.next=@map^.header) then
 begin
  i:=map^.max_offset;
 end else
 begin
  i:=entry^.next^.start;
 end;
 entry^.adj_free:=i-entry^.__end;
 gpu_map_entry_set_max_free(entry);
 map^.root:=entry;
end;

procedure gpu_map_entry_unlink(
           map  :p_gpu_map;
           entry:p_gpu_map_entry);
var
 next,prev,root:p_gpu_map_entry;
 i:TVkDeviceSize;
begin
 if (entry<>map^.root) then
 begin
  gpu_map_entry_splay(entry^.start, map^.root);
 end;
 if (entry^.left=nil) then
 begin
  root:=entry^.right;
 end else
 begin
  root:=gpu_map_entry_splay(entry^.start, entry^.left);
  root^.right:=entry^.right;
  if (entry^.next=@map^.header) then
  begin
   i:=map^.max_offset;
  end else
  begin
   i:=entry^.next^.start;
  end;
  root^.adj_free:=i-root^.__end;
  gpu_map_entry_set_max_free(root);
 end;
 map^.root:=root;

 prev:=entry^.prev;
 next:=entry^.next;
 next^.prev:=prev;
 prev^.next:=next;
 Dec(map^.nentries);
end;

procedure gpu_map_entry_resize_free(map:p_gpu_map;entry:p_gpu_map_entry);
begin
 if (entry<>map^.root) then
 begin
  map^.root:=gpu_map_entry_splay(entry^.start, map^.root);
 end;

 if (entry^.next=@map^.header) then
 begin
  entry^.adj_free:=map^.max_offset-entry^.__end;
 end else
 begin
  entry^.adj_free:=entry^.next^.start-entry^.__end;
 end;
 gpu_map_entry_set_max_free(entry);
end;

function gpu_map_lookup_entry(
           map    :p_gpu_map;
           address:TVkDeviceSize;
           entry  :pp_gpu_map_entry):Boolean;
var
 cur:p_gpu_map_entry;
begin
 cur:=map^.root;
 if (cur=nil) then
 begin
  entry^:=@map^.header;
 end else
 if (address>=cur^.start) and (cur^.__end>address) then
 begin
  entry^:=cur;
  Exit(TRUE);
 end else
 begin

  cur:=gpu_map_entry_splay(address,cur);
  map^.root:=cur;

  if (address>=cur^.start) then
  begin
   entry^:=cur;
   if (cur^.__end>address) then
   begin
    Exit(TRUE);
   end;
  end else
  begin
   entry^:=cur^.prev;
  end;
 end;
 Result:=(FALSE);
end;

function gpu_map_adj_free(map:p_gpu_map;
                          start:TVkDeviceSize;
                          entry:p_gpu_map_entry):TVkDeviceSize;
begin
 if (entry=@map^.header) then
 begin
  if (entry^.next=@map^.header) then
  begin
   Result:=entry^.adj_free - start;
  end else
  begin
   Result:=entry^.next^.start - start;
  end;
 end else
 begin
  Result:=entry^.adj_free - (start - entry^.__end);
 end;
end;

function gpu_map_test(
           map  :p_gpu_map;
           size :PVkDeviceSize;
           start:TVkDeviceSize;
           __end:TVkDeviceSize
          ):Integer;
var
 entry:p_gpu_map_entry;
 _size:TVkDeviceSize;
begin
 size^:=0;

 if (start<map^.min_offset) or (__end>map^.max_offset) or (start>=__end) then
 begin
  Exit(GPU_INVALID_ADDRESS);
 end;

 if gpu_map_lookup_entry(map,start,@entry) then
 begin
  Exit(GPU_NO_SPACE);
 end;

 if (entry^.next<>@map^.header) and
    (entry^.next^.start<__end) then
 begin
  Exit(GPU_NO_SPACE);
 end;

 _size:=gpu_map_adj_free(map,start,entry);

 if (entry^.next<>@map^.header) then
 if (entry^.next^.start<(start + _size)) then
 begin
  //something is wrong
  Exit(GPU_NO_SPACE);
 end;

 if (_size<(__end-start)) then
 begin
  Assert(false,'gpu_map_test');
 end;

 if ((start+_size)>map^.max_offset) then
 begin
  Assert(false,'gpu_map_test');
 end;

 size^:=_size;

 Result:=GPU_SUCCESS;
end;

function gpu_map_insert(
           map  :p_gpu_map;
           start:TVkDeviceSize;
           __end:TVkDeviceSize;
           desc :Pointer
          ):Integer;
var
 new_entry :p_gpu_map_entry;
 prev_entry:p_gpu_map_entry;
 temp_entry:p_gpu_map_entry;
begin

 if (start<map^.min_offset) or (__end>map^.max_offset) or (start>=__end) then
 begin
  Exit(GPU_INVALID_ADDRESS);
 end;

 if gpu_map_lookup_entry(map,start,@temp_entry) then
 begin
  Exit(GPU_NO_SPACE);
 end;

 prev_entry:=temp_entry;

 if (prev_entry^.next<>@map^.header) and
    (prev_entry^.next^.start<__end) then
 begin
  Exit(GPU_NO_SPACE);
 end;

 new_entry:=gpu_map_entry_create(map);
 new_entry^.start:=start;
 new_entry^.__end:=__end;
 new_entry^.desc :=desc;

 gpu_map_entry_link(map, prev_entry, new_entry);
 map^.size:=map^.size+(new_entry^.__end - new_entry^.start);

 Result:=GPU_SUCCESS;
end;

function gpu_map_findspace(map   :p_gpu_map;
                           start :TVkDeviceSize;
                           length:TVkDeviceSize;
                           addr  :PVkDeviceSize):Integer;
label
 _nxt;
var
 entry:p_gpu_map_entry;
 st:TVkDeviceSize;
begin

 if (start<map^.min_offset) then
 begin
  start:=map^.min_offset;
 end;
 if (start + length>map^.max_offset) or (start + length<start) then
 begin
  Exit(1);
 end;

 if (map^.root=nil) then
 begin
  addr^:=start;
  Exit(0);
 end;

 map^.root:=gpu_map_entry_splay(start, map^.root);
 if (start + length<=map^.root^.start) then
 begin
  addr^:=start;
  Exit(0);
 end;

 if (start>map^.root^.__end) then
 begin
  st:=start;
 end else
 begin
  st:=map^.root^.__end
 end;

 if (length<=map^.root^.__end + map^.root^.adj_free - st) then
 begin
  addr^:=st;
  Exit(0);
 end;

 entry:=map^.root^.right;

 if (entry=nil) then
 begin
  Exit(1);
 end;

 if (length>entry^.max_free) then
 begin
  Exit(1);
 end;

 while (entry<>nil) do
 begin
  if (entry^.left<>nil) then
  begin
   if not (entry^.left^.max_free>=length) then goto _nxt;
   entry:=entry^.left;
  end else
  begin
   _nxt:
   if (entry^.adj_free>=length) then
   begin
    addr^:=entry^.__end;
    Exit(0);
   end else
   begin
    entry:=entry^.right;
   end;
  end;
 end;

 { Can't get here, so panic if we do. }
 Assert(false,'gpu_map_findspace: max_free corrupt');
end;

type
 t_gpu_find_mode=Set of (fmInsert,fmBestfit);

function gpu_map_find(map   :p_gpu_map;
                      addr  :PVkDeviceSize;
                      size  :PVkDeviceSize;
                      length:TVkDeviceSize;
                      align :TVkDeviceSize;
                      desc  :Pointer;
                      mode  :t_gpu_find_mode
                     ):Integer;
var
 start    :TVkDeviceSize;
 tmp      :TVkDeviceSize;
 save_addr:TVkDeviceSize;
 save_size:TVkDeviceSize;
begin
 if (addr=nil) then
 begin
  start:=0;
 end else
 begin
  start:=addr^;
 end;

 save_addr:=0;
 save_size:=0;

 repeat
  tmp:=0;
  if (gpu_map_findspace(map, start, length, @tmp)<>0) then
  begin
   Break;
  end;

  if ((tmp mod align)<>0) then
  begin
   tmp:=tmp - (tmp mod align);
   tmp:=tmp + align;
  end;

  start:=tmp;

  tmp:=0;
  Result:=gpu_map_test(map, @tmp, start, start + length);

  if (Result=GPU_SUCCESS) then
  begin
   if (fmBestfit in mode) then
   begin
    //save best fit
    if (save_size=0) or
       (save_size>tmp) then
    begin
     save_addr:=start;
     save_size:=tmp;
    end;
   end else
   begin
    //find first
    save_addr:=start;
    save_size:=tmp;
    Break;
   end;
  end;

 until (Result<>GPU_NO_SPACE);

 if (save_size=0) then
 begin
  Result:=GPU_NO_SPACE;
 end else
 if (fmInsert in mode) then
 begin
  Result:=gpu_map_insert(map, start, start + length, desc);
 end else
 begin
  Result:=GPU_SUCCESS;
 end;

 if (addr<>nil) then
 begin
  addr^:=save_addr;
 end;

 if (size<>nil) then
 begin
  size^:=save_size;
 end;
end;

procedure gpu_map_entry_delete(map:p_gpu_map;entry:p_gpu_map_entry);
var
 size:TVkDeviceSize;
begin
 gpu_map_entry_unlink(map, entry);

 size:=entry^.__end - entry^.start;
 map^.size:=map^.size-size;

 gpu_map_entry_dispose(map,entry);
end;

function gpu_map_delete(map:p_gpu_map;start:TVkDeviceSize):Integer;
var
 entry:p_gpu_map_entry;
begin
 if (not gpu_map_lookup_entry(map, start, @entry)) then
 begin
  entry:=entry^.next;
 end;

 if (entry=@map^.header) then
 begin
  Exit(GPU_INVALID_ADDRESS);
 end;

 if (entry^.start<>start) then
 begin
  Exit(GPU_INVALID_ADDRESS);
 end;

 gpu_map_entry_delete(map, entry);

 Result:=GPU_SUCCESS;
end;

function gpu_map_add(map:p_gpu_map;start,length:TVkDeviceSize;desc:Pointer):Integer;
begin
 Result:=gpu_map_insert(map, start, start + length, desc);
end;

function gpu_map_remove(map:p_gpu_map;start:TVkDeviceSize):Integer;
begin
 Result:=gpu_map_delete(map, start);
end;

procedure gpu_map_remove_all(map:p_gpu_map);
var
 entry,next:p_gpu_map_entry;
begin
 entry:=map^.header.next;

 while (entry<>@map^.header) do
 begin
  next:=entry^.next;

  gpu_map_entry_delete(map, entry);

  entry:=next;
 end;
end;

//

function convert_meminfo(mem_type:Byte;mem_info:PVkMemoryType):TvMemInfo; inline;
begin
 Result.heap_index     :=mem_info^.heapIndex;
 Result.mem_type       :=mem_type;
 Result.device_local   :=(mem_info^.propertyFlags and ord(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT       ))<>0;
 Result.device_coherent:=(mem_info^.propertyFlags and ord(VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD))<>0;
 Result.host_visible   :=(mem_info^.propertyFlags and ord(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT       ))<>0;
 Result.host_coherent  :=(mem_info^.propertyFlags and ord(VK_MEMORY_PROPERTY_HOST_COHERENT_BIT      ))<>0;
end;

Constructor TvDeviceMemory.Create(Handle:TVkDeviceMemory;Size:TVkDeviceSize;mem_type:Byte;mem_info:PVkMemoryType);
begin
 FHandle:=Handle;
 FSize  :=Size;
 //
 FMemInfo:=convert_meminfo(mem_type,mem_info);
 //
 gpu_map_init(@FMap,0,Size);
end;

Destructor TvDeviceMemory.Destroy;
var
 F:TVkDeviceMemory;
begin
 ReleaseAllDependencies(Self);
 //
 gpu_map_remove_all(@FMap);
 //
 TGuard.WaitFor(Self);
 //
 F:=System.InterlockedExchange64(FHandle,VK_NULL_HANDLE);
 if (F<>VK_NULL_HANDLE) then
 begin
  vkFreeMemory(Device.FHandle,F,nil);
  //PrintMemoryBudget;
 end;
 //
 inherited;
end;

Procedure TvDeviceMemory.remove_all;
begin
 gpu_map_remove_all(@FMap);
end;

Procedure TvDeviceMemory.Unmap;
var
 F:TVkDeviceMemory;
begin
 F:=System.InterlockedExchangeAdd64(FHandle,0);
 if (F<>VK_NULL_HANDLE) then
 begin
  vkUnmapMemory(Device.FHandle,F);
 end;
end;

Procedure TvDeviceMemory.Flush;
var
 range:TVkMappedMemoryRange;
begin
 if (not FMemInfo.host_coherent) then
 begin
  range:=Default(TVkMappedMemoryRange);
  range.sType :=VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
  range.memory:=FHandle;
  range.offset:=0;
  range.size  :=FSize;
  //
  vkFlushMappedMemoryRanges(Device.FHandle,1,@range);
 end;
end;

//

function TvDeviceMemory.Acquire(Sender:TObject):Boolean;
const
 mark_delete:QWORD=QWORD(1) shl (SizeOf(FRefs)*8-1);
var
 i:ptruint;
begin
 Result:=False;
 repeat
  i:=load_acq_rel(FRefs);
  if (i and mark_delete)<>0 then Exit;
 until CAS(FRefs,i,i+1);
 Result:=True;
end;

function TvDeviceMemory.Release(Sender:TObject):Boolean;
const
 mark_delete:QWORD=QWORD(1) shl (SizeOf(FRefs)*8-1);
var
 i:ptruint;
begin
 Result:=False;
 repeat
  i:=load_acq_rel(FRefs);
  if (i and mark_delete)<>0 then Exit;
  if (i=1) then
  begin
   if CAS(FRefs,i,mark_delete) then Break;
  end else
  begin
   if CAS(FRefs,i,i-1) then Break;
  end;
 until false;
 if (i=1) then
 begin
  Free;
 end;
 Result:=True;
end;

//

function TvPointer.Acquire:TvPointer;
var
 F:TvDeviceMemory;
 Guard:TGuard;
begin
 Result:=Default(TvPointer);

 Guard:=TGuard.New;
 F:=TvDeviceMemory(Guard.Protect(Pointer(FMemory)));

 if (F=nil) then
 begin
  Guard.Free;
  Exit;
 end;

 if F.Acquire(nil) then
 begin
  Result.FMemory:=F;
  Result.FOffset:=FOffset;
 end;

 Guard.Free;
end;

function TvPointer.Release:Boolean;
var
 F:TvDeviceMemory;
 Guard:TGuard;
begin
 Result:=False;

 Guard:=TGuard.New;
 F:=TvDeviceMemory(Guard.Protect(Pointer(FMemory)));

 if (F=nil) then
 begin
  Guard.Free;
  Exit;
 end;

 Result:=F.Release(nil);

 Guard.Free;
end;

function TvPointer.Hold:Boolean;
var
 F:TvDeviceMemory;
 Guard:TGuard;
begin
 Result:=False;

 Guard:=TGuard.New;
 F:=TvDeviceMemory(Guard.Protect(Pointer(FMemory)));

 if (F=nil) then
 begin
  Guard.Free;
  Exit;
 end;

 Result:=F.Hold(nil);

 Guard.Free;
end;

function TvPointer.Drop:Boolean;
var
 F:TvDeviceMemory;
 Guard:TGuard;
begin
 Result:=False;

 Guard:=TGuard.New;
 F:=TvDeviceMemory(Guard.Protect(Pointer(FMemory)));

 if (F=nil) then
 begin
  Guard.Free;
  Exit;
 end;

 Result:=F.Drop(nil);

 Guard.Free;
end;

//

function GetCompatibleHostMapped(flags:TVkBufferCreateFlags):Boolean;
var
 info:TVkPhysicalDeviceExternalBufferInfo;
 prop:TVkExternalBufferProperties;
begin
 if (vkGetPhysicalDeviceExternalBufferProperties=nil) then Exit(False);

 info:=Default(TVkPhysicalDeviceExternalBufferInfo);
 info.sType     :=VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_BUFFER_INFO;
 info.flags     :=flags;
 info.usage     :=ord(VK_BUFFER_USAGE_TRANSFER_SRC_BIT) or ord(VK_BUFFER_USAGE_TRANSFER_DST_BIT);
 info.handleType:=VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT;

 prop:=Default(TVkExternalBufferProperties);
 prop.sType:=VK_STRUCTURE_TYPE_EXTERNAL_BUFFER_PROPERTIES;

 vkGetPhysicalDeviceExternalBufferProperties(VulkanApp.FPhysicalDevice,@info,@prop);

 Result:=(prop.externalMemoryProperties.compatibleHandleTypes and
         ord(VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT))<>0;
end;

function GetHostMappedRequirements:TVkMemoryRequirements;
var
 cinfo:TVkBufferCreateInfo;
 r:TVkResult;
 FHandle:TVkBuffer;

begin
 Result:=Default(TVkMemoryRequirements);

 cinfo:=Default(TVkBufferCreateInfo);
 cinfo.sType      :=VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
 cinfo.size       :=64*1024;
 cinfo.usage      :=ord(VK_BUFFER_USAGE_TRANSFER_SRC_BIT) or ord(VK_BUFFER_USAGE_TRANSFER_DST_BIT);
 cinfo.sharingMode:=VK_SHARING_MODE_EXCLUSIVE;

 if limits.VK_EXT_external_memory_host then
 begin
  cinfo.pNext:=@buf_ext;
 end;

 r:=vkCreateBuffer(Device.FHandle,@cinfo,nil,@FHandle);
 if (r=VK_SUCCESS) then
 begin
  vkGetBufferMemoryRequirements(Device.FHandle,FHandle,@Result);
  vkDestroyBuffer(Device.FHandle,FHandle,nil);
 end;
end;

function GetSparceMemoryTypes:TVkUInt32;
var
 cinfo:TVkBufferCreateInfo;
 mr:TVkMemoryRequirements;
 r:TVkResult;
 FHandle:TVkBuffer;
begin
 Result:=0;
 if not sparseBinding then Exit;

 mr:=Default(TVkMemoryRequirements);

 cinfo:=Default(TVkBufferCreateInfo);
 cinfo.sType      :=VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
 cinfo.flags      :=ord(VK_BUFFER_CREATE_SPARSE_BINDING_BIT);
 cinfo.size       :=64*1024;
 cinfo.usage      :=ord(VK_BUFFER_USAGE_TRANSFER_SRC_BIT) or ord(VK_BUFFER_USAGE_TRANSFER_DST_BIT);
 cinfo.sharingMode:=VK_SHARING_MODE_EXCLUSIVE;
 cinfo.pNext      :=@buf_ext;

 r:=vkCreateBuffer(Device.FHandle,@cinfo,nil,@FHandle);
 if (r=VK_SUCCESS) then
 begin
  vkGetBufferMemoryRequirements(Device.FHandle,FHandle,@mr);
  vkDestroyBuffer(Device.FHandle,FHandle,nil);
  Result:=mr.memoryTypeBits;
 end;
end;

Constructor TvMemManager.Create;
var
 c:Boolean;
 mr:TVkMemoryRequirements;
 s:RawByteString;
 i:Byte;
begin
 Writeln('[HostMappedRequirements]');

 c:=GetCompatibleHostMapped(0);
 Writeln('  BufferHostMapped=',c);

 if c then
 begin
  mr:=GetHostMappedRequirements;

  Writeln('  Alignment=',mr.alignment);
  s:='';
  For i:=0 to VK_MAX_MEMORY_TYPES-1 do
  if ((1 shl i) and (mr.memoryTypeBits))<>0 then
  begin
   if (s='') then
   begin
    s:=IntToStr(i);
   end else
   begin
    s:=s+','+IntToStr(i);
   end;
  end;
  Writeln('  MemoryType=',S);
 end;

 FSparceMemoryTypes:=0;

 c:=GetCompatibleHostMapped(ord(VK_BUFFER_CREATE_SPARSE_BINDING_BIT));
 Writeln('  SparceHostMapped=',c);

 if c then
 begin
  FSparceMemoryTypes:=GetSparceMemoryTypes;

  s:='';
  For i:=0 to VK_MAX_MEMORY_TYPES-1 do
  if ((1 shl i) and (FSparceMemoryTypes))<>0 then
  begin
   if (s='') then
   begin
    s:=IntToStr(i);
   end else
   begin
    s:=s+','+IntToStr(i);
   end;
  end;
  Writeln('  SparceType=',s);
 end;

 FProperties:=Default(TVkPhysicalDeviceMemoryProperties);
 vkGetPhysicalDeviceMemoryProperties(VulkanApp.FPhysicalDevice,@FProperties);

 LoadMemoryHeaps;

 PrintMemoryHeaps;
 PrintMemoryBudget;

 TAILQ_INIT(@FDevs );
 TAILQ_INIT(@FHosts);
 FUnmapQueue.Create;
end;

function TvMemManager.findMemoryType(Filter:TVkUInt32;prop:TVkMemoryPropertyFlags;start:Integer):Integer;
var
 i:Integer;
begin
 Result:=-1;
 if (start<0) or (start>=FProperties.memoryTypeCount) then Exit;
 For i:=start to FProperties.memoryTypeCount-1 do
 begin
  if  ((Filter and (1 shl i))<>0) and ((FProperties.memoryTypes[i].propertyFlags and prop)=prop) then
  begin
   Exit(i);
  end;
 end;
end;

procedure TvMemManager.LoadMemoryHeaps;
var
 i:TVkUInt32;
 mtype:Integer;

 function get_host_visible(heapIndex:TVkUInt32):Boolean; inline;
 var
  i:TVkUInt32;
 begin
  Result:=False;
  For i:=0 to FProperties.memoryTypeCount-1 do
  if (FProperties.memoryTypes[i].heapIndex=heapIndex) then
  if ((FProperties.memoryTypes[i].propertyFlags and
       ord(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT))<>0) then

  begin
   Exit(True);
  end;
 end;

 function get_mem_type(heapIndex:TVkUInt32;
                       device_local,
                       host_visible,
                       device_coherent,
                       host_coherent:Boolean):Integer; inline;
 var
  i,mask:TVkUInt32;
 begin
  Result:=-1;

  mask:=(ord(device_local   )*ord(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) or
        (ord(host_visible   )*ord(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) or
        (ord(device_coherent)*ord(VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD)) or
        (ord(host_coherent  )*ord(VK_MEMORY_PROPERTY_HOST_COHERENT_BIT));

  For i:=0 to FProperties.memoryTypeCount-1 do
  if (FProperties.memoryTypes[i].heapIndex=heapIndex) then
  if ((FProperties.memoryTypes[i].propertyFlags and mask)=mask) then
  begin
   Exit(i);
  end;
 end;

begin
 SetLength(FHeaps,FProperties.memoryHeapCount);

 if (FProperties.memoryHeapCount<>0) then
 For i:=0 to FProperties.memoryHeapCount-1 do
 begin
  FHeaps[i].heap_index  :=i;
  FHeaps[i].heap_size   :=FProperties.memoryHeaps[i].size;
  FHeaps[i].device_local:=(FProperties.memoryHeaps[i].flags and
                           ord(VK_MEMORY_HEAP_DEVICE_LOCAL_BIT))<>0;
  FHeaps[i].host_visible:=get_host_visible(i);

  FHeaps[i].device_coherent:=FHeaps[i].device_local;
  FHeaps[i].host_coherent  :=FHeaps[i].host_visible;

  repeat
   mtype:=get_mem_type(i,
                       FHeaps[i].device_local,
                       FHeaps[i].host_visible,
                       FHeaps[i].device_coherent,
                       FHeaps[i].host_coherent);

   if (mtype=-1) then
   begin
    if FHeaps[i].device_coherent then
    begin
     FHeaps[i].device_coherent:=False;
    end else
    if FHeaps[i].host_coherent then
    begin
     FHeaps[i].host_coherent:=False;
    end else
    begin
     Assert(false,'load memory type');
    end;

   end else
   begin
    Break;
   end;

  until false;

  FHeaps[i].def_mem_type:=mtype;
 end;

end;

procedure TvMemManager.PrintMemoryHeaps;
var
 i:TVkUInt32;

 function get_flags_str(flags:TVkUInt32):RawByteString; inline;
 begin
  Result:='';
  if (flags and ord(VK_MEMORY_HEAP_DEVICE_LOCAL_BIT))<>0 then
  begin
   Result:='DEVICE_LOCAL';
  end else
  begin
   Result:='HOST_LOCAL';
  end;
  if (flags and ord(VK_MEMORY_HEAP_MULTI_INSTANCE_BIT))<>0 then
  begin
   if (Result<>'') then Result:=Result+',';
   Result:=Result+'MULTI_INSTANCE';
  end;
 end;

 function get_types_str(heapIndex:TVkUInt32):RawByteString; inline;
 var
  i:TVkUInt32;
 begin
  Result:='';
  For i:=0 to FProperties.memoryTypeCount-1 do
  if (FProperties.memoryTypes[i].heapIndex=heapIndex) then
  begin
   if (Result<>'') then Result:=Result+',';
   Result:=Result+IntToStr(i);
  end;
 end;

begin
 For i:=0 to FProperties.memoryHeapCount-1 do
 begin
  Writeln('[Heap]:',i);
  Writeln(' size =0x',HexStr(FProperties.memoryHeaps[i].size,16));
  Writeln(' flags=',get_flags_str(FProperties.memoryHeaps[i].flags));
  Writeln(' types=',get_types_str(i));

 end;

 //

 if Length(FHeaps)<>0 then
 For i:=0 to High(FHeaps) do
 begin
  Writeln('[Heap]:',i);
  Writeln(' heap_size      =0x',HexStr(FHeaps[i].heap_size,16));
  Writeln(' heap_id        =',FHeaps[i].heap_index);
  Writeln(' def_mem_type   =',FHeaps[i].def_mem_type);
  Writeln(' device_local   =',FHeaps[i].device_local);
  Writeln(' device_coherent=',FHeaps[i].device_coherent);
  Writeln(' host_visible   =',FHeaps[i].host_visible);
  Writeln(' host_coherent  =',FHeaps[i].host_coherent);
 end;
end;

procedure TvMemManager.PrintMemoryType(typeFilter:TVkUInt32);
var
 s:RawByteString;
 i:TVkUInt32;

 procedure append(TestFlag:TVkFlags;const name:RawByteString); inline;
 begin
  if ((FProperties.memoryTypes[i].propertyFlags and TestFlag)<>0) then
  begin
   if (s='') then
   begin
    s:=s+name;
   end else
   begin
    s:=s+'|'+name;
   end;
  end;
 end;

begin

 For i:=0 to FProperties.memoryTypeCount-1 do
 begin
  if  ((typeFilter and (1 shl i))<>0) then
  begin
   s:='';

   append(ord(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT       ),'DEVICE_LOCAL');
   append(ord(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT       ),'HOST_VISIBLE');
   append(ord(VK_MEMORY_PROPERTY_HOST_COHERENT_BIT      ),'HOST_COHERENT');
   append(ord(VK_MEMORY_PROPERTY_HOST_CACHED_BIT        ),'HOST_CACHED');
   append(ord(VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT   ),'LAZILY_ALLOCATED');
   append(ord(VK_MEMORY_PROPERTY_PROTECTED_BIT          ),'PROTECTED');
   append(ord(VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD),'DEVICE_COHERENT_AMD');
   append(ord(VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD),'DEVICE_UNCACHED_AMD');
   append(ord(VK_MEMORY_PROPERTY_RDMA_CAPABLE_BIT_NV    ),'RDMA_CAPABLE_NV');

   Write(i,':',HexStr(FProperties.memoryTypes[i].propertyFlags,8),':',s);
  end;
 end;

end;

Function TvMemManager._AllocDevBlock(Size:TVkDeviceSize;mtindex:Byte):TvDeviceMemory;
label
 _retry;
var
 FHandle:TVkDeviceMemory;
 node:TvDeviceMemory;
 tmp:TVkDeviceSize;
 heap_id:Byte;
begin
 Result:=nil;

 node:=nil;

 if (FBacked<>nil) then
 begin
  if (FBacked.FSize>=Size) and
     (FBacked.FMemInfo.mem_type=mtindex) then
  begin
   node:=FBacked;
   FBacked:=nil;
  end;
 end;

 if (node=nil) then
 begin
  _retry:

  heap_id:=FProperties.memoryTypes[mtindex].heapIndex;

  if MemoryBudgetCanBeAlloc(heap_id,Size) then
  begin
   FHandle:=vkAllocMemory(Device.FHandle,Size,mtindex);
   //PrintMemoryBudget;
  end else
  begin
   FHandle:=VK_NULL_HANDLE;
  end;

  if (FHandle=VK_NULL_HANDLE) then
  begin

   //try shrink
   tmp:=_shrink(Size,mtindex,0);

   if (tmp>0) then
   begin
    //fail
    Exit;
   end else
   begin
    goto _retry;
   end;

  end;

  //
  node:=TvDeviceMemory.Create(FHandle,Size,mtindex,@FProperties.memoryTypes[mtindex]);

  node.Acquire(nil); //list
 end;

 TAILQ_INSERT_HEAD(@FDevs,node,@node.entry);

 //
 Result:=node;
end;

procedure TvMemManager._FreeDevBlock(node:TvDeviceMemory);
begin
 if (node=nil) then Exit;

 TAILQ_REMOVE(@FDevs,node,@node.entry);

 if (FBacked<>nil) then
 begin
  if (node.FSize>FBacked.FSize) then
  begin
   ReleaseAndNil(FBacked); //free old
   FBacked:=node;          //set new
  end else
  begin
   ReleaseAndNil(node); //free new
  end;
 end else
 begin
  FBacked:=node; //set new
 end;

end;

//GRANULAR_DEV_BLOCK_SIZE

Function TvMemManager.FetchMemory(const mr:TVkMemoryRequirements;prop:TVkMemoryPropertyFlags;desc:Pointer):TvPointer;
var
 i:Byte;
 memoryTypeBits:TVkUInt32;
 best_fit      :Boolean;
begin
 Result:=Default(TvPointer);

 memoryTypeBits:=mr.memoryTypeBits;

 best_fit:=(prop and V_PROP_BEST_FIT)<>0;
 prop    :=prop and (not V_PROP_BEST_FIT);

 //filter by prop
 For i:=0 to VK_MAX_MEMORY_TYPES-1 do
 if (((1 shl i) and memoryTypeBits)<>0) then
 begin
  //if not contain flags
  if ((FProperties.memoryTypes[i].propertyFlags and prop)<>prop) then
  begin
   //exclude
   memoryTypeBits:=memoryTypeBits and (not (1 shl i));
  end;
 end;

 Result:=FetchMemory(mr.size,mr.alignment,memoryTypeBits,desc,best_fit);
end;

Function TvMemManager.FetchMemory(Size          :TVkDeviceSize;
                                  Align         :TVkDeviceSize;
                                  memoryTypeBits:TVkUInt32;
                                  desc          :Pointer;
                                  best_fit      :Boolean):TvPointer;
label
 _repeat;
var
 node:TvDeviceMemory;

 _addr:TVkDeviceSize;
 _size:TVkDeviceSize;

 save_addr:TVkDeviceSize;
 save_size:TVkDeviceSize;
 save_node:TvDeviceMemory;

 mem_type:Integer;

 i:Byte;

begin
 Result:=Default(TvPointer);
 if (Size=0) or (memoryTypeBits=0) then Exit;
 //
 if (Align=0) then Align:=1;
 //
 save_addr:=0;
 save_size:=0;
 save_node:=nil;
 //
 rw_wlock(global_mem_lock);
 _unmap_host_queue;
 //

 _repeat:

 node:=TvDeviceMemory(TAILQ_FIRST(@FDevs));

 while (node<>nil) do
 begin
  _addr:=0;
  _size:=0;

  if (((1 shl node.FMemInfo.mem_type) and memoryTypeBits)<>0) then
  begin
   if best_fit then
   begin
    if gpu_map_find(@node.FMap,
                    @_addr,
                    @_size,
                    Size,
                    Align,
                    desc,
                    [fmBestfit]
                   )=GPU_SUCCESS then
    begin
     //save best fit
     if (save_size=0) or
        (save_size>_size) then
     begin
      save_addr:=_addr;
      save_size:=_size;
      save_node:=node;
     end;
    end;
   end else
   begin
    if gpu_map_find(@node.FMap,
                    @_addr,
                    @_size,
                    Size,
                    Align,
                    desc,
                    [fmInsert]
                   )=GPU_SUCCESS then
    begin
     save_addr:=_addr;
     save_size:=_size;
     save_node:=node;
     Break;
    end;
   end;
  end;

  node:=TvDeviceMemory(TAILQ_NEXT(node,@node.entry));
 end;

 if (save_size=0) then
 begin
  mem_type:=-1;

  //find by default memtypes
  For i:=0 to High(FHeaps) do
  if (((1 shl FHeaps[i].def_mem_type) and memoryTypeBits)<>0) then
  begin
   mem_type:=FHeaps[i].def_mem_type;
   Break;
  end;

  if (mem_type=-1) then
  begin
   //find first bit
   mem_type:=BsfByte(memoryTypeBits);
  end;

  save_node:=_AllocDevBlock(AlignUp(Size,GRANULAR_DEV_BLOCK_SIZE),mem_type);

  if (save_node<>nil) then
  begin
   if gpu_map_add(@save_node.FMap,0,Size,desc)=GPU_SUCCESS then
   begin
    Result.FMemory:=save_node;
    Result.FOffset:=0;
   end else
   begin
    //wtf?
    goto _repeat;
   end;
  end;

 end else
 if best_fit then
 begin
  if gpu_map_add(@save_node.FMap,save_addr,size,desc)=GPU_SUCCESS then
  begin
   Result.FMemory:=save_node;
   Result.FOffset:=save_addr;
  end else
  begin
   //Something has changed
   goto _repeat;
  end;
 end else
 begin
  //Already inserted
  Result.FMemory:=save_node;
  Result.FOffset:=save_addr;
 end;
 //
 if (Result.FMemory<>nil) then
 begin
  if (Result.FMemory<>TvDeviceMemory(TAILQ_FIRST(@FDevs))) then
  begin
   TAILQ_REMOVE     (@FDevs,Result.FMemory,@Result.FMemory.entry);
   TAILQ_INSERT_HEAD(@FDevs,Result.FMemory,@Result.FMemory.entry);
  end;
  //
  Result.FMemory.Acquire(nil); //fetch ref
 end;
 //
 rw_wunlock(global_mem_lock);
end;

Function TvMemManager.FreeMemory(P:TvPointer):Boolean;
begin
 Result:=False;
 if (P.FMemory=nil) then Exit;
 if (P.FMemory.ClassType<>TvDeviceMemory) then Exit;
 //
 rw_wlock(global_mem_lock);
 _unmap_host_queue;
 //
 if (gpu_map_remove(@P.FMemory.FMap,P.FOffset)=GPU_SUCCESS) then
 begin
  Result:=True;
  //
  if (P.FMemory.FMap.size=0) then //is free
  begin
   _FreeDevBlock(P.FMemory);
  end;
 end;
 //
 rw_wunlock(global_mem_lock);
end;

function get_str_mem_info(const m:TvMemInfo):RawByteString; forward;

Function TvMemManager._shrink_dev_block(max:TVkDeviceSize;heap_index:Byte):TVkDeviceSize;
var
 node,prev:TvDeviceMemory;
begin
 Result:=0;

 if (FBacked<>nil) then
 begin
  node:=FBacked;
  FBacked:=nil;
  Writeln('Unload:0x',HexStr(node),':0x',HexStr(node.FHandle,16),':[',get_str_mem_info(node.FMemInfo),']');
  Result:=Result+node.FSize;
  ReleaseAndNil(node);
  if (Result>=max) then Exit;
 end;

 node:=TvDeviceMemory(TAILQ_LAST(@FDevs));

 while (node<>nil) do
 begin
  prev:=TvDeviceMemory(TAILQ_PREV(node,@node.entry));

  if (node.FMemInfo.heap_index=heap_index) then
  if (node.FHold=0) then //lock Hold?
  begin
   Writeln('Unload:0x',HexStr(node),':0x',HexStr(node.FHandle,16),':[',get_str_mem_info(node.FMemInfo),']');
   //
   Result:=Result+node.FSize;
   //
   TAILQ_REMOVE(@FDevs,node,@node.entry);
   node.ReleaseAllDependencies(node);
   node.remove_all;
   ReleaseAndNil(node); //list
   //
   if (Result>=max) then Break;
  end;

  node:=prev;
 end;

end;

Function TvMemManager._shrink_host_map(max:TVkDeviceSize;heap_index:Byte):TVkDeviceSize;
var
 node,prev:TvHostMemory;
 i:Integer;
begin
 Result:=0;

 node:=TvHostMemory(TAILQ_LAST(@FHosts));
 while (node<>nil) do
 begin
  prev:=TvHostMemory(TAILQ_PREV(node,@node.entry));

  if (node.FMemInfo.heap_index=heap_index) then
  if (node.FHold=0) then //lock Hold?
  begin
   Writeln('Unload:0x',HexStr(node),':0x',HexStr(node.FHandle,16),':[',get_str_mem_info(node.FMemInfo),']');
   //
   Result:=Result+node.FSize;
   //
   Dec(FHosts_count);
   TAILQ_REMOVE(@FHosts,node,@node.entry);
   node.ReleaseAllDependencies(node);
   ReleaseAndNil(node); //list
   //
   if (Result>=max) then Break;
  end;

  node:=prev;
 end;

end;

Function TvMemManager._shrink(max:TVkDeviceSize;mtindex:Byte;mode:Byte):TVkDeviceSize;
var
 tmp:QWORD;
 heap_index:Byte;
begin
 heap_index:=FProperties.memoryTypes[mtindex].heapIndex;

 if (mode=0) then
 begin
  //dev->host
  tmp:=_shrink_dev_block(max,heap_index);
 end else
 begin
  //host->dev
  tmp:=_shrink_host_map(max,heap_index);
 end;

 if (tmp<max) then
 begin
  max:=max-tmp;
 end else
 begin
  max:=0;
 end;

 if (max>0) then
 begin
  //try shrink 2

  if (mode=0) then
  begin
   //dev->host
   tmp:=_shrink_host_map(max,heap_index);
  end else
  begin
   //host->dev
   tmp:=_shrink_dev_block(max,heap_index);
  end;

  if (tmp<max) then
  begin
   max:=max-tmp;
  end else
  begin
   max:=0;
  end;
 end;

 Result:=max;
end;

procedure TvMemManager.unmap_host(start,__end:QWORD);
var
 node:PQUnmapNode;
begin
 if (start=__end) then Exit;

 node:=AllocMem(SizeOf(TQUnmapNode));
 node^.start:=start;
 node^.__end:=__end;

 FUnmapQueue.Push(node);
end;

procedure TvMemManager._unmap_host_queue;
var
 node:PQUnmapNode;
 start,__end:QWORD;
begin

 node:=nil;
 while FUnmapQueue.Pop(node) do
 begin
  start:=node^.start;
  __end:=node^.__end;
  FreeMem(node);

  _unmap_host(start,__end);
 end;

end;

procedure TvMemManager._unmap_host(start,__end:QWORD);
var
 node,next:TvHostMemory;
begin

 node:=TvHostMemory(TAILQ_FIRST(@FHosts));
 while (node<>nil) do
 begin
  next:=TvHostMemory(TAILQ_NEXT(node,@node.entry));

  if (__end>node.FStart) and (start<node.F__End) then
  begin
   Dec(FHosts_count);
   TAILQ_REMOVE(@FHosts,node,@node.entry);
   ReleaseAndNil(node); //list
  end;

  node:=next;
 end;

end;

procedure _print_dmem_fd; external;

Function TvMemManager.FetchHostMap(Addr,Size:TVkDeviceSize;mtindex:Byte):TvPointer;
label
 _retry,
 _fail;
var
 FStart:QWORD;
 F__End:QWORD;
 FStart_align:QWORD;
 F__End_align:QWORD;
 tmp:QWORD;
 node:TvHostMemory;
 FHandle:TVkDeviceMemory;
 heap_id:Byte;
begin
 Result:=Default(TvPointer);
 if (Addr=0) or (Size=0) then Exit;
 //
 FStart:=QWORD(Addr);
 F__End:=FStart+Size;
 //
 FStart:=Max(FStart,VM_MIN_GPU_ADDRESS);
 F__End:=Min(F__End,VM_MAX_GPU_ADDRESS);
 //
 rw_wlock(global_mem_lock);
 _unmap_host_queue;
 //

 node:=TvHostMemory(TAILQ_FIRST(@FHosts));
 while (node<>nil) do
 begin

  if (FStart>=node.FStart) and
     (F__End<=node.F__End) then
  begin
   Break;
  end;

  node:=TvHostMemory(TAILQ_NEXT(node,@node.entry));
 end;

 if (node=nil) then
 begin
  FStart_align:=FStart;
  F__End_align:=F__End;

  gpu_get_bound(FStart_align,F__End_align);

  if (FStart_align=0) then
  begin
   Writeln('Addres:0x',HexStr(FStart,11),' not gpu mapped!');
   Assert(false,'FetchHostMap:gpu_get_bound');
  end;

  FStart_align:=Max(FStart_align,AlignDw(FStart,GRANULAR_MAP_BLOCK_SIZE));
  F__End_align:=Min(F__End_align,AlignUp(F__End,GRANULAR_MAP_BLOCK_SIZE));

  _retry:

  tmp:=F__End_align-FStart_align;

  heap_id:=FProperties.memoryTypes[mtindex].heapIndex;

  if MemoryBudgetCanBeAlloc(heap_id,tmp) then
  begin
   FHandle:=vkAllocHostMemory(Device.FHandle,tmp,mtindex,Pointer(FStart_align));
   //PrintMemoryBudget;
  end else
  begin
   FHandle:=VK_NULL_HANDLE;
  end;

  if (FHandle=VK_NULL_HANDLE) then
  begin

   //try shrink
   tmp:=_shrink(tmp,mtindex,1);

   if (tmp>0) then
   begin
    node:=nil;
    goto _fail;
   end else
   begin
    goto _retry;
   end;

  end;

  node:=TvHostMemory.Create(FHandle,tmp,mtindex,@FProperties.memoryTypes[mtindex]);

  node.FStart:=FStart_align;
  node.F__End:=F__End_align;

  node.Acquire(nil); //map ref
  TAILQ_INSERT_HEAD(@FHosts,node,@node.entry);
  Inc(FHosts_count);
 end;

 node.Acquire(nil);

 _fail:

 if (node=nil) then
 begin
  _print_host;
  _print_devs;
  _print_dmem_fd;
  PrintMemoryBudget;
 end;

 //
 rw_wunlock(global_mem_lock);
 //

 if (node<>nil) then
 begin
  Result.FMemory:=TvDeviceMemory(node);
  Result.FOffset:=Addr-node.FStart;

  if ((Result.FOffset+Size)>node.FSize) then
  begin
   Assert(false);
  end;

 end;
end;

Function TvMemManager.FetchHostMap(Addr,Size:TVkDeviceSize;device_local:Boolean):TvPointer;
var
 i:Byte;
begin
 Result:=Default(TvPointer);

 Assert(Length(FHeaps)<>0);

 For i:=0 to High(FHeaps) do
 if (FHeaps[i].host_visible) then
 if (FHeaps[i].device_local=device_local) then
 begin
  Exit(FetchHostMap(Addr,Size,FHeaps[i].def_mem_type));
 end;

end;

Procedure TvMemManager.Flush;
var
 node:TvHostMemory;
begin
 if (Self=nil) then Exit;

 rw_wlock(global_mem_lock);
 _unmap_host_queue;
 //

 node:=TvHostMemory(TAILQ_FIRST(@FHosts));
 while (node<>nil) do
 begin

  node.Flush;

  node:=TvHostMemory(TAILQ_NEXT(node,@node.entry));
 end;

 //
 rw_wunlock(global_mem_lock);
end;

function get_str_mem_info(const m:TvMemInfo):RawByteString;
begin
 Result:='HI:0x'+HexStr(m.heap_index,2)+'|'+
         'MI:0x'+HexStr(m.mem_type  ,2)+'|';

 if m.device_local then
 begin
  Result:=Result+'DL|';
 end;

 if m.device_coherent then
 begin
  Result:=Result+'DC|';
 end;

 if m.host_visible then
 begin
  Result:=Result+'HV|';
 end;

 if m.host_coherent then
 begin
  Result:=Result+'HC|';
 end;
end;

Procedure TvMemManager._print_host;
var
 node:TvHostMemory;
begin
 if (Self=nil) then Exit;

 Writeln('[Host]:');

 node:=TvHostMemory(TAILQ_FIRST(@FHosts));
 while (node<>nil) do
 begin

  Writeln(' 0x',HexStr(node.FHandle,16),':[',get_str_mem_info(node.FMemInfo),']');

  Writeln('   0x',HexStr(node.FStart,16),'..',HexStr(node.F__End,16));

  node:=TvHostMemory(TAILQ_NEXT(node,@node.entry));
 end;

end;

function get_desc_str(desc:Pointer):RawByteString;
begin
 if (desc=nil) then
 begin
  Result:='(nil)';
 end else
 begin
  Result:='(0x'+HexStr(desc)+')'+TObject(desc).ClassName;
 end;
end;

procedure gpu_map_print_all(map:p_gpu_map);
var
 entry:p_gpu_map_entry;
begin
 entry:=map^.header.next;

 while (entry<>@map^.header) do
 begin

  Writeln('    ',(entry^.start),'..',(entry^.__end),':',(entry^.__end-entry^.start),':',get_desc_str(entry^.desc));

  entry:=entry^.next;
 end;
end;

Procedure TvMemManager._print_devs;
var
 node:TvDeviceMemory;
begin
 if (Self=nil) then Exit;

 Writeln('[Devs]:');

 node:=TvDeviceMemory(TAILQ_FIRST(@FDevs));
 while (node<>nil) do
 begin

  Writeln(' 0x',HexStr(node.FHandle,16),':[',get_str_mem_info(node.FMemInfo),']');

  Writeln('   ',(node.FMap.size),'/',(node.FSize),':',(node.FMap.size/node.FSize)*100:0:2,'%');

  gpu_map_print_all(@node.FMap);

  node:=TvDeviceMemory(TAILQ_NEXT(node,@node.entry));
 end;

end;

threadvar
 last_alloc_error:TVkResult;

//

function vkAllocMemory(device:TVkDevice;Size:TVkDeviceSize;mtindex:TVkUInt32):TVkDeviceMemory;
var
 ainfo:TVkMemoryAllocateInfo;
 r:TVkResult;
begin
 ainfo:=Default(TVkMemoryAllocateInfo);
 ainfo.sType          :=VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
 ainfo.allocationSize :=Size;
 ainfo.memoryTypeIndex:=mtindex;
 //
 Result:=VK_NULL_HANDLE;
 r:=vkAllocateMemory(device,@ainfo,nil,@Result);
 if (r<>VK_SUCCESS) then
 begin
  last_alloc_error:=r;
  Writeln(StdErr,'vkAllocateMemory:',r,' Size=0x',HexStr(Size,16),' mtindex=',mtindex);
  //print_backtrace(StdErr,Get_pc_addr,get_frame,0);
 end;
end;

function vkAllocHostMemory(device:TVkDevice;Size:TVkDeviceSize;mtindex:TVkUInt32;addr:Pointer):TVkDeviceMemory;
var
 ainfo:TVkMemoryAllocateInfo;
 import:TVkImportMemoryHostPointerInfoEXT;
 r:TVkResult;
begin
 ainfo:=Default(TVkMemoryAllocateInfo);
 ainfo.sType          :=VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
 ainfo.allocationSize :=Size;
 ainfo.memoryTypeIndex:=mtindex;
 ainfo.pNext:=@import;
 //
 import:=Default(TVkImportMemoryHostPointerInfoEXT);
 import.sType       :=VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT;
 import.handleType  :=VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT;
 import.pHostPointer:=addr;
 //
 Result:=VK_NULL_HANDLE;
 r:=vkAllocateMemory(device,@ainfo,nil,@Result);
 if (r<>VK_SUCCESS) then
 begin
  last_alloc_error:=r;
  Writeln(StdErr,'vkAllocHostMemory:',r,' Size=0x',HexStr(Size,16),' mtindex=',mtindex,' addr=0x',HexStr(addr));
 end;
end;

function vkAllocDedicatedImage(device:TVkDevice;Size:TVkDeviceSize;mtindex:TVkUInt32;FHandle:TVkImage):TVkDeviceMemory;
var
 ainfo:TVkMemoryAllocateInfo;
 dinfo:TVkMemoryDedicatedAllocateInfo;
 r:TVkResult;
begin
 ainfo:=Default(TVkMemoryAllocateInfo);
 ainfo.sType          :=VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
 ainfo.allocationSize :=Size;
 ainfo.memoryTypeIndex:=mtindex;
 ainfo.pNext:=@dinfo;
 dinfo:=Default(TVkMemoryDedicatedAllocateInfo);
 dinfo.sType:=VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
 dinfo.image:=FHandle;
 Result:=VK_NULL_HANDLE;
 r:=vkAllocateMemory(device,@ainfo,nil,@Result);
 if (r<>VK_SUCCESS) then
 begin
  last_alloc_error:=r;
  Writeln(StdErr,'vkAllocateMemory:',r);
 end;
end;

function vkAllocDedicatedBuffer(device:TVkDevice;Size:TVkDeviceSize;mtindex:TVkUInt32;FHandle:TVkBuffer):TVkDeviceMemory;
var
 ainfo:TVkMemoryAllocateInfo;
 dinfo:TVkMemoryDedicatedAllocateInfo;
 r:TVkResult;
begin
 ainfo:=Default(TVkMemoryAllocateInfo);
 ainfo.sType          :=VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
 ainfo.allocationSize :=Size;
 ainfo.memoryTypeIndex:=mtindex;
 ainfo.pNext:=@dinfo;
 dinfo:=Default(TVkMemoryDedicatedAllocateInfo);
 dinfo.sType:=VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
 dinfo.buffer:=FHandle;
 Result:=VK_NULL_HANDLE;
 r:=vkAllocateMemory(device,@ainfo,nil,@Result);
 if (r<>VK_SUCCESS) then
 begin
  last_alloc_error:=r;
  Writeln(StdErr,'vkAllocateMemory:',r);
 end;
end;

function GetMemoryBudget(var budget:TVkPhysicalDeviceMemoryBudgetPropertiesEXT):Boolean;
var
 prop:TVkPhysicalDeviceMemoryProperties2;
begin
 if (vkGetPhysicalDeviceMemoryProperties2=nil) then Exit(False);
 //
 prop.sType:=VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2;
 prop.pNext:=@budget;
 //
 budget.sType:=VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT;
 budget.pNext:=nil;
 //
 vkGetPhysicalDeviceMemoryProperties2(VulkanApp.FPhysicalDevice,@prop);
 //
 Result:=True;
end;

function MemoryBudgetCanBeAlloc(heap_id:Byte;size:TVkDeviceSize):Boolean;
var
 budget:TVkPhysicalDeviceMemoryBudgetPropertiesEXT;
 i:TVkDeviceSize;
begin
 Result:=True;
 budget:=Default(TVkPhysicalDeviceMemoryBudgetPropertiesEXT);
 if GetMemoryBudget(budget) then
 begin
  i:=budget.heapUsage[heap_id] + size + 128*1024*1024;

  Result:=(i<=budget.heapBudget[heap_id]);
 end;
end;

procedure PrintMemoryBudget;
var
 budget:TVkPhysicalDeviceMemoryBudgetPropertiesEXT;
 i:Integer;
 FMemInfo:TvMemInfo;
 mem_type:Byte;
begin
 budget:=Default(TVkPhysicalDeviceMemoryBudgetPropertiesEXT);
 if GetMemoryBudget(budget) then
 begin
  Writeln('[MemoryBudget]');
  For i:=0 to VK_MAX_MEMORY_HEAPS-1 do
  if (budget.heapBudget[i]<>0) then
  begin

   if MemManager<>nil then
   begin
    mem_type:=MemManager.FHeaps[i].def_mem_type;

    FMemInfo:=convert_meminfo(mem_type,@MemManager.FProperties.memoryTypes[mem_type]);
   end;

   Writeln(' [',i,']:',get_str_mem_info(FMemInfo),':0x',HexStr(budget.heapUsage[i],16),'/',HexStr(budget.heapBudget[i],16),':',
           (budget.heapUsage[i]/budget.heapBudget[i]*100):0:2,'%'
          );
  end;
 end;
end;

end.

