vulkan/screenshot-layer: Add RGBA surface format support

The mesa screenshot layer attempts to use VK_FORMAT_R8G8B8_UNORM by
default. Using this, we can directly & efficiently write out to a
PNG file without further modifications. However, some GPUs don't
support the given format, so for those that don't, we'll attempt to
use VK_FORMAT_R8G8B8A8_UNORM, which will require some work to ensure
the alpha values are set to opaque to make RGB comparisons easier.

If both surface formats fail, a more descriptive failure
will be shown.

Signed-off-by: Casey Bowman <casey.g.bowman@intel.com>
Reviewed-by: Felix DeGrood <felix.j.degrood@intel.com>
Reviewed-by: Karmjit Mahil <karmjit.mahil@igalia.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/33295>
This commit is contained in:
Casey Bowman 2025-01-29 21:24:23 -08:00 committed by Marge Bot
parent 96de531b5a
commit 1da221427e

View file

@ -748,6 +748,7 @@ struct ThreadSaveData {
VkFence fence;
uint32_t const width;
uint32_t const height;
uint32_t const numChannels;
};
/* Write the copied image to a PNG file */
@ -765,8 +766,10 @@ void *writePNG(void *data) {
uint64_t rowPitch = threadData->srLayout.rowPitch;
uint64_t start_time, end_time;
const int RGB_NUM_CHANNELS = 3;
const int RGBA_NUM_CHANNELS = 4;
int localHeight = threadData->height;
int localWidth = threadData->width;
int numChannels = threadData->numChannels;
int matrixSize = localHeight * rowPitch;
bool checks_failed = true;
memcpy(filename, threadData->filename, length);
@ -797,6 +800,12 @@ void *writePNG(void *data) {
start_time = get_time();
row_pointer = (png_byte *)malloc(sizeof(png_byte) * matrixSize);
memcpy(row_pointer, threadData->pFramebuffer, matrixSize);
/* Ensure alpha bits are set to 'opaque' if image is of RGBA format */
if (numChannels == RGBA_NUM_CHANNELS) {
for (int i = 3; i < matrixSize; i += RGBA_NUM_CHANNELS) {
row_pointer[i] = 0xFF;
}
}
end_time = get_time();
print_time_difference(start_time, end_time);
// We've created all local copies of data,
@ -809,7 +818,7 @@ void *writePNG(void *data) {
localWidth, // Image width
localHeight, // Image height
8, // Color depth
PNG_COLOR_TYPE_RGB,
numChannels == RGB_NUM_CHANNELS ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGBA,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT
@ -888,30 +897,59 @@ static bool write_image(
VkQueue queue = queue_data->queue;
VkResult err;
/* Force destination format to be RGB to make writing to file much faster */
VkFormat destination_format = VK_FORMAT_R8G8B8_UNORM;
VkFormatProperties device_format_properties;
instance_data->pd_vtable.GetPhysicalDeviceFormatProperties(physical_device,
destination_format,
&device_format_properties);
/* Attempt to set destination format to RGB to make writing to file much faster.
If not available, try to fall back to RGBA. If both fail, abort the screenshot */
VkFormat supported_formats[] = {VK_FORMAT_R8G8B8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_UNDEFINED};
uint32_t supported_formats_count = sizeof(supported_formats) / sizeof(VkFormat);
VkFormat destination_format;
uint32_t numChannels = 0;
/* If origin and destination formats are the same, no need to convert */
bool copyOnly = false;
bool needs_2_steps = false;
if (destination_format == format && not instance_data->region_enabled) {
copyOnly = true;
LOG(DEBUG, "Only copying since the src/dest formats are the same\n");
} else {
bool const blt_linear = device_format_properties.linearTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT ? true : false;
bool const blt_optimal = device_format_properties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT ? true : false;
if (!blt_linear && !blt_optimal) {
bool blt_linear, blt_optimal;
VkFormatProperties device_format_properties;
for (uint32_t i = 0; i < supported_formats_count; i++) {
destination_format = supported_formats[i];
instance_data->pd_vtable.GetPhysicalDeviceFormatProperties(physical_device,
destination_format,
&device_format_properties);
if(destination_format == VK_FORMAT_UNDEFINED) {
LOG(ERROR, "Could not use the supported surface formats!\n");
return false;
} else if (!blt_linear && blt_optimal) {
// Can't blit to linear target, but can blit to optimal
needs_2_steps = true;
LOG(DEBUG, "Needs 2 steps\n");
}
if (destination_format == format && not instance_data->region_enabled) {
copyOnly = true;
LOG(DEBUG, "Only copying since the src/dest surface formats are the same.\n");
break;
} else {
blt_linear = device_format_properties.linearTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT ? true : false;
blt_optimal = device_format_properties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT ? true : false;
if (!blt_linear && !blt_optimal) {
LOG(DEBUG, "Can't blit to linear nor optimal with surface format '%s'\n", vk_Format_to_str(supported_formats[i]));
} else if (blt_linear) {
break;
} else if (blt_optimal) {
// Can't blit to linear target, but can blit to optimal
needs_2_steps = true;
LOG(DEBUG, "Needs 2 steps\n");
break;
}
}
}
LOG(DEBUG, "Using surface format '%s' for copy.\n", vk_Format_to_str(destination_format));
switch (destination_format)
{
case VK_FORMAT_R8G8B8_UNORM:
numChannels = 3;
break;
case VK_FORMAT_R8G8B8A8_UNORM:
numChannels = 4;
break;
default:
LOG(ERROR, "Unsupported format, aborting screenshot!\n");
break;
}
WriteFileCleanupData data = {};
@ -1153,7 +1191,7 @@ static bool write_image(
// Thread off I/O operations
pthread_t ioThread;
pthread_mutex_lock(&ptLock); // Grab lock, we need to wait until thread has copied values of pointers
struct ThreadSaveData threadData = {device_data, filename, pFramebuffer, srLayout, copyDone, newWidth, newHeight};
struct ThreadSaveData threadData = {device_data, filename, pFramebuffer, srLayout, copyDone, newWidth, newHeight, numChannels};
// Write the data to a PNG file.
pthread_create(&ioThread, NULL, writePNG, (void *)&threadData);