Skip to content

Commit

Permalink
sRGB storage image views take 2;
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
bjornbytes committed Nov 29, 2023
1 parent 6da3f8e commit d375e96
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 44 deletions.
3 changes: 2 additions & 1 deletion src/core/gpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
56 changes: 32 additions & 24 deletions src/core/gpu_vk.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand All @@ -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],
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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),
Expand Down Expand Up @@ -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;
Expand Down
67 changes: 48 additions & 19 deletions src/modules/graphics/graphics.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ struct Texture {
Sync sync;
gpu_texture* gpu;
gpu_texture* renderView;
gpu_texture* linearView;
gpu_texture* storageView;
Material* material;
TextureInfo info;
};
Expand Down Expand Up @@ -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");
Expand All @@ -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");
Expand Down Expand Up @@ -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
};
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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,
Expand All @@ -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;
}
Expand All @@ -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);
Expand Down Expand Up @@ -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");
}
Expand Down

0 comments on commit d375e96

Please sign in to comment.