# HG changeset patch # User sam # Date 1724339913 -25200 # Node ID d5a6f69dc855ebff30518754803e714ee4bbe8c4 # Parent 966032c7a3aa0603ae9ac6977780d0fe272c0cde add: support for multi-layer images diff -r 966032c7a3aa -r d5a6f69dc855 semicongine/gltf.nim --- a/semicongine/gltf.nim Thu Aug 22 18:32:21 2024 +0700 +++ b/semicongine/gltf.nim Thu Aug 22 22:18:33 2024 +0700 @@ -211,7 +211,8 @@ let bufferView = root["bufferViews"][root["images"][imageIndex]["bufferView"].getInt()] - result = loadImageData[BGRA](getBufferViewData(bufferView, mainBuffer)) + let img = loadImageData[BGRA](getBufferViewData(bufferView, mainBuffer)) + result = Image[BGRA](width: img.width, height: img.height, data: img.data) if textureNode.hasKey("sampler"): let sampler = root["samplers"][textureNode["sampler"].getInt()] diff -r 966032c7a3aa -r d5a6f69dc855 semicongine/image.nim --- a/semicongine/image.nim Thu Aug 22 18:32:21 2024 +0700 +++ b/semicongine/image.nim Thu Aug 22 22:18:33 2024 +0700 @@ -45,12 +45,14 @@ ImageArray*[T: PixelType] = ImageObject[T, true] template nLayers*(image: Image): untyped = - 1 + 1'u32 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] = +proc loadImageData*[T: PixelType]( + pngData: string | seq[uint8] +): tuple[width: uint32, height: uint32, data: seq[T]] = when T is Gray: let nChannels = 1.cint elif T is BGRA: @@ -69,8 +71,8 @@ if data == nil: raise newException(Exception, "An error occured while loading PNG file") - let imagesize = w * h * 4 - result = Image[T](width: w.uint32, height: h.uint32, data: newSeq[T](w * h)) + let imagesize = w * h * nChannels + result = (width: w.uint32, height: h.uint32, data: newSeq[T](w * h)) copyMem(result.data.ToCPointer, data, imagesize) nativeFree(data) @@ -78,40 +80,18 @@ 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") +proc addImageLayer*[T: PixelType]( + image: var ImageArray[T], pngData: string | seq[uint8] +) = + let (w, h, data) = loadImageData[T](pngData) 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]) + image.data.add data proc loadImage*[T: PixelType](path: string, package = DEFAULT_PACKAGE): Image[T] = assert path.splitFile().ext.toLowerAscii == ".png", @@ -121,7 +101,27 @@ elif T is BGRA: let pngType = 6.cint - result = loadImageData[T](loadResource_intern(path, package = package).readAll()) + let (width, height, data) = + loadImageData[T](loadResource_intern(path, package = package).readAll()) + result = Image[T](width: width, height: height, data: data) + +proc loadImageArray*[T: PixelType]( + paths: openArray[string], package = DEFAULT_PACKAGE +): ImageArray[T] = + assert paths.len > 0, "Image array cannot contain 0 images" + for path in paths: + assert path.splitFile().ext.toLowerAscii == ".png", + "Unsupported image type: " & path.splitFile().ext.toLowerAscii + when T is Gray: + let pngType = 0.cint + elif T is BGRA: + let pngType = 6.cint + + let (width, height, data) = + loadImageData[T](loadResource_intern(paths[0], package = package).readAll()) + result = ImageArray[T](width: width, height: height, data: data, nLayers: 1) + for path in paths[1 .. ^1]: + result.addImageLayer(loadResource_intern(path, package = package).readAll()) proc `[]`*(image: Image, x, y: uint32): auto = assert x < image.width, &"{x} < {image.width} is not true" diff -r 966032c7a3aa -r d5a6f69dc855 semicongine/rendering.nim --- a/semicongine/rendering.nim Thu Aug 22 18:32:21 2024 +0700 +++ b/semicongine/rendering.nim Thu Aug 22 22:18:33 2024 +0700 @@ -189,7 +189,7 @@ ): untyped = var `bindingNumber` {.inject.} = 0'u32 for theFieldname, `valuename` in fieldPairs(shader): - when typeof(`valuename`) is Image: + when typeof(`valuename`) is ImageObject: block: const `typename` {.inject.} = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER const `countname` {.inject.} = 1'u32 @@ -202,7 +202,7 @@ body `bindingNumber`.inc elif typeof(`valuename`) is array: - when elementType(`valuename`) is Image: + when elementType(`valuename`) is ImageObject: block: const `typename` {.inject.} = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER const `countname` {.inject.} = uint32(typeof(`valuename`).len) diff -r 966032c7a3aa -r d5a6f69dc855 semicongine/rendering/renderer.nim --- a/semicongine/rendering/renderer.nim Thu Aug 22 18:32:21 2024 +0700 +++ b/semicongine/rendering/renderer.nim Thu Aug 22 22:18:33 2024 +0700 @@ -289,7 +289,7 @@ template selectedBlock(): untyped = renderData.memory[memoryType][selectedBlockI] - # let selectedBlock = + # let selectedBlock = renderData.memory[memoryType][selectedBlockI].offsetNextFree = alignedTo(selectedBlock.offsetNextFree, memoryRequirements.alignment) checkVkResult vkBindBufferMemory( @@ -425,7 +425,9 @@ for memory in memoryBlocks: vkFreeMemory(vulkan.device, memory.vk, nil) -proc transitionImageLayout(image: VkImage, oldLayout, newLayout: VkImageLayout) = +proc transitionImageLayout( + image: VkImage, oldLayout, newLayout: VkImageLayout, nLayers: uint32 +) = var barrier = VkImageMemoryBarrier( sType: VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, @@ -439,7 +441,7 @@ baseMipLevel: 0, levelCount: 1, baseArrayLayer: 0, - layerCount: 1, + layerCount: nLayers, ), ) srcStage: VkPipelineStageFlagBits @@ -557,16 +559,19 @@ # data transfer and layout transition transitionImageLayout( - image.vk, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL + image.vk, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + image.nLayers, ) if image.data.len > 0: withStagingBuffer( - (image.vk, image.width, image.height), memoryRequirements.size, stagingPtr + (image.vk, image.width, image.height, image.nLayers), + memoryRequirements.size, + stagingPtr, ): copyMem(stagingPtr, image.data.ToCPointer, image.size) transitionImageLayout( image.vk, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, image.nLayers, ) proc uploadImages*(renderdata: var RenderData, descriptorSet: var DescriptorSetData) = diff -r 966032c7a3aa -r d5a6f69dc855 semicongine/rendering/vulkan_wrappers.nim --- a/semicongine/rendering/vulkan_wrappers.nim Thu Aug 22 18:32:21 2024 +0700 +++ b/semicongine/rendering/vulkan_wrappers.nim Thu Aug 22 22:18:33 2024 +0700 @@ -128,7 +128,7 @@ imageType: VK_IMAGE_TYPE_2D, extent: VkExtent3D(width: width, height: height, depth: 1), mipLevels: min(1'u32, imageProps.maxMipLevels), - arrayLayers: min(nLayers, imageProps.maxArrayLayers), + arrayLayers: nLayers, format: format, tiling: VK_IMAGE_TILING_OPTIMAL, initialLayout: VK_IMAGE_LAYOUT_UNDEFINED, @@ -147,7 +147,7 @@ var createInfo = VkImageViewCreateInfo( sType: VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, image: image, - viewType: VK_IMAGE_VIEW_TYPE_2D, + viewType: if nLayers == 1: VK_IMAGE_VIEW_TYPE_2D else: VK_IMAGE_VIEW_TYPE_2D_ARRAY, format: format, components: VkComponentMapping( r: VK_COMPONENT_SWIZZLE_IDENTITY, @@ -363,7 +363,7 @@ vkDestroyFence(vulkan.device, fence, nil) vkDestroyCommandPool(vulkan.device, commandBufferPool, nil) -template withStagingBuffer*[T: (VkBuffer, uint64) | (VkImage, uint32, uint32)]( +template withStagingBuffer*[T: (VkBuffer, uint64) | (VkImage, uint32, uint32, uint32)]( target: T, bufferSize: uint64, dataPointer, body: untyped ): untyped = var `dataPointer` {.inject.}: pointer @@ -419,7 +419,7 @@ regionCount = 1, pRegions = addr(copyRegion), ) - elif T is (VkImage, uint32, uint32): + elif T is (VkImage, uint32, uint32, uint32): let region = VkBufferImageCopy( bufferOffset: 0, bufferRowLength: 0, @@ -428,7 +428,7 @@ aspectMask: toBits [VK_IMAGE_ASPECT_COLOR_BIT], mipLevel: 0, baseArrayLayer: 0, - layerCount: 1, + layerCount: target[3], ), imageOffset: VkOffset3D(x: 0, y: 0, z: 0), imageExtent: VkExtent3D(width: target[1], height: target[2], depth: 1), diff -r 966032c7a3aa -r d5a6f69dc855 tests/resources/default/art1.png Binary file tests/resources/default/art1.png has changed diff -r 966032c7a3aa -r d5a6f69dc855 tests/test_rendering.nim --- a/tests/test_rendering.nim Thu Aug 22 18:32:21 2024 +0700 +++ b/tests/test_rendering.nim Thu Aug 22 22:18:33 2024 +0700 @@ -666,6 +666,86 @@ destroyPipeline(pipeline4) destroyRenderData(renderdata) +proc test_08_texture_array(time: float32) = + var renderdata = initRenderData() + + type + Uniforms = object + textures: ImageArray[BGRA] + + Shader = object + position {.VertexAttribute.}: Vec3f + uv {.VertexAttribute.}: Vec2f + fragmentUv {.Pass.}: Vec2f + outColor {.ShaderOutput.}: Vec4f + descriptorSets {.DescriptorSet: 0.}: Uniforms + # code + vertexCode: string = + """ +void main() { + fragmentUv = uv; + gl_Position = vec4(position, 1); +}""" + fragmentCode: string = + """ +void main() { + vec4 col1 = texture(textures, vec3(fragmentUv, 0)); + vec4 col2 = texture(textures, vec3(fragmentUv, 1)); + float w = length(fragmentUv * 2 - 1) / 1.41421; + outColor = (1 - w) * col1 + w * col2; +}""" + + Quad = object + position: GPUArray[Vec3f, VertexBuffer] + uv: GPUArray[Vec2f, VertexBuffer] + + var mesh = Quad( + position: asGPUArray( + [ + vec3(-0.8, -0.5), + vec3(-0.8, 0.5), + vec3(0.8, 0.5), + vec3(0.8, 0.5), + vec3(0.8, -0.5), + vec3(-0.8, -0.5), + ], + VertexBuffer, + ), + uv: asGPUArray( + [vec2(0, 1), vec2(0, 0), vec2(1, 0), vec2(1, 0), vec2(1, 1), vec2(0, 1)], + VertexBuffer, + ), + ) + assignBuffers(renderdata, mesh) + renderdata.flushAllMemory() + + var pipeline = createPipeline[Shader](renderPass = vulkan.swapchain.renderPass) + var uniforms1 = asDescriptorSetData( + Uniforms(textures: loadImageArray[BGRA](["art.png", "art1.png"])) + ) + uploadImages(renderdata, uniforms1) + initDescriptorSet(renderdata, pipeline.descriptorSetLayouts[0], uniforms1) + + var start = getMonoTime() + while ((getMonoTime() - start).inMilliseconds().int / 1000) < time: + withNextFrame(framebuffer, commandbuffer): + withRenderPass( + vulkan.swapchain.renderPass, + framebuffer, + commandbuffer, + vulkan.swapchain.width, + vulkan.swapchain.height, + vec4(0, 0, 0, 0), + ): + withPipeline(commandbuffer, pipeline): + bindDescriptorSet(commandbuffer, uniforms1, 0, pipeline) + render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = mesh) + + # cleanup + checkVkResult vkDeviceWaitIdle(vulkan.device) + destroyPipeline(pipeline) + destroyRenderData(renderdata) + proc test_07_png_texture(time: float32) = var renderdata = initRenderData() @@ -741,7 +821,7 @@ destroyPipeline(pipeline) destroyRenderData(renderdata) -proc test_08_triangle_2pass( +proc test_09_triangle_2pass( time: float32, depthBuffer: bool, samples: VkSampleCountFlagBits ) = var (offscreenRP, presentRP) = @@ -952,32 +1032,33 @@ setupSwapchain(renderpass = renderpass) # tests a simple triangle with minimalistic shader and vertex format - test_01_triangle(time) + # test_01_triangle(time) # tests instanced triangles and quads, mixing meshes and instances - test_02_triangle_quad_instanced(time) + # test_02_triangle_quad_instanced(time) # teste descriptor sets - test_03_simple_descriptorset(time) + # test_03_simple_descriptorset(time) # tests multiple descriptor sets and arrays - test_04_multiple_descriptorsets(time) + # test_04_multiple_descriptorsets(time) # rotating cube - test_05_cube(time) + # test_05_cube(time) # different draw modes (lines, points, and topologies) - test_06_different_draw_modes(time) + # test_06_different_draw_modes(time) - # load PNG texture - test_07_png_texture(time) + # test_07_png_texture(time) + + test_08_texture_array(time) checkVkResult vkDeviceWaitIdle(vulkan.device) destroyRenderPass(renderpass) clearSwapchain() # test multiple render passes - for i, (depthBuffer, samples) in renderPasses: - test_08_triangle_2pass(time, depthBuffer, samples) + # for i, (depthBuffer, samples) in renderPasses: + # test_09_triangle_2pass(time, depthBuffer, samples) destroyVulkan()