<!-- markdownlint-disable MD033 -->
<!-- markdownlint-disable MD041 -->
<p align="left"><img src="https://vulkan.lunarg.com/img/NewLunarGLogoBlack.png" alt="LunarG" width=263 height=113 /></p>

[![Creative Commons][1]][2]

[1]: https://i.creativecommons.org/l/by-nd/4.0/88x31.png "Creative Commons License"
[2]: https://creativecommons.org/licenses/by-nd/4.0/

Copyright &copy; 2018-2021 LunarG, Inc.

# GFXReconstruct API Capture and Replay

## **Android Version**

This document describes the GFXReconstruct software for capturing and replaying
Vulkan API calls on Android devices.

## Index

1. [Capturing API Calls](#capturing-api-calls)
    1. [Before Use](#before-use)
    2. [Enabling the Layer with ADB](#enabling-the-layer-with-adb)
    3. [Capture Options](#capture-options)
    4. [Capture Files](#capture-files)
2. [Replaying API Calls](#replaying-api-calls)
    1. [Launch Script](#launch-script)
    2. [Install APK Command](#install-apk-command)
    3. [Replay Command](#replay-command)
    2. [Touch Controls](#touch-controls)
    2. [Key Controls](#key-controls)
    3. [Virtual Swapchain](#virtual-swapchain)

## Capturing API Calls

The GFXReconstruct capture layer is a Vulkan layer that intercepts Vulkan API
calls and logs them to a GFXReconstruct capture file.

### Before Use

#### Permissions

The GFXReconstruct layer can optionally read a configuration file
from or write capture files to external storage.  This requires that
the application loading the layer have external storage permissions.

The read and write external storage permission may be requested in
the application's manifest file.  When installing the application,
it may be necessary to ensure that the requested permissions are
granted through one of the following actions:

When installing the application with `adb install`:

* Specify the -g option: `adb install -g`

When deploying from Android Studio:

* Click on "Run" in the menu
* Choose "Edit Configurations..."
* In the dialog box, look for the "Install Flags:" text box
* Enter `-g`
* Click "Apply"

It may also be possible to grant external storage permissions to
an installed application through the device Settings.

**Failure to enable the write external storage permission** can cause
the layer to return `VK_ERROR_INITIALIZATION_FAILED` from its
`vkCreateInstance` function if it fails to create a capture file.

#### Disabling Debug Breaks Triggered by the GFXReconstruct Layer

The Vulkan API allows Vulkan memory objects to be mapped by an application
for direct modification. To successfully capture an application,
the GFXReconstruct layer must be able to detect when the application
modifies the mapped memory. The layer can be configured to detect memory
modifications by marking the mapped memory as write protected, triggering
an access violation when the application writes to the memory. The
layer then uses a signal handler to intercept the signal generated by the
access violation, where it removes the write protection, marks the modified
memory page as dirty, and allows the application to continue.

When running an application in a debugger with the layer enabled, the
access violations triggered by the layer's memory tracking behavior may
cause the debugger to break. These debug breaks may be disabled for LLDB with
the following command:

```text
process handle SIGSEGV -n true -p true -s false
```

This command may be entered manually through the LLDB tab on Android Studio's
Debug panel.

It may also be set as a post attach command in the project configuration:

* Click on "Run" in the menu
* Choose "Edit Configurations..."
* In the dialog box, select the "Debugger" tab
* In the "Debugger" tab, select the "LLDB Post Attach Commands" tab
* Click the `+` to add the command to the command list
* Enter the `process handle SIGSEGV -n true -p true -s false` command
* Click "Apply"

### Enabling the Layer with ADB

The layer can be enabled through a system property by executing the following
ADB command:

```bash
adb shell "setprop debug.vulkan.layers 'VK_LAYER_LUNARG_gfxreconstruct'"
```

The following command will disable the layer:

```bash
adb shell "setprop debug.vulkan.layers ''"
```

### Capture Options

The GFXReconstruct layer supports the following options, which may be enabled
through Android system properties or a layer settings file.  Each Android
system property begins with the prefix `debug.gfxrecon`, and can be set
through ADB with the following command syntax:

```bash
adb shell "setprop <option> '<value>'"
```

For example, to set the log_level to "warning", specify:

```bash
adb shell "setprop debug.gfxrecon.log_level 'warning'"
```

#### Supported Options

Options with the BOOL type accept the following values:

* A case-insensitive string value 'true' or a non-zero integer value indicate true.
* A case-insensitive string value 'false' or a zero integer value indicate false.

The capture layer will generate a warning message for unrecognized or invalid
option values.

Option | Property | Type | Description
------| ------------- |------|-------------
Capture File Name | debug.gfxrecon.capture_file | STRING | Path to use when creating the capture file.  Default is: `/sdcard/gfxrecon_capture.gfxr`
Capture Specific Frames | debug.gfxrecon.capture_frames | STRING | Specify one or more comma-separated frame ranges to capture.  Each range will be written to its own file.  A frame range can be specified as a single value, to specify a single frame to capture, or as two hyphenated values, to specify the first and last frame to capture.  Frame ranges should be specified in ascending order and cannot overlap. Note that frame numbering is 1-based (i.e. the first frame is frame 1).  Example: `200,301-305` will create two capture files, one containing a single frame and one containing five frames.  Default is: Empty string (all frames are captured).
Capture trigger for Android | debug.gfxrecon.capture_android_trigger | BOOL | Set during runtime to `true` to start capturing and to `false` to stop. If not set at all then it is disabled (non-trimmed capture). Default is not set.
Capture File Compression Type | debug.gfxrecon.capture_compression_type | STRING | Compression format to use with the capture file.  Valid values are: `LZ4`, `ZLIB`, `ZSTD`, and `NONE`. Default is: `LZ4`
Capture File Timestamp | debug.gfxrecon.capture_file_timestamp | BOOL | Add a timestamp to the capture file as described by [Timestamps](#timestamps).  Default is: `true`
Capture File Flush After Write | debug.gfxrecon.capture_file_flush | BOOL | Flush output stream after each packet is written to the capture file.  Default is: `false`
Log Level | debug.gfxrecon.log_level | STRING | Specify the highest level message to log.  Options are: `debug`, `info`, `warning`, `error`, and `fatal`.  The specified level and all levels listed after it will be enabled for logging.  For example, choosing the `warning` level will also enable the `error` and `fatal` levels. Default is: `info`
Log Output to Console | debug.gfxrecon.log_output_to_console | BOOL | Log messages will be written to Logcat. Default is: `true`
Log File | debug.gfxrecon.log_file | STRING | When set, log messages will be written to a file at the specified path. Default is: Empty string (file logging disabled).
Log Detailed | debug.gfxrecon.log_detailed | BOOL | Include name and line number from the file responsible for the log message. Default is: `false`
Log Allow Indents | debug.gfxrecon.log_allow_indents | BOOL | Apply additional indentation formatting to log messages. Default is: `false`
Log Break on Error | debug.gfxrecon.log_break_on_error | BOOL | Trigger a debug break when logging an error. Default is: `false`
Log File Create New | debug.gfxrecon.log_file_create_new | BOOL | Specifies that log file initialization should overwrite an existing file when true, or append to an existing file when false. Default is: `true`
Log File Flush After Write | debug.gfxrecon.log_file_flush_after_write | BOOL | Flush the log file to disk after each write when true. Default is: `false`
Log File Keep Open | debug.gfxrecon.log_file_keep_open | BOOL | Keep the log file open between log messages when true, or close and reopen the log file for each message when false. Default is: `true`
Memory Tracking Mode | debug.gfxrecon.memory_tracking_mode | STRING | Specifies the memory tracking mode to use for detecting modifications to mapped Vulkan memory objects. Available options are: `page_guard`, `assisted`, and `unassisted`. Default is `page_guard` <ul><li>`page_guard` tracks modifications to individual memory pages, which are written to the capture file on calls to `vkFlushMappedMemoryRanges`, `vkUnmapMemory`, and `vkQueueSubmit`. Tracking modifications requires allocating shadow memory for all mapped memory and that the `SIGSEGV` signal is enabled in the thread's signal mask.</li><li>`assisted` expects the application to call `vkFlushMappedMemoryRanges` after memory is modified; the memory ranges specified to the `vkFlushMappedMemoryRanges` call will be written to the capture file during the call.</li><li>`unassisted` writes the full content of mapped memory to the capture file on calls to `vkUnmapMemory` and `vkQueueSubmit`. It is very inefficient and may be unusable with real-world applications that map large amounts of memory.</li></ul>
Page Guard Copy on Map | debug.gfxrecon.page_guard_copy_on_map | BOOL | When the `page_guard` memory tracking mode is enabled, copies the content of the mapped memory to the shadow memory immediately after the memory is mapped. Default is: `true`
Page Guard Separate Read Tracking | debug.gfxrecon.page_guard_separate_read | BOOL | When the `page_guard` memory tracking mode is enabled, copies the content of pages accessed for read from mapped memory to shadow memory on each read. Can overwrite unprocessed shadow memory content when an application is reading from and writing to the same page. Default is: `true`
Page Guard Persistent Memory | debug.gfxrecon.page_guard_persistent_memory | BOOL | When the `page_guard` memory tracking mode is enabled, this option changes the way that the shadow memory used to detect modifications to mapped memory is allocated. The default behavior is to allocate and copy the mapped memory range on map and free the allocation on unmap. When this option is enabled, an allocation with a size equal to that of the object being mapped is made once on the first map and is not freed until the object is destroyed.  This option is intended to be used with applications that frequently map and unmap large memory ranges, to avoid frequent allocation and copy operations that can have a negative impact on performance.  This option is ignored when GFXRECON_PAGE_GUARD_EXTERNAL_MEMORY is enabled. Default is `false`
Page Guard Align Buffer Sizes | debug.gfxrecon.page_guard_align_buffer_sizes | BOOL | When the `page_guard` memory tracking mode is enabled, this option overrides the Vulkan API calls that report buffer memory properties to report that buffer sizes and alignments must be a multiple of the system page size.  This option is intended to be used with applications that perform CPU writes and GPU writes/copies to different buffers that are bound to the same page of mapped memory, which may result in data being lost when copying pages from the `page_guard` shadow allocation to the real allocation.  This data loss can result in visible corruption during capture.  Forcing buffer sizes and alignments to a multiple of the system page size prevents multiple buffers from being bound to the same page, avoiding data loss from simultaneous CPU writes to the shadow allocation and GPU writes to the real allocation for different buffers bound to the same page.  This option is only available for the Vulkan API.  Default is `false`
Omit calls with NULL AHardwareBuffer* | debug.gfxrecon.omit_null_hardware_buffers | BOOL | Some GFXReconstruct capture files may replay with a NULL AHardwareBuffer* parameter, for example, vkGetAndroidHardwareBufferPropertiesANDROID.  Although this is invalid Vulkan usage, some drivers may ignore these calls and some may not. This option causes replay to omit Vulkan calls for which the AHardwareBuffer* would be NULL. Default is `false`
Page guard unblock SIGSEGV | debug.gfxrecon.page_guard_unblock_sigsegv | BOOL | When the `page_guard` memory tracking mode is enabled and in the case that SIGSEGV has been marked as blocked in thread's signal mask, setting this enviroment variable to `true` will forcibly re-enable the signal in the thread's signal mask. Default is `false`
Page guard signal handler watcher | debug.gfxrecon.page_guard_signal_handler_watcher | BOOL | When the `page_guard` memory tracking mode is enabled, setting this enviroment variable to `true` will spawn a thread which will periodically reinstall the `SIGSEGV` handler if it has been replaced by the application being traced. Default is `false`
Page guard signal handler watcher max restores | debug.gfxrecon.page_guard_signal_handler_watcher_max_restores | INTEGER | Sets the number of times the watcher will attempt to restore the signal handler. Setting it to a negative value will make the watcher thread run indefinitely. Default is `1`

#### Memory Tracking Known Issues

There is a known issue with the page guard memory tracking method. The logic behind that method is to apply a memory protection to the guarded/shadowed regions so that accesses made by the user to trigger a segmentation fault which is handled by GFXReconstruct.
If the access is made by a system call (like `fread()`) then there won't be a segmentation fault generated and the function will fail. As a result the mapped region will not be updated.

#### Settings File

Capture options may also be specified through a layer settings file.  The layer
settings file will be loaded before the Android system properties are
processed, allowing system properties to override individual settings file
entries.

The `debug.gfxrecon.settings_path` Android system property is used to enable
a settings file:

```bash
adb shell "setprop debug.gfxrecon.settings_path /sdcard/vk_layer_settings.txt"
```

The system property may be set as either the path to the folder containing a
file named `vk_layer_settings.txt` or the full path to a file with a custom
name. When set to a folder, the capture layer will try to open a file in that
folder named `vk_layer_settings.txt`.  When set to a file, the capture layer
will try to open a file with the specified name.

The settings file may be combined with settings files for other layers. The
capture layer will ignore entries that do not start with the
'lunarg_gfxreconstruct.' prefix.

A sample layer settings file, documenting each available setting, can be found
in the GFXReconstruct GitHub repository at `layer/vk_layer_settings.txt`. Most
binary distributions of the GFXReconstruct software will also include a sample
settings file.

#### Selecting Settings for the page_guard Memory Tracking Mode

The default settings selected for the `page_guard` memory tracking mode are the settings that are most likely to work on a given platform, but may not provide the best performance for all cases.

If capture performs poorly with the the default settings, try setting `debug.gfxrecon.page_guard_persistent_memory` to `true`.

If corruption is observed during capture, try setting `debug.gfxrecon.page_guard_align_buffer_sizes` to `true`. If this does not help, try setting `debug.gfxrecon.page_guard_separate_read` to `false`.

### Capture Files

Capture files are created on the first call to `vkCreateInstance`, when the
Vulkan loader loads the capture layer, and are closed on `vkDestroyInstance`,
when the last active instance is destroyed and the layer is unloaded.

If multiple instances are active concurrently, only one capture file will be
created. If multiple instances are active consecutively (i.e. an instance is
created and destroyed before the next instance is created), the creation of
each instance will generate a new file. For applications that create multiple
instances consecutively, it will be necessary to enable capture file timestamps
to prevent each new instance from overwriting the file created by the previous
instance.

If the layer fails to open the capture file, it will make the call to
`vkCreateInstance` fail, returning `VK_ERROR_INITIALIZATION_FAILED`.

#### Specifying Capture File Location

The capture file's save location can be specified by setting the
`debug.gfxrecon.capture_file` system property, described above in
the [Layer Options](#layer-options) section.

#### Timestamps

When capture file timestamps are enabled, a timestamp with an
[ISO 8601-based](https://en.wikipedia.org/wiki/ISO_8601)
format will be added to the name of every file created by the layer. The
timestamp is generated when the capture file is created by the layer's
`vkCreateInstance` function and is added to the base filename specified
through the `debug.gfxrecon.capture_file` system property. Timestamps have
the form:

```text
_yyyymmddThhmmss
```

where the lower-case letters stand for: Year, Month, Day, Hours, Minutes,
Seconds.  The `T` is a designator that separates the date and time components.
Time is reported for the local timezone and is specified with the 24-hour
format.

The following example shows a timestamp that was added to a file that was
originally named `gfxrecon_capture.gfxr` and was created at 2:35 PM
on November 25, 2018:
  `gfxrecon_capture_20181125T143527.gfxr`

## Replaying API Calls

### Launch Script

The `gfxrecon.py` script, located in android/scripts directory of the
[gfxreconstruct git repository](https://github.com/LunarG/gfxreconstruct)
is provided as a convenient method for deploying and launching the Android replay tool. The script
currently supports the following commands:

Command | Description
--------|------------
install-apk | Install the specified APK file with the `-g -t -r` options.
replay | Launch the replay tool with the specified command line options.

### Install APK Command

The `gfxrecon.py install-apk` command has the following usage:

```text
usage: gfxrecon.py install-apk [-h] <file>

positional arguments:
  file        APK file to install

optional arguments:
  -h, --help  show this help message and exit
```

The command is equivalent to:

```bash
adb install -g -t -r <file>
```

The install-apk option of the `gfxrecon.py` script with the install-apk option is
is a convenient way to install the gfxrecon replay tool. For example:

```bash
python gfxrecon.py install replay-debug.apk
```

### Replay Command

The `gfxrecon.py replay` command has the following usage:

```text
usage: gfxrecon.py replay [-h] [-p LOCAL_FILE] [--version] [--pause-frame N]
                          [--paused] [--screenshot-all] [--screenshots RANGES]
                          [--screenshot-format FORMAT] [--screenshot-dir DIR]
                          [--screenshot-prefix PREFIX] [--sfa] [--opcd]
                          [--surface-index N] [--sync] [--remove-unsupported]
                          [-m MODE] [--use-captured-swapchain-indices]
                          [file]

Launch the replay tool.

positional arguments:
  file                  File on device to play (forwarded to replay tool)

optional arguments:
  -h, --help            show this help message and exit
  -p LOCAL_FILE, --push-file LOCAL_FILE
                        Local file to push to the location on device specified
                        by <file>
  --version             Print version information and exit (forwarded to
                        replay tool)
  --pause-frame N       Pause after replaying frame number N (forwarded to
                        replay tool)
  --paused              Pause after replaying the first frame (same as "--
                        pause-frame 1"; forwarded to replay tool)
  --screenshot-all      Generate screenshots for all frames. When this option
                        is specified, --screenshots is ignored (forwarded to
                        replay tool)
  --screenshots RANGES  Generate screenshots for the specified frames. Target
                        frames are specified as a comma separated list of
                        frame ranges. A frame range can be specified as a
                        single value, to specify a single frame, or as two
                        hyphenated values, to specify the first and last
                        frames to process. Frame ranges should be specified in
                        ascending order and cannot overlap. Note that frame
                        numbering is 1-based (i.e. the first frame is frame
                        1). Example: 200,301-305 will generate six screenshots
                        (forwarded to replay tool)
  --screenshot-format FORMAT
                        Image file format to use for screenshot generation.
                        Available formats are: bmp (forwarded to replay tool)
  --screenshot-dir DIR  Directory to write screenshots. Default is "/sdcard"
                        (forwarded to replay tool)
  --screenshot-prefix PREFIX
                        Prefix to apply to the screenshot file name. Default
                        is "screenshot" (forwarded to replay tool)
  --sfa, --skip-failed-allocations
                        Skip vkAllocateMemory, vkAllocateCommandBuffers, and
                        vkAllocateDescriptorSets calls that failed during
                        capture (forwarded to replay tool)
  --opcd, --omit-pipeline-cache-data
                        Omit pipeline cache data from calls to
                        vkCreatePipelineCache and skip calls to
                        vkGetPipelineCacheData (forwarded to replay tool)
  --surface-index N     Restrict rendering to the Nth surface object created.
                        Used with captures that include multiple surfaces.
                        Default is -1 (render to all surfaces; forwarded to
                        replay tool)
  --sync                Synchronize after each queue submission with
                        vkQueueWaitIdle (forwarded to replay tool)
  --remove-unsupported  Remove unsupported extensions and features from
                        instance and device creation parameters (forwarded to
                        replay tool)
  -m MODE, --memory-translation MODE
                        Enable memory translation for replay on GPUs with
                        memory types that are not compatible with the capture
                        GPU's memory types. Available modes are: none, remap,
                        realign, rebind (forwarded to replay tool)
  --onhb, --omit-null-hardware-buffers
                        Omit Vulkan API calls which would pass a NULL
                        AHardwareBuffer*.  (forwarded to replay tool)
  --use-captured-swapchain-indices
                        Use the swapchain indices stored in the capture directly on the swapchain
                        setup for replay. The default without this option is to use a Virtual Swapchain
                        of images which match the swapchain in effect at capture time and which are
                        copied to the underlying swapchain of the implementation being replayed on.
```

The command will force-stop an active replay process before starting the replay
activity with the following:

```bash
adb shell am force-stop com.lunarg.gfxreconstruct.replay
adb shell am start -n "com.lunarg.gfxreconstruct.replay/android.app.NativeActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER --es "args" "<arg-list>"
```

If `gfxrecon-replay` was built with Vulkan Validation Layer support, `VK_LAYER_KHRONOS_validation` can be enabled and disabled in the same manner as `VK_LAYER_LUNARG_gfxreconstruct`

### Touch Controls

The `gfxrecon-replay` tool for Android supports the following touch controls:

Key(s) | Action
-------|-------
Tap | Toggle pause/play.
Swipe left | Advance to the next frame when paused.

### Key Controls

The `gfxrecon-replay` tool for Android supports the following key controls. Key
events can be simulated through `adb` with the
`adb shell input keyevent <key-code>` command:

Key(s) | Key code(s) | Action
-------|-------------|-------
Space, p | Space = 62, p = 44 |Toggle pause/play.
D-pad right, n | D-pad right = 22, n = 42 | Advance to the next frame when paused.

### Virtual Swapchain

During replay, swapchain indices for present can be different from captured indices. Causes for this can include the swapchain image count differing between capture and replay, and `vkAcquireNextImageKHR` returning a different `pImageIndex` at replay to the one that was captured. These issues can cause unexpected rendering or even crashes.

Virtual Swapchain insulates higher layers in the Vulkan stack from these problems by creating a set of images, exactly matching the swapchain configuration at capture time, which it exposes for them to render into.  Before a present, it copies the virtual image to a target swapchain image for display. Since this issue can happen in many situations, virtual swapchain is the default setup. If the user wants to bypass the feature and use the captured indices to present directly on the swapchain of the replay implementation, they should add the `--use-captured-swapchain-indices` option when invoking `gfxrecon-replay`.
