implement support for loading textures from uncompressed TGA files

This commit is contained in:
Zack Buhman 2026-04-21 21:07:27 -05:00
parent 65455ad471
commit 352958f031
8 changed files with 323 additions and 98 deletions

View File

@ -25,7 +25,7 @@ CFLAGS += -I./data
CFLAGS += -I../SDL3-dist/include CFLAGS += -I../SDL3-dist/include
CFLAGS += -fpic CFLAGS += -fpic
#FLAGS += -fstack-protector -fstack-protector-all -fno-omit-frame-pointer -fsanitize=address FLAGS += -fstack-protector -fstack-protector-all -fno-omit-frame-pointer -fsanitize=address
LDFLAGS += -lm LDFLAGS += -lm
ifeq ($(UNAME),Linux) ifeq ($(UNAME),Linux)
@ -42,6 +42,7 @@ OBJS = \
src/file.o \ src/file.o \
src/pack.o \ src/pack.o \
src/dds/validate.o \ src/dds/validate.o \
src/tga/tga.o \
src/vulkan_helper.o \ src/vulkan_helper.o \
src/collada/scene/vulkan.o \ src/collada/scene/vulkan.o \
src/collada/scene.o \ src/collada/scene.o \

View File

@ -10,4 +10,18 @@ struct DDS_FILE {
namespace dds { namespace dds {
DDS_FILE const * validate(void const * data, uint32_t size, uint32_t ** out_offsets, void ** out_data, uint32_t * out_size); DDS_FILE const * validate(void const * data, uint32_t size, uint32_t ** out_offsets, void ** out_data, uint32_t * out_size);
static inline bool isDDSExtension(const char * filename, size_t length)
{
char a = filename[length - 4];
char b = filename[length - 3];
char c = filename[length - 2];
char d = filename[length - 1];
return
(a == '.') &&
(b == 'd' || b == 'D') &&
(c == 'd' || c == 'D') &&
(d == 's' || d == 'S');
}
} }

43
include/tga/tga.h Normal file
View File

@ -0,0 +1,43 @@
#include <stdint.h>
#define PACKED __attribute__((packed))
namespace tga {
struct PACKED header {
uint8_t idLength;
uint8_t colorMapType;
uint8_t imageTypeCode;
struct PACKED {
uint16_t origin;
uint16_t length;
uint8_t depth;
} colorMap;
struct PACKED {
uint16_t xOrigin;
uint16_t yOrigin;
uint16_t width;
uint16_t height;
uint8_t bitsPerPixel;
} image;
uint8_t descriptor;
};
static_assert((sizeof (header)) == 18);
header const * validate(void const * data, uint32_t size, void ** outData, uint32_t * outSize);
static inline bool isTGAExtension(const char * filename, size_t length)
{
char a = filename[length - 4];
char b = filename[length - 3];
char c = filename[length - 2];
char d = filename[length - 1];
return
(a == '.') &&
(b == 't' || b == 'T') &&
(c == 'g' || c == 'G') &&
(d == 'a' || d == 'A');
}
}
#undef PACKED

View File

@ -62,3 +62,14 @@ void createImageFromFilenameDDS(VkDevice device,
VkImage * outImage, VkImage * outImage,
VkDeviceMemory * outMemory, VkDeviceMemory * outMemory,
VkImageView * outImageView); VkImageView * outImageView);
void createImageFromFilenameTGA(VkDevice device,
VkQueue queue,
VkCommandBuffer commandBuffer,
VkFence fence,
VkDeviceSize nonCoherentAtomSize,
VkPhysicalDeviceMemoryProperties const & physicalDeviceMemoryProperties,
char const * const filename,
VkImage * outImage,
VkDeviceMemory * outMemory,
VkImageView * outImageView);

View File

