unit vImageTiling;

{$mode objfpc}{$H+}

interface

uses
 SysUtils,
 sys_bootparam,
 ps4_tiling,
 kern_dmem,
 Vulkan,
 vDevice,
 vMemory,
 vBuffer,
 vImage,
 vImageManager,
 vHostBufferManager,
 vCmdBuffer;

Function  pm4_load_from (cmd:TvCustomCmdBuffer;image:TvCustomImage2;IMAGE_USAGE:Byte):Boolean;
Function  pm4_write_back(cmd:TvCustomCmdBuffer;image:TvCustomImage2):Boolean;
Function  get_image_size(const key:TvImageKey):Ptruint;

implementation

const
 VK_ACCESS_BUF_ANY=ord(VK_ACCESS_MEMORY_READ_BIT) or ord(VK_ACCESS_MEMORY_WRITE_BIT);
 VK_STAGE_BUF_ANY =ord(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);

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

Function GetLinearAlignWidth(bpp,width,height:Ptruint):Ptruint; inline;
var
 pitch_align:Ptruint;
 slice_align:Ptruint;
 log_sz:Ptruint;
begin

 pitch_align:=max(8,64 div bpp);

 width:=(width + pitch_align - 1) and (not (pitch_align - 1));

 slice_align:=max(64,256 div bpp);

 log_sz:=width * height{ * num_samples};
 while ((log_sz mod slice_align)<>0) do
 begin
  width:=width+pitch_align;
  log_sz:=width * height{ * num_samples};
 end;

 Result:=width;
end;

function nextPowerOfTwo(x:Ptruint):Ptruint; inline;
begin
 x:=(x-1);
 x:=x or (x shr 1);
 x:=x or (x shr 2);
 x:=x or (x shr 4);
 x:=x or (x shr 8);
 x:=x or (x shr 16);
 x:=x or (x shr 32);
 Result:=(x+1);
end;

Function GetLinearSize(const key:TvImageKey;align:Boolean):Ptruint;
var
 m_bytePerElement:Ptruint;
 m_level,m_width,m_height:Ptruint;
 m_padwidth,m_padheight:Ptruint;
 m_slice:Ptruint;
begin
 if key.params.samples>1 then
 begin
  Writeln(stderr,'TODO:samples:',key.params.samples);
  Assert(key.params.samples<=1,'key.params.samples>1');
 end;

 m_bytePerElement:=getFormatSize(key.cformat);

 m_level :=key.params.mipLevels;
 m_width :=key.params.width;
 m_height:=key.params.height;

 if (key.params.pow2pad<>0) then
 begin
  m_width :=nextPowerOfTwo(m_width);
  m_height:=nextPowerOfTwo(m_height);
 end;

 Result:=0;

 while (m_level>0) do
 begin
  m_padwidth :=m_width;
  m_padheight:=m_height;

  if align then
  begin
   m_padwidth:=GetLinearAlignWidth(m_bytePerElement,m_padwidth,m_padheight);
  end;

  if IsTexelFormat(key.cformat) then
  begin
   m_padwidth :=(m_padwidth +3) shr 2;
   m_padheight:=(m_padheight+3) shr 2;
  end;

  m_slice:=m_padwidth*
           m_padheight*
           key.params.depth*
           key.params.arrayLayers*
           m_bytePerElement;

  //64?

  Result:=Result+m_slice;

  Dec(m_level);
  m_width :=Max(1,m_width  shr 1);
  m_height:=Max(1,m_height shr 1);
 end;

end;

Function GetLinearAlignSize(const key:TvImageKey):Ptruint;
begin
 Result:=GetLinearSize(key,true);
end;

Function Get1dThinSize(const key:TvImageKey):Ptruint;
const
 m_tileThickness=1;
var
 m_bytePerElement:Ptruint;
 m_level,m_width,m_height:Ptruint;
 m_padwidth   :Ptruint;
 m_padheight  :Ptruint;
 m_depth      :Ptruint;
 m_arrayLayers:Ptruint;
 m_slice      :Ptruint;
 log_sz       :Ptruint;
begin
 Assert(key.params.samples<=1,'key.params.samples>1');

 m_bytePerElement:=getFormatSize(key.cformat);

 m_level      :=key.params.mipLevels;
 //
 m_width      :=key.params.width;
 m_height     :=key.params.height;
 m_depth      :=key.params.depth;
 m_arrayLayers:=key.params.arrayLayers;

 if (key.params.pow2pad<>0) then
 begin
  m_width      :=nextPowerOfTwo(m_width);
  m_height     :=nextPowerOfTwo(m_height);
  m_depth      :=nextPowerOfTwo(m_depth);
  m_arrayLayers:=nextPowerOfTwo(m_arrayLayers);
 end;

 Result:=0;

 while (m_level>0) do
 begin
  m_padwidth :=m_width;
  m_padheight:=m_height;

  if IsTexelFormat(key.cformat) then
  begin
   m_padwidth :=(m_padwidth +3) shr 2;
   m_padheight:=(m_padheight+3) shr 2;
  end;

  //microtile align
  m_padwidth :=(m_padwidth +7) and (not 7);
  m_padheight:=(m_padheight+7) and (not 7);

  //for 1d textures
  m_padheight:=max(m_padheight,8);

  //align pitch to pipe_interleave_size
  log_sz:=(m_padwidth*m_padheight*m_bytePerElement*m_tileThickness);
  while (log_sz and 255)<>0 do //(log_sz mod 256)<>0
  begin
   m_padwidth:=m_padwidth+8;
   log_sz:=(m_padwidth*m_padheight*m_bytePerElement*m_tileThickness);
  end;

  if (m_level<>1) then
  begin
   m_slice:=m_padwidth*
            m_padheight*
            m_depth*
            m_arrayLayers*
            m_bytePerElement;

   m_slice:=(m_slice+255) and (not Ptruint(255));
  end else
  begin
   //Trim the last layer

   m_slice:=m_padwidth*
            m_padheight*
            key.params.depth*
            key.params.arrayLayers*
            m_bytePerElement;

  end;

  Result:=Result+m_slice;

  Dec(m_level);
  m_width :=Max(1,m_width  shr 1);
  m_height:=Max(1,m_height shr 1);
 end;

