blob: e02b2ddbf43a21ceac5c7d1eee48f0c60adbc0df [file] [log] [blame]
/*
*
* Copyright (C) 2015-2016 Valve Corporation
* Copyright (C) 2015-2016 LunarG, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Author: Cody Northrop <cody@lunarg.com>
* Author: David Pinedo <david@lunarg.com>
* Author: Jon Ashburn <jon@lunarg.com>
*/
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unordered_map>
#include <iostream>
#include <algorithm>
#include <list>
#include <map>
#include <set>
#include <vector>
#include <fstream>
using namespace std;
#include "vk_dispatch_table_helper.h"
#include "vk_struct_string_helper_cpp.h"
#include "vk_layer_config.h"
#include "vk_layer_table.h"
#include "vk_layer_extension_utils.h"
#include "vk_layer_utils.h"
#ifdef ANDROID
#include <android/log.h>
#include <sys/system_properties.h>
static char android_env[64] = {};
const char* env_var = "debug.vulkan.screenshot";
char* android_exec(const char* cmd) {
FILE* pipe = popen(cmd, "r");
if (pipe != nullptr) {
fgets(android_env, 64, pipe);
pclose(pipe);
}
// Only if the value is set will we get a string back
if (strlen(android_env) > 0) {
__android_log_print(ANDROID_LOG_INFO, "screenshot", "Vulkan screenshot layer capturing: %s", android_env);
return android_env;
}
return nullptr;
}
char* android_getenv(const char *key)
{
std::string command("getprop ");
command += key;
return android_exec(command.c_str());
}
static inline char *local_getenv(const char *name) {
return android_getenv(name);
}
static inline void local_free_getenv(const char *val) {}
#elif defined(__linux__)
const char* env_var = "_VK_SCREENSHOT";
static inline char *local_getenv(const char *name) { return getenv(name); }
static inline void local_free_getenv(const char *val) {}
#elif defined(_WIN32)
const char* env_var = "_VK_SCREENSHOT";
static inline char *local_getenv(const char *name) {
char *retVal;
DWORD valSize;
valSize = GetEnvironmentVariableA(name, NULL, 0);
// valSize DOES include the null terminator, so for any set variable
// will always be at least 1. If it's 0, the variable wasn't set.
if (valSize == 0)
return NULL;
// TODO; FIXME This should be using any app defined memory allocation
retVal = (char *)malloc(valSize);
GetEnvironmentVariableA(name, retVal, valSize);
return retVal;
}
static inline void local_free_getenv(const char *val) { free((void *)val); }
#endif
namespace screenshot {
static int globalLockInitialized = 0;
static loader_platform_thread_mutex globalLock;
// unordered map: associates a swap chain with a device, image extent, format,
// and list of images
typedef struct {
VkDevice device;
VkExtent2D imageExtent;
VkFormat format;
VkImage *imageList;
} SwapchainMapStruct;
static unordered_map<VkSwapchainKHR, SwapchainMapStruct *> swapchainMap;
// unordered map: associates an image with a device, image extent, and format
typedef struct {
VkDevice device;
VkExtent2D imageExtent;
VkFormat format;
} ImageMapStruct;
static unordered_map<VkImage, ImageMapStruct *> imageMap;
// unordered map: associates a device with a queue, commandPool, and physical
// device also contains per device info including dispatch table
typedef struct {
VkLayerDispatchTable *device_dispatch_table;
bool wsi_enabled;
VkQueue queue;
VkCommandPool commandPool;
VkPhysicalDevice physicalDevice;
PFN_vkSetDeviceLoaderData pfn_dev_init;
} DeviceMapStruct;
static unordered_map<VkDevice, DeviceMapStruct *> deviceMap;
// unordered map: associates a physical device with an instance
typedef struct { VkInstance instance; } PhysDeviceMapStruct;
static unordered_map<VkPhysicalDevice, PhysDeviceMapStruct *> physDeviceMap;
// set: list of frames to take screenshots without duplication.
static set<int> screenshotFrames;
// Flag indicating we have queried env_var
static bool screenshotEnvQueried = false;
static bool
memory_type_from_properties(VkPhysicalDeviceMemoryProperties *memory_properties,
uint32_t typeBits, VkFlags requirements_mask,
uint32_t *typeIndex) {
// Search memtypes to find first index with those properties
for (uint32_t i = 0; i < 32; i++) {
if ((typeBits & 1) == 1) {
// Type is available, does it match user properties?
if ((memory_properties->memoryTypes[i].propertyFlags &
requirements_mask) == requirements_mask) {
*typeIndex = i;
return true;
}
}
typeBits >>= 1;
}
// No memory types matched, return failure
return false;
}
static DeviceMapStruct *get_dev_info(VkDevice dev) {
auto it = deviceMap.find(dev);
if (it == deviceMap.end())
return NULL;
else
return it->second;
}
static void init_screenshot() {
if (!globalLockInitialized) {
// TODO/TBD: Need to delete this mutex sometime. How??? One
// suggestion is to call this during vkCreateInstance(), and then we
// can clean it up during vkDestroyInstance(). However, that requires
// that the layer have per-instance locks. We need to come back and
// address this soon.
loader_platform_thread_create_mutex(&globalLock);
globalLockInitialized = 1;
}
}
// Track allocated resources in writePPM()
// and clean them up when they go out of scope.
struct WritePPMCleanupData {
VkDevice device;
VkLayerDispatchTable *pTableDevice;
VkImage image2;
VkImage image3;
VkDeviceMemory mem2;
VkDeviceMemory mem3;
bool mem2mapped;
bool mem3mapped;
VkCommandBuffer commandBuffer;
VkCommandPool commandPool;
~WritePPMCleanupData();
};
WritePPMCleanupData::~WritePPMCleanupData() {
if (mem2mapped)
pTableDevice->UnmapMemory(device, mem2);
if (mem2)
pTableDevice->FreeMemory(device, mem2, NULL);
if (image2)
pTableDevice->DestroyImage(device, image2, NULL);
if (mem3mapped)
pTableDevice->UnmapMemory(device, mem3);
if (mem3)
pTableDevice->FreeMemory(device, mem3, NULL);
if (image3)
pTableDevice->DestroyImage(device, image3, NULL);
if (commandBuffer)
pTableDevice->FreeCommandBuffers(device, commandPool, 1,
&commandBuffer);
}
// Save an image to a PPM image file.
//
// This function issues commands to copy/convert the swapchain image
// from whatever compatible format the swapchain image uses
// to a single format (VK_FORMAT_R8G8B8A8_UNORM) so that the converted
// result can be easily written to a PPM file.
//
// Error handling: If there is a problem, this function should silently
// fail without affecting the Present operation going on in the caller.
// The numerous debug asserts are to catch programming errors and are not
// expected to assert. Recovery and clean up are implemented for image memory
// allocation failures.
// (TODO) It would be nice to pass any failure info to DebugReport or something.
static void writePPM(const char *filename, VkImage image1) {
VkResult err;
bool pass;
// Bail immediately if we can't find the image.
if (imageMap.empty() || imageMap.find(image1) == imageMap.end())
return;
// Collect object info from maps. This info is generally recorded
// by the other functions hooked in this layer.
VkDevice device = imageMap[image1]->device;
VkPhysicalDevice physicalDevice = deviceMap[device]->physicalDevice;
VkInstance instance = physDeviceMap[physicalDevice]->instance;
VkQueue queue = deviceMap[device]->queue;
DeviceMapStruct *devMap = get_dev_info(device);
if (NULL == devMap) {
assert(0);
return;
}
VkLayerDispatchTable *pTableDevice = devMap->device_dispatch_table;
VkLayerDispatchTable *pTableQueue =
get_dev_info(static_cast<VkDevice>(static_cast<void *>(queue)))
->device_dispatch_table;
VkLayerInstanceDispatchTable *pInstanceTable;
pInstanceTable = instance_dispatch_table(instance);
// Gather incoming image info and check image format for compatibility with
// the target format.
// This function supports both 24-bit and 32-bit swapchain images.
VkFormat const target32bitFormat = VK_FORMAT_R8G8B8A8_UNORM;
VkFormat const target24bitFormat = VK_FORMAT_R8G8B8_UNORM;
uint32_t const width = imageMap[image1]->imageExtent.width;
uint32_t const height = imageMap[image1]->imageExtent.height;
VkFormat const format = imageMap[image1]->format;
uint32_t const numChannels = vk_format_get_channel_count(format);
if ((vk_format_get_compatibility_class(target24bitFormat) !=
vk_format_get_compatibility_class(format)) &&
(vk_format_get_compatibility_class(target32bitFormat) !=
vk_format_get_compatibility_class(format))) {
assert(0);
return;
}
if ((3 != numChannels) && (4 != numChannels)) {
assert(0);
return;
}
// General Approach
//
// The idea here is to copy/convert the swapchain image into another image
// that can be mapped and read by the CPU to produce a PPM file.
// The image must be untiled and converted to a specific format for easy
// parsing. The memory for the final image must be host-visible.
// Note that in Vulkan, a BLIT operation must be used to perform a format
// conversion.
//
// Devices vary in their ability to blit to/from linear and optimal tiling.
// So we must query the device properties to get this information.
//
// If the device cannot BLIT to a LINEAR image, then the operation must be
// done in two steps:
// 1) BLIT the swapchain image (image1) to a temp image (image2) that is
// created with TILING_OPTIMAL.
// 2) COPY image2 to another temp image (image3) that is created with
// TILING_LINEAR.
// 3) Map image 3 and write the PPM file.
//
// If the device can BLIT to a LINEAR image, then:
// 1) BLIT the swapchain image (image1) to a temp image (image2) that is
// created with TILING_LINEAR.
// 2) Map image 2 and write the PPM file.
//
// There seems to be no way to tell if the swapchain image (image1) is tiled
// or not. We therefore assume that the BLIT operation can always read from
// both linear and optimal tiled (swapchain) images.
// There is therefore no point in looking at the BLIT_SRC properties.
//
// There is also the optimization where the incoming and target formats are
// the same. In this case, just do a COPY.
VkFormatProperties targetFormatProps;
pInstanceTable->GetPhysicalDeviceFormatProperties(
physicalDevice,
(3 == numChannels) ? target24bitFormat : target32bitFormat,
&targetFormatProps);
bool need2steps = false;
bool copyOnly = false;
if ((target24bitFormat == format) || (target32bitFormat == format)) {
copyOnly = true;
} else {
bool const bltLinear = targetFormatProps.linearTilingFeatures &
VK_FORMAT_FEATURE_BLIT_DST_BIT
? true
: false;
bool const bltOptimal = targetFormatProps.optimalTilingFeatures &
VK_FORMAT_FEATURE_BLIT_DST_BIT
? true
: false;
if (!bltLinear && !bltOptimal) {
// Cannot blit to either target tiling type. It should be pretty
// unlikely to have a device that cannot blit to either type.
// But punt by just doing a copy and possibly have the wrong
// colors. This should be quite rare.
copyOnly = true;
} else if (!bltLinear && bltOptimal) {
// Cannot blit to a linear target but can blt to optimal, so copy
// after blit is needed.
need2steps = true;
}
// Else bltLinear is available and only 1 step is needed.
}
// Put resources that need to be cleaned up in a struct with a destructor
// so that things get cleaned up when this function is exited.
WritePPMCleanupData data = {};
data.device = device;
data.pTableDevice = pTableDevice;
// Set up the image creation info for both the blit and copy images, in case
// both are needed.
VkImageCreateInfo imgCreateInfo2 = {
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
NULL,
0,
VK_IMAGE_TYPE_2D,
VK_FORMAT_R8G8B8A8_UNORM,
{width, height, 1},
1,
1,
VK_SAMPLE_COUNT_1_BIT,
VK_IMAGE_TILING_LINEAR,
VK_IMAGE_USAGE_TRANSFER_DST_BIT,
VK_SHARING_MODE_EXCLUSIVE,
0,
NULL,
VK_IMAGE_LAYOUT_UNDEFINED,
};
VkImageCreateInfo imgCreateInfo3 = imgCreateInfo2;
// If we need both images, set up image2 to be read/write and tiled.
if (need2steps) {
imgCreateInfo2.tiling = VK_IMAGE_TILING_OPTIMAL;
imgCreateInfo2.usage =
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
VkMemoryAllocateInfo memAllocInfo = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL,
0, // allocationSize, queried later
0 // memoryTypeIndex, queried later
};
VkMemoryRequirements memRequirements;
VkPhysicalDeviceMemoryProperties memoryProperties;
// Create image2 and allocate its memory. It could be the intermediate or
// final image.
err =
pTableDevice->CreateImage(device, &imgCreateInfo2, NULL, &data.image2);
assert(!err);
if (VK_SUCCESS != err)
return;
pTableDevice->GetImageMemoryRequirements(device, data.image2,
&memRequirements);
memAllocInfo.allocationSize = memRequirements.size;
pInstanceTable->GetPhysicalDeviceMemoryProperties(physicalDevice,
&memoryProperties);
pass = memory_type_from_properties(
&memoryProperties, memRequirements.memoryTypeBits,
need2steps ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
: VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
&memAllocInfo.memoryTypeIndex);
assert(pass);
err = pTableDevice->AllocateMemory(device, &memAllocInfo, NULL, &data.mem2);
assert(!err);
if (VK_SUCCESS != err)
return;
err = pTableQueue->BindImageMemory(device, data.image2, data.mem2, 0);
assert(!err);
if (VK_SUCCESS != err)
return;
// Create image3 and allocate its memory, if needed.
if (need2steps) {
err = pTableDevice->CreateImage(device, &imgCreateInfo3, NULL,
&data.image3);
assert(!err);
if (VK_SUCCESS != err)
return;
pTableDevice->GetImageMemoryRequirements(device, data.image3,
&memRequirements);
memAllocInfo.allocationSize = memRequirements.size;
pInstanceTable->GetPhysicalDeviceMemoryProperties(physicalDevice,
&memoryProperties);
pass = memory_type_from_properties(
&memoryProperties, memRequirements.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &memAllocInfo.memoryTypeIndex);
assert(pass);
err = pTableDevice->AllocateMemory(device, &memAllocInfo, NULL,
&data.mem3);
assert(!err);
if (VK_SUCCESS != err)
return;
err = pTableQueue->BindImageMemory(device, data.image3, data.mem3, 0);
assert(!err);
if (VK_SUCCESS != err)
return;
}
// Set up the command buffer. We get a command buffer from a pool we saved
// in a hooked function, which would be the application's pool.
const VkCommandBufferAllocateInfo allocCommandBufferInfo = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, NULL,
deviceMap[device]->commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1};
data.commandPool = deviceMap[device]->commandPool;
err = pTableDevice->AllocateCommandBuffers(device, &allocCommandBufferInfo,
&data.commandBuffer);
assert(!err);
if (VK_SUCCESS != err)
return;
VkDevice cmdBuf =
static_cast<VkDevice>(static_cast<void *>(data.commandBuffer));
deviceMap.emplace(cmdBuf, devMap);
VkLayerDispatchTable *pTableCommandBuffer;
pTableCommandBuffer = get_dev_info(cmdBuf)->device_dispatch_table;
// We have just created a dispatchable object, but the dispatch table has
// not been placed in the object yet. When a "normal" application creates
// a command buffer, the dispatch table is installed by the top-level api
// binding (trampoline.c). But here, we have to do it ourselves.
if (!devMap->pfn_dev_init) {
*((const void **)data.commandBuffer) = *(void **)device;
} else {
err = devMap->pfn_dev_init(device, (void *)data.commandBuffer);
assert(!err);
}
const VkCommandBufferBeginInfo commandBufferBeginInfo = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
err = pTableCommandBuffer->BeginCommandBuffer(data.commandBuffer,
&commandBufferBeginInfo);
assert(!err);
// This barrier is used to transition from/to present Layout
VkImageMemoryBarrier presentMemoryBarrier = {
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_TRANSFER_READ_BIT,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
image1,
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
// This barrier is used to transition from a newly-created layout to a blt
// or copy destination layout.
VkImageMemoryBarrier destMemoryBarrier = {
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
0,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
data.image2,
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
// This barrier is used to transition a dest layout to general layout.
VkImageMemoryBarrier generalMemoryBarrier = {
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_TRANSFER_READ_BIT,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_GENERAL,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
data.image2,
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_TRANSFER_BIT;
VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_TRANSFER_BIT;
// The source image needs to be transitioned from present to transfer
// source.
pTableCommandBuffer->CmdPipelineBarrier(data.commandBuffer, srcStages,
dstStages, 0, 0, NULL, 0, NULL, 1,
&presentMemoryBarrier);
// image2 needs to be transitioned from its undefined state to transfer
// destination.
pTableCommandBuffer->CmdPipelineBarrier(data.commandBuffer, srcStages,
dstStages, 0, 0, NULL, 0, NULL, 1,
&destMemoryBarrier);
const VkImageCopy imageCopyRegion = {{VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
{0, 0, 0},
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
{0, 0, 0},
{width, height, 1}};
if (copyOnly) {
pTableCommandBuffer->CmdCopyImage(
data.commandBuffer, image1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
data.image2, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
&imageCopyRegion);
} else {
VkImageBlit imageBlitRegion = {};
imageBlitRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
imageBlitRegion.srcSubresource.baseArrayLayer = 0;
imageBlitRegion.srcSubresource.layerCount = 1;
imageBlitRegion.srcSubresource.mipLevel = 0;
imageBlitRegion.srcOffsets[1].x = width;
imageBlitRegion.srcOffsets[1].y = height;
imageBlitRegion.srcOffsets[1].z = 1;
imageBlitRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
imageBlitRegion.dstSubresource.baseArrayLayer = 0;
imageBlitRegion.dstSubresource.layerCount = 1;
imageBlitRegion.dstSubresource.mipLevel = 0;
imageBlitRegion.dstOffsets[1].x = width;
imageBlitRegion.dstOffsets[1].y = height;
imageBlitRegion.dstOffsets[1].z = 1;
pTableCommandBuffer->CmdBlitImage(
data.commandBuffer, image1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
data.image2, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
&imageBlitRegion, VK_FILTER_NEAREST);
if (need2steps) {
// image 3 needs to be transitioned from its undefined state to a
// transfer destination.
destMemoryBarrier.image = data.image3;
pTableCommandBuffer->CmdPipelineBarrier(
data.commandBuffer, srcStages, dstStages, 0, 0, NULL, 0, NULL,
1, &destMemoryBarrier);
// Transition image2 so that it can be read for the upcoming copy to
// image 3.
destMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
destMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
destMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
destMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
destMemoryBarrier.image = data.image2;
pTableCommandBuffer->CmdPipelineBarrier(
data.commandBuffer, srcStages, dstStages, 0, 0, NULL, 0, NULL,
1, &destMemoryBarrier);
// This step essentially untiles the image.
pTableCommandBuffer->CmdCopyImage(
data.commandBuffer, data.image2,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, data.image3,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &imageCopyRegion);
generalMemoryBarrier.image = data.image3;
}
}
// The destination needs to be transitioned from the optimal copy format to
// the format we can read with the CPU.
pTableCommandBuffer->CmdPipelineBarrier(data.commandBuffer, srcStages,
dstStages, 0, 0, NULL, 0, NULL, 1,
&generalMemoryBarrier);
// Restore the swap chain image layout to what it was before.
// This may not be strictly needed, but it is generally good to restore
// things to original state.
presentMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
presentMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
presentMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
presentMemoryBarrier.dstAccessMask = 0;
pTableCommandBuffer->CmdPipelineBarrier(data.commandBuffer, srcStages,
dstStages, 0, 0, NULL, 0, NULL, 1,
&presentMemoryBarrier);
err = pTableCommandBuffer->EndCommandBuffer(data.commandBuffer);
assert(!err);
VkFence nullFence = {VK_NULL_HANDLE};
VkSubmitInfo submitInfo;
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.pNext = NULL;
submitInfo.waitSemaphoreCount = 0;
submitInfo.pWaitSemaphores = NULL;
submitInfo.pWaitDstStageMask = NULL;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &data.commandBuffer;
submitInfo.signalSemaphoreCount = 0;
submitInfo.pSignalSemaphores = NULL;
err = pTableQueue->QueueSubmit(queue, 1, &submitInfo, nullFence);
assert(!err);
err = pTableQueue->QueueWaitIdle(queue);
assert(!err);
err = pTableDevice->DeviceWaitIdle(device);
assert(!err);
// Map the final image so that the CPU can read it.
const VkImageSubresource sr = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0};
VkSubresourceLayout srLayout;
const char *ptr;
if (!need2steps) {
pTableDevice->GetImageSubresourceLayout(device, data.image2, &sr,
&srLayout);
err = pTableDevice->MapMemory(device, data.mem2, 0, VK_WHOLE_SIZE, 0,
(void **)&ptr);
assert(!err);
if (VK_SUCCESS != err)
return;
data.mem2mapped = true;
} else {
pTableDevice->GetImageSubresourceLayout(device, data.image3, &sr,
&srLayout);
err = pTableDevice->MapMemory(device, data.mem3, 0, VK_WHOLE_SIZE, 0,
(void **)&ptr);
assert(!err);
if (VK_SUCCESS != err)
return;
data.mem3mapped = true;
}
// Write the data to a PPM file.
ofstream file(filename, ios::binary);
assert(file.is_open());
if (!file.is_open()) {
#ifdef ANDROID
__android_log_print(ANDROID_LOG_DEBUG, "screenshot", "Failed to open output file: %s. Be sure to grant read and write permissions.", filename);
#endif
return;
}
file << "P6\n";
file << width << "\n";
file << height << "\n";
file << 255 << "\n";
ptr += srLayout.offset;
if (3 == numChannels) {
for (uint32_t y = 0; y < height; y++) {
file.write(ptr, 3 * width);
ptr += srLayout.rowPitch;
}
} else if (4 == numChannels) {
for (uint32_t y = 0; y < height; y++) {
const unsigned int *row = (const unsigned int *)ptr;
for (uint32_t x = 0; x < width; x++) {
file.write((char *)row, 3);
row++;
}
ptr += srLayout.rowPitch;
}
}
file.close();
// Clean up handled by ~WritePPMCleanupData()
}
VKAPI_ATTR VkResult VKAPI_CALL
CreateInstance(const VkInstanceCreateInfo *pCreateInfo,
const VkAllocationCallbacks *pAllocator, VkInstance *pInstance) {
VkLayerInstanceCreateInfo *chain_info =
get_chain_info(pCreateInfo, VK_LAYER_LINK_INFO);
assert(chain_info->u.pLayerInfo);
PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr =
chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr;
assert(fpGetInstanceProcAddr);
PFN_vkCreateInstance fpCreateInstance =
(PFN_vkCreateInstance)fpGetInstanceProcAddr(VK_NULL_HANDLE,
"vkCreateInstance");
if (fpCreateInstance == NULL) {
return VK_ERROR_INITIALIZATION_FAILED;
}
// Advance the link info for the next element on the chain
chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext;
VkResult result = fpCreateInstance(pCreateInfo, pAllocator, pInstance);
if (result != VK_SUCCESS)
return result;
initInstanceTable(*pInstance, fpGetInstanceProcAddr);
init_screenshot();
return result;
}
// TODO hook DestroyInstance to cleanup
static void
createDeviceRegisterExtensions(const VkDeviceCreateInfo *pCreateInfo,
VkDevice device) {
uint32_t i;
DeviceMapStruct *devMap = get_dev_info(device);
VkLayerDispatchTable *pDisp = devMap->device_dispatch_table;
PFN_vkGetDeviceProcAddr gpa = pDisp->GetDeviceProcAddr;
pDisp->CreateSwapchainKHR =
(PFN_vkCreateSwapchainKHR)gpa(device, "vkCreateSwapchainKHR");
pDisp->GetSwapchainImagesKHR =
(PFN_vkGetSwapchainImagesKHR)gpa(device, "vkGetSwapchainImagesKHR");
pDisp->AcquireNextImageKHR =
(PFN_vkAcquireNextImageKHR)gpa(device, "vkAcquireNextImageKHR");
pDisp->QueuePresentKHR =
(PFN_vkQueuePresentKHR)gpa(device, "vkQueuePresentKHR");
devMap->wsi_enabled = false;
for (i = 0; i < pCreateInfo->enabledExtensionCount; i++) {
if (strcmp(pCreateInfo->ppEnabledExtensionNames[i],
VK_KHR_SWAPCHAIN_EXTENSION_NAME) == 0)
devMap->wsi_enabled = true;
}
}
VKAPI_ATTR VkResult VKAPI_CALL
CreateDevice(VkPhysicalDevice gpu, const VkDeviceCreateInfo *pCreateInfo,
const VkAllocationCallbacks *pAllocator, VkDevice *pDevice) {
VkLayerDeviceCreateInfo *chain_info =
get_chain_info(pCreateInfo, VK_LAYER_LINK_INFO);
assert(chain_info->u.pLayerInfo);
PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr =
chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr;
PFN_vkGetDeviceProcAddr fpGetDeviceProcAddr =
chain_info->u.pLayerInfo->pfnNextGetDeviceProcAddr;
VkInstance instance = physDeviceMap[gpu]->instance;
PFN_vkCreateDevice fpCreateDevice =
(PFN_vkCreateDevice)fpGetInstanceProcAddr(instance, "vkCreateDevice");
if (fpCreateDevice == NULL) {
return VK_ERROR_INITIALIZATION_FAILED;
}
// Advance the link info for the next element on the chain
chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext;
VkResult result = fpCreateDevice(gpu, pCreateInfo, pAllocator, pDevice);
if (result != VK_SUCCESS) {
return result;
}
assert(deviceMap.find(*pDevice) == deviceMap.end());
DeviceMapStruct *deviceMapElem = new DeviceMapStruct;
deviceMap[*pDevice] = deviceMapElem;
// Setup device dispatch table
deviceMapElem->device_dispatch_table = new VkLayerDispatchTable;
layer_init_device_dispatch_table(
*pDevice, deviceMapElem->device_dispatch_table, fpGetDeviceProcAddr);
createDeviceRegisterExtensions(pCreateInfo, *pDevice);
// Create a mapping from a device to a physicalDevice
deviceMapElem->physicalDevice = gpu;
// store the loader callback for initializing created dispatchable objects
chain_info = get_chain_info(pCreateInfo, VK_LOADER_DATA_CALLBACK);
if (chain_info) {
deviceMapElem->pfn_dev_init = chain_info->u.pfnSetDeviceLoaderData;
} else {
deviceMapElem->pfn_dev_init = NULL;
}
return result;
}
VKAPI_ATTR VkResult VKAPI_CALL
EnumeratePhysicalDevices(VkInstance instance, uint32_t *pPhysicalDeviceCount,
VkPhysicalDevice *pPhysicalDevices) {
VkResult result;
VkLayerInstanceDispatchTable *pTable = instance_dispatch_table(instance);
result = pTable->EnumeratePhysicalDevices(instance, pPhysicalDeviceCount,
pPhysicalDevices);
if (result == VK_SUCCESS && *pPhysicalDeviceCount > 0 && pPhysicalDevices) {
for (uint32_t i = 0; i < *pPhysicalDeviceCount; i++) {
// Create a mapping from a physicalDevice to an instance
if (physDeviceMap[pPhysicalDevices[i]] == NULL) {
PhysDeviceMapStruct *physDeviceMapElem =
new PhysDeviceMapStruct;
physDeviceMap[pPhysicalDevices[i]] = physDeviceMapElem;
}
physDeviceMap[pPhysicalDevices[i]]->instance = instance;
}
}
return result;
}
VKAPI_ATTR void VKAPI_CALL
DestroyDevice(VkDevice device, const VkAllocationCallbacks *pAllocator) {
DeviceMapStruct *devMap = get_dev_info(device);
assert(devMap);
VkLayerDispatchTable *pDisp = devMap->device_dispatch_table;
pDisp->DestroyDevice(device, pAllocator);
loader_platform_thread_lock_mutex(&globalLock);
delete pDisp;
delete devMap;
deviceMap.erase(device);
loader_platform_thread_unlock_mutex(&globalLock);
}
VKAPI_ATTR void VKAPI_CALL GetDeviceQueue(VkDevice device,
uint32_t queueNodeIndex,
uint32_t queueIndex,
VkQueue *pQueue) {
DeviceMapStruct *devMap = get_dev_info(device);
assert(devMap);
VkLayerDispatchTable *pDisp = devMap->device_dispatch_table;
pDisp->GetDeviceQueue(device, queueNodeIndex, queueIndex, pQueue);
// Save the device queue in a map if we are taking screenshots.
loader_platform_thread_lock_mutex(&globalLock);
if (screenshotEnvQueried && screenshotFrames.empty()) {
// No screenshots in the list to take
loader_platform_thread_unlock_mutex(&globalLock);
return;
}
VkDevice que = static_cast<VkDevice>(static_cast<void *>(*pQueue));
deviceMap.emplace(que, devMap);
// Create a mapping from a device to a queue
devMap->queue = *pQueue;
loader_platform_thread_unlock_mutex(&globalLock);
}
VKAPI_ATTR VkResult VKAPI_CALL CreateCommandPool(
VkDevice device, const VkCommandPoolCreateInfo *pCreateInfo,
const VkAllocationCallbacks *pAllocator, VkCommandPool *pCommandPool) {
DeviceMapStruct *devMap = get_dev_info(device);
assert(devMap);
VkLayerDispatchTable *pDisp = devMap->device_dispatch_table;
VkResult result =
pDisp->CreateCommandPool(device, pCreateInfo, pAllocator, pCommandPool);
// Save the command pool on a map if we are taking screenshots.
loader_platform_thread_lock_mutex(&globalLock);
if (screenshotEnvQueried && screenshotFrames.empty()) {
// No screenshots in the list to take
loader_platform_thread_unlock_mutex(&globalLock);
return result;
}
// Create a mapping from a device to a commandPool
devMap->commandPool = *pCommandPool;
loader_platform_thread_unlock_mutex(&globalLock);
return result;
}
VKAPI_ATTR VkResult VKAPI_CALL CreateSwapchainKHR(
VkDevice device, const VkSwapchainCreateInfoKHR *pCreateInfo,
const VkAllocationCallbacks *pAllocator, VkSwapchainKHR *pSwapchain) {
DeviceMapStruct *devMap = get_dev_info(device);
assert(devMap);
VkLayerDispatchTable *pDisp = devMap->device_dispatch_table;
// This layer does an image copy later on, and the copy command expects the
// transfer src bit to be on.
VkSwapchainCreateInfoKHR myCreateInfo = *pCreateInfo;
myCreateInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
VkResult result = pDisp->CreateSwapchainKHR(device, &myCreateInfo,
pAllocator, pSwapchain);
// Save the swapchain in a map of we are taking screenshots.
loader_platform_thread_lock_mutex(&globalLock);
if (screenshotEnvQueried && screenshotFrames.empty()) {
// No screenshots in the list to take
loader_platform_thread_unlock_mutex(&globalLock);
return result;
}
if (result == VK_SUCCESS) {
// Create a mapping for a swapchain to a device, image extent, and
// format
SwapchainMapStruct *swapchainMapElem = new SwapchainMapStruct;
swapchainMapElem->device = device;
swapchainMapElem->imageExtent = pCreateInfo->imageExtent;
swapchainMapElem->format = pCreateInfo->imageFormat;
swapchainMap.insert(make_pair(*pSwapchain, swapchainMapElem));
// Create a mapping for the swapchain object into the dispatch table
// TODO is this needed? screenshot_device_table_map.emplace((void
// *)pSwapchain, pTable);
}
loader_platform_thread_unlock_mutex(&globalLock);
return result;
}
VKAPI_ATTR VkResult VKAPI_CALL
GetSwapchainImagesKHR(VkDevice device, VkSwapchainKHR swapchain,
uint32_t *pCount, VkImage *pSwapchainImages) {
DeviceMapStruct *devMap = get_dev_info(device);
assert(devMap);
VkLayerDispatchTable *pDisp = devMap->device_dispatch_table;
VkResult result = pDisp->GetSwapchainImagesKHR(device, swapchain, pCount,
pSwapchainImages);
// Save the swapchain images in a map if we are taking screenshots
loader_platform_thread_lock_mutex(&globalLock);
if (screenshotEnvQueried && screenshotFrames.empty()) {
// No screenshots in the list to take
loader_platform_thread_unlock_mutex(&globalLock);
return result;
}
if (result == VK_SUCCESS && pSwapchainImages && !swapchainMap.empty() &&
swapchainMap.find(swapchain) != swapchainMap.end()) {
unsigned i;
for (i = 0; i < *pCount; i++) {
// Create a mapping for an image to a device, image extent, and
// format
if (imageMap[pSwapchainImages[i]] == NULL) {
ImageMapStruct *imageMapElem = new ImageMapStruct;
imageMap[pSwapchainImages[i]] = imageMapElem;
}
imageMap[pSwapchainImages[i]]->device =
swapchainMap[swapchain]->device;
imageMap[pSwapchainImages[i]]->imageExtent =
swapchainMap[swapchain]->imageExtent;
imageMap[pSwapchainImages[i]]->format =
swapchainMap[swapchain]->format;
}
// Add list of images to swapchain to image map
SwapchainMapStruct *swapchainMapElem = swapchainMap[swapchain];
if (i >= 1 && swapchainMapElem) {
VkImage *imageList = new VkImage[i];
swapchainMapElem->imageList = imageList;
for (unsigned j = 0; j < i; j++) {
swapchainMapElem->imageList[j] = pSwapchainImages[j];
}
}
}
loader_platform_thread_unlock_mutex(&globalLock);
return result;
}
VKAPI_ATTR VkResult VKAPI_CALL
QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR *pPresentInfo) {
static int frameNumber = 0;
if (frameNumber == 10) {
fflush(stdout); /* *((int*)0)=0; */
}
DeviceMapStruct *devMap = get_dev_info((VkDevice)queue);
assert(devMap);
VkLayerDispatchTable *pDisp = devMap->device_dispatch_table;
VkResult result = pDisp->QueuePresentKHR(queue, pPresentInfo);
loader_platform_thread_lock_mutex(&globalLock);
if (!screenshotEnvQueried) {
const char *_vk_screenshot = local_getenv(env_var);
if (_vk_screenshot && *_vk_screenshot) {
string spec(_vk_screenshot), word;
size_t start = 0, comma = 0;
while (start < spec.size()) {
int frameToAdd;
comma = spec.find(',', start);
if (comma == string::npos)
word = string(spec, start);
else
word = string(spec, start, comma - start);
frameToAdd = atoi(word.c_str());
// Add the frame number to set, but only do it if the word
// started with a digit and if
// it's not already in the list
if (*(word.c_str()) >= '0' && *(word.c_str()) <= '9') {
screenshotFrames.insert(frameToAdd);
}
if (comma == string::npos)
break;
start = comma + 1;
}
}
local_free_getenv(_vk_screenshot);
screenshotEnvQueried = true;
}
if (result == VK_SUCCESS && !screenshotFrames.empty()) {
set<int>::iterator it;
it = screenshotFrames.find(frameNumber);
if (it != screenshotFrames.end()) {
string fileName;
#ifdef ANDROID
// std::to_string is not supported currently
char buffer [64];
snprintf(buffer, sizeof(buffer), "/sdcard/Android/%d", frameNumber);
std::string base(buffer);
fileName = base + ".ppm";
#else
fileName = to_string(frameNumber) + ".ppm";
#endif
VkImage image;
VkSwapchainKHR swapchain;
// We'll dump only one image: the first
swapchain = pPresentInfo->pSwapchains[0];
image = swapchainMap[swapchain]
->imageList[pPresentInfo->pImageIndices[0]];
writePPM(fileName.c_str(), image);
screenshotFrames.erase(it);
if (screenshotFrames.empty()) {
// Free all our maps since we are done with them.
for (auto it = swapchainMap.begin(); it != swapchainMap.end();
it++) {
SwapchainMapStruct *swapchainMapElem = it->second;
delete swapchainMapElem;
}
for (auto it = imageMap.begin(); it != imageMap.end(); it++) {
ImageMapStruct *imageMapElem = it->second;
delete imageMapElem;
}
for (auto it = physDeviceMap.begin(); it != physDeviceMap.end();
it++) {
PhysDeviceMapStruct *physDeviceMapElem = it->second;
delete physDeviceMapElem;
}
swapchainMap.clear();
imageMap.clear();
physDeviceMap.clear();
}
}
}
frameNumber++;
loader_platform_thread_unlock_mutex(&globalLock);
return result;
}
static const VkLayerProperties global_layer = {
"VK_LAYER_LUNARG_screenshot", VK_MAKE_VERSION(1, 0, VK_HEADER_VERSION), 1,
"Layer: screenshot",
};
VKAPI_ATTR VkResult VKAPI_CALL EnumerateInstanceLayerProperties(
uint32_t *pCount, VkLayerProperties *pProperties) {
return util_GetLayerProperties(1, &global_layer, pCount, pProperties);
}
VKAPI_ATTR VkResult VKAPI_CALL EnumerateDeviceLayerProperties(
VkPhysicalDevice physicalDevice, uint32_t *pCount,
VkLayerProperties *pProperties) {
return util_GetLayerProperties(1, &global_layer, pCount, pProperties);
}
VKAPI_ATTR VkResult VKAPI_CALL
EnumerateInstanceExtensionProperties(const char *pLayerName, uint32_t *pCount,
VkExtensionProperties *pProperties) {
if (pLayerName && !strcmp(pLayerName, global_layer.layerName))
return util_GetExtensionProperties(0, NULL, pCount, pProperties);
return VK_ERROR_LAYER_NOT_PRESENT;
}
VKAPI_ATTR VkResult VKAPI_CALL EnumerateDeviceExtensionProperties(
VkPhysicalDevice physicalDevice, const char *pLayerName, uint32_t *pCount,
VkExtensionProperties *pProperties) {
if (pLayerName && !strcmp(pLayerName, global_layer.layerName))
return util_GetExtensionProperties(0, NULL, pCount, pProperties);
assert(physicalDevice);
VkLayerInstanceDispatchTable *pTable =
instance_dispatch_table(physicalDevice);
return pTable->EnumerateDeviceExtensionProperties(
physicalDevice, pLayerName, pCount, pProperties);
}
static PFN_vkVoidFunction intercept_core_instance_command(const char *name);
static PFN_vkVoidFunction intercept_core_device_command(const char *name);
static PFN_vkVoidFunction intercept_khr_swapchain_command(const char *name,
VkDevice dev);
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
GetDeviceProcAddr(VkDevice dev, const char *funcName) {
PFN_vkVoidFunction proc = intercept_core_device_command(funcName);
if (proc)
return proc;
if (dev == NULL) {
return NULL;
}
proc = intercept_khr_swapchain_command(funcName, dev);
if (proc)
return proc;
DeviceMapStruct *devMap = get_dev_info(dev);
assert(devMap);
VkLayerDispatchTable *pDisp = devMap->device_dispatch_table;
if (pDisp->GetDeviceProcAddr == NULL)
return NULL;
return pDisp->GetDeviceProcAddr(dev, funcName);
}
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
GetInstanceProcAddr(VkInstance instance, const char *funcName) {
PFN_vkVoidFunction proc = intercept_core_instance_command(funcName);
if (proc)
return proc;
assert(instance);
proc = intercept_core_device_command(funcName);
if (!proc)
proc = intercept_khr_swapchain_command(funcName, VK_NULL_HANDLE);
if (proc)
return proc;
VkLayerInstanceDispatchTable *pTable = instance_dispatch_table(instance);
if (pTable->GetInstanceProcAddr == NULL)
return NULL;
return pTable->GetInstanceProcAddr(instance, funcName);
}
static PFN_vkVoidFunction intercept_core_instance_command(const char *name) {
static const struct {
const char *name;
PFN_vkVoidFunction proc;
} core_instance_commands[] = {
{"vkGetInstanceProcAddr",
reinterpret_cast<PFN_vkVoidFunction>(GetInstanceProcAddr)},
{"vkCreateInstance",
reinterpret_cast<PFN_vkVoidFunction>(CreateInstance)},
{"vkCreateDevice", reinterpret_cast<PFN_vkVoidFunction>(CreateDevice)},
{"vkEnumeratePhysicalDevices",
reinterpret_cast<PFN_vkVoidFunction>(EnumeratePhysicalDevices)},
{"vkEnumerateInstanceLayerProperties",
reinterpret_cast<PFN_vkVoidFunction>(
EnumerateInstanceLayerProperties)},
{"vkEnumerateDeviceLayerProperties",
reinterpret_cast<PFN_vkVoidFunction>(EnumerateDeviceLayerProperties)},
{"vkEnumerateInstanceExtensionProperties",
reinterpret_cast<PFN_vkVoidFunction>(
EnumerateInstanceExtensionProperties)},
{"vkEnumerateDeviceExtensionProperties",
reinterpret_cast<PFN_vkVoidFunction>(
EnumerateDeviceExtensionProperties)}};
for (size_t i = 0; i < ARRAY_SIZE(core_instance_commands); i++) {
if (!strcmp(core_instance_commands[i].name, name))
return core_instance_commands[i].proc;
}
return nullptr;
}
static PFN_vkVoidFunction intercept_core_device_command(const char *name) {
static const struct {
const char *name;
PFN_vkVoidFunction proc;
} core_device_commands[] = {
{"vkGetDeviceProcAddr",
reinterpret_cast<PFN_vkVoidFunction>(GetDeviceProcAddr)},
{"vkGetDeviceQueue",
reinterpret_cast<PFN_vkVoidFunction>(GetDeviceQueue)},
{"vkCreateCommandPool",
reinterpret_cast<PFN_vkVoidFunction>(CreateCommandPool)},
{"vkDestroyDevice",
reinterpret_cast<PFN_vkVoidFunction>(DestroyDevice)},
};
for (size_t i = 0; i < ARRAY_SIZE(core_device_commands); i++) {
if (!strcmp(core_device_commands[i].name, name))
return core_device_commands[i].proc;
}
return nullptr;
}
static PFN_vkVoidFunction intercept_khr_swapchain_command(const char *name,
VkDevice dev) {
static const struct {
const char *name;
PFN_vkVoidFunction proc;
} khr_swapchain_commands[] = {
{"vkCreateSwapchainKHR",
reinterpret_cast<PFN_vkVoidFunction>(CreateSwapchainKHR)},
{"vkGetSwapchainImagesKHR",
reinterpret_cast<PFN_vkVoidFunction>(GetSwapchainImagesKHR)},
{"vkQueuePresentKHR",
reinterpret_cast<PFN_vkVoidFunction>(QueuePresentKHR)},
};
if (dev) {
DeviceMapStruct *devMap = get_dev_info(dev);
if (!devMap->wsi_enabled)
return nullptr;
}
for (size_t i = 0; i < ARRAY_SIZE(khr_swapchain_commands); i++) {
if (!strcmp(khr_swapchain_commands[i].name, name))
return khr_swapchain_commands[i].proc;
}
return nullptr;
}
} // namespace screenshot
// loader-layer interface v0, just wrappers since there is only a layer
VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL
vkEnumerateInstanceLayerProperties(uint32_t *pCount,
VkLayerProperties *pProperties) {
return screenshot::EnumerateInstanceLayerProperties(pCount, pProperties);
}
VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties(
VkPhysicalDevice physicalDevice, uint32_t *pCount,
VkLayerProperties *pProperties) {
// the layer command handles VK_NULL_HANDLE just fine internally
assert(physicalDevice == VK_NULL_HANDLE);
return screenshot::EnumerateDeviceLayerProperties(VK_NULL_HANDLE, pCount,
pProperties);
}
VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL
vkEnumerateInstanceExtensionProperties(const char *pLayerName, uint32_t *pCount,
VkExtensionProperties *pProperties) {
return screenshot::EnumerateInstanceExtensionProperties(pLayerName, pCount,
pProperties);
}
VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL
vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice,
const char *pLayerName, uint32_t *pCount,
VkExtensionProperties *pProperties) {
// the layer command handles VK_NULL_HANDLE just fine internally
assert(physicalDevice == VK_NULL_HANDLE);
return screenshot::EnumerateDeviceExtensionProperties(
VK_NULL_HANDLE, pLayerName, pCount, pProperties);
}
VK_LAYER_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
vkGetDeviceProcAddr(VkDevice dev, const char *funcName) {
return screenshot::GetDeviceProcAddr(dev, funcName);
}
VK_LAYER_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
vkGetInstanceProcAddr(VkInstance instance, const char *funcName) {
return screenshot::GetInstanceProcAddr(instance, funcName);
}