Skip to content

Commit fe8f995

Browse files
Option for offscreen frame boundary conversion
Add `--use-ext-frame-boundary` option to convert all offscreen frame boundaries to `VK_EXT_frame_boundary` frame boundaries. Extensions used to specify an offscreen frame boundary can be unsupported by the replay device. As there are many of such extensions, it seems important to be able to "convert" these frame boundaries to a more "standard" one. The `VK_EXT_frame_boundary` seems to be the best choice as it is specialy designed for this purpose, platform independent, and supports all functionnalities supported by other extensions: - Specifying resources attached to the frame (images and buffers) - Depending on the execution of other commands - Specifying frame number, label and description Change-Id: I9fb932b219380c6f9098a16a83de2086f2b424ee
1 parent b9a9916 commit fe8f995

File tree

12 files changed

+325
-16
lines changed

12 files changed

+325
-16
lines changed

USAGE_android.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,10 @@ optional arguments:
876876
in the original capture. This allows preserving frames
877877
when capturing a replay that uses. offscreen swapchain.
878878
(forwarded to replay tool)
879+
--use-ext-frame-boundary
880+
Convert all offscreen frame boundaries to
881+
`VK_EXT_frame_boundary` frame boundaries.
882+
(forwarded to replay tool)
879883
```
880884

881885
The command will force-stop an active replay process before starting the replay

USAGE_desktop_Vulkan.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,9 @@ Optional arguments:
549549
was called in the original capture.
550550
This allows preserving frames when capturing a replay that uses.
551551
offscreen swapchain.
552+
--use-ext-frame-boundary
553+
Convert all offscreen frame boundaries to `VK_EXT_frame_boundary`
554+
frame boundaries.
552555
```
553556

554557
### Key Controls

android/scripts/gfxrecon.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ def CreateReplayParser():
8686
parser.add_argument('--onhb', '--omit-null-hardware-buffers', action='store_true', default=False, help='Omit Vulkan calls that would pass a NULL AHardwareBuffer* (forwarded to replay tool)')
8787
parser.add_argument('--use-colorspace-fallback', action='store_true', default=False, help='Swap the swapchain color space if unsupported by replay device. Check if color space is not supported by replay device and swap to VK_COLOR_SPACE_SRGB_NONLINEAR_KHR. (forwarded to replay tool).')
8888
parser.add_argument('--offscreen-swapchain-frame-boundary', action='store_true', default=False, help='Should only be used with offscreen swapchain. Activates the extension VK_EXT_frame_boundary (always supported if trimming, checks for driver support otherwise) and inserts command buffer submission with VkFrameBoundaryEXT where vkQueuePresentKHR was called in the original capture. This allows preserving frames when capturing a replay that uses. offscreen swapchain. (forwarded to replay tool)')
89+
parser.add_argument('--use-ext-frame-boundary', action='store_true', default=False, help='Convert all offscreen frame boundaries to `VK_EXT_frame_boundary` frame boundaries. (forwarded to replay tool)')
8990
parser.add_argument('--mfr', '--measurement-frame-range', metavar='START-END', help='Custom framerange to measure FPS for. This range will include the start frame but not the end frame. The measurement frame range defaults to all frames except the loading frame but can be configured for any range. If the end frame is past the last frame in the trace it will be clamped to the frame after the last (so in that case the results would include the last frame). (forwarded to replay tool)')
9091
parser.add_argument('--measurement-file', metavar='DEVICE_FILE', help='Write measurements to a file at the specified path. Default is: \'/sdcard/gfxrecon-measurements.json\' on android and \'./gfxrecon-measurements.json\' on desktop. (forwarded to replay tool)')
9192
parser.add_argument('--quit-after-measurement-range', action='store_true', default=False, help='If this is specified the replayer will abort when it reaches the <end_frame> specified in the --measurement-frame-range argument. (forwarded to replay tool)')
@@ -198,6 +199,9 @@ def MakeExtrasString(args):
198199

199200
if args.offscreen_swapchain_frame_boundary:
200201
arg_list.append('--offscreen-swapchain-frame-boundary')
202+
203+
if args.use_ext_frame_boundary:
204+
arg_list.append('--use-ext-frame-boundary')
201205

202206
if args.memory_translation:
203207
arg_list.append('-m')

framework/application/application.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ class Application final
8484

8585
void StopRunning() { running_ = false; }
8686