end;

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

type
 TvTempBuffer=class(TvBuffer)
  Function ReleaseTmp(Sender:TObject):Boolean; virtual; register;
 end;

Function TvTempBuffer.ReleaseTmp(Sender:TObject):Boolean; register;
begin
 //force free
 Result:=Drop(nil);
end;

procedure load_clear(cmd:TvCustomCmdBuffer;image:TvCustomImage2);
var
 Color:TVkClearColorValue;
 DepthStencil:TVkClearDepthStencilValue;
 Range:TVkImageSubresourceRange;
begin

 image.PushBarrier(cmd,
                   ord(VK_ACCESS_TRANSFER_WRITE_BIT),
                   VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                   ord(VK_PIPELINE_STAGE_TRANSFER_BIT));

 case image.key.cformat of
  //stencil
  VK_FORMAT_S8_UINT,
  //depth
  VK_FORMAT_D16_UNORM,
  VK_FORMAT_X8_D24_UNORM_PACK32,
  VK_FORMAT_D32_SFLOAT,
  //depth stencil
  VK_FORMAT_D16_UNORM_S8_UINT,
  VK_FORMAT_D24_UNORM_S8_UINT,
  VK_FORMAT_D32_SFLOAT_S8_UINT:
   begin
    DepthStencil:=Default(TVkClearDepthStencilValue);
    Range:=image.GetSubresRange;

    {
    vkCmdClearDepthStencilImage(cmd.FCmdbuf,
                                image.FHandle,
                                VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                                @DepthStencil,
                                1,
                                @Range);}

   end;
  else
   begin
    Color:=Default(TVkClearColorValue);
    Range:=image.GetSubresRange;

    {
    vkCmdClearColorImage(cmd.FCmdbuf,
                         image.FHandle,
                         VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                         @Color,
                         1,
                         @Range);}

   end;
 end;


end;

type
 TTGAHeader=packed record
  idlength       :Byte;
  colourmaptype  :Byte;
  datatypecode   :Byte;
  colourmaporigin:Word;
  colourmaplength:Word;
  colourmapdepth :Byte;
  x_origin       :Word;
  y_origin       :Word;
  width          :Word;
  height         :Word;
  bitsperpixel   :Byte;
  imagedescriptor:Byte;
 end;

Procedure SaveToTGA(const name:RawByteString;pData:Pointer;width,height,bpp:Ptruint);
var
 F:THandle;
 Header:TTGAHeader;
 slice:Ptruint;
begin
 slice:=(width*height*bpp+7) div 8;

 Header:=Default(TTGAHeader);

 Header.datatypecode   :=3;

 Header.width          :=width;
 Header.height         :=height;
 Header.bitsperpixel   :=bpp;
 Header.imagedescriptor:=32;

 F:=FileCreate(name);
 FileWrite(F,Header,SizeOf(TTGAHeader));
 FileWrite(F,pData^,slice);
 FileClose(F);
end;

type
 t_copy_type=(BufferToImage,ImageToBuffer);

Procedure _Copy_Linear(ctype:t_copy_type;cmd:TvCustomCmdBuffer;buf:TvTempBuffer;image:TvCustomImage2);
var
 BufferImageCopy:TVkBufferImageCopy;
 size:Ptruint;

 BufferImageCopyA:array of TVkBufferImageCopy;

 m_bytePerElement:Ptruint;
 m_level,m_width,m_height:Ptruint;
 m_padwidth,m_padheight:Ptruint;
 m_slice :Ptruint;
 m_offset:Ptruint;

 a,d,b:Ptruint;
