Implementing a KTX loader

I’m interested in using the KTX format, primarily because of the support for Basis compression. However, I am finding the documentation to be a bit confusing. It’s hard to tell what documentation is referring to the old version 1 format and what is for version 2. I started using the ktxTexture2 data structure with the ktxTexture2_CreateFromMemory command, but the ktxTexture2 structure is not compatible with any of the ktxTexture commands. I don’t know how to delete a ktxTexture2 object, how to get image data out of it, and all the documentation is either uploading data directly to Vulkan or referring to the old version 1 format. Where is the secret hidden documentation for what I ssek?

This is what I’ve got so far:

	bool KTXTextureLoader::Reload(shared_ptr<Stream> stream, shared_ptr<Object> o, const LoadFlags flags)
	{
		auto texture = o->As<Texture>();
		auto pixmap = o->As<Pixmap>();

		ktxTexture2* ktx = NULL;
		KTX_error_code result;
		ktx_size_t offset;
		ktx_uint8_t* image;
		ktx_uint32_t level, layer, faceSlice;

		shared_ptr<Buffer> data;
		auto bstream = stream->As<BufferStream>();
		if (bstream)
		{
			data = bstream->data;
		}
		else
		{
			data = CreateBuffer(stream->GetSize() - stream->GetPosition());
			stream->Read(data->Data(), data->GetSize());
		}

		result = ktxTexture2_CreateFromMemory((const ktx_uint8_t*)data->Data(), data->GetSize(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktx);
		if (result != KTX_error_code::KTX_SUCCESS) return false;

		//Super decompression
		if (ktx->supercompressionScheme == KTX_SUPERCOMPRESSION_BASIS)
		{
			if (ktxTexture2_NeedsTranscoding(ktx))
			{
				ktx_transcode_flags tflags = 0;
				ktx_transcode_fmt_e tfmt;
				if (pixmap)
				{
					tfmt = KTX_TTF_RGBA32;
				}
				else
				{
					switch (ktx->vkFormat)
					{
					case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
					case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
					case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
					case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
						tfmt = KTX_TTF_BC1_RGB;
						break;
					case VK_FORMAT_BC3_SRGB_BLOCK:
					case VK_FORMAT_BC3_UNORM_BLOCK:
						tfmt = KTX_TTF_BC3_RGBA;
						break;
					case VK_FORMAT_R8G8B8A8_UNORM:
					case VK_FORMAT_R8G8B8A8_SNORM:
					case VK_FORMAT_R8G8B8A8_UINT:
						tfmt = KTX_TTF_RGBA32;
						break;
					default:
						tfmt = KTX_TTF_BC7_RGBA;
						break;
					}
				}
				if (ktxTexture2_TranscodeBasis(ktx, tfmt, tflags) != KTX_SUCCESS)
				{
					return false;
				}
			}
		}

		if (texture)
		{
			texture->m_size.x = ktx->baseWidth;
			texture->m_size.y = ktx->baseHeight;
			texture->m_size.z = ktx->baseDepth;
			if (ktx->isCubemap) texture->type = TEXTURE_CUBE;
			if (ktx->baseDepth > 1) texture->type = TEXTURE_3D;
			texture->format = TextureFormat(ktx->vkFormat);
		}

		for (int n = 0; n < ktx->numLevels; ++n)
		{
			for (int face = 0; face < ktx->numFaces; ++face)
			{
				ktxtextre2
			}
		}

		return true;		
	}

That seems pretty clear from the KTX docs: ktxTexture2::Destroy.

ktx_GetData

It is neither secret nor hidden. I found it in 3 different ways: I did a GitHub search for “Khronos KTX”, which linked to it. I went to the Khronos KTX page, which linked to it. And I did a general internet search for “KTX library documentation”, and it was the first hit.

The quality of the documentation is pretty poor; it took a while to find out that ktxTexture is actually a base class.

1 Like

This is very strange. I am using libktx 4.0.0 from this page:

There is no ktxTexture2_Destroy() command, and the ktxTexture() commands do not accept a ktxTexture2 object.

I am guessing maybe the ktXTexture2 can be cast to a ktxTexture object, even though it is not derived from that class?

Okay, here is a very basic working loader:

	bool KTXTextureLoader::Reload(shared_ptr<Stream> stream, shared_ptr<Object> o, const LoadFlags flags)
	{
		auto texture = o->As<Texture>();
		auto pixmap = o->As<Pixmap>();

		ktxTexture2* ktx = NULL;
		KTX_error_code result;
		ktx_size_t offset;
		ktx_uint8_t* image;
		ktx_uint32_t level, layer, faceSlice;

		shared_ptr<Buffer> data;
		auto bstream = stream->As<BufferStream>();
		if (bstream)
		{
			data = bstream->data;
		}
		else
		{
			data = CreateBuffer(stream->GetSize() - stream->GetPosition());
			stream->Read(data->Data(), data->GetSize());
		}

		result = ktxTexture2_CreateFromMemory((const ktx_uint8_t*)data->Data(), data->GetSize(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktx);
		if (result != KTX_error_code::KTX_SUCCESS) return false;

		//Super decompression
		if (ktx->supercompressionScheme == KTX_SUPERCOMPRESSION_BASIS)
		{
			if (ktxTexture2_NeedsTranscoding(ktx))
			{
				ktx_transcode_flags tflags = 0;
				ktx_transcode_fmt_e tfmt;
				if (pixmap)
				{
					tfmt = KTX_TTF_RGBA32;
				}
				else
				{
					switch (ktx->vkFormat)
					{
					case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
					case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
					case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
					case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
						tfmt = KTX_TTF_BC1_RGB;
						break;
					case VK_FORMAT_BC3_SRGB_BLOCK:
					case VK_FORMAT_BC3_UNORM_BLOCK:
						tfmt = KTX_TTF_BC3_RGBA;
						break;
					case VK_FORMAT_R8G8B8A8_SRGB:
					case VK_FORMAT_R8G8B8A8_UNORM:
					case VK_FORMAT_R8G8B8A8_SNORM:
					case VK_FORMAT_R8G8B8A8_UINT:
						tfmt = KTX_TTF_RGBA32;
						break;
					default:
						tfmt = KTX_TTF_BC7_RGBA;
						break;
					}
				}
				if (ktxTexture2_TranscodeBasis(ktx, tfmt, tflags) != KTX_SUCCESS)
				{
					ktxTexture_Destroy((ktxTexture*)ktx);
					return false;
				}
			}
		}

		if (texture)
		{
			texture->m_size.x = ktx->baseWidth;
			texture->m_size.y = ktx->baseHeight;
			texture->m_size.z = ktx->baseDepth;
			if (ktx->isCubemap) texture->type = TEXTURE_CUBE;
			if (ktx->baseDepth > 1) texture->type = TEXTURE_3D;
			texture->format = TextureFormat(ktx->vkFormat);
		}

		for (int n = 0; n < ktx->numLevels; ++n)
		{
			for (int face = 0; face < ktx->numFaces; ++face)
			{
				pixmap->m_size.x = ktx->baseWidth;
				pixmap->m_size.y = ktx->baseHeight;
				pixmap->m_blocks = pixmap->size;
				pixmap->m_blocksize = 1;
				pixmap->m_format = TEXTURE_RGBA;
				pixmap->m_pixels = CreateBuffer(pixmap->size.x * pixmap->size.y * 4);
				ktx_uint64_t offset;
				if (ktx->vtbl->GetImageOffset((ktxTexture*)ktx, n, 0, face, &offset) != ktx_error_code_e::KTX_SUCCESS)
				{
					ktxTexture_Destroy((ktxTexture*)ktx);
					return false;
				}
				memcpy(pixmap->pixels->Data(), ktx->pData + offset, pixmap->pixels->GetSize());
				ktxTexture_Destroy((ktxTexture*)ktx);
				return true;
			}
		}

		ktxTexture_Destroy((ktxTexture*)ktx);
		return true;		
	}
};