@ -13,6 +13,7 @@
#include "vulkan_helper.h" #include "vulkan_helper.h"
#include "dds/validate.h" #include "dds/validate.h"
#include "dds/vulkan.h" #include "dds/vulkan.h"
#include "tga/tga.h"
#include "check.h" #include "check.h"
#include "new.h" #include "new.h"
@ -670,16 +671,34 @@ namespace collada::scene {
images = NewM<Image>(descriptor->images_count); images = NewM<Image>(descriptor->images_count);
for (int i = 0; i < descriptor->images_count; i++) { for (int i = 0; i < descriptor->images_count; i++) {
char const * filename = descriptor->images[i]->uri;
size_t length = strlen(filename);
if (dds::isDDSExtension(filename, length)) {
createImageFromFilenameDDS(device, createImageFromFilenameDDS(device,
queue, queue,
commandBuffer, commandBuffer,
fence, fence,
physicalDeviceProperties.limits.nonCoherentAtomSize, physicalDeviceProperties.limits.nonCoherentAtomSize,
physicalDeviceMemoryProperties, physicalDeviceMemoryProperties,
descriptor->images[i]->uri, filename,
&images[i].image, &images[i].image,
&images[i].memory, &images[i].memory,
&images[i].imageView); &images[i].imageView);
} else if (tga::isTGAExtension(filename, length)) {
createImageFromFilenameTGA(device,
queue,
commandBuffer,
fence,
physicalDeviceProperties.limits.nonCoherentAtomSize,
physicalDeviceMemoryProperties,
filename,
&images[i].image,
&images[i].memory,
&images[i].imageView);
} else {
fprintf(stderr, "filename: %s\n", filename);
ASSERT(false, "invalid image filename extension");
}
} }
// cleanup // cleanup

25
src/tga/tga.cpp Normal file
View File

@ -0,0 +1,25 @@
#include <assert.h>
#include <stddef.h>
#include "tga/tga.h"
namespace tga {
header const * validate(void const * data, uint32_t size, void ** outData, uint32_t * outSize)
{
header const * const tga = (tga::header const *)data;
assert(tga->colorMapType == 0);
assert(tga->imageTypeCode == 2);
assert(tga->image.xOrigin == 0);
assert(tga->image.yOrigin == 0);
assert(tga->image.bitsPerPixel == 32);
uint32_t bytesPerPixel = tga->image.bitsPerPixel / 8;
size_t imageOffset = (sizeof (header)) + tga->idLength;
*outData = (void *)(((size_t)data) + imageOffset);
*outSize = tga->image.width * tga->image.width * bytesPerPixel;
return tga;
}
}

View File

@ -13,6 +13,7 @@
#include "dds/validate.h" #include "dds/validate.h"
#include "dds/vulkan.h" #include "dds/vulkan.h"
#include "tga/tga.h"
#include "vulkan_helper.h" #include "vulkan_helper.h"
@ -115,85 +116,25 @@ VkDeviceSize allocateFromMemoryRequirements2(VkDevice device,
return memoryAllocateInfo.allocationSize; return memoryAllocateInfo.allocationSize;
} }
void createImageFromFilenameDDS(VkDevice device, // ddsFile->header.dwWidth
// ddsFile->header.dwHeight
// ddsFile->header.dwMipMapCount
// uint32_t * mipOffsets;
void textureTransfer(VkDevice device,
VkQueue queue, VkQueue queue,
VkCommandBuffer commandBuffer, VkCommandBuffer commandBuffer,
VkFence fence, VkFence fence,
VkDeviceSize nonCoherentAtomSize, VkDeviceSize nonCoherentAtomSize,
VkPhysicalDeviceMemoryProperties const & physicalDeviceMemoryProperties, VkPhysicalDeviceMemoryProperties const & physicalDeviceMemoryProperties,
char const * const filename, uint32_t imageDataSize,
VkImage * outImage, void * imageData,
VkDeviceMemory * outMemory, VkImage image,
VkImageView * outImageView) uint32_t width,
uint32_t height,
uint32_t levelCount,
uint32_t * levelOffsets)
{ {
uint32_t imageSize;
void const * imageStart = file::open(filename, &imageSize);
void * imageData;
uint32_t * mipOffsets;
uint32_t imageDataSize;
DDS_FILE const * ddsFile = dds::validate(imageStart, imageSize, &mipOffsets, &imageData, &imageDataSize);
VkFormat format = dds::dxgi_to_vulkan(ddsFile->header10.dxgiFormat);
// image
VkImage image;
VkImageCreateInfo imageCreateInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent = {
.width = ddsFile->header.dwWidth,
.height = ddsFile->header.dwHeight,
.depth = 1
},
.mipLevels = ddsFile->header.dwMipMapCount,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
};
VK_CHECK(vkCreateImage(device, &imageCreateInfo, nullptr, &image));
*outImage = image;
// image view
VkDeviceMemory imageMemory;
VkMemoryRequirements imageMemoryRequirements;
vkGetImageMemoryRequirements(device, image, &imageMemoryRequirements);
VkMemoryPropertyFlags imageMemoryPropertyFlags{ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT };
VkMemoryAllocateFlags imageMemoryAllocateFlags{ };
VkDeviceSize stride;
allocateFromMemoryRequirements(device,
nonCoherentAtomSize,
physicalDeviceMemoryProperties,
imageMemoryRequirements,
imageMemoryPropertyFlags,
imageMemoryAllocateFlags,
1,
&imageMemory,
&stride);
*outMemory = imageMemory;
VK_CHECK(vkBindImageMemory(device, image, imageMemory, 0));
VkImageView imageView;
VkImageViewCreateInfo textureViewCreateInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = image,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = format,
.subresourceRange{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.levelCount = ddsFile->header.dwMipMapCount,
.layerCount = 1
}
};
VK_CHECK(vkCreateImageView(device, &textureViewCreateInfo, nullptr, &imageView));
*outImageView = imageView;
// texture transfer: source buffer
VkBuffer sourceBuffer{}; VkBuffer sourceBuffer{};
VkBufferCreateInfo sourceBufferCreateInfo{ VkBufferCreateInfo sourceBufferCreateInfo{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
@ -243,7 +184,7 @@ void createImageFromFilenameDDS(VkDevice device,
.image = image, .image = image,
.subresourceRange = { .subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.levelCount = ddsFile->header.dwMipMapCount, .levelCount = levelCount,
.layerCount = 1 .layerCount = 1
} }
}; };
@ -253,24 +194,23 @@ void createImageFromFilenameDDS(VkDevice device,
.pImageMemoryBarriers = &imageBarrier .pImageMemoryBarriers = &imageBarrier
}; };
vkCmdPipelineBarrier2(commandBuffer, &imageDependencyInfo); vkCmdPipelineBarrier2(commandBuffer, &imageDependencyInfo);
VkBufferImageCopy * copyRegions = NewM<VkBufferImageCopy>(ddsFile->header.dwMipMapCount); VkBufferImageCopy * copyRegions = NewM<VkBufferImageCopy>(levelCount);
for (uint32_t level = 0; level < ddsFile->header.dwMipMapCount; level++) { for (uint32_t level = 0; level < levelCount; level++) {
copyRegions[level] = { copyRegions[level] = {
.bufferOffset = mipOffsets[level], .bufferOffset = levelOffsets[level],
.imageSubresource{ .imageSubresource{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.mipLevel = level, .mipLevel = level,
.layerCount = 1 .layerCount = 1
}, },
.imageExtent{ .imageExtent{
.width = max(1u, ddsFile->header.dwWidth >> level), .width = max(1u, width >> level),
.height = max(1u, ddsFile->header.dwHeight >> level), .height = max(1u, height >> level),
.depth = 1 .depth = 1
}, },
}; };
} }
vkCmdCopyBufferToImage(commandBuffer, sourceBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, ddsFile->header.dwMipMapCount, copyRegions); vkCmdCopyBufferToImage(commandBuffer, sourceBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, levelCount, copyRegions);
free(mipOffsets);
free(copyRegions); free(copyRegions);
VkImageMemoryBarrier2 readBarrier{ VkImageMemoryBarrier2 readBarrier{
@ -282,7 +222,7 @@ void createImageFromFilenameDDS(VkDevice device,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL, .newLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL,
.image = image, .image = image,
.subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .levelCount = ddsFile->header.dwMipMapCount, .layerCount = 1 } .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .levelCount = levelCount, .layerCount = 1 }
}; };
VkDependencyInfo readDependencyInfo{ VkDependencyInfo readDependencyInfo{
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
@ -305,3 +245,175 @@ void createImageFromFilenameDDS(VkDevice device,
vkDestroyBuffer(device, sourceBuffer, nullptr); vkDestroyBuffer(device, sourceBuffer, nullptr);
vkFreeMemory(device, sourceBufferMemory, nullptr); vkFreeMemory(device, sourceBufferMemory, nullptr);
} }
void createImage(VkDevice device,
VkDeviceSize nonCoherentAtomSize,
VkPhysicalDeviceMemoryProperties const & physicalDeviceMemoryProperties,
VkFormat format,
uint32_t width,
uint32_t height,
uint32_t levelCount,
VkImage * outImage,
VkDeviceMemory * outMemory,
VkImageView * outImageView)
{
// image
VkImage image;
VkImageCreateInfo imageCreateInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent = {
.width = width,
.height = height,
.depth = 1
},
.mipLevels = levelCount,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
};
VK_CHECK(vkCreateImage(device, &imageCreateInfo, nullptr, &image));
*outImage = image;
// image view
VkDeviceMemory imageMemory;
VkMemoryRequirements imageMemoryRequirements;
vkGetImageMemoryRequirements(device, image, &imageMemoryRequirements);
VkMemoryPropertyFlags imageMemoryPropertyFlags{ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT };
VkMemoryAllocateFlags imageMemoryAllocateFlags{ };
VkDeviceSize stride;
allocateFromMemoryRequirements(device,
nonCoherentAtomSize,
physicalDeviceMemoryProperties,
imageMemoryRequirements,
imageMemoryPropertyFlags,
imageMemoryAllocateFlags,
1,
&imageMemory,
&stride);
*outMemory = imageMemory;
VK_CHECK(vkBindImageMemory(device, image, imageMemory, 0));
VkImageView imageView;
VkImageViewCreateInfo textureViewCreateInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = image,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = format,
.subresourceRange{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.levelCount = levelCount,
.layerCount = 1
}
};
VK_CHECK(vkCreateImageView(device, &textureViewCreateInfo, nullptr, &imageView));
*outImageView = imageView;
}
void createImageFromFilenameDDS(VkDevice device,
VkQueue queue,
VkCommandBuffer commandBuffer,
VkFence fence,
VkDeviceSize nonCoherentAtomSize,
VkPhysicalDeviceMemoryProperties const & physicalDeviceMemoryProperties,
char const * const filename,
VkImage * outImage,
VkDeviceMemory * outMemory,
VkImageView * outImageView)
{
uint32_t imageSize;
void const * imageStart = file::open(filename, &imageSize);
void * imageData;
uint32_t * levelOffsets;
uint32_t imageDataSize;
DDS_FILE const * ddsFile = dds::validate(imageStart, imageSize, &levelOffsets, &imageData, &imageDataSize);
VkFormat format = dds::dxgi_to_vulkan(ddsFile->header10.dxgiFormat);
uint32_t width = ddsFile->header.dwWidth;
uint32_t height = ddsFile->header.dwHeight;
uint32_t levelCount = ddsFile->header.dwMipMapCount;
createImage(device,
nonCoherentAtomSize,
physicalDeviceMemoryProperties,
format,
width,
height,
levelCount,
outImage,
outMemory,
outImageView);
textureTransfer(device,
queue,
commandBuffer,
fence,
nonCoherentAtomSize,
physicalDeviceMemoryProperties,
imageDataSize,
imageData,
*outImage,
width,
height,
levelCount,
levelOffsets);
free(levelOffsets);
// imageData is not malloc'ed, it is a pointer to file:: data, which is also not malloc'ed
}
void createImageFromFilenameTGA(VkDevice device,
VkQueue queue,
VkCommandBuffer commandBuffer,
VkFence fence,
VkDeviceSize nonCoherentAtomSize,
VkPhysicalDeviceMemoryProperties const & physicalDeviceMemoryProperties,
char const * const filename,
VkImage * outImage,
VkDeviceMemory * outMemory,
VkImageView * outImageView)
{
uint32_t imageSize;
void const * imageStart = file::open(filename, &imageSize);
void * imageData;
uint32_t imageDataSize;
tga::header const * tga = tga::validate(imageStart, imageSize, &imageData, &imageDataSize);
VkFormat format = VK_FORMAT_B8G8R8A8_UNORM;
uint32_t width = tga->image.width;
uint32_t height = tga->image.height;
uint32_t levelCount = 1;
uint32_t levelOffset = 0;
createImage(device,
nonCoherentAtomSize,
physicalDeviceMemoryProperties,
format,
width,
height,
levelCount,
outImage,
outMemory,
outImageView);
textureTransfer(device,
queue,
commandBuffer,
fence,
nonCoherentAtomSize,
physicalDeviceMemoryProperties,
imageDataSize,
imageData,
*outImage,
width,
height,
levelCount,
&levelOffset);
// imageData is not malloc'ed, it is a pointer to file:: data, which is also not malloc'ed
}