87+
uint32_t GetCurrentFrameNumber() const { return file_processor_->GetCurrentFrameNumber(); }
88+
8789
private:
8890
// clang-format off
8991
std::string name_; ///< Application name to display in window title bar.

framework/decode/vulkan_object_info.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@ struct ShaderEXTInfo : VulkanObjectInfo<VkShaderEXT>
460460
struct CommandBufferInfo : public VulkanPoolObjectInfo<VkCommandBuffer>
461461
{
462462
bool is_frame_boundary{ false };
463+
std::string frame_boundary_label;
463464
std::vector<format::HandleId> frame_buffer_ids;
464465
std::unordered_map<format::HandleId, VkImageLayout> image_layout_barriers;
465466
};

framework/decode/vulkan_replay_consumer_base.cpp

Lines changed: 270 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2143,6 +2143,59 @@ void VulkanReplayConsumerBase::WriteScreenshots(const Decoded_VkPresentInfoKHR*
21432143
}
21442144
}
21452145

2146+
void VulkanReplayConsumerBase::FillFrameBoundaryExtFromCommandBufferInfo(const CommandBufferInfo* command_buffer_info,
2147+
VkFrameBoundaryEXT* frame_boundary,
2148+
std::vector<VkImage>& frame_boundary_images)
2149+
{
2150+
assert(command_buffer_info->is_frame_boundary);
2151+
2152+
frame_boundary_images.clear();
2153+
2154+
for (size_t i = 0; i < command_buffer_info->frame_buffer_ids.size(); ++i)
2155+
{
2156+
auto framebuffer_info = object_info_table_.GetFramebufferInfo(command_buffer_info->frame_buffer_ids[i]);
2157+
2158+
for (size_t j = 0; j < framebuffer_info->attachment_image_view_ids.size(); ++j)
2159+
{
2160+
auto image_view_id = framebuffer_info->attachment_image_view_ids[j];
2161+
auto image_view_info = object_info_table_.GetImageViewInfo(image_view_id);
2162+
auto image_info = object_info_table_.GetImageInfo(image_view_info->image_id);
2163+
2164+
frame_boundary_images.push_back(image_info->handle);
2165+
}
2166+
}
2167+
2168+
frame_boundary->sType = VK_STRUCTURE_TYPE_FRAME_BOUNDARY_EXT;
2169+
frame_boundary->pNext = nullptr;
2170+
frame_boundary->flags = VK_FRAME_BOUNDARY_FRAME_END_BIT_EXT;
2171+
frame_boundary->frameID = application_->GetCurrentFrameNumber();
2172+
frame_boundary->imageCount = frame_boundary_images.size();
2173+
frame_boundary->pImages = frame_boundary_images.data();
2174+
frame_boundary->bufferCount = 0;
2175+
frame_boundary->pBuffers = nullptr;
2176+
frame_boundary->tagName = application_->GetCurrentFrameNumber();
2177+
frame_boundary->tagSize = command_buffer_info->frame_boundary_label.size();
2178+
frame_boundary->pTag = command_buffer_info->frame_boundary_label.data();
2179+
}
2180+
2181+
void VulkanReplayConsumerBase::InsertFrameBoundaryExt(void* pnext_chain, const VkFrameBoundaryEXT* frame_boundary)
2182+
{
2183+
VkBaseOutStructure* current = reinterpret_cast<VkBaseOutStructure*>(pnext_chain);
2184+
while (current->pNext != nullptr)
2185+
{
2186+
current = current->pNext;
2187+
2188+
if (current->sType == VK_STRUCTURE_TYPE_FRAME_BOUNDARY_EXT)
2189+
{
2190+
GFXRECON_LOG_WARNING(
2191+
"Trying to insert VkFrameBoundaryEXT but there already is one. The new one will be ignored.");
2192+
return;
2193+
}
2194+
}
2195+
2196+
current->pNext = reinterpret_cast<VkBaseOutStructure*>(&frame_boundary);
2197+
}
2198+
21462199
bool VulkanReplayConsumerBase::CheckCommandBufferInfoForFrameBoundary(const CommandBufferInfo* command_buffer_info)
21472200
{
21482201
GFXRECON_ASSERT(command_buffer_info != nullptr);
@@ -2503,6 +2556,24 @@ VulkanReplayConsumerBase::OverrideCreateInstance(VkResult original_result,
25032556
}
25042557
}
25052558