begin

 buf.Hold(nil);
 cmd.AddDependence(@buf.ReleaseTmp);

 m_bytePerElement:=getFormatSize(image.key.cformat);

 size:=GetLinearSize(image.key,false);

 case ctype of
  BufferToImage:
   begin
    image.PushBarrier(cmd,
                      ord(VK_ACCESS_TRANSFER_WRITE_BIT),
                      VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                      ord(VK_PIPELINE_STAGE_TRANSFER_BIT));

    cmd.BufferMemoryBarrier(buf.FHandle,
                            VK_ACCESS_BUF_ANY,
                            ord(VK_ACCESS_TRANSFER_READ_BIT),
                            0,size,
                            VK_STAGE_BUF_ANY,
                            ord(VK_PIPELINE_STAGE_TRANSFER_BIT)
                           );
   end;
  ImageToBuffer:
   begin
    image.PushBarrier(cmd,
                      ord(VK_ACCESS_TRANSFER_READ_BIT),
                      VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
                      ord(VK_PIPELINE_STAGE_TRANSFER_BIT));

    cmd.BufferMemoryBarrier(buf.FHandle,
                            VK_ACCESS_BUF_ANY,
                            ord(VK_ACCESS_TRANSFER_WRITE_BIT),
                            0,size,
                            VK_STAGE_BUF_ANY,
                            ord(VK_PIPELINE_STAGE_TRANSFER_BIT)
                           );
   end;
 end;

 BufferImageCopy:=Default(TVkBufferImageCopy);
 BufferImageCopy.imageSubresource:=image.GetSubresLayer;
 BufferImageCopy.imageSubresource.layerCount:=1;
 BufferImageCopy.imageExtent.depth:=1;

 BufferImageCopyA:=nil;
 SetLength(BufferImageCopyA,image.key.params.arrayLayers*image.key.params.depth*image.key.params.mipLevels);
 b:=0;

 m_offset:=0;

 //mips
 m_level :=image.key.params.mipLevels;
 m_width :=image.key.params.width;
 m_height:=image.key.params.height;

 while (m_level>0) do
 begin
  BufferImageCopy.imageSubresource.mipLevel:=image.key.params.mipLevels-m_level;

  BufferImageCopy.imageExtent.width :=m_width;
  BufferImageCopy.imageExtent.height:=m_height;

  if IsTexelFormat(image.key.cformat) then
  begin
   m_padwidth :=(m_width +3) shr 2;
   m_padheight:=(m_height+3) shr 2;
  end else
  begin
   m_padwidth :=m_width ;
   m_padheight:=m_height;
  end;

  m_slice:=m_padwidth*m_padheight*m_bytePerElement;

  //array
  for a:=0 to image.key.params.arrayLayers-1 do
  for d:=0 to image.key.params.depth-1 do
  begin
   //Assert((m_offset and 3)=0,'align by 4');
   BufferImageCopy.bufferOffset:=m_offset;

   BufferImageCopy.imageSubresource.baseArrayLayer:=a;
   BufferImageCopy.imageOffset.z:=d;

   BufferImageCopyA[b]:=BufferImageCopy;
   Inc(b);

   m_offset:=m_offset+m_slice;
  end;
  //array

  Dec(m_level);
  m_width :=Max(1,m_width  shr 1);
  m_height:=Max(1,m_height shr 1);
 end;
 //mips

 case ctype of
  BufferToImage:
   begin
    cmd.CopyBufferToImage(buf.FHandle,
                          image.FHandle,
                          VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                          Length(BufferImageCopyA),
                          @BufferImageCopyA[0]);
   end;
  ImageToBuffer:
   begin
    cmd.CopyImageToBuffer(image.FHandle,
                          VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
                          buf.FHandle,
                          Length(BufferImageCopyA),
                          @BufferImageCopyA[0]);
   end;
 end;

end;

Procedure copy_1dThin_to_linear(var tiler:Tiler1d;src,dst:Pointer);
var
 m_bytePerElement:Ptruint;
 m_slice_size:Ptruint;
 i,x,y,z:QWORD;
 pSrc,pDst:Pointer;
begin
 m_bytePerElement:=tiler.m_bytePerElement;
 m_slice_size:=(tiler.m_linearWidth*tiler.m_linearHeight);
 //
 For z:=0 to tiler.m_linearDepth-1 do
  For y:=0 to tiler.m_linearHeight-1 do
   For x:=0 to tiler.m_linearWidth-1 do
    begin
     i:=0;
     tiler.getTiledElementByteOffset(i,x,y,z);
     pSrc:=@PByte(src)[i];
     //
     pDst:=@PByte(dst)[(z*m_slice_size+y*tiler.m_linearWidth+x)*m_bytePerElement];
     //
     Move(pSrc^,pDst^,m_bytePerElement);
    end;
end;

Procedure copy_linear_to_1dThin(var tiler:Tiler1d;src,dst:Pointer);
var
 m_bytePerElement:Ptruint;
 m_slice_size:Ptruint;
 i,x,y,z:QWORD;
 pSrc,pDst:Pointer;
begin

 //ImageToBuffer

 dst:=get_dmem_ptr(dst);

 //
 m_bytePerElement:=tiler.m_bytePerElement;
 m_slice_size:=(tiler.m_linearWidth*tiler.m_linearHeight);
 //
 For z:=0 to tiler.m_linearDepth-1 do
  For y:=0 to tiler.m_linearHeight-1 do
   For x:=0 to tiler.m_linearWidth-1 do
    begin
     pSrc:=@PByte(src)[(z*m_slice_size+y*tiler.m_linearWidth+x)*m_bytePerElement];
     //
     i:=0;
     tiler.getTiledElementByteOffset(i,x,y,z);
     pDst:=@PByte(dst)[i];
     //
     Move(pSrc^,pDst^,m_bytePerElement);
    end;
end;

Procedure load_write_1dThin(ctype:t_copy_type;
                            image:TvCustomImage2;
                            m_full_linear_size:Ptruint;
                            m_base:Pointer);
