# HG changeset patch # User sam # Date 1724326263 -25200 # Node ID 1abdd42f5cfedea542b79a224060c01996a08c9c # Parent b165359f45d7b0851bd2beb09048b99e298a41a3 add: image layers diff -r b165359f45d7 -r 1abdd42f5cfe semicongine/image.nim --- a/semicongine/image.nim Mon Aug 19 19:48:40 2024 +0700 +++ b/semicongine/image.nim Thu Aug 22 18:31:03 2024 +0700 @@ -22,7 +22,8 @@ Gray* = TVec1[uint8] BGRA* = TVec4[uint8] PixelType* = Gray | BGRA - Image*[T: PixelType] = object + + ImageObject*[T: PixelType, IsArray: static bool] = object width*: uint32 height*: uint32 minInterpolation*: VkFilter = VK_FILTER_LINEAR @@ -35,7 +36,17 @@ sampler*: VkSampler isRenderTarget*: bool = false samples*: VkSampleCountFlagBits = VK_SAMPLE_COUNT_1_BIT + when IsArray: + nLayers*: uint32 + Image*[T: PixelType] = ImageObject[T, false] + ImageArray*[T: PixelType] = ImageObject[T, true] +template nLayers*(image: Image): untyped = + 1 + +proc `=copy`[S, T](dest: var ImageObject[S, T]; source: ImageObject[S, T]) {.error.} + +# loads single layer image proc loadImageData*[T: PixelType](pngData: string|seq[uint8]): Image[T] = when T is Gray: let nChannels = 1.cint @@ -60,10 +71,43 @@ copyMem(result.data.ToCPointer, data, imagesize) nativeFree(data) - when T is BGRA: # converkt to BGRA + when T is BGRA: # convert to BGRA for i in 0 ..< result.data.len: swap(result.data[i][0], result.data[i][2]) +proc addImageLayer*[T: PixelType](image: var Image[T], pngData: string|seq[uint8]) = + when T is Gray: + const nChannels = 1.cint + elif T is BGRA: + const nChannels = 4.cint + + var w, h, c: cint + + let data = stbi_load_from_memory( + buffer = cast[ptr uint8](pngData.ToCPointer), + len = pngData.len.cint, + x = addr(w), + y = addr(h), + channels_in_file = addr(c), + desired_channels = nChannels + ) + if data == nil: + raise newException(Exception, "An error occured while loading PNG file") + + assert w == image.width, "New image layer has dimension {(w, y)} but image has dimension {(image.width, image.height)}" + assert h == image.height, "New image layer has dimension {(w, y)} but image has dimension {(image.width, image.height)}" + + let imagesize = image.width * image.height * nChannels + let layerOffset = image.width * image.height * image.nLayers + inc image.nLayers + image.data.setLen(image.nLayers * image.width * image.height) + copyMem(addr(image.data[layerOffset]), data, imagesize) + nativeFree(data) + + when T is BGRA: # convert to BGRA + for i in 0 ..< image.data.len: + swap(image.data[layerOffset + i][0], image.data[layerOffset + i][2]) + proc loadImage*[T: PixelType](path: string, package = DEFAULT_PACKAGE): Image[T] = assert path.splitFile().ext.toLowerAscii == ".png", "Unsupported image type: " & path.splitFile().ext.toLowerAscii when T is Gray: @@ -73,6 +117,32 @@ result = loadImageData[T](loadResource_intern(path, package = package).readAll()) +proc `[]`*(image: Image, x, y: uint32): auto = + assert x < image.width, &"{x} < {image.width} is not true" + assert y < image.height, &"{y} < {image.height} is not true" + + image.data[y * image.width + x] + +proc `[]=`*[T](image: var Image[T], x, y: uint32, value: T) = + assert x < image.width + assert y < image.height + + image.data[y * image.width + x] = value + +proc `[]`*(image: ImageArray, layer, x, y: uint32): auto = + assert layer < image.nLayers, &"Tried to access image layer {layer}, but image has only {image.nLayers} layers" + assert x < image.width, &"Tried to access pixel coordinate {(x, y)} but image has size {(image.width, image.height)}" + assert y < image.height, &"Tried to access pixel coordinate {(x, y)} but image has size {(image.width, image.height)}" + + image.data[layer * (image.width * image.height) + y * image.width + x] + +proc `[]=`*[T](image: var ImageArray[T], layer, x, y: uint32, value: T) = + assert layer < image.nLayers, &"Tried to access image layer {layer}, but image has only {image.nLayers} layers" + assert x < image.width, &"Tried to access pixel coordinate {(x, y)} but image has size {(image.width, image.height)}" + assert y < image.height, &"Tried to access pixel coordinate {(x, y)} but image has size {(image.width, image.height)}" + + image.data[layer * (image.width * image.height) + y * image.width + x] = value + # stb_image.h has no encoding support, maybe check stb_image_write or similar # # proc lodepng_encode_memory(out_data: ptr cstring, outsize: ptr csize_t, image: cstring, w: cuint, h: cuint, colorType: cint, bitdepth: cuint): cuint {.importc.} diff -r b165359f45d7 -r 1abdd42f5cfe semicongine/rendering.nim --- a/semicongine/rendering.nim Mon Aug 19 19:48:40 2024 +0700 +++ b/semicongine/rendering.nim Thu Aug 22 18:31:03 2024 +0700 @@ -374,16 +374,3 @@ limits.framebufferColorSampleCounts.uint32 and limits.framebufferDepthSampleCounts.uint32 ).toEnums return min(max(available), maxSamples) - - -proc `[]`*(image: Image, x, y: uint32): auto = - assert x < image.width, &"{x} < {image.width} is not true" - assert y < image.height, &"{y} < {image.height} is not true" - - image.data[y * image.width + x] - -proc `[]=`*[T](image: var Image[T], x, y: uint32, value: T) = - assert x < image.width - assert y < image.height - - image.data[y * image.width + x] = value diff -r b165359f45d7 -r 1abdd42f5cfe semicongine/rendering/renderer.nim --- a/semicongine/rendering/renderer.nim Mon Aug 19 19:48:40 2024 +0700 +++ b/semicongine/rendering/renderer.nim Thu Aug 22 18:31:03 2024 +0700 @@ -56,7 +56,7 @@ (if count == 0: gpuArray.data.len.uint64 else: count).uint64 * sizeof(elementType(gpuArray.data)).uint64 template size(gpuValue: GPUValue): uint64 = sizeof(gpuValue.data).uint64 -func size(image: Image): uint64 = +func size(image: ImageObject): uint64 = image.data.len.uint64 * sizeof(elementType(image.data)).uint64 template rawPointer(gpuArray: GPUArray): pointer = @@ -80,12 +80,12 @@ for theName, value in descriptorSet.data.fieldPairs: when typeof(value) is GPUValue: assert value.buffer.vk.Valid - elif typeof(value) is Image: + elif typeof(value) is ImageObject: assert value.vk.Valid assert value.imageview.Valid assert value.sampler.Valid elif typeof(value) is array: - when elementType(value) is Image: + when elementType(value) is ImageObject: for t in value.litems: assert t.vk.Valid assert t.imageview.Valid @@ -132,7 +132,7 @@ pImageInfo: nil, pBufferInfo: addr(bufferWrites[^1]), ) - elif typeof(fieldValue) is Image: + elif typeof(fieldValue) is ImageObject: imageWrites.add VkDescriptorImageInfo( sampler: fieldValue.sampler, imageView: fieldValue.imageView, @@ -149,7 +149,7 @@ pBufferInfo: nil, ) elif typeof(fieldValue) is array: - when elementType(fieldValue) is Image: + when elementType(fieldValue) is ImageObject: for image in fieldValue.litems: imageWrites.add VkDescriptorImageInfo( sampler: image.sampler, @@ -465,14 +465,14 @@ ) checkVkResult vkCreateSampler(vulkan.device, addr(samplerInfo), nil, addr(result)) -proc createVulkanImage(renderData: var RenderData, image: var Image) = +proc createVulkanImage(renderData: var RenderData, image: var ImageObject) = assert image.vk == VkImage(0), "Image has already been created" var usage = @[VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_USAGE_SAMPLED_BIT] if image.isRenderTarget: usage.add VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT let format = getVkFormat(grayscale = elementType(image.data) is Gray, usage = usage) - image.vk = svkCreate2DImage(image.width, image.height, format, usage, image.samples) + image.vk = svkCreate2DImage(image.width, image.height, format, usage, image.samples, image.nLayers) renderData.images.add image.vk image.sampler = createSampler( magFilter = image.magInterpolation, @@ -512,7 +512,7 @@ renderData.memory[memoryType][selectedBlockI].offsetNextFree += memoryRequirements.size # imageview can only be created after memory is bound - image.imageview = svkCreate2DImageView(image.vk, format) + image.imageview = svkCreate2DImageView(image.vk, format, nLayers=image.nLayers) renderData.imageViews.add image.imageview # data transfer and layout transition @@ -529,10 +529,10 @@ proc uploadImages*(renderdata: var RenderData, descriptorSet: var DescriptorSetData) = for name, value in fieldPairs(descriptorSet.data): - when typeof(value) is Image: + when typeof(value) is ImageObject: renderdata.createVulkanImage(value) elif typeof(value) is array: - when elementType(value) is Image: + when elementType(value) is ImageObject: for image in value.mitems: renderdata.createVulkanImage(image) diff -r b165359f45d7 -r 1abdd42f5cfe semicongine/rendering/shaders.nim --- a/semicongine/rendering/shaders.nim Mon Aug 19 19:48:40 2024 +0700 +++ b/semicongine/rendering/shaders.nim Thu Aug 22 18:31:03 2024 +0700 @@ -1,4 +1,4 @@ -func glslType[T: SupportedGPUType|Image](value: T): string = +func glslType[T: SupportedGPUType|ImageObject](value: T): string = when T is float32: "float" elif T is float64: "double" elif T is int8 or T is int16 or T is int32 or T is int64: "int" @@ -36,6 +36,7 @@ elif T is TMat4[float32]: "mat4" elif T is TMat4[float64]: "dmat4" elif T is Image: "sampler2D" + elif T is ImageArray: "sampler2DArray" else: const n = typetraits.name(T) {.error: "Unsupported data type on GPU: " & n.} @@ -86,7 +87,7 @@ else: {.error: "Unsupported data type on GPU".} -func numberOfVertexInputAttributeDescriptors[T: SupportedGPUType|Image](value: T): uint32 = +func numberOfVertexInputAttributeDescriptors[T: SupportedGPUType|ImageObject](value: T): uint32 = when T is TMat2[float32] or T is TMat2[float64] or T is TMat23[float32] or T is TMat23[float64]: 2 elif T is TMat32[float32] or T is TMat32[float64] or T is TMat3[float32] or T is TMat3[float64] or T is TMat34[float32] or T is TMat34[float64]: @@ -96,7 +97,7 @@ else: 1 -func nLocationSlots[T: SupportedGPUType|Image](value: T): uint32 = +func nLocationSlots[T: SupportedGPUType|ImageObject](value: T): uint32 = #[ single location: - any scalar @@ -169,7 +170,7 @@ var descriptorBinding = 0 for descriptorName, descriptorValue in fieldPairs(value): - when typeof(descriptorValue) is Image: + when typeof(descriptorValue) is ImageObject: samplers.add "layout(set=" & $setIndex & ", binding = " & $descriptorBinding & ") uniform " & glslType(descriptorValue) & " " & descriptorName & ";" descriptorBinding.inc @@ -189,7 +190,7 @@ elif typeof(descriptorValue) is array: - when elementType(descriptorValue) is Image: + when elementType(descriptorValue) is ImageObject: let arrayDecl = "[" & $typeof(descriptorValue).len & "]" samplers.add "layout(set=" & $setIndex & ", binding = " & $descriptorBinding & ") uniform " & glslType(default(elementType(descriptorValue))) & " " & descriptorName & "" & arrayDecl & ";" diff -r b165359f45d7 -r 1abdd42f5cfe semicongine/rendering/vulkan_wrappers.nim --- a/semicongine/rendering/vulkan_wrappers.nim Mon Aug 19 19:48:40 2024 +0700 +++ b/semicongine/rendering/vulkan_wrappers.nim Thu Aug 22 18:31:03 2024 +0700 @@ -103,7 +103,7 @@ addr(result), ) -proc svkCreate2DImage*(width, height: uint32, format: VkFormat, usage: openArray[VkImageUsageFlagBits], samples = VK_SAMPLE_COUNT_1_BIT): VkImage = +proc svkCreate2DImage*(width, height: uint32, format: VkFormat, usage: openArray[VkImageUsageFlagBits], samples = VK_SAMPLE_COUNT_1_BIT, nLayers = 1'u32): VkImage = var imageProps: VkImageFormatProperties checkVkResult vkGetPhysicalDeviceImageFormatProperties( vulkan.physicalDevice, @@ -120,7 +120,7 @@ imageType: VK_IMAGE_TYPE_2D, extent: VkExtent3D(width: width, height: height, depth: 1), mipLevels: min(1'u32, imageProps.maxMipLevels), - arrayLayers: min(1'u32, imageProps.maxArrayLayers), + arrayLayers: min(nLayers, imageProps.maxArrayLayers), format: format, tiling: VK_IMAGE_TILING_OPTIMAL, initialLayout: VK_IMAGE_LAYOUT_UNDEFINED, @@ -130,7 +130,7 @@ ) checkVkResult vkCreateImage(vulkan.device, addr imageInfo, nil, addr(result)) -proc svkCreate2DImageView*(image: VkImage, format: VkFormat, aspect = VK_IMAGE_ASPECT_COLOR_BIT): VkImageView = +proc svkCreate2DImageView*(image: VkImage, format: VkFormat, aspect = VK_IMAGE_ASPECT_COLOR_BIT, nLayers=1'u32): VkImageView = var createInfo = VkImageViewCreateInfo( sType: VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, image: image, @@ -147,7 +147,7 @@ baseMipLevel: 0, levelCount: 1, baseArrayLayer: 0, - layerCount: 1, + layerCount: nLayers, ), ) checkVkResult vkCreateImageView(vulkan.device, addr(createInfo), nil, addr(result))