# HG changeset patch # User sam # Date 1712070164 -25200 # Node ID ec014e90bc46b8e050468e1abfe9b823cbb210f1 # Parent ea10907b9b6024038fa2cf41e8b2365b114cfd2d add: support for color space conversion, convert color space if srgb is not available for images diff -r ea10907b9b60 -r ec014e90bc46 semicongine/core/imagetypes.nim --- a/semicongine/core/imagetypes.nim Tue Apr 02 16:48:23 2024 +0700 +++ b/semicongine/core/imagetypes.nim Tue Apr 02 22:02:44 2024 +0700 @@ -1,3 +1,4 @@ +import std/math import std/strformat import ./vulkanapi @@ -38,6 +39,41 @@ converter toGrayscale*(p: GrayPixel): float32 = float32(p) / 255'f32 +# colorspace conversion functions + +func linear2srgb(value: float): float = + clamp( + if (value < 0.0031308): value * 12.92 + else: pow(value, 1.0 / 2.4) * 1.055 - 0.055, + 0, + 1, + ) +func srgb2linear(value: float): float = + clamp( + if (value < 0.04045): value / 12.92 + else: pow((value + 0.055) / 1.055, 2.4), + 0, + 1, + ) +func linear2srgb(value: uint8): uint8 = # also covers GrayPixel + uint8(round(linear2srgb(float(value) / 255.0) * 255)) +func linear2srgb(value: RGBAPixel): RGBAPixel = + [linear2srgb(value[0]), linear2srgb(value[1]), linear2srgb(value[2]), value[3]] +func srgb2linear(value: uint8): uint8 = # also covers GrayPixel + uint8(round(srgb2linear(float(value) / 255.0) * 255)) +func srgb2linear(value: RGBAPixel): RGBAPixel = + [srgb2linear(value[0]), srgb2linear(value[1]), srgb2linear(value[2]), value[3]] + +proc asSRGB*[T](image: Image[T]): Image[T] = + result = Image[T](width: image.width, height: image.height, imagedata: newSeq[T](image.imagedata.len)) + for i in 0 .. image.imagedata.len: + result.imagedata[i] = linear2srgb(image.imagedata[i]) + +proc asLinear*[T](image: Image[T]): Image[T] = + result = Image[T](width: image.width, height: image.height, imagedata: newSeq[T](image.imagedata.len)) + for i in 0 .. image.imagedata.len: + result.imagedata[i] = srgb2linear(image.imagedata[i]) + proc `$`*(image: Image): string = &"{image.width}x{image.height}" diff -r ea10907b9b60 -r ec014e90bc46 semicongine/vulkan/image.nim --- a/semicongine/vulkan/image.nim Tue Apr 02 16:48:23 2024 +0700 +++ b/semicongine/vulkan/image.nim Tue Apr 02 22:02:44 2024 +0700 @@ -35,12 +35,20 @@ sampler*: VulkanSampler const DEPTH_FORMAT_MAP = { - PixelDepth(1): VK_FORMAT_R8_SRGB, - PixelDepth(2): VK_FORMAT_R8G8_SRGB, - PixelDepth(3): VK_FORMAT_R8G8B8_SRGB, - PixelDepth(4): VK_FORMAT_R8G8B8A8_SRGB, + PixelDepth(1): [VK_FORMAT_R8_SRGB, VK_FORMAT_R8_UNORM], + PixelDepth(2): [VK_FORMAT_R8G8_SRGB, VK_FORMAT_R8G8_UNORM], + PixelDepth(3): [VK_FORMAT_R8G8B8_SRGB, VK_FORMAT_R8G8B8_UNORM], + PixelDepth(4): [VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_R8G8B8A8_UNORM], }.toTable +const LINEAR_FORMATS = [ + VK_FORMAT_R8_UNORM, + VK_FORMAT_R8G8_UNORM, + VK_FORMAT_R8G8B8_UNORM, + VK_FORMAT_R8G8B8A8_UNORM, +] + + proc requirements(image: VulkanImage): MemoryRequirements = assert image.vk.valid assert image.device.vk.valid @@ -143,36 +151,55 @@ ) # currently only usable for texture access from shader -proc createImage(device: Device, queue: Queue, width, height: uint32, depth: PixelDepth, data: pointer): VulkanImage = +proc createImage[T](device: Device, queue: Queue, width, height: uint32, depth: PixelDepth, image: Image[T]): VulkanImage = assert device.vk.valid assert width > 0 assert height > 0 assert depth != 2 - assert data != nil + + result.device = device + result.usage = @[VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_USAGE_SAMPLED_BIT] let size: uint64 = width * height * uint32(depth) - result.device = device + let usageBits = toBits result.usage + var formatList = DEPTH_FORMAT_MAP[depth] + var selectedFormat: VkFormat + var formatProperties = VkImageFormatProperties2(sType: VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2) + + for format in formatList: + var formatInfo = VkPhysicalDeviceImageFormatInfo2( + sType: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, + format: format, + thetype: VK_IMAGE_TYPE_2D, + tiling: VK_IMAGE_TILING_OPTIMAL, + usage: usageBits, + ) + let formatCheck = device.physicalDevice.vk.vkGetPhysicalDeviceImageFormatProperties2( + addr formatInfo, + addr formatProperties, + ) + if formatCheck == VK_SUCCESS: # found suitable format + selectedFormat = format + break + elif formatCheck == VK_ERROR_FORMAT_NOT_SUPPORTED: # nope, try to find other format + continue + else: # raise error + checkVkResult formatCheck + + # assumption: images comes in sRGB color space + var data = addr image.imagedata[0] + if selectedFormat in LINEAR_FORMATS: + let linearImage = image.asLinear() + data = addr image.imagedata[0] + + assert size <= uint64(formatProperties.imageFormatProperties.maxResourceSize) + assert width <= uint64(formatProperties.imageFormatProperties.maxExtent.width) + assert height <= uint64(formatProperties.imageFormatProperties.maxExtent.height) + result.width = width result.height = height result.depth = depth - result.format = DEPTH_FORMAT_MAP[depth] - result.usage = @[VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_USAGE_SAMPLED_BIT] - - var formatInfo = VkPhysicalDeviceImageFormatInfo2( - sType: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, - format: result.format, - thetype: VK_IMAGE_TYPE_2D, - tiling: VK_IMAGE_TILING_OPTIMAL, - usage: toBits result.usage, - ) - var formatProperties = VkImageFormatProperties2(sType: VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2) - checkVkResult device.physicalDevice.vk.vkGetPhysicalDeviceImageFormatProperties2( - addr formatInfo, - addr formatProperties, - ) - assert size <= uint64(formatProperties.imageFormatProperties.maxResourceSize) - assert width <= uint64(formatProperties.imageFormatProperties.maxExtent.width) - assert height <= uint64(formatProperties.imageFormatProperties.maxExtent.height) + result.format = selectedFormat var imageInfo = VkImageCreateInfo( sType: VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, @@ -183,7 +210,7 @@ format: result.format, tiling: VK_IMAGE_TILING_OPTIMAL, initialLayout: VK_IMAGE_LAYOUT_UNDEFINED, - usage: toBits result.usage, + usage: usageBits, sharingMode: VK_SHARING_MODE_EXCLUSIVE, samples: VK_SAMPLE_COUNT_1_BIT, ) @@ -291,9 +318,9 @@ proc uploadTexture*(device: Device, queue: Queue, texture: Texture): VulkanTexture = assert device.vk.valid if texture.isGrayscale: - result.image = createImage(device = device, queue = queue, width = texture.grayImage.width, height = texture.grayImage.height, depth = 1, data = addr texture.grayImage.imagedata[0]) + result.image = createImage(device = device, queue = queue, width = texture.grayImage.width, height = texture.grayImage.height, depth = 1, image = texture.grayImage) else: - result.image = createImage(device = device, queue = queue, width = texture.colorImage.width, height = texture.colorImage.height, depth = 4, data = addr texture.colorImage.imagedata[0][0]) + result.image = createImage(device = device, queue = queue, width = texture.colorImage.width, height = texture.colorImage.height, depth = 4, image = texture.colorImage) result.imageView = result.image.createImageView() result.sampler = result.image.device.createSampler(texture.sampler)