var
 tiler:Tiler1d;

 m_bytePerElement:Ptruint;
 m_level,m_width,m_height:Ptruint;

 src:Pointer;
 dst:Pointer;

 a:Ptruint;
begin

 dst:=m_base;

 m_bytePerElement:=getFormatSize(image.key.cformat);

 tiler.init_surface(m_bytePerElement,
                    ord(IsTexelFormat(image.key.cformat)),
                    image.key.params.tiling.idx,
                    image.key.params.tiling.alt);

 //TvBuffer

 //mips
 m_level :=image.key.params.mipLevels;
 //
 m_width :=image.key.params.width;
 m_height:=image.key.params.height;

 if (image.key.params.pow2pad<>0) then
 begin
  m_width :=nextPowerOfTwo(m_width);
  m_height:=nextPowerOfTwo(m_height);
 end;

 src:=image.key.addr;

 while (m_level>0) do
 begin
  tiler.init_size_2d(m_width,m_height);

  //array
  for a:=0 to image.key.params.arrayLayers-1 do
  begin

   if (ptruint(dst-m_base)+tiler.m_linearSizeBytes)>m_full_linear_size then
   begin
    Writeln(ptruint(dst-m_base)+tiler.m_linearSizeBytes,'>',m_full_linear_size);
    Assert(false);
   end;

   //x,y,z
   case ctype of
    BufferToImage:copy_1dThin_to_linear(tiler,src,dst);
    ImageToBuffer:copy_linear_to_1dThin(tiler,dst,src);
   end;
   //x,y,z

   {
   if ctype=BufferToImage then
   SaveToTGA('shader_dump\texture_a'+IntToStr(a)+
                                   '_mip'+IntToStr(m_level)+
                                   '_'+IntToStr(m_width)+
                                   'x'+IntToStr(m_height)+
                                       '.tga',
             dst,
             tiler.m_linearWidth,
             tiler.m_linearHeight,
            tiler.m_bitsPerElement);
   }

   src:=src+tiler.m_tiledSizeBytes;
   dst:=dst+tiler.m_linearSizeBytes;

  end;
  //array

  if (image.key.params.pow2pad<>0) then
  begin
   a:=nextPowerOfTwo(image.key.params.arrayLayers)-image.key.params.arrayLayers;

   src:=src+tiler.m_tiledSizeBytes*a;
  end;

  //Writeln('nextPowerOfTwo =',nextPowerOfTwo(image.key.params.arrayLayers));

  src:=Pointer((qword(src)+255) and (not 255));

  Dec(m_level);
  m_width :=Max(1,m_width  shr 1);
  m_height:=Max(1,m_height shr 1);
 end;
 //mips

 //Writeln('size1=',(src-image.key.addr));
 //Writeln('size2=',Get1dThinSize(image.key));

end;

function load_1dThin(cmd:TvCustomCmdBuffer;image:TvCustomImage2):Boolean;
var
 buf:TvTempBuffer;
 vmem:TvPointer;

 m_full_linear_size:Ptruint;

 m_base:Pointer;
begin
 Result:=True;

 Assert(image.key.params.samples<=1,'image.key.params.samples>1');

 m_full_linear_size:=GetLinearSize(image.key,False);

 buf:=TvTempBuffer.Create(m_full_linear_size,ord(VK_BUFFER_USAGE_TRANSFER_SRC_BIT),nil);

 vmem:=MemManager.FetchMemory(buf.GetRequirements,V_PROP_HOST_VISIBLE or V_PROP_DEVICE_LOCAL,buf);

 if (vmem.FMemory=nil) then
 begin
  vmem:=MemManager.FetchMemory(buf.GetRequirements,V_PROP_HOST_VISIBLE,buf);
 end;

 if (vmem.FMemory=nil) then
 begin
  FreeAndNil(buf);
  Exit(False);
 end;

 buf.BindMem(vmem);

 vmem.Release; //FetchMemory

 m_base:=nil;
 vkMapMemory(Device.FHandle,
             buf.FBind.FMemory.FHandle,
             buf.FBind.FOffset,
             m_full_linear_size,
             0,
             @m_base);

 load_write_1dThin(BufferToImage,
                   image,
                   m_full_linear_size,
                   m_base);

 vkUnmapMemory(Device.FHandle,buf.FBind.FMemory.FHandle);

 _Copy_Linear(BufferToImage,cmd,buf,image);
end;

type
 TvTempBufferWriteback=class(TvTempBuffer)
  image:TvCustomImage2;
  m_full_linear_size:Ptruint;
  function ReleaseTmp(Sender:TObject):Boolean; override; register;
 end;

function TvTempBufferWriteback.ReleaseTmp(Sender:TObject):Boolean; register;
var
 m_base:Pointer;
begin
 m_base:=nil;
 vkMapMemory(Device.FHandle,
             FBind.FMemory.FHandle,
             FBind.FOffset,
             m_full_linear_size,
             0,
             @m_base);

 load_write_1dThin(ImageToBuffer,
                   image,
                   m_full_linear_size,
                   m_base);

 vkUnmapMemory(Device.FHandle,FBind.FMemory.FHandle);

 image.Drop(Self);
 Result:=inherited;
end;

