题图是B站一个up 獭可 看着可爱现在这借用一下OwO
这是一篇简单的小记录---集成对ktx文件的支持到我在写的渲染器中,期间遇到的一点小挫折。
Khronos texture,就是ktx。
ktx是一种纹理容器,他是内部可以是不同的压缩格式。目前比较常用的是ASTC纹理的ktx文件。
首先最直观的感受就是,转化为了ktx后文件变小了,而且清晰度也差不多。
当然最重要的事情是,这种纹理的压缩是能在GPU内实时解算的,意味着我们能直接把数据送到GPU里当texture采样,作为对比,如果使用jpeg格式,我们可能先需要把其用库(stbi?)解压成rgba8888格式,然后才能送进显存中采样。
更好的一点是:你甚至可以把纹理数组、mipmap、CubeMap一股脑的送进一个KTX文件,然后一次性加载出来送到GPU里去,非常方便!
这种格式的加载是需要GPU支持的,但好消息是:这东西支持十分广泛(或者说采用了ASTC纹理的KTX2支持十分广泛)。
对于Android,85%使用GooglePlay的手机都支持ASTC纹理(官方Android指南有写道),对于Apple A8以上的芯片可以完美支持ASTC(在这之前的通用格式是PVRTC,当然之后也通用),对于PC,openGL和Vulkan支持十分的好....DX有自己的DDS就是了
所以为了跨平台性,我选择KTX + ASTC!
khronos官方的库 LibKTX提供了方便快捷的加载方式,可以一次性的把所有数据上传到显存中,然后返回给你一个VkImage对象!
真是太方便辣......才怪
在这个库的设计之初竟然没考虑到用外部的Vulkan显存分配器,这也意味着你一旦使用了他的加载方式,就得为即将迎来的显存碎片化,OOM做好准备....
直到23年在社区的推动下外部分配器的嵌入接口才完成了...
mantainer:我和VMA不熟 (真-原话)
于是巨他妈难用的接口诞生了,首先你先得创建一个叫vdi的东西,这是个vulkan设备的大打包盒,放着物理设备,逻辑设备,队列...
然后你得手动给其设置回调函数,把VMA Allocation, VMAMap, VMAUnMap等等函数再狠狠包装一遍....然后你并不能调用VMA CreateImage那套,得用最传统的VMA Allocation,也是VMA官方最不建议用的那种...
并不是我用错了,详情可以去查看libKtx-VKLoadTests这个官方示例,非常,非常挫,我一开始尝试把这套移植到我的框架上,然后发现...这东西直接击穿了我现有的Buffer/Texture管理,并且十分十分不好写。
当然我们可以绕过他的实现手动把数据写到显存里去....有点复杂就是
经过我不懈的寻找下....终于发现SaschaWillems的Vulkan exsample里确实有使用到KTX加载
具体的正确代码去看他那的就好了..我这贴一下我的实现,如果有看不懂的地方就是我的封装了....脑内自动翻译下就好
layerCount = ktxTexture->arrLen;
......
if (!isCube) {
for (uint32_t layer = 0; layer < std::min(layerCount,textureHandler->arrayLayer()); layer++)
{
for (uint32_t level = 0; level < std::min(mipLevels, textureHandler->mipLevelCount()); level++)
{
ktx_size_t offset;
KTX_error_code result = ktxTexture_GetImageOffset(texture, level, layer, 0, &offset);
if (result != KTX_SUCCESS) {
Logger::instance()->log("KTX Loader", lvl::err, "Get image offset error -> {}. || in arraylayer {}, mipmapLvl {}", ktxErrorString(result), layer, level);
return ;
}
BufferManager::BufferToImage bufferCopyInfo{};
bufferCopyInfo.baseArrayer = layer;
bufferCopyInfo.layerCount = 1;
bufferCopyInfo.width = std::max(1u, texture->baseWidth >> level);
bufferCopyInfo.height = std::max(1u, texture->baseHeight >> level);
bufferCopyInfo.depth = 1;
bufferCopyInfo.bufferOffset = offset;
bufferCopyInfo.mipmapLevel = level;
mCopyInfo.push_back(bufferCopyInfo);
}
}
}
else {
uint32_t offset = 0;
if (info.arrayLen > 1) {
Logger::instance()->log("KTX Loader", lvl::err, "Currently cube map array is not supported, The behaviours followed is unknown.");
return ;
}
for (uint32_t face = 0; face < 6; face++)
{
for (uint32_t level = 0; level < mipLevels; level++)
{
// Calculate offset into staging buffer for the current mip level and face
ktx_size_t offset;
KTX_error_code ret = ktxTexture_GetImageOffset(texture, level, 0, face, &offset);
if (ret != KTX_SUCCESS) {
Logger::instance()->log("KTX Loader", lvl::err, "Get image offset error -> {}. || in face {}, mipmapLvl {}", ktxErrorString(ret), face, level);
return ;
}
BufferManager::BufferToImage bufferCopyInfo{};
bufferCopyInfo.baseArrayer = face;
bufferCopyInfo.layerCount = 1;
bufferCopyInfo.width = std::max(1u, texture->baseWidth >> level);
bufferCopyInfo.height = std::max(1u, texture->baseHeight >> level);
bufferCopyInfo.depth = 1;
bufferCopyInfo.bufferOffset = offset;
bufferCopyInfo.mipmapLevel = level;
mCopyInfo.push_back(bufferCopyInfo);
}
}
}
auto totoalDataSize = texture->dataSize;
auto textureDataPtr = ktxTexture_GetData(texture);
auto bufferHandler = this->mBufferManager->requestBuffer(Buffer::StageBuffer, totoalDataSize, false, true, textureDataPtr, totoalDataSize);
this->mBufferManager->copyBufferToImage(bufferHandler, textureHandler);
//To you: vkPipelineBarrier -> imageLayoutTransferDst -> vkBufferToImage -> vkPipelineBarrier -> imageLayoutShaderSAmpledReadOnly
简单来说:就是对每个CubeMapface(对于普通图像就是只有一面)的每个数组成员的每个mipmap等级,提取其在ktx data中的offset,然后复制到显存中....
那为什么我会称其为挫折呢...很容易不是
其实我一开始看的是libktx内部的实现,只能说我看不懂....还以为是什么不同的mipmap等级之间还会有会随着elementSize变的padding...同时还在想---ImageMipmap/layer的内存位置不会也有约定吧(后来想想copyImage的subResource应该已经把这些指定好了)
真的在看到libktx的实现的时候我是害怕了,然后疯狂寻求借助官方封装....反正很神奇
目前我司的自研引擎就是用的ktx(貌似ASTC?)作为Android平台上的贴图格式,这东西确实好,就是转换时间特别特别慢。
突然想想国内没几个经过商业项目验证的全平台自研引擎...我司算一个(虽然效果挺搓的但毕竟一开始是针对手机平台开发的...能理解,但理解的不多),里面的很多模块处于一种尚未非常屎山的地步,实现还是能看着学习一些的(比如我最近在看字体渲染
所以做这种自研引擎的引擎开发还是挺有趣的(估计以后也不会有这种机会了)
...只要别让我写UI对接客户端就好QwQ