{.deadCodeElim: on.} {.experimental: "codeReordering".} import ktxtypes , glformat , math , bitops , ../vk/vulkan , ../vk/[ vulkan_utils , vkTypes , vulkan_record ] , streams , bitops , ../drawable/texture , ../utils/lets type KtxInfo = object endianness*: uint32 glType*: uint32 glTypeSize*: uint32 glFormat*: uint32 glInternalFormat*: uint32 glBaseInternalFormat*: uint32 pixelWidth*: uint32 pixelHeight*: uint32 pixelDepth*: uint32 numberOfArrayElements*: uint32 numberOfFaces*: uint32 numberOfMipmapLevels*: uint32 bytesOfKeyValueData*: uint32 const identBytes: array[12, uint8] = [0xAB.uint8, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A] #from std/system/io import open proc ktxSwapEndian32*(pData32: var uint32, count: int) = for x in 0 ..< count: # *pData32++ = (x << 24) | ((x addr 0xFF00) << 8) | ((x addr 0xFF0000) >> 8) | (x >> 24) pData32 += 1 pdata32 = bitor( (x shl 24) , ( bitand(x, 0xFF00) shl 8) , ( bitand(x, 0xFF0000) shr 8) ).uint32 #Equivalent to (n * ceil(nbytes / n)) - nbytes proc padLen*(n: uint32, nBytes: uint32 ): uint32 = (n - 1) - (nBytes + ( n - 1 ) and (n-1) ).uint32 #Calculate bytes of of padding needed to reach KTX_GL_UNPACK_ALIGNMENT. proc padUnpackAlignLen*(rowBytes: uint32): uint32 = padLen(KTX_GL_UNPACK_ALIGNMENT, rowBytes) proc padRow*(rowBytes: var uint32) = var padding: uint32 = padUnpackAlignLen rowBytes rowBytes += padding proc aktxTextureImageSize*( texture: KtxTexture, level: uint32): csize_t = # THIS IS NOT CORRECT # my levels are not always right #, so my levelWidth/height are wrong var formatInfo: GlFormatSize = texture.formatInfo bc = BlockCount() blockSizeInBytes: uint32 rowBytes: uint32 levelWidth = (texture.baseWidth shr level) levelHeight = (texture.baseHeight shr level) bc.x = ceil(levelWidth.float / formatInfo.blockWidth.float).uint32 bc.y = ceil(levelHeight.float / formatInfo.blockHeight.float).uint32 bc.x = max(1.uint32, bc.x) bc.y = max(1.uint32, bc.y) blockSizeInBytes = (formatInfo.blockSizeInBits.int / 8).uint32 if bitand(formatInfo.flags, 2).bool: # 2 -> GL_FORMAT_SIZE_COMPRESSED_BIT = 0x00000002, assert texture.isCompressed return bc.x + bc.y * blockSizeInBytes else: assert formatInfo.blockDepth == 1 assert formatInfo.blockDepth == formatinfo.blockHeight assert formatInfo.blockWidth == formatInfo.blockHeight rowBytes = bc.x * blockSizeInBytes padRow rowBytes return rowBytes * bc.y proc aktxTextureLayerSize*( texture: KtxTexture, level: uint32): csize_t = #[ As there are no 3D cubemaps, the image's z block count will always be 1 for cubemaps and numFaces will always be 1 for 3D textures so the multiply is safe. 3D cubemaps, if they existed, would require imageSize * (blockCount.z + texture.numFaces) ]# # echo "aktxTextureLayerSize called GIS with level: ", level var formatInfo: GlFormatSize = texture.formatInfo blockCountZ: uint32 = max(1.uint32, (texture.baseDepth.int / formatInfo.blockDepth.int).uint32 shr level) #the trouble starts here imageSize: csize_t = aktxTextureImageSize(texture, level) layerSize: csize_t = imageSize * blockCountZ #echo "fi.blockdepth:: ", formatInfo.blockDepth return layerSize * texture.numFaces proc aktxTextureLevelSize*( texture: KtxTexture, level: uint32): csize_t = result = aktxTextureLayerSize(texture, level) * texture.numLayers proc aktxTextureDataSize*( texture: KtxTexture, levels: uint32): csize_t = var dataSize: csize_t for i in 0 ..< levels: # i IS_ corrrect here var levelSize = texture.aktxTextureLevelSize(i) #echo("levelSize: ", levelSize) dataSize += cast[csize_t](levelSize) dataSize proc aKtxTextureImageOffset*( texture: KtxTexture , level: uint32 , layer: uint32 , faceSlice: uint32 , pOffset: var csize_t ) = if texture.isNil: quit("ERROR: aKtxTextureImageOffset: Texture is nil") if level >= texture.numLevels: quit("ERROR: aKtxTextureImageOffset: level > texture.numLevels") if layer >= texture.numLayers: quit("ERROR: aKtxTextureImageOffset: layer > texture.numLevels") if texture.isCubeMap: var maxSlice = max(1.uint, (texture.baseDepth shr level)) if faceSlice >= maxSlice: quit("ERROR: aKtxTextureImageOffset: cubemap: faceSlize > maxSlice") #Get the size of the data up to the start of the indexed level. pOffSet = aktxTextureDataSize(texture, level) #All layers, faces & slices within a level are the same size. if layer != 0: var ls: csize_t = aktxTextureLayerSize(texture, level) pOffSet += layer * ls if faceSlice != 0: #echo "aKtxTextureImageOffset called GIS with level: ", level var imageSize: csize_t = aktxTextureImageSize(texture, level) pOffSet += faceSlice * imageSize # TODO: # 1. Right now we just assume Vkmrec.vkRec.queue usage for the copyQueue # But this is not flexible, so for the future, de-couple pls proc loadFromFile*( rec: Vulkan_Record , texture: Texture2D , texturePath: string = "/run/media/j/ZZZ/Dev/shapes/data/textures/font_sdf_rgba.ktx" , format: VkFormat = VK_FORMAT_R8G8B8A8_UNORM , imageUsageFlags: VkImageUsageFlags = VkImageUsageFlags 4 , imageLayout: VkImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL #, VkQueue copyQueue ) = var readIdent: array[12, uint8] info: KtxInfo p: seq[uint32] p2: seq[uint8] imageSize: uint32 kvd: seq[char] ktxTexture = KtxTexture( extraInfo: KtxExtraInfo() , formatInfo: GlFormatSize() , stream: StringStream() ) loadKTXFile(texturePath, ktxTexture) let fs = newFileStream "/run/media/j/ZZZ/Dev/shapes/data/textures/font_sdf_rgba.ktx" #1. Read the 12 byte identifier and make sure it's valid discard fs.readData(addr readIdent[0], 12) assert readIdent == identBytes #2. Parse the 13 uint32 info section into struct discard fs.readData(addr info, uint32.sizeof * 13) #handle checking spec-specifics in another fuction #3 parse key-value meta-data within the size of bytesOfKeyValueData kvd.setLen info.bytesOfKeyValueData discard fs.readData(addr kvd[0], info.bytesOfKeyValueData.int) #4 get image size & create texture buffer for mip in 0..info.numberOfMipmapLevels: p.setLen p.len + 1 p.add fs.readuint32 imageSize = p[mip] p2.setLen p2.len + imageSize.int for face in 0..info.numberOfFaces - 1: p2.setLen p2.len + 1 discard fs.readData(addr p2[0], imageSize.int) texture.width = 512 texture.height = 512 texture.mipLevels = 1 var #ktxTextureData: ptr uint8 = cast[ptr uint8](ktxTexture.pData) #isNil ktxTextureSize: csize_t = csize_t (p2.len * uint8.sizeof) formatProperties: VkFormatProperties useStaging: bool = true # false if linear tiling ever becomes useful? memReqs: VkMemoryRequirements #memAllocInfo = VkMemoryAllocateInfo() copyCmd: VkCommandBuffer #cmdBufAllocateInfo = VkCommandBufferAllocateInfo( commandPool: rec.commandPool) # Get device properties for the requested texture format vkGetPhysicalDeviceFormatProperties(rec.gpu.handle, format, addr formatProperties) # Only use linear tiling if requested (and supported by the device) # Support for linear tiling is mostly limited # so prefer to use optimal tiling instead # On most implementations linear tiling will only support a very # limited amount of formats and features (mip maps, cubemaps, arrays, etc.) # Use a separate command buffer for texture loading copyCmd = rec.vk_device.createCommandBuffers( rec.command_pool , amount = 1 , begin = true )[0] if useStaging: # Create a host-visible staging buffer that contains the raw image data var data: pointer offset: csize_t bufferCopyRegions: seq[VkBufferImageCopy] memAllocInfo = VkMemoryAllocateInfo(sType: VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO) stagingBuffer: VkBuffer stagingMemory: VkDeviceMemory # # This buffer is used as a transfer source for the buffer copy bufferCreateInfo = VkBufferCreateInfo( sType: VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO , size: VkDevicesize ktxTextureSize , usage: VkBufferUsageFlags VK_BUFFER_USAGE_TRANSFER_SRC_BIT , sharingMode : VK_SHARING_MODE_EXCLUSIVE ) discard vkCreateBuffer(rec.vk_device, addr bufferCreateInfo, nil, addr stagingBuffer) # Get memory requirements for the staging buffer (alignment, memory type bits) vkGetBufferMemoryRequirements(rec.vk_device, stagingBuffer, addr memReqs) memAllocInfo.allocationSize = memReqs.size # Get memory type index for a host visible buffer memAllocInfo.memoryTypeIndex = rec.gpu.memory_properties.getMemoryType(memReqs.memoryTypeBits, VkMemoryPropertyFlags bitor( ord VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, ord VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) discard vkAllocateMemory(rec.vk_device, addr memAllocInfo, nil, addr stagingMemory) discard vkBindBufferMemory(rec.vk_device, stagingBuffer, stagingMemory, vk0) # Copy texture data into staging buffer discard vkMapMemory(rec.logicalDevice, stagingMemory, vk0, memReqs.size, VkMemoryMapFlags 0, addr data) copymem(data, p2[0].addr, ktxTextureSize) vkUnmapMemory(rec.logicalDevice, stagingMemory) # Setup buffer copy regions for each mip level # echo texture.miplevels for i in 0 ..< texture.mipLevels: aKtxTextureImageOffset(ktxTexture, i.uint32, 0, 0, offset) var bufferCopyRegion = VkBufferImageCopy() bufferCopyRegion.imageSubresource.aspectMask = VkImageAspectFlags VK_IMAGE_ASPECT_COLOR_BIT bufferCopyRegion.imageSubresource.mipLevel = i bufferCopyRegion.imageSubresource.baseArrayLayer = 0 bufferCopyRegion.imageSubresource.layerCount = 1 bufferCopyRegion.imageExtent.width = max(1.uint, ktxTexture.baseWidth shr i).uint32 bufferCopyRegion.imageExtent.height = max(1.uint, ktxTexture.baseHeight shr i).uint32 bufferCopyRegion.imageExtent.depth = 1 bufferCopyRegion.bufferOffset = VKDeviceSize offset bufferCopyRegions.add bufferCopyRegion # Create optimal tiled target image var imageCreateInfo = VkImageCreateInfo() imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO imageCreateInfo.imageType = VK_IMAGE_TYPE_2D imageCreateInfo.format = format imageCreateInfo.mipLevels = texture.mipLevels imageCreateInfo.arrayLayers = 1 imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED imageCreateInfo.extent = VkExtent3d(width: texture.width, height: texture.height, depth: 1) imageCreateInfo.usage = VkImageUsageFlags bitor(imageUsageFlags.ord, VK_IMAGE_USAGE_TRANSFER_DST_BIT.ord) # Ensure that the TRANSFER_DST bit is set for staging #if not(imageCreateInfo.usage addr VK_IMAGE_USAGE_TRANSFER_DST_BIT)): #imageCreateInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT discard vkCreateImage(rec.logicalDevice, addr imageCreateInfo, nil, addr texture.image) vkGetImageMemoryRequirements(rec.logicalDevice, texture.image, addr memReqs) memAllocInfo.allocationSize = memReqs.size memAllocInfo.memoryTypeIndex = rec.gpu.memory_properties.getMemoryType(memReqs.memoryTypeBits, VkMemoryPropertyFlags VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) discard vkAllocateMemory(rec.logicalDevice, addr memAllocInfo, nil, addr texture.deviceMemory) discard vkBindImageMemory(rec.logicalDevice, texture.image, texture.deviceMemory, vk0) var subresourceRange = VkImageSubresourceRange() subresourceRange.aspectMask = VkImageAspectFlags VK_IMAGE_ASPECT_COLOR_BIT subresourceRange.baseMipLevel = 0 subresourceRange.levelCount = texture.mipLevels subresourceRange.layerCount = 1 # Image barrier for optimal image (target) # Optimal image will be used as destination for the copy setImageLayout( copyCmd , texture.image , VK_IMAGE_LAYOUT_UNDEFINED , VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL , subresourceRange , srcStageMask = VkPipelineStageFlags VK_PIPELINE_STAGE_TRANSFER_BIT , dstStageMask = VkPipelineStageFlags VK_PIPELINE_STAGE_TRANSFER_BIT ) # Copy mip levels from staging buffer vkCmdCopyBufferToImage( copyCmd , stagingBuffer , texture.image , VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL , bufferCopyRegions.len.uint32 , addr bufferCopyRegions[0] ) # Change texture image layout to shader read after all mip levels have been copied texture.imageLayout = imageLayout setImageLayout( copyCmd , texture.image , VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL , imageLayout , subresourceRange , srcStageMask = VkPipelineStageFlags VK_PIPELINE_STAGE_TRANSFER_BIT , dstStageMask = VkPipelineStageFlags VK_PIPELINE_STAGE_VERTEX_INPUT_BIT.ord or VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT.ord ) rec.vk_device.flushCommandBuffer(rec.queue , rec.command_pool , copyCmd ) # Clean up staging resources vkFreeMemory(rec.logicalDevice, stagingMemory, nil) vkDestroyBuffer(rec.logicalDevice, stagingBuffer, nil) # Create a default sampler var samplerCreateInfo = VkSamplerCreateInfo( sType: VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO , magFilter: VK_FILTER_LINEAR , minFilter: VK_FILTER_LINEAR , mipmapMode: VK_SAMPLER_MIPMAP_MODE_LINEAR , addressModeU: VK_SAMPLER_ADDRESS_MODE_REPEAT , addressModeV: VK_SAMPLER_ADDRESS_MODE_REPEAT , addressModeW: VK_SAMPLER_ADDRESS_MODE_REPEAT , mipLodBias: 0.0 , compareOp: VK_COMPARE_OP_NEVER , minLod: 0.0 , anisotropyEnable: vkfalse , maxAnisotropy: 1.0 ) # # Max level-of-detail should match mip level count samplerCreateInfo.maxLod = texture.mipLevels.float32 # Only enable anisotropic filtering if enabled on the device #samplerCreateInfo.maxAnisotropy = if (rec.deviceFeatures.samplerAnisotropy).bool: rec.deviceProperties.limits.maxSamplerAnisotropy else: 1.0f #samplerCreateInfo.anisotropyEnable = if (rec.deviceFeatures.samplerAnisotropy).bool: vktrue else: vkfalse samplerCreateInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE discard vkCreateSampler(rec.logicalDevice, addr samplerCreateInfo, nil, addr texture.sampler) # Create image view # Textures are not directly accessed by the shaders and # are abstracted by image views containing additional # information and sub resource ranges var viewCreateInfo = VkImageViewCreateInfo() viewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO viewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D viewCreateInfo.format = format viewCreateInfo.components = VkComponentMapping( r: VK_COMPONENT_SWIZZLE_R , g: VK_COMPONENT_SWIZZLE_G , b: VK_COMPONENT_SWIZZLE_B , a: VK_COMPONENT_SWIZZLE_A ) viewCreateInfo.subresourceRange = VkImageSubresourceRange( aspectMask: VkImageAspectFlags VK_IMAGE_ASPECT_COLOR_BIT , baseMipLevel: 0 , levelCount: 1 , baseArrayLayer: 0 , layerCount: 1 ) # Linear tiling usually won't support mip maps # Only set mip map count if optimal tiling is used viewCreateInfo.subresourceRange.levelCount = texture.mipLevels viewCreateInfo.image = texture.image discard vkCreateImageView(rec.logicalDevice, addr viewCreateInfo, nil, addr texture.view) # # Update descriptor image info member that can be used for setting up descriptor sets #updateDescriptor() texture.descriptor.sampler = texture.sampler texture.descriptor.imageView = texture.view texture.descriptor.imageLayout = texture.imageLayout proc checkKtxHeader( pHeader: ptr KTX_header , pSuppInfo: var KtxExtraInfo) = var # KTX_IDENTIFIER_REF ident_ref: array[12, uint8] = [0xAB.uint8, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A] max_dim: uint32 #assert(pHeader != nil and pSuppInfo != nil) # Compare identifier, is this a KTX file? if not equalMem(addr pHeader.identifier, addr ident_ref, 12): quit("checkKTXHeader: KTX_UNKNOWN_FILE_FORMAT") if (pHeader.endianness == endianRefRev): echo "indian hit" # Convert endianness of pHeader fields. ktxSwapEndian32( pHeader.glType, 12) if (pHeader.glTypeSize != 1) and (pHeader.glTypeSize != 2) and (pHeader.glTypeSize != 4): # Only 8-, 16-, and 32-bit types supported so far. quit("KTX_FILE_DATA_ERROR") elif pHeader.endianness != 67305985: # . KTX_ENDIAN_REF (0x04030201) quit("KTX_FILE_DATA_ERROR[2]") # Check glType and glFormat pSuppInfo.compressed = 0 if (pHeader.glType == 0 or pHeader.glFormat == 0): # either both or none of glType, glFormat must be zero if (pHeader.glType + pHeader.glFormat != 0): quit("KTX_FILE_DATA_ERROR[3]") pSuppInfo.compressed = 1 # glInternalFormat is either unsized (which is no longer and should # never have been supported by libktx) or glFormat is sized. if (pHeader.glFormat == pHeader.glInternalformat): quit("KTX_FILE_DATA_ERROR[4]") # Check texture dimensions. KTX files can store 8 types of textures: # 1D, 2D, 3D, cube, and array variants of these. There is currently # no GL extension for 3D array textures. # texture must have width # texture must have height if it has depth if ((pHeader.pixelWidth == 0) or (pHeader.pixelDepth > 0 and pHeader.pixelHeight == 0)): quit("KTX_FILE_DATA_ERROR[5]") if (pHeader.pixelDepth > 0): # No 3D array textures yet. if (pHeader.numberOfArrayElements > 0): quit("KTX_UNSUPPORTED_TEXTURE_TYPE") pSuppInfo.textureDimension = 3 elif (pHeader.pixelHeight > 0): pSuppInfo.textureDimension = 2 else: pSuppInfo.textureDimension = 1 if (pHeader.numberOfFaces == 6): # cube map needs 2D faces if (pSuppInfo.textureDimension != 2): quit("KTX_FILE_DATA_ERROR[6]") # numberOfFaces must be either 1 or 6 elif (pHeader.numberOfFaces != 1): quit("KTX_FILE_DATA_ERROR[7]") # Check number of mipmap levels if (pHeader.numberOfMipmapLevels == 0): pSuppInfo.generateMipmaps = 1 pHeader.numberOfMipmapLevels = 1 else: pSuppInfo.generateMipmaps = 0 # This test works for arrays too because height or depth will be 0. max_dim = max(max(pHeader.pixelWidth, pHeader.pixelHeight), pHeader.pixelDepth) # Can't have more mip levels than 1 + log2(max(width, height, depth)) if (max_dim < (1.uint32 shl (pHeader.numberOfMipmapLevels - 1))): quit("KTX_FILE_DATA_ERROR: mip levels > 1 + log2(max(width, height, depth)) ") proc loadKTXFile*(filename: string , target: var KtxTexture , createFlags: uint32 = 1 # KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT #[, newTex: KtxTexture]#) = var header = KtxHeader() supp = KtxExtraInfo() #size: csize_t fs = newFileStream "/run/media/j/ZZZ/Dev/shapes/data/textures/font_sdf_rgba.ktx" discard fs.readData(cast[pointer](header), 64) #KTX_HEADER_SIZE 64 ) checkKtxHeader(addr header, supp ) target.glFormat = header.glFormat target.glInternalformat = header.glInternalformat target.glType = header.glType aformatSize(GLFormats target.glInternalformat, target.formatInfo) target.glBaseInternalformat = header.glBaseInternalformat target.numDimensions = supp.textureDimension target.baseWidth = header.pixelWidth assert(supp.textureDimension > 0 and supp.textureDimension < 4) case supp.textureDimension: of 1: # super->baseHeight = super->baseDepth = 1; (are they <=> ??) target.baseHeight = 1 target.baseDepth = 1 of 2: target.baseHeight = header.pixelHeight target.baseDepth = 1 of 3: target.baseHeight = header.pixelHeight target.baseDepth = header.pixelDepth else: echo "[!] Default case HIT for: `case supp.textureDimension`" if header.numberOfArrayElements > 0: target.numLayers = header.numberOfArrayElements target.isArray = true else: target.numLayers = 1 target.isArray = false target.numFaces = header.numberOfFaces if header.numberOfFaces == 6: target.isCubemap = true else: target.isCubemap = false target.numLevels = header.numberOfMipmapLevels target.isCompressed = supp.compressed.bool target.generateMipmaps = supp.generateMipmaps.bool if header.endianness == endianRefRev: target.needSwap = true target.glTypeSize = header.glTypeSize