Figured it out guys. I had to use .Lock() on texture, which gave me back pointer to pixels, similar to what ScanLine does. Much faster, and with that gray multiplication trick I made it to work.

Code:
procedure TTableResources.DesaturateImage(const AImage: TAsphyreImage);
type
  PPixelRec = ^TPixelRec;
  TPixelRec = packed record
    B: Byte;
    G: Byte;
    R: Byte;
    A: Byte;
  end;
var
  C1, x, y: Integer;
  bitsp: pointer;
  pitch: Integer;
  bytes_per_pixel: Integer;
  pixel: PPixelRec;
  gray_value: Byte;
begin
  for C1 := 0 to AImage.TextureCount - 1 do
  begin
    AImage.Texture[C1].Lock(Rect(0, 0, AImage.Texture[C1].Width, AImage.Texture[C1].Height), bitsp, pitch);
    try
      bytes_per_pixel := pitch div AImage.Texture[C1].Width;
      for y := 0 to AImage.Texture[C1].Height - 1 do
        for x := 0 to AImage.Texture[C1].Width - 1 do
        begin
          pixel := PPixelRec(Integer(bitsp) + y * pitch + x * bytes_per_pixel);
          gray_value := Round(0.30 * pixel^.r + 0.59 * pixel^.g + 0.11 * pixel^.b);
          pixel^.r := gray_value;
          pixel^.g := gray_value;
          pixel^.b := gray_value;
        end;
    finally
      AImage.Texture[C1].Unlock;
    end;
  end;
end;
Thanks!