function write_1dThin(cmd:TvCustomCmdBuffer;image:TvCustomImage2):Boolean;
var
 buf:TvTempBufferWriteback;
 vmem:TvPointer;

 m_full_linear_size:Ptruint;

begin
 Result:=True;

 Assert(image.key.params.samples<=1,'image.key.params.samples>1');

 m_full_linear_size:=GetLinearSize(image.key,False);

 buf:=TvTempBufferWriteback.Create(m_full_linear_size,ord(VK_BUFFER_USAGE_TRANSFER_DST_BIT),nil);

 buf.image:=image;
 buf.m_full_linear_size:=m_full_linear_size;

 image.Hold(buf);

 vmem:=MemManager.FetchMemory(buf.GetRequirements,V_PROP_HOST_VISIBLE or V_PROP_DEVICE_LOCAL,buf);

 if (vmem.FMemory=nil) then
 begin
  vmem:=MemManager.FetchMemory(buf.GetRequirements,V_PROP_HOST_VISIBLE,buf);
 end;

 if (vmem.FMemory=nil) then
 begin
  FreeAndNil(buf);
  Exit(False);
 end;

 buf.BindMem(vmem);

 vmem.Release; //FetchMemory

 _Copy_Linear(ImageToBuffer,cmd,buf,image);
end;

function Load_Linear(cmd:TvCustomCmdBuffer;image:TvCustomImage2):Boolean;
var
 buf:TvHostBuffer;
 BufferImageCopy:TVkBufferImageCopy;
 size:Ptruint;

 BufferImageCopyA:array of TVkBufferImageCopy;

 m_bytePerElement:Ptruint;
 m_level,m_width,m_height:Ptruint;
 m_padwidth,m_padheight:Ptruint;
 m_slice :Ptruint;
 m_offset:Ptruint;

 a,d,b:Ptruint;
begin
 Result:=True;

 m_bytePerElement:=getFormatSize(image.key.cformat);

 size:=GetLinearSize(image.key,(image.key.params.tiling.idx<>kTileModeDisplay_LinearGeneral));

 buf:=FetchHostBuffer(cmd,
                      QWORD(image.key.addr),
                      size);

 if (buf=nil) then
 begin
  Exit(False);
 end;

 m_offset:=QWORD(image.key.addr)-buf.FAddr;

 image.PushBarrier(cmd,
                   ord(VK_ACCESS_TRANSFER_WRITE_BIT),
                   VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                   ord(VK_PIPELINE_STAGE_TRANSFER_BIT));

 cmd.BufferMemoryBarrier(buf.FHandle,
                         VK_ACCESS_BUF_ANY,
                         ord(VK_ACCESS_TRANSFER_READ_BIT),
                         m_offset,
                         size,
                         VK_STAGE_BUF_ANY,
                         ord(VK_PIPELINE_STAGE_TRANSFER_BIT)
                        );

 BufferImageCopy:=Default(TVkBufferImageCopy);
 BufferImageCopy.imageSubresource:=image.GetSubresLayer;
 BufferImageCopy.imageSubresource.layerCount:=1;
 BufferImageCopy.imageExtent.depth:=1;

 BufferImageCopyA:=nil;
 SetLength(BufferImageCopyA,image.key.params.arrayLayers*image.key.params.depth*image.key.params.mipLevels);
 b:=0;

 //mips
 m_level :=image.key.params.mipLevels;
 m_width :=image.key.params.width;
 m_height:=image.key.params.height;

 {
 SaveToTGA('shader_dump\texture_mip'+IntToStr(m_level)+
                                 '_'+IntToStr(m_width)+
                                 'x'+IntToStr(m_height)+
                                     '.tga',
           Pointer(image.key.addr),
           m_width,
           m_height,
           32);
 }

 while (m_level>0) do
 begin
  BufferImageCopy.imageSubresource.mipLevel:=image.key.params.mipLevels-m_level;

  BufferImageCopy.imageExtent.width :=m_width;
  BufferImageCopy.imageExtent.height:=m_height;

  if (image.key.params.tiling.idx=kTileModeDisplay_LinearAligned) then
  begin
   BufferImageCopy.bufferRowLength:=GetLinearAlignWidth(m_bytePerElement,m_width,m_height);
  end;

  if IsTexelFormat(image.key.cformat) then
  begin
   m_padwidth :=(m_width +3) shr 2;
   m_padheight:=(m_height+3) shr 2;
  end else
  begin
   m_padwidth :=m_width ;
   m_padheight:=m_height;
  end;

  m_slice:=m_padwidth*m_padheight*m_bytePerElement;

  //array
  for a:=0 to image.key.params.arrayLayers-1 do
  for d:=0 to image.key.params.depth-1 do
  begin
   //Assert((m_offset and 3)=0,'align by 4');

   BufferImageCopy.bufferOffset:=m_offset;

   BufferImageCopy.imageSubresource.baseArrayLayer:=a;
   BufferImageCopy.imageOffset.z:=d;

   BufferImageCopyA[b]:=BufferImageCopy;
   Inc(b);

   m_offset:=m_offset+m_slice;
  end;
  //array

  Dec(m_level);
  m_width :=Max(1,m_width  shr 1);
  m_height:=Max(1,m_height shr 1);
 end;
 //mips

 cmd.CopyBufferToImage(buf.FHandle,
                       image.FHandle,
                       VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                       Length(BufferImageCopyA),
                       @BufferImageCopyA[0]);


end;

function Writeback_Linear(cmd:TvCustomCmdBuffer;image:TvCustomImage2):Boolean;
var
 buf:TvHostBuffer;
 BufferImageCopy:TVkBufferImageCopy;
 size:Ptruint;

 BufferImageCopyA:array of TVkBufferImageCopy;

 m_bytePerElement:Ptruint;
 m_level,m_width,m_height:Ptruint;
 m_padwidth,m_padheight:Ptruint;
 m_slice :Ptruint;
 m_offset:Ptruint;

 a,d,b:Ptruint;
begin
 Result:=True;

 m_bytePerElement:=getFormatSize(image.key.cformat);

 size:=GetLinearSize(image.key,(image.key.params.tiling.idx<>kTileModeDisplay_LinearGeneral));

 buf:=FetchHostBuffer(cmd,
                      QWORD(image.key.addr),
                      size);

 if (buf=nil) then
 begin
  Exit(False);
 end;

 m_offset:=QWORD(image.key.addr)-buf.FAddr;

 image.PushBarrier(cmd,
                   ord(VK_ACCESS_TRANSFER_READ_BIT),
                   VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
                   ord(VK_PIPELINE_STAGE_TRANSFER_BIT));

 cmd.BufferMemoryBarrier(buf.FHandle,
                         VK_ACCESS_BUF_ANY,
                         ord(VK_ACCESS_TRANSFER_WRITE_BIT),
                         m_offset,
                         size,
                         VK_STAGE_BUF_ANY,
                         ord(VK_PIPELINE_STAGE_TRANSFER_BIT)
                        );

 BufferImageCopy:=Default(TVkBufferImageCopy);
 BufferImageCopy.imageSubresource:=image.GetSubresLayer;
 BufferImageCopy.imageSubresource.layerCount:=1;
 BufferImageCopy.imageExtent.depth:=1;

 BufferImageCopyA:=nil;
 SetLength(BufferImageCopyA,image.key.params.arrayLayers*image.key.params.depth*image.key.params.mipLevels);
 b:=0;

 //mips
 m_level :=image.key.params.mipLevels;
 m_width :=image.key.params.width;
 m_height:=image.key.params.height;

 while (m_level>0) do
 begin
  BufferImageCopy.imageSubresource.mipLevel:=image.key.params.mipLevels-m_level;

  BufferImageCopy.imageExtent.width :=m_width;
  BufferImageCopy.imageExtent.height:=m_height;

  if (image.key.params.tiling.idx=kTileModeDisplay_LinearAligned) then
  begin
   BufferImageCopy.bufferRowLength:=GetLinearAlignWidth(m_bytePerElement,m_width,m_height);
  end;

  if IsTexelFormat(image.key.cformat) then
  begin
   m_padwidth :=(m_width +3) shr 2;
   m_padheight:=(m_height+3) shr 2;
  end else
  begin
   m_padwidth :=m_width ;
   m_padheight:=m_height;
  end;

  m_slice:=m_padwidth*m_padheight*m_bytePerElement;

  //array
  for a:=0 to image.key.params.arrayLayers-1 do
  for d:=0 to image.key.params.depth-1 do
  begin
   //Assert((m_offset and 3)=0,'align by 4');
   BufferImageCopy.bufferOffset:=m_offset;

   BufferImageCopy.imageSubresource.baseArrayLayer:=a;
   BufferImageCopy.imageOffset.z:=d;

   BufferImageCopyA[b]:=BufferImageCopy;
   Inc(b);

   m_offset:=m_offset+m_slice;
  end;
  //array

  Dec(m_level);
  m_width :=Max(1,m_width  shr 1);
  m_height:=Max(1,m_height shr 1);
 end;
 //mips

 cmd.CopyImageToBuffer(image.FHandle,
                       VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
                       buf.FHandle,
                       Length(BufferImageCopyA),
                       @BufferImageCopyA[0]);


end;

type
 t_load_from_cb =function(cmd:TvCustomCmdBuffer;image:TvCustomImage2):Boolean;
 t_write_back_cb=t_load_from_cb;
 t_get_size_cb  =function(const key:TvImageKey):Ptruint;

 t_tiling_cbs=record
  load_from :t_load_from_cb;
  write_back:t_write_back_cb;
  get_size  :t_get_size_cb;
 end;

var
 a_tiling_cbs:array[0..63] of t_tiling_cbs;

function TileIdx(idx,alt:Byte):Byte; inline;
begin
 Result:=idx;
 TvTiling(Result).alt:=alt;
end;

procedure set_tiling_cbs(idx,alt:Byte;
                         l:t_load_from_cb;
                         w:t_write_back_cb;
                         g:t_get_size_cb
                        ); inline;
begin
 with a_tiling_cbs[TileIdx(idx,alt)] do
 begin
  load_from :=l;
  write_back:=w;
  get_size  :=g;
 end;
end;

procedure Init;
begin
 set_tiling_cbs(kTileModeDisplay_2dThin       ,0,@Load_Linear,@Writeback_Linear,@GetLinearAlignSize); //@load_clear;
 set_tiling_cbs(kTileModeDisplay_2dThin       ,1,@Load_Linear,@Writeback_Linear,@GetLinearAlignSize); //@load_clear;

 set_tiling_cbs(kTileModeDepth_2dThin_256     ,0,@Load_Linear,@Writeback_Linear,@GetLinearAlignSize); //@load_clear;
 set_tiling_cbs(kTileModeDepth_2dThin_256     ,1,@Load_Linear,@Writeback_Linear,@GetLinearAlignSize); //@load_clear;

 set_tiling_cbs(kTileModeDepth_2dThin_64      ,0,@Load_Linear,@Writeback_Linear,@GetLinearAlignSize); //@load_clear;
 set_tiling_cbs(kTileModeDepth_2dThin_64      ,1,@Load_Linear,@Writeback_Linear,@GetLinearAlignSize); //@load_clear;

 set_tiling_cbs(kTileModeThin_2dThin          ,0,@Load_Linear,@Writeback_Linear,@GetLinearAlignSize); //@load_clear;
 set_tiling_cbs(kTileModeThin_2dThin          ,1,@Load_Linear,@Writeback_Linear,@GetLinearAlignSize); //@load_clear;

 set_tiling_cbs(kTileModeThick_1dThick        ,0,@Load_Linear,@Writeback_Linear,@GetLinearAlignSize); //@load_clear;
 set_tiling_cbs(kTileModeThick_1dThick        ,1,@Load_Linear,@Writeback_Linear,@GetLinearAlignSize); //@load_clear;

 //
 set_tiling_cbs(kTileModeDepth_1dThin         ,0,@load_1dThin,@write_1dThin,@Get1dThinSize);
 set_tiling_cbs(kTileModeDepth_1dThin         ,1,@load_1dThin,@write_1dThin,@Get1dThinSize);

 set_tiling_cbs(kTileModeDisplay_1dThin       ,0,@load_1dThin,@write_1dThin,@Get1dThinSize);
 set_tiling_cbs(kTileModeDisplay_1dThin       ,1,@load_1dThin,@write_1dThin,@Get1dThinSize);

 set_tiling_cbs(kTileModeThin_1dThin          ,0,@load_1dThin,@write_1dThin,@Get1dThinSize);
 set_tiling_cbs(kTileModeThin_1dThin          ,1,@load_1dThin,@write_1dThin,@Get1dThinSize);
 //

 set_tiling_cbs(kTileModeDisplay_LinearAligned,0,@Load_Linear,@Writeback_Linear,@GetLinearAlignSize);
 set_tiling_cbs(kTileModeDisplay_LinearAligned,1,@Load_Linear,@Writeback_Linear,@GetLinearAlignSize);
end;

function get_tiling_name(i:Byte):RawByteString;
begin
 case i of
  // Depth modes (for depth buffers)
  kTileModeDepth_2dThin_64    :Result:='Depth_2dThin_64';
  kTileModeDepth_2dThin_128   :Result:='Depth_2dThin_128';
  kTileModeDepth_2dThin_256   :Result:='Depth_2dThin_256';
  kTileModeDepth_2dThin_512   :Result:='Depth_2dThin_512';
  kTileModeDepth_2dThin_1K    :Result:='Depth_2dThin_1K';
  kTileModeDepth_1dThin       :Result:='Depth_1dThin';
  kTileModeDepth_2dThinPrt_256:Result:='Depth_2dThinPrt_256';
  kTileModeDepth_2dThinPrt_1K :Result:='Depth_2dThinPrt_1K';
  // Display modes
  kTileModeDisplay_LinearAligned:Result:='LinearAligned';
  kTileModeDisplay_1dThin       :Result:='Display_1dThin';
  kTileModeDisplay_2dThin       :Result:='Display_2dThin';
  kTileModeDisplay_ThinPrt      :Result:='Display_ThinPrt';
  kTileModeDisplay_2dThinPrt    :Result:='Display_2dThinPrt';
  // Thin modes (for non-displayable 1D/2D/3D
  kTileModeThin_1dThin   :Result:='Thin_1dThin';
  kTileModeThin_2dThin   :Result:='Thin_2dThin';
  kTileModeThin_3dThin   :Result:='Thin_3dThin';
  kTileModeThin_ThinPrt  :Result:='Thin_ThinPrt';
  kTileModeThin_2dThinPrt:Result:='Thin_2dThinPrt';
  kTileModeThin_3dThinPrt:Result:='Thin_3dThinPrt';
  // Thick modes (for 3D textures)
  kTileModeThick_1dThick   :Result:='Thick_1dThick';
  kTileModeThick_2dThick   :Result:='Thick_2dThick';
  kTileModeThick_3dThick   :Result:='Thick_3dThick';
  kTileModeThick_ThickPrt  :Result:='Thick_ThickPrt';
  kTileModeThick_2dThickPrt:Result:='Thick_2dThickPrt';
  kTileModeThick_3dThickPrt:Result:='Thick_3dThickPrt';
  kTileModeThick_2dXThick  :Result:='Thick_2dXThick';
  kTileModeThick_3dXThick  :Result:='Thick_3dXThick';
  // Hugely inefficient linear display mode -
  kTileModeDisplay_LinearGeneral:Result:='LinearGeneral';
  else
   Result:=IntToStr(i);
 end;
end;

function pm4_load_from(cmd:TvCustomCmdBuffer;image:TvCustomImage2;IMAGE_USAGE:Byte):Boolean;
var
 cb:t_load_from_cb;
 change_rate:t_change_rate;
begin
 Result:=True;
 if (cmd=nil) or (image=nil) then Exit;

 if (IMAGE_USAGE and TM_READ)=0 then Exit;

 if (image.key.params.samples>1) then
 begin
  Exit;
 end;

 if (image.key.params.invalid<>0) then
 begin
  Exit;
 end;

 if image.IsDepthAndStencil then
 begin
  Result:=Result and pm4_load_from(cmd,image.DepthOnly  ,IMAGE_USAGE);
  Result:=Result and pm4_load_from(cmd,image.StencilOnly,IMAGE_USAGE);
  Exit;
 end;

 change_rate:=image.get_change_rate;

 if not change_rate.need_read then Exit;

 if p_print_gpu_ops then
 begin
  Writeln('loadfrom: ',image.FName);
 end;

 cb:=a_tiling_cbs[Byte(image.key.params.tiling)].load_from;

 if (cb=nil) then
 begin
  Writeln(stderr,'tiling:'+get_tiling_name(image.key.params.tiling.idx)+' alt:'+IntToStr(image.key.params.tiling.alt));
  Assert (false ,'tiling:'+get_tiling_name(image.key.params.tiling.idx)+' alt:'+IntToStr(image.key.params.tiling.alt));
  Exit;
 end;

 cmd.EndRenderPass;

 image.restore_vm_track;

 cmd.BeginLabel('loadfrom');

 Result:=cb(cmd,image);

 cmd.EndLabel();

 if not Result then Exit;

 change_rate.mark_init;

 image.apply_change_rate(change_rate);

 image.assign_vm_track;
end;

function pm4_write_back(cmd:TvCustomCmdBuffer;image:TvCustomImage2):Boolean;
var
 cb:t_write_back_cb;
begin
 Result:=True;
 if (cmd=nil) or (image=nil) then Exit;

 if (image.key.params.samples>1) then
 begin
  Exit;
 end;

 if (image.key.params.invalid<>0) then
 begin
  Exit;
 end;

 if image.IsDepthAndStencil then
 begin
  Result:=Result and pm4_write_back(cmd,image.DepthOnly  );
  Result:=Result and pm4_write_back(cmd,image.StencilOnly);
  Exit;
 end;

 if p_print_gpu_ops then
 begin
  Writeln('writeback:',image.FName);
 end;

 cb:=a_tiling_cbs[Byte(image.key.params.tiling)].write_back;

 if (cb=nil) then
 begin
  Writeln(stderr,'tiling:'+get_tiling_name(image.key.params.tiling.idx)+' alt:'+IntToStr(image.key.params.tiling.alt));
  Assert (false ,'tiling:'+get_tiling_name(image.key.params.tiling.idx)+' alt:'+IntToStr(image.key.params.tiling.alt));
  Exit;
 end;

 cmd.EndRenderPass;

 cmd.BeginLabel('writeback');

 Result:=cb(cmd,image);

 cmd.EndLabel();

 if not Result then Exit;

 image.mark_init;

 image.assign_vm_track;

 cmd.AddPlannedTrigger(QWORD(image.key.Addr),QWORD(image.key.Addr)+image.size,image.tobj);
end;

Function get_image_size(const key:TvImageKey):Ptruint; [public, alias:'tiling_get_image_size'];
var
 cb:t_get_size_cb;
begin

 cb:=a_tiling_cbs[Byte(key.params.tiling)].get_size;

 if (cb=nil) then
 begin
  Writeln(stderr,'tiling:'+get_tiling_name(key.params.tiling.idx)+' alt:'+IntToStr(key.params.tiling.alt));
  Assert (false ,'tiling:'+get_tiling_name(key.params.tiling.idx)+' alt:'+IntToStr(key.params.tiling.alt));
  Exit(0);
 end;

 Result:=cb(key);
end;


{
Procedure LoadFromBuffer(cmd:TvCustomCmdBuffer;image:TObject);
begin
 if (cmd=nil) then Exit;

 Case TvCustomImage2(image).key.params.tiling_idx of
  kTileModeDisplay_LinearAligned,
  kTileModeDisplay_LinearGeneral:
   _Load_Linear(cmd,TvCustomImage2(image));

  kTileModeDisplay_2dThin: //render target tiling todo
   _Load_Linear(cmd,TvCustomImage2(image));

  kTileModeDepth_2dThin_64 ,
  kTileModeDepth_2dThin_128,
  kTileModeDepth_2dThin_256,
  kTileModeDepth_2dThin_512,
  kTileModeDepth_2dThin_1K : //depth tiling todo
   _Load_Linear(cmd,TvCustomImage2(image));

  kTileModeDepth_1dThin,
  kTileModeDisplay_1dThin,
  kTileModeThin_1dThin,  //texture
  $1B:
   _Load_Thin_1dThin(cmd,TvCustomImage2(image));

  kTileModeThin_2dThin:
   _Load_Linear(cmd,TvCustomImage2(image)); //TODO

  else
   if not SKIP_UNKNOW_TILING then
   Assert(false,'TODO tiling_idx:'+get_tiling_idx_str(TvCustomImage2(image).key.params.tiling_idx));
 end;

end;
}

initialization
 Init;

end.