2559+
if (options_.use_ext_frame_boundary)
2560+
{
2561+
bool frame_boundary_extension_found = false;
2562+
for (const char* extension_name : filtered_extensions)
2563+
{
2564+
if (gfxrecon::util::platform::StringCompareNoCase(extension_name, VK_EXT_FRAME_BOUNDARY_EXTENSION_NAME))
2565+
{
2566+
frame_boundary_extension_found = true;
2567+
break;
2568+
}
2569+
}
2570+
2571+
if (!frame_boundary_extension_found)
2572+
{
2573+
filtered_extensions.push_back(VK_EXT_FRAME_BOUNDARY_EXTENSION_NAME);
2574+
}
2575+
}
2576+
25062577
// Disable layers; any layers needed for replay should be enabled for the replay app with the VK_INSTANCE_LAYERS
25072578
// environment variable or debug.vulkan.layers Android property.
25082579
if (modified_create_info.enabledLayerCount > 0)
@@ -2788,11 +2859,19 @@ void VulkanReplayConsumerBase::OverrideDestroyDevice(
27882859

27892860
if (device_info != nullptr)
27902861
{
2791-
device = device_info->handle;
2862+
device = device_info->handle;
2863+
auto device_table = GetDeviceTable(device);
2864+
2865+
auto it = fba_resources_.find(device);
2866+
if (it != fba_resources_.end())
2867+
{
2868+
device_table->DestroyCommandPool(device, it->second.first, nullptr);
2869+
fba_resources_.erase(device);
2870+
}
27922871

27932872
if (screenshot_handler_ != nullptr)
27942873
{
2795-
screenshot_handler_->DestroyDeviceResources(device, GetDeviceTable(device));
2874+
screenshot_handler_->DestroyDeviceResources(device, device_table);
27962875
}
27972876

27982877
device_info->allocator->Destroy();
@@ -3327,6 +3406,31 @@ VkResult VulkanReplayConsumerBase::OverrideQueueSubmit(PFN_vkQueueSubmit func,
33273406
fence = fence_info->handle;
33283407
}
33293408

3409+
std::vector<VkFrameBoundaryEXT> inserted_frame_boundaries;
3410+
std::vector<std::vector<VkImage>> inserted_frame_boundaries_images;
3411+
if (options_.use_ext_frame_boundary)
3412+
{
3413+
for (uint32_t i = 0; i < submitCount; ++i)
3414+
{
3415+
size_t command_buffer_count = submit_info_data[i].pCommandBuffers.GetLength();
3416+
const format::HandleId* command_buffer_ids = submit_info_data[i].pCommandBuffers.GetPointer();
3417+
for (uint32_t j = 0; j < command_buffer_count; ++j)
3418+
{
3419+
const CommandBufferInfo* command_buffer_info =
3420+
GetObjectInfoTable().GetCommandBufferInfo(command_buffer_ids[j]);
3421+
3422+
if (command_buffer_info->is_frame_boundary)
3423+
{
3424+
FillFrameBoundaryExtFromCommandBufferInfo(command_buffer_info,
3425+
&inserted_frame_boundaries.emplace_back(),
3426+
inserted_frame_boundaries_images.emplace_back());
3427+
InsertFrameBoundaryExt(submit_info_data[i].decoded_value, &inserted_frame_boundaries.back());
3428+
break;
3429+
}
3430+
}
3431+
}
3432+
}
3433+
33303434
// Only attempt to filter imported semaphores if we know at least one has been imported.
33313435
// If rendering is restricted to a specific surface, shadow semaphore and forward progress state will need to be
33323436
// tracked.
@@ -3494,6 +3598,32 @@ VkResult VulkanReplayConsumerBase::OverrideQueueSubmit2(PFN_vkQueueSubmit2 func,
34943598
fence = fence_info->handle;
34953599
}
34963600

3601+
std::vector<VkFrameBoundaryEXT> inserted_frame_boundaries;
3602+
std::vector<std::vector<VkImage>> inserted_frame_boundaries_images;
3603+
if (options_.use_ext_frame_boundary)
3604+
{
3605+
for (uint32_t i = 0; i < submitCount; ++i)
3606+
{
3607+
size_t command_buffer_count = submit_info_data[i].pCommandBufferInfos->GetLength();
3608+
const auto command_buffer_infos = submit_info_data[i].pCommandBufferInfos->GetMetaStructPointer();
3609+
3610+
for (uint32_t j = 0; j < command_buffer_count; ++j)
3611+
{
3612+
const CommandBufferInfo* command_buffer_info =
3613+
GetObjectInfoTable().GetCommandBufferInfo(command_buffer_infos[j].commandBuffer);
3614+
3615+
if (command_buffer_info->is_frame_boundary)
3616+
{
3617+
FillFrameBoundaryExtFromCommandBufferInfo(command_buffer_info,
3618+
&inserted_frame_boundaries.emplace_back(),
3619+
inserted_frame_boundaries_images.emplace_back());
3620+
InsertFrameBoundaryExt(submit_info_data[i].decoded_value, &inserted_frame_boundaries.back());
3621+
break;
3622+
}
3623+
}
3624+
}
3625+
}
3626+
34973627
// Only attempt to filter imported semaphores if we know at least one has been imported.
34983628
// If rendering is restricted to a specific surface, shadow semaphore and forward progress state will need to be
34993629
// tracked.
@@ -5152,6 +5282,33 @@ VkResult VulkanReplayConsumerBase::OverrideCreateDebugUtilsMessengerEXT(
51525282
pMessenger->GetHandlePointer());
51535283
}
51545284

5285+
void VulkanReplayConsumerBase::OverrideCmdInsertDebugUtilsLabelEXT(
5286+
PFN_vkCmdInsertDebugUtilsLabelEXT func,
5287+
CommandBufferInfo* command_buffer_info,
5288+
StructPointerDecoder<Decoded_VkDebugUtilsLabelEXT>* label_info_decoder)
5289+
{
5290+
const VkDebugUtilsLabelEXT* label_info = label_info_decoder->GetPointer();
5291+
5292+
bool call_next_layer = true;
5293+
5294+
// Look for the label that identifies this command buffer as a VR frame boundary.
5295+
if (util::platform::StringContains(label_info->pLabelName, graphics::kVulkanVrFrameDelimiterString))
5296+
{
5297+
command_buffer_info->is_frame_boundary = true;
5298+
command_buffer_info->frame_boundary_label = label_info->pLabelName;
5299+
5300+
if (options_.use_ext_frame_boundary)
5301+
{
5302+
call_next_layer = false;
5303+
}
5304+
}
5305+
5306+
if (call_next_layer)
5307+
{
5308+
func(command_buffer_info->handle, label_info);
5309+
}
5310+
}
5311+
51555312
VkResult VulkanReplayConsumerBase::OverrideCreateSwapchainKHR(
51565313
PFN_vkCreateSwapchainKHR func,
51575314
VkResult original_result,
@@ -6977,12 +7134,24 @@ void VulkanReplayConsumerBase::OverrideCmdDebugMarkerInsertEXT(
69777134
StructPointerDecoder<Decoded_VkDebugMarkerMarkerInfoEXT>* marker_info_decoder)
69787135
{
69797136
const VkDebugMarkerMarkerInfoEXT* marker_info = marker_info_decoder->GetPointer();
6980-
func(command_buffer_info->handle, marker_info);
7137+
7138+
bool call_next_layer = true;
69817139

69827140
// Look for the debug marker that identifies this command buffer as a VR frame boundary.
69837141
if (util::platform::StringContains(marker_info->pMarkerName, graphics::kVulkanVrFrameDelimiterString))
69847142
{
6985-
command_buffer_info->is_frame_boundary = true;
7143+
command_buffer_info->is_frame_boundary = true;
7144+
command_buffer_info->frame_boundary_label = marker_info->pMarkerName;
7145+
7146+
if (options_.use_ext_frame_boundary)
7147+
{
7148+
call_next_layer = false;
7149+
}
7150+
}
7151+
7152+
if (call_next_layer)
7153+
{
7154+
func(command_buffer_info->handle, marker_info);
69867155
}
69877156
};
69887157

@@ -7074,6 +7243,103 @@ VkResult VulkanReplayConsumerBase::OverrideCreateFramebuffer(
70747243
return result;
70757244
}
70767245

7246+
void VulkanReplayConsumerBase::OverrideFrameBoundaryANDROID(PFN_vkFrameBoundaryANDROID func,
7247+
const DeviceInfo* device_info,
7248+
const SemaphoreInfo* semaphore_info,
7249+
const ImageInfo* image_info)
7250+
{
7251+
GFXRECON_ASSERT((device_info != nullptr));
7252+
7253+
VkDevice device = device_info->handle;
7254+
VkSemaphore semaphore = semaphore_info ? semaphore_info->handle : VK_NULL_HANDLE;
7255+
VkImage image = image_info ? image_info->handle : VK_NULL_HANDLE;
7256+
7257+
if (options_.use_ext_frame_boundary)
7258+
{
7259+
auto device_table = GetDeviceTable(device);
7260+
7261+
// Retrieve adequate queue family
7262+
7263+
uint32_t queueFamily = 0;
7264+
7265+
// Create command pool and command buffer if necessary
7266+
7267+
auto it = fba_resources_.find(device);
7268+
if (it == fba_resources_.end())
7269+
{
7270+
VkCommandPoolCreateInfo commandPoolCreateInfo;
7271+
commandPoolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
7272+
commandPoolCreateInfo.pNext = nullptr;
7273+
commandPoolCreateInfo.flags = 0;
7274+
commandPoolCreateInfo.queueFamilyIndex = queueFamily;
7275+
7276+
VkCommandPool commandPool;
7277+
device_table->CreateCommandPool(device, &commandPoolCreateInfo, nullptr, &commandPool);
7278+
7279+
VkCommandBufferAllocateInfo commandBufferAllocateInfo;
7280+
commandBufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
7281+
commandBufferAllocateInfo.pNext = nullptr;
7282+
commandBufferAllocateInfo.commandPool = commandPool;
7283+
commandBufferAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
7284+
commandBufferAllocateInfo.commandBufferCount = 1;
7285+
7286+
VkCommandBuffer commandBuffer;
7287+
device_table->AllocateCommandBuffers(device, &commandBufferAllocateInfo, &commandBuffer);
7288+
7289+
VkCommandBufferBeginInfo commandBufferBeginInfo;
7290+
commandBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
7291+
commandBufferBeginInfo.pNext = nullptr;
7292+
commandBufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
7293+
commandBufferBeginInfo.pInheritanceInfo = nullptr;
7294+
7295+
device_table->BeginCommandBuffer(commandBuffer, &commandBufferBeginInfo);
7296+
device_table->EndCommandBuffer(commandBuffer);
7297+
7298+
it = fba_resources_.emplace(device, std::make_pair(commandPool, commandBuffer)).first;
7299+
}
7300+
7301+
// Queue submission with VkFrameBoundaryEXT
7302+
7303+
VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
7304+
7305+
VkSubmitInfo submitInfo;
7306+
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
7307+
submitInfo.pNext = nullptr;
7308+
submitInfo.waitSemaphoreCount = 1;
7309+
submitInfo.pWaitSemaphores = &semaphore;
7310+
submitInfo.pWaitDstStageMask = &dstStageMask;
7311+
submitInfo.commandBufferCount = 1;
7312+
submitInfo.pCommandBuffers = &it->second.second;
7313+
submitInfo.signalSemaphoreCount = 0;
7314+
submitInfo.pSignalSemaphores = nullptr;
7315+
7316+
VkFrameBoundaryEXT frameBoundaryExt;
7317+
frameBoundaryExt.sType = VK_STRUCTURE_TYPE_FRAME_BOUNDARY_EXT;
7318+
frameBoundaryExt.pNext = nullptr;
7319+
frameBoundaryExt.flags = VK_FRAME_BOUNDARY_FRAME_END_BIT_EXT;
7320+
frameBoundaryExt.frameID = application_->GetCurrentFrameNumber();
7321+
frameBoundaryExt.imageCount = (image == VK_NULL_HANDLE ? 0 : 1);
7322+
frameBoundaryExt.pImages = (image == VK_NULL_HANDLE ? nullptr : &image);
7323+
frameBoundaryExt.bufferCount = 0;
7324+
frameBoundaryExt.pBuffers = nullptr;
7325+
frameBoundaryExt.tagName = frameBoundaryExt.frameID;
7326+
frameBoundaryExt.tagSize = 0;
7327+
frameBoundaryExt.pTag = nullptr;
7328+
7329+
submitInfo.pNext = &frameBoundaryExt;
7330+
7331+
VkQueue queue;
7332+
device_table->GetDeviceQueue(device, queueFamily, 0, &queue);
7333+
device_table->QueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE);
7334+
7335+
// Destruction of command pool and command buffer is done at destruction of the device
7336+
}
7337+
else
7338+
{
7339+
func(device, semaphore, image);
7340+
}
7341+
}
7342+
70777343
// We want to allow skipping the query for tool properties because the capture layer actually adds this extension
70787344
// and the application may end up using the query. However, this extension may not be present for replay, so
70797345
// we stub it out in that case. This will generate warnings in the GfxReconstruct output, but it shouldn't result

0 commit comments

Comments
 (0)