From d375e96c135fa5cbb37f25674d8653bddec3aafa Mon Sep 17 00:00:00 2001 From: bjorn Date: Tue, 28 Nov 2023 22:44:37 -0800 Subject: [PATCH] sRGB storage image views take 2; There were numerous problems with the previous effort to add support for linear views of sRGB storage textures. Here's another attempt: - Images are always created with the linear version of their format. - The default texture view uses the sRGB format if the parent is sRGB. - Use ImageViewUsageCreateInfo to specify the usage for render/storage views. - sRGB image views always have their storage bit forcibly cleared. The storage view now behaves more like the existing renderView -- if we detect that you couldn't use the default texture view for storage, we'll create one that is guaranteed to be usable for storage bindings (by clearing the sRGB flag on it). --- src/core/gpu.h | 3 +- src/core/gpu_vk.c | 56 +++++++++++++++------------ src/modules/graphics/graphics.c | 67 +++++++++++++++++++++++---------- 3 files changed, 82 insertions(+), 44 deletions(-) diff --git a/src/core/gpu.h b/src/core/gpu.h index fb9a555b5..72b93290c 100644 --- a/src/core/gpu.h +++ b/src/core/gpu.h @@ -114,7 +114,8 @@ typedef enum { typedef struct { gpu_texture* source; gpu_texture_type type; - bool linear; + uint32_t usage; + bool srgb; uint32_t layerIndex; uint32_t layerCount; uint32_t levelIndex; diff --git a/src/core/gpu_vk.c b/src/core/gpu_vk.c index ded027b4a..7aba01769 100644 --- a/src/core/gpu_vk.c +++ b/src/core/gpu_vk.c @@ -426,16 +426,12 @@ void gpu_buffer_destroy(gpu_buffer* buffer) { // Texture bool gpu_texture_init(gpu_texture* texture, gpu_texture_info* info) { - VkImageType type; - VkImageCreateFlags flags = 0; - - switch (info->type) { - case GPU_TEXTURE_2D: type = VK_IMAGE_TYPE_2D; break; - case GPU_TEXTURE_3D: type = VK_IMAGE_TYPE_3D, flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; break; - case GPU_TEXTURE_CUBE: type = VK_IMAGE_TYPE_2D, flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; break; - case GPU_TEXTURE_ARRAY: type = VK_IMAGE_TYPE_2D; break; - default: return false; - } + static const VkImageType imageTypes[] = { + [GPU_TEXTURE_2D] = VK_IMAGE_TYPE_2D, + [GPU_TEXTURE_3D] = VK_IMAGE_TYPE_3D, + [GPU_TEXTURE_CUBE] = VK_IMAGE_TYPE_2D, + [GPU_TEXTURE_ARRAY] = VK_IMAGE_TYPE_2D + }; switch (info->format) { case GPU_FORMAT_D16: texture->aspect = VK_IMAGE_ASPECT_DEPTH_BIT; break; @@ -445,19 +441,16 @@ bool gpu_texture_init(gpu_texture* texture, gpu_texture_info* info) { default: texture->aspect = VK_IMAGE_ASPECT_COLOR_BIT; break; } - if (info->srgb && (info->usage & GPU_TEXTURE_STORAGE)) { - flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; - } - texture->layout = getNaturalLayout(info->usage, texture->aspect); - texture->layers = type == VK_IMAGE_TYPE_2D ? info->size[2] : 0; + texture->layers = info->type == GPU_TEXTURE_3D ? 0 : info->size[2]; texture->samples = info->samples; texture->format = info->format; texture->srgb = info->srgb; gpu_texture_view_info viewInfo = { .source = texture, - .type = info->type + .type = info->type, + .usage = info->usage }; if (info->handle) { @@ -469,9 +462,12 @@ bool gpu_texture_init(gpu_texture* texture, gpu_texture_info* info) { VkImageCreateInfo imageInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, - .flags = flags, - .imageType = type, - .format = convertFormat(texture->format, texture->srgb), + .flags = + (info->type == GPU_TEXTURE_3D ? VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT : 0) | + (info->type == GPU_TEXTURE_CUBE ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0) | + (info->srgb ? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT : 0), + .imageType = imageTypes[info->type], + .format = convertFormat(texture->format, LINEAR), .extent.width = info->size[0], .extent.height = info->size[1], .extent.depth = texture->layers ? 1 : info->size[2], @@ -492,15 +488,15 @@ bool gpu_texture_init(gpu_texture* texture, gpu_texture_info* info) { VkFormat formats[2]; VkImageFormatListCreateInfo imageFormatList; - if ((flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) && state.extensions.formatList) { + if (info->srgb && state.extensions.formatList) { imageFormatList = (VkImageFormatListCreateInfo) { .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO, .viewFormatCount = COUNTOF(formats), .pViewFormats = formats }; - formats[0] = convertFormat(texture->format, texture->srgb); - formats[1] = convertFormat(texture->format, !texture->srgb); + formats[0] = imageInfo.format; + formats[1] = convertFormat(texture->format, SRGB); if (formats[0] != formats[1]) { imageFormatList.pNext = imageInfo.pNext; @@ -534,6 +530,7 @@ bool gpu_texture_init(gpu_texture* texture, gpu_texture_info* info) { } bool needsView = info->usage & (GPU_TEXTURE_RENDER | GPU_TEXTURE_SAMPLE | GPU_TEXTURE_STORAGE); + if (info->usage == GPU_TEXTURE_STORAGE && info->srgb) needsView = false; if (needsView && !gpu_texture_init_view(texture, &viewInfo)) { vkDestroyImage(state.device, texture->handle, NULL); @@ -646,7 +643,7 @@ bool gpu_texture_init_view(gpu_texture* texture, gpu_texture_view_info* info) { texture->samples = info->source->samples; texture->layers = info->layerCount ? info->layerCount : (info->source->layers - info->layerIndex); texture->format = info->source->format; - texture->srgb = !info->linear; + texture->srgb = info->srgb; } VkImageViewType type; @@ -657,8 +654,18 @@ bool gpu_texture_init_view(gpu_texture* texture, gpu_texture_view_info* info) { case GPU_TEXTURE_ARRAY: type = VK_IMAGE_VIEW_TYPE_2D_ARRAY; break; } + VkImageViewUsageCreateInfo usage = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO, + .usage = + ((info->usage & GPU_TEXTURE_SAMPLE) ? VK_IMAGE_USAGE_SAMPLED_BIT : 0) | + (((info->usage & GPU_TEXTURE_RENDER) && texture->aspect == VK_IMAGE_ASPECT_COLOR_BIT) ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : 0) | + (((info->usage & GPU_TEXTURE_RENDER) && texture->aspect != VK_IMAGE_ASPECT_COLOR_BIT) ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT : 0) | + ((info->usage & GPU_TEXTURE_STORAGE) && !texture->srgb ? VK_IMAGE_USAGE_STORAGE_BIT : 0) + }; + VkImageViewCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = &usage, .image = info->source->handle, .viewType = type, .format = convertFormat(texture->format, texture->srgb), @@ -825,7 +832,8 @@ void gpu_surface_resize(uint32_t width, uint32_t height) { gpu_texture_view_info view = { .source = texture, - .type = GPU_TEXTURE_2D + .type = GPU_TEXTURE_2D, + .usage = GPU_TEXTURE_RENDER }; CHECK(gpu_texture_init_view(texture, &view), "Failed to create swapchain texture views") return; diff --git a/src/modules/graphics/graphics.c b/src/modules/graphics/graphics.c index c5d8e8602..2a3f6873a 100644 --- a/src/modules/graphics/graphics.c +++ b/src/modules/graphics/graphics.c @@ -78,7 +78,7 @@ struct Texture { Sync sync; gpu_texture* gpu; gpu_texture* renderView; - gpu_texture* linearView; + gpu_texture* storageView; Material* material; TextureInfo info; }; @@ -2016,6 +2016,7 @@ Texture* lovrTextureCreate(const TextureInfo* info) { uint32_t mipmaps = CLAMP(info->mipmaps, 1, mipmapCap); bool srgb = supportsSRGB(info->format) && info->srgb; uint8_t supports = state.features.formats[info->format][srgb]; + uint8_t linearSupports = state.features.formats[info->format][false]; lovrCheck(info->width > 0, "Texture width must be greater than zero"); lovrCheck(info->height > 0, "Texture height must be greater than zero"); @@ -2035,7 +2036,7 @@ Texture* lovrTextureCreate(const TextureInfo* info) { lovrCheck(info->samples == 1 || mipmaps == 1, "Multisampled textures can only have 1 mipmap"); lovrCheck(~info->usage & TEXTURE_SAMPLE || (supports & GPU_FEATURE_SAMPLE), "GPU does not support the 'sample' flag for this texture format/encoding"); lovrCheck(~info->usage & TEXTURE_RENDER || (supports & GPU_FEATURE_RENDER), "GPU does not support the 'render' flag for this texture format/encoding"); - lovrCheck(~info->usage & TEXTURE_STORAGE || (supports & GPU_FEATURE_STORAGE), "GPU does not support the 'storage' flag for this texture format/encoding"); + lovrCheck(~info->usage & TEXTURE_STORAGE || (linearSupports & GPU_FEATURE_STORAGE), "GPU does not support the 'storage' flag for this texture format"); lovrCheck(~info->usage & TEXTURE_RENDER || info->width <= state.limits.renderSize[0], "Texture has 'render' flag but its size exceeds the renderSize limit"); lovrCheck(~info->usage & TEXTURE_RENDER || info->height <= state.limits.renderSize[1], "Texture has 'render' flag but its size exceeds the renderSize limit"); lovrCheck(~info->usage & TEXTURE_RENDER || info->type != TEXTURE_3D || !isDepthFormat(info->format), "3D depth textures can not have the 'render' flag"); @@ -2121,6 +2122,8 @@ Texture* lovrTextureCreate(const TextureInfo* info) { gpu_texture_view_info view = { .source = texture->gpu, .type = GPU_TEXTURE_ARRAY, + .usage = GPU_TEXTURE_RENDER, + .srgb = srgb, .layerCount = info->layers, .levelCount = 1 }; @@ -2131,20 +2134,20 @@ Texture* lovrTextureCreate(const TextureInfo* info) { } } - // Make a linear view of sRGB textures for storage bindings, since most GPUs don't support sRGB - // storage textures + // Make a linear view of sRGB textures for storage bindings if (srgb && (info->usage & TEXTURE_STORAGE)) { gpu_texture_view_info view = { .source = texture->gpu, .type = (gpu_texture_type) info->type, - .linear = true + .usage = GPU_TEXTURE_STORAGE, + .srgb = false }; - texture->linearView = malloc(gpu_sizeof_texture()); - lovrAssert(texture->linearView, "Out of memory"); - lovrAssert(gpu_texture_init_view(texture->linearView, &view), "Failed to create texture view"); + texture->storageView = malloc(gpu_sizeof_texture()); + lovrAssert(texture->storageView, "Out of memory"); + lovrAssert(gpu_texture_init_view(texture->storageView, &view), "Failed to create texture view"); } else { - texture->linearView = texture->gpu; + texture->storageView = texture->gpu; } // Sample-only textures are exempt from sync tracking to reduce overhead. Instead, they are @@ -2188,14 +2191,20 @@ Texture* lovrTextureCreateView(const TextureViewInfo* view) { texture->info.height = MAX(info->height >> view->levelIndex, 1); texture->info.layers = view->layerCount; - gpu_texture_init_view(texture->gpu, &(gpu_texture_view_info) { - .source = view->parent->gpu, - .type = (gpu_texture_type) view->type, - .layerIndex = view->layerIndex, - .layerCount = view->layerCount, - .levelIndex = view->levelIndex, - .levelCount = view->levelCount - }); + if (info->usage & (TEXTURE_SAMPLE | TEXTURE_RENDER)) { + gpu_texture_init_view(texture->gpu, &(gpu_texture_view_info) { + .source = view->parent->gpu, + .type = (gpu_texture_type) view->type, + .usage = info->usage, + .srgb = info->srgb, + .layerIndex = view->layerIndex, + .layerCount = view->layerCount, + .levelIndex = view->levelIndex, + .levelCount = view->levelCount + }); + } else { + texture->gpu = NULL; + } if ((info->usage & TEXTURE_RENDER) && view->layerCount <= state.limits.renderSize[2]) { if (view->levelCount == 1) { @@ -2204,6 +2213,7 @@ Texture* lovrTextureCreateView(const TextureViewInfo* view) { gpu_texture_view_info subview = { .source = view->parent->gpu, .type = GPU_TEXTURE_ARRAY, + .usage = GPU_TEXTURE_RENDER, .layerIndex = view->layerIndex, .layerCount = view->layerCount, .levelIndex = view->levelIndex, @@ -2216,6 +2226,25 @@ Texture* lovrTextureCreateView(const TextureViewInfo* view) { } } + if ((info->usage & TEXTURE_STORAGE) && info->srgb) { + gpu_texture_view_info subview = { + .source = view->parent->gpu, + .type = (gpu_texture_type) info->type, + .usage = GPU_TEXTURE_STORAGE, + .srgb = false, + .layerIndex = view->layerIndex, + .layerCount = view->layerCount, + .levelIndex = view->levelIndex, + .levelCount = view->levelCount + }; + + texture->storageView = malloc(gpu_sizeof_texture()); + lovrAssert(texture->storageView, "Out of memory"); + lovrAssert(gpu_texture_init_view(texture->storageView, &subview), "Failed to create texture view"); + } else { + texture->storageView = texture->gpu; + } + lovrRetain(view->parent); return texture; } @@ -2226,7 +2255,7 @@ void lovrTextureDestroy(void* ref) { lovrRelease(texture->material, lovrMaterialDestroy); lovrRelease(texture->info.parent, lovrTextureDestroy); if (texture->renderView && texture->renderView != texture->gpu) gpu_texture_destroy(texture->renderView); - if (texture->linearView && texture->linearView != texture->gpu) gpu_texture_destroy(texture->linearView); + if (texture->storageView && texture->storageView != texture->gpu) gpu_texture_destroy(texture->storageView); if (texture->gpu) gpu_texture_destroy(texture->gpu); } free(texture); @@ -5649,7 +5678,7 @@ void lovrPassSendTexture(Pass* pass, const char* name, size_t length, uint32_t s gpu_texture* view = texture->gpu; if (shader->storageMask & (1u << slot)) { lovrCheck(texture->info.usage & TEXTURE_STORAGE, "Textures must be created with the 'storage' usage to send them to image variables in shaders"); - view = texture->linearView; + view = texture->storageView; } else { lovrCheck(texture->info.usage & TEXTURE_SAMPLE, "Textures must be created with the 'sample' usage to send them to sampler variables in shaders"); }