diff --git a/Blaze.vcxproj b/Blaze.vcxproj
index a5132e9..c3171e5 100644
--- a/Blaze.vcxproj
+++ b/Blaze.vcxproj
@@ -178,6 +178,7 @@
+
@@ -188,6 +189,7 @@
+
diff --git a/Blaze.vcxproj.filters b/Blaze.vcxproj.filters
index 53936ef..317fcb2 100644
--- a/Blaze.vcxproj.filters
+++ b/Blaze.vcxproj.filters
@@ -71,6 +71,9 @@
Header Files
+
+ Header Files
+
@@ -100,6 +103,9 @@
Source Files
+
+ Source Files
+
diff --git a/Blaze/AppState.cpp b/Blaze/AppState.cpp
index a090fb1..0082860 100644
--- a/Blaze/AppState.cpp
+++ b/Blaze/AppState.cpp
@@ -6,6 +6,7 @@
#include "GlobalMemory.h"
#include "MiscData.h"
#include "RenderDevice.h"
+#include "TextureManager.h"
bool AppState::isInit() const
{
diff --git a/Blaze/AppState.h b/Blaze/AppState.h
index 3469d58..1e99e08 100644
--- a/Blaze/AppState.h
+++ b/Blaze/AppState.h
@@ -3,11 +3,12 @@
#include
-struct EntityManager;
struct SDL_Window;
struct GlobalMemory;
struct RenderDevice;
+struct EntityManager;
+struct TextureManager;
struct MiscData;
struct AppState
diff --git a/Blaze/EntityManager.cpp b/Blaze/EntityManager.cpp
index 96d798d..262b3bc 100644
--- a/Blaze/EntityManager.cpp
+++ b/Blaze/EntityManager.cpp
@@ -8,6 +8,7 @@
#include
#include "Frame.h"
+#include "TextureManager.h"
Entity* EntityManager::createEntity(
Transform const& transform,
@@ -64,15 +65,12 @@ Entity* EntityManager::createEntity(
Material material;
{
- VkImage texture;
- VmaAllocation textureAllocation;
- VkImageView textureView;
- VkSampler sampler;
+ VkSampler sampler;
- uint32_t width;
- uint32_t height;
- uint32_t numChannels = 4;
- stbi_uc* textureData;
+ uint32_t width;
+ uint32_t height;
+ uint32_t numChannels = 4;
+ stbi_uc* textureData;
{
int w;
int h;
@@ -82,7 +80,7 @@ Entity* EntityManager::createEntity(
textureData = stbi_load( textureFile, &w, &h, &nc, requestedChannels );
ASSERT( nc <= requestedChannels );
- if ( !textureData )
+ if ( not textureData )
{
vmaDestroyBuffer( pRenderDevice->gpuAllocator, Take( mesh.vertexBuffer ), Take( mesh.vertexBufferAllocation ) );
SDL_LogError( SDL_LOG_CATEGORY_ERROR, "%s", stbi_failure_reason() );
@@ -93,69 +91,16 @@ Entity* EntityManager::createEntity(
height = static_cast( h );
}
- // Calculate mips
- uint32_t mipLevels =
- 1 + static_cast( floorf( log2f( static_cast( std::max( width, height ) ) ) ) );
-
- VkImageCreateInfo const imageCreateInfo = {
- .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
- .pNext = nullptr,
- .flags = 0,
- .imageType = VK_IMAGE_TYPE_2D,
- .format = VK_FORMAT_R8G8B8A8_SRGB,
- .extent = { .width = width, .height = height, .depth = 1 },
- .mipLevels = mipLevels,
- .arrayLayers = 1,
- .samples = VK_SAMPLE_COUNT_1_BIT,
- .tiling = VK_IMAGE_TILING_OPTIMAL,
- .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
- .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
- .queueFamilyIndexCount = 0,
- .pQueueFamilyIndices = nullptr,
- .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
- };
-
- VmaAllocationCreateInfo constexpr allocationCreateInfo = {
- .flags = 0,
- .usage = VMA_MEMORY_USAGE_AUTO,
- .requiredFlags = 0,
- .preferredFlags = 0,
- .memoryTypeBits = 0,
- .pool = nullptr,
- .pUserData = nullptr,
- .priority = 1.0f,
- };
-
- VK_CHECK( vmaCreateImage(
- renderDevice.gpuAllocator, &imageCreateInfo, &allocationCreateInfo, &texture, &textureAllocation, nullptr ) );
-
- VkImageSubresourceRange const subresourceRange = {
- .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
- .baseMipLevel = 0,
- .levelCount = mipLevels,
- .baseArrayLayer = 0,
- .layerCount = 1,
- };
-
- VkComponentMapping constexpr componentMapping = {
- .r = VK_COMPONENT_SWIZZLE_IDENTITY,
- .g = VK_COMPONENT_SWIZZLE_IDENTITY,
- .b = VK_COMPONENT_SWIZZLE_IDENTITY,
- .a = VK_COMPONENT_SWIZZLE_IDENTITY,
- };
-
- VkImageViewCreateInfo const imageViewCreateInfo = {
- .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
- .pNext = nullptr,
- .flags = 0,
- .image = texture,
- .viewType = VK_IMAGE_VIEW_TYPE_2D,
- .format = imageCreateInfo.format,
- .components = componentMapping,
- .subresourceRange = subresourceRange,
- };
-
- VK_CHECK( vkCreateImageView( renderDevice.device, &imageViewCreateInfo, nullptr, &textureView ) );
+ auto textureOpt = renderDevice.textureManager->createTexture( { width, height, 1 } );
+ if ( not textureOpt )
+ {
+ vmaDestroyBuffer( pRenderDevice->gpuAllocator, Take( mesh.vertexBuffer ), Take( mesh.vertexBufferAllocation ) );
+ SDL_LogError( SDL_LOG_CATEGORY_ERROR, "%s", stbi_failure_reason() );
+ stbi_image_free( textureData );
+ return nullptr;
+ }
+ TextureID texture = textureOpt.value();
+ VkImage textureImage = renderDevice.textureManager->fetchImage( texture ).value();
VkSamplerCreateInfo constexpr samplerCreateInfo = {
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
@@ -243,6 +188,16 @@ Entity* EntityManager::createEntity(
.pInheritanceInfo = nullptr,
};
+ uint32_t mipLevels = TextureManager::calculateRequiredMipLevels( width, height, 1 );
+
+ VkImageSubresourceRange const subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = mipLevels,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ };
+
VkImageMemoryBarrier2 const creationToTransferImageBarrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
.pNext = nullptr,
@@ -254,7 +209,7 @@ Entity* EntityManager::createEntity(
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
- .image = texture,
+ .image = renderDevice.textureManager->fetchImage( texture ).value(),
.subresourceRange = subresourceRange,
};
@@ -283,7 +238,7 @@ Entity* EntityManager::createEntity(
.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
- .image = texture,
+ .image = textureImage,
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
@@ -303,7 +258,7 @@ Entity* EntityManager::createEntity(
.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
- .image = texture,
+ .image = textureImage,
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = mipLevels-1,
@@ -347,7 +302,7 @@ Entity* EntityManager::createEntity(
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
- .image = texture,
+ .image = textureImage,
.subresourceRange = mipLevelSubresource,
},
// prepareNextMipLevelDstImageBarrier
@@ -362,7 +317,7 @@ Entity* EntityManager::createEntity(
.newLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
- .image = texture,
+ .image = textureImage,
.subresourceRange = mipLevelSubresource,
}
};
@@ -395,8 +350,8 @@ Entity* EntityManager::createEntity(
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource = imageSubresourceLayers,
- .imageOffset = { 0, 0, 0 },
- .imageExtent = imageCreateInfo.extent
+ .imageOffset = { 0, 0, 0 },
+ .imageExtent = { width, height, 1 }
};
// Start
@@ -404,7 +359,12 @@ Entity* EntityManager::createEntity(
// Staging -> Image L0
vkCmdCopyBufferToImage(
- frameInUse.commandBuffer, stagingBuffer, texture, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©Region );
+ frameInUse.commandBuffer,
+ stagingBuffer,
+ textureImage,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ 1,
+ ©Region );
prepareNextMipLevelBarriers[0].subresourceRange.baseMipLevel = 0;
prepareNextMipLevelBarriers[1].subresourceRange.baseMipLevel = 1;
@@ -440,9 +400,9 @@ Entity* EntityManager::createEntity(
VkBlitImageInfo2 blitInfo = {
.sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2,
.pNext = nullptr,
- .srcImage = texture,
+ .srcImage = textureImage,
.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
- .dstImage = texture,
+ .dstImage = textureImage,
.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.regionCount = 1,
.pRegions = &imageBlit,
@@ -511,6 +471,8 @@ Entity* EntityManager::createEntity(
VkDescriptorSet descriptorSet;
VK_CHECK( vkAllocateDescriptorSets( renderDevice.device, &descriptorSetAllocateInfo, &descriptorSet ) );
+ VkImageView textureView = renderDevice.textureManager->fetchImageView( texture ).value();
+
VkDescriptorImageInfo const descriptorImageInfo = {
.sampler = sampler,
.imageView = textureView,
@@ -531,7 +493,7 @@ Entity* EntityManager::createEntity(
vkUpdateDescriptorSets( renderDevice.device, 1, &writeDescriptorSet, 0, nullptr );
- material = { texture, textureAllocation, textureView, sampler, descriptorSet };
+ material = { texture, sampler, descriptorSet };
}
entities[count++] = Entity( transform, mesh, material );
@@ -548,9 +510,8 @@ void EntityManager::destroyEntity( Entity* entity )
VmaAllocator const allocator = pRenderDevice->gpuAllocator;
vkDestroySampler( device, Take( entity->material().sampler ), nullptr );
- vkDestroyImageView( device, Take( entity->material().textureView ), nullptr );
- vmaDestroyImage( allocator, Take( entity->material().texture ), Take( entity->material().textureAllocation ) );
+ pRenderDevice->textureManager->freeTexture( entity->material().texture );
vmaDestroyBuffer( allocator, Take( entity->mesh().vertexBuffer ), Take( entity->mesh().vertexBufferAllocation ) );
// TODO: Leaking descriptor set.
diff --git a/Blaze/EntityManager.h b/Blaze/EntityManager.h
index 64fcad0..9e47ee8 100644
--- a/Blaze/EntityManager.h
+++ b/Blaze/EntityManager.h
@@ -9,6 +9,8 @@
#include
#include
+// TODO: Remove this dependency
+#include "TextureManager.h"
struct RenderDevice;
struct GlobalMemory;
@@ -37,9 +39,7 @@ struct Mesh
struct Material
{
- VkImage texture;
- VmaAllocation textureAllocation;
- VkImageView textureView;
+ TextureID texture;
VkSampler sampler; // TODO: Reuse
VkDescriptorSet descriptorSet;
};
diff --git a/Blaze/GlobalMemory.cpp b/Blaze/GlobalMemory.cpp
index e556490..4424178 100644
--- a/Blaze/GlobalMemory.cpp
+++ b/Blaze/GlobalMemory.cpp
@@ -24,6 +24,7 @@ std::byte* GlobalMemory::allocate( size_t const size )
assert( size <= available && "No enough space available" );
std::byte* retVal = memory;
+ memset( retVal, 0, size );
memory += size;
available -= size;
SDL_LogInfo(
diff --git a/Blaze/MacroUtils.h b/Blaze/MacroUtils.h
index 137d5fc..9bdf4ed 100644
--- a/Blaze/MacroUtils.h
+++ b/Blaze/MacroUtils.h
@@ -6,6 +6,8 @@
#include
+#define DEPRECATE_JULY_2025
+
#define G_ASSERT( COND ) \
do \
{ \
diff --git a/Blaze/RenderDevice.cpp b/Blaze/RenderDevice.cpp
index 8a7cf14..41a6bf2 100644
--- a/Blaze/RenderDevice.cpp
+++ b/Blaze/RenderDevice.cpp
@@ -11,6 +11,7 @@
#include "Frame.h"
#include "GlobalMemory.h"
#include "MathUtil.h"
+#include "TextureManager.h"
RenderDevice::~RenderDevice()
{
@@ -377,7 +378,9 @@ RenderDevice* RenderDevice_Create( GlobalMemory* mem, RenderDevice::CreateInfo c
}
std::byte* allocation = mem->allocate( sizeof( RenderDevice ), alignof( RenderDevice ) );
- return new ( allocation ) RenderDevice{
+ if ( not allocation ) return nullptr;
+
+ RenderDevice* renderDevice = new ( allocation ) RenderDevice{
instance,
surface,
physicalDeviceInUse,
@@ -393,23 +396,39 @@ RenderDevice* RenderDevice_Create( GlobalMemory* mem, RenderDevice::CreateInfo c
frames,
swapchainImageCount,
};
+
+ TextureManager* textureManager = TextureManager_Create( mem, renderDevice, 10000 );
+ if ( !textureManager )
+ {
+ SDL_LogError( SDL_LOG_CATEGORY_APPLICATION, "TextureManager failed to init" );
+ renderDevice->destroy();
+ return nullptr;
+ }
+
+ renderDevice->textureManager = textureManager;
+
+ ASSERT( renderDevice->textureManager );
+
+ return renderDevice;
}
inline bool RenderDevice::isInit() const
{
- return instance and device;
+ return instance and device and textureManager;
}
void RenderDevice::destroy()
{
if ( not isInit() ) return;
- for ( Frame& frame : std::span{ frames, swapchainImageCount } )
+ Take( textureManager )->destroy();
+
+ for ( Frame& frame : std::span{ Take( frames ), swapchainImageCount } )
{
frame.destroy( *this );
}
- for ( auto const& view : std::span{ swapchainViews, swapchainImageCount } )
+ for ( auto const& view : std::span{ Take( swapchainViews ), swapchainImageCount } )
{
vkDestroyImageView( device, view, nullptr );
}
@@ -465,4 +484,5 @@ RenderDevice::RenderDevice(
, swapchainViews{ swapchainViews }
, frames{ frames }
, swapchainImageCount{ swapchainImageCount }
+ , textureManager{ nullptr }
{}
diff --git a/Blaze/RenderDevice.h b/Blaze/RenderDevice.h
index 8f88fd8..c7ba37d 100644
--- a/Blaze/RenderDevice.h
+++ b/Blaze/RenderDevice.h
@@ -11,6 +11,7 @@
struct GlobalMemory;
struct Frame;
+struct TextureManager;
/// The Rendering backend abstraction
/// If this fails to initialize, we crash
@@ -43,6 +44,8 @@ struct RenderDevice
uint32_t swapchainImageCount;
uint32_t frameIndex = 0;
+ TextureManager* textureManager;
+
[[nodiscard]] bool isInit() const;
void destroy();
void waitIdle() const;
diff --git a/Blaze/TextureManager.cpp b/Blaze/TextureManager.cpp
new file mode 100644
index 0000000..9c786e3
--- /dev/null
+++ b/Blaze/TextureManager.cpp
@@ -0,0 +1,223 @@
+
+#include "TextureManager.h"
+
+#include "GlobalMemory.h"
+#include "RenderDevice.h"
+
+std::optional TextureManager::createTexture( VkExtent3D const extent )
+{
+ if ( m_freeList.empty() )
+ {
+ return std::nullopt;
+ }
+
+ Texture* textureSlot = reinterpret_cast( m_freeList.popFront() );
+
+ ASSERT( m_pRenderDevice );
+ RenderDevice const& renderDevice = *m_pRenderDevice;
+
+ VkFormat const format = VK_FORMAT_R8G8B8A8_SRGB;
+
+ VkImage texture;
+ VmaAllocation textureAllocation;
+ VkImageView textureView;
+
+ uint32_t const mipLevels = calculateRequiredMipLevels( extent.width, extent.height, extent.depth );
+
+ VkImageCreateInfo const imageCreateInfo = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = format,
+ .extent = extent,
+ .mipLevels = mipLevels,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ };
+
+ VmaAllocationCreateInfo constexpr allocationCreateInfo = {
+ .flags = 0,
+ .usage = VMA_MEMORY_USAGE_AUTO,
+ .requiredFlags = 0,
+ .preferredFlags = 0,
+ .memoryTypeBits = 0,
+ .pool = nullptr,
+ .pUserData = nullptr,
+ .priority = 1.0f,
+ };
+
+ VK_CHECK( vmaCreateImage(
+ renderDevice.gpuAllocator, &imageCreateInfo, &allocationCreateInfo, &texture, &textureAllocation, nullptr ) );
+
+ VkImageSubresourceRange const subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = mipLevels,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ };
+
+ VkComponentMapping constexpr componentMapping = {
+ .r = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .g = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .b = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .a = VK_COMPONENT_SWIZZLE_IDENTITY,
+ };
+
+ VkImageViewCreateInfo const imageViewCreateInfo = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .image = texture,
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = imageCreateInfo.format,
+ .components = componentMapping,
+ .subresourceRange = subresourceRange,
+ };
+
+ VK_CHECK( vkCreateImageView( renderDevice.device, &imageViewCreateInfo, nullptr, &textureView ) );
+
+ // NOTE: textureSlot preserves index between uses.
+ uint32_t index = textureSlot->index;
+ new ( textureSlot ) Texture{
+ .image = texture,
+ .allocation = textureAllocation,
+ .view = textureView,
+ .extent = extent,
+ .format = format,
+ .index = index,
+ };
+
+ // NOTE: Memory hackery to create TextureID;
+ return *reinterpret_cast( &index );
+}
+
+bool TextureManager::isValidID( TextureID rid ) const
+{
+ uint32_t const index = *reinterpret_cast( &rid );
+ uint32_t const innerIndex = index & INDEX_MASK;
+
+ if ( innerIndex > m_capacity ) return false;
+
+ return m_aTextures[innerIndex].index == index;
+}
+
+void TextureManager::freeTexture( TextureID const rid )
+{
+ if ( not isValidID( rid ) ) return;
+
+ Texture& texture = fetchTextureUnchecked( rid );
+
+ destroyTexture( texture );
+}
+
+std::optional TextureManager::fetchImage( TextureID const rid )
+{
+ if ( not isValidID( rid ) ) return std::nullopt;
+
+ return fetchTextureUnchecked( rid ).image;
+}
+
+std::optional TextureManager::fetchImageView( TextureID const rid )
+{
+ if ( not isValidID( rid ) ) return std::nullopt;
+
+ return fetchTextureUnchecked( rid ).view;
+}
+
+void TextureManager::destroy()
+{
+#if defined( _DEBUG )
+ if ( m_count > 0 )
+ {
+ SDL_LogError( SDL_LOG_CATEGORY_ERROR, "%u textures still allocated.", m_count );
+ }
+#endif
+
+ while ( not m_freeList.empty() )
+ {
+ Texture* tex = reinterpret_cast( m_freeList.popFront() );
+ memset( tex, 0, sizeof *tex );
+ }
+
+ for ( Texture& tex : std::span{ m_aTextures, m_count } )
+ {
+ destroyTexture( tex );
+ }
+}
+TextureManager::~TextureManager()
+{
+ ASSERT( not m_aTextures );
+}
+
+void TextureManager::destroyTexture( Texture& tex )
+{
+ if ( not tex.image ) return;
+
+ ASSERT( m_pRenderDevice );
+
+ uint32_t const index = tex.index;
+ uint32_t const innerIndex = index & INDEX_MASK;
+ uint32_t const generation = ( index & GENERATION_MASK ) >> GENERATION_OFFSET;
+
+ RenderDevice const& renderDevice = *m_pRenderDevice;
+
+ vkDestroyImageView( renderDevice.device, Take( tex.view ), nullptr );
+
+ vmaDestroyImage( renderDevice.gpuAllocator, Take( tex.image ), Take( tex.allocation ) );
+
+ tex.extent = {};
+ tex.format = VK_FORMAT_UNDEFINED;
+ tex.index = innerIndex | ( generation + 1 ) << GENERATION_OFFSET;
+
+ // NOTE: DO NOT EDIT INNER INDEX.
+ ASSERT( innerIndex == ( tex.index & INDEX_MASK ) and "Index should not be modified" );
+ ASSERT( tex.index > index and "Generation should increase." );
+
+ m_freeList.pushBack( reinterpret_cast( &tex ) );
+}
+
+uint32_t TextureManager::calculateRequiredMipLevels( uint32_t const w, uint32_t const h, uint32_t const d )
+{
+ uint32_t const maxDim = std::max( std::max( w, h ), d );
+ return 1 + static_cast( floorf( log2f( static_cast( maxDim ) ) ) );
+}
+
+Texture& TextureManager::fetchTextureUnchecked( TextureID rid )
+{
+ uint32_t const index = *reinterpret_cast( &rid );
+ uint32_t const innerIndex = index & INDEX_MASK;
+
+ return m_aTextures[innerIndex];
+}
+
+TextureManager::TextureManager( RenderDevice* pRenderDevice, Texture* aTextures, uint32_t const capacity )
+ : m_pRenderDevice{ pRenderDevice }, m_aTextures{ aTextures }, m_count{ 0 }, m_capacity{ capacity }
+{
+ uint32_t i = 0;
+ for ( Texture& tex : std::span{ m_aTextures, m_capacity } )
+ {
+ // Default Generation is 1
+ // TODO: Fix this by creating 0,0 as a valid texture.
+ tex.index = i++ | ( 1 << GENERATION_OFFSET );
+ m_freeList.pushFront( reinterpret_cast( &tex ) );
+ }
+}
+
+TextureManager* TextureManager_Create( GlobalMemory* mem, RenderDevice* renderDevice, uint32_t maxCount )
+{
+ Texture* textures = reinterpret_cast( mem->allocate( maxCount * sizeof( Texture ), alignof( Texture ) ) );
+ if ( not textures ) return nullptr;
+
+ std::byte* allocation = mem->allocate( sizeof( TextureManager ), alignof( TextureManager ) );
+ if ( not allocation ) return nullptr;
+
+ return new ( allocation ) TextureManager{ renderDevice, textures, maxCount };
+}
diff --git a/Blaze/TextureManager.h b/Blaze/TextureManager.h
new file mode 100644
index 0000000..a108433
--- /dev/null
+++ b/Blaze/TextureManager.h
@@ -0,0 +1,194 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include
+
+#include "MacroUtils.h"
+#include "RenderDevice.h"
+
+
+struct GlobalMemory;
+struct RenderDevice;
+
+struct FreeListNode
+{
+ FreeListNode* pNext;
+ FreeListNode* pPrev;
+};
+
+struct FreeList
+{
+ using Node = FreeListNode;
+
+ struct Iterator
+ {
+ FreeListNode* pIter;
+
+ Iterator& operator++()
+ {
+ pIter = pIter->pNext;
+ return *this;
+ }
+
+ bool operator==( Iterator const& other ) const
+ {
+ return this->pIter == other.pIter;
+ }
+
+ FreeListNode& operator*()
+ {
+ return *pIter;
+ }
+ };
+
+private:
+ FreeListNode m_head;
+ FreeListNode m_tail;
+
+public:
+ FreeList() : m_head{ .pNext = &m_tail, .pPrev = nullptr }, m_tail{ .pNext = nullptr, .pPrev = &m_head }
+ {}
+
+ void pushBack( Node* pNode )
+ {
+ Node* prev = m_tail.pPrev;
+
+ // Set prev as previous of pNode
+ prev->pNext = pNode;
+ pNode->pPrev = prev;
+
+ // Set tail as next of pNode
+ pNode->pNext = &m_tail;
+ m_tail.pPrev = pNode;
+ }
+
+ void pushFront( Node* pNode )
+ {
+ Node* next = m_head.pNext;
+
+ // Set next as next of pNode
+ next->pPrev = pNode;
+ pNode->pNext = next;
+
+ // Set head as prev of pNode
+ pNode->pPrev = &m_head;
+ m_head.pNext = pNode;
+ }
+
+ Node* popFront()
+ {
+ ASSERT( not empty() );
+
+ Node* element = m_head.pNext;
+ element->pPrev->pNext = element->pNext;
+ element->pNext->pPrev = element->pPrev;
+ return element;
+ }
+
+ [[nodiscard]] bool empty() const
+ {
+ return m_head.pNext == &m_tail;
+ }
+
+ Iterator begin()
+ {
+ return { m_head.pNext };
+ }
+
+ Iterator end()
+ {
+ return { &m_tail };
+ }
+
+ FreeList( FreeList&& ) = delete;
+ FreeList( FreeList const& ) = delete;
+ FreeList& operator=( FreeList const& ) = delete;
+ FreeList& operator=( FreeList&& ) = delete;
+
+ ~FreeList() = default;
+};
+
+template
+struct RID
+{
+private:
+ uint32_t m_index = 0;
+
+ explicit RID( uint32_t const index ) : m_index{ index } {};
+
+public:
+ RID() = default;
+ static RID null()
+ {
+ return {};
+ }
+
+ operator bool() const
+ {
+ return m_index == 0;
+ }
+};
+
+struct Texture
+{
+ VkImage image;
+ VmaAllocation allocation;
+ VkImageView view;
+ VkExtent3D extent;
+ VkFormat format;
+ uint32_t index;
+};
+
+static_assert( sizeof( Texture ) > sizeof( FreeListNode ) and "Texture is used intrusively by FreeList" );
+static_assert(
+ offsetof( Texture, index ) >= sizeof( FreeListNode ) and "Index should not be overwritten even in invalid state" );
+
+using TextureID = RID;
+
+struct TextureManager
+{
+private:
+ constexpr static uint32_t INDEX_MASK = 0x0007FFFF;
+ constexpr static uint32_t GENERATION_MASK = ~INDEX_MASK;
+ constexpr static uint32_t GENERATION_OFFSET = 19;
+ static_assert(
+ ( ( GENERATION_MASK >> GENERATION_OFFSET & 0x1 ) == 0x1 ) and
+ ( ( GENERATION_MASK >> ( GENERATION_OFFSET - 1 ) & 0x1 ) != 0x1 ) and "Checks boundary" );
+
+ RenderDevice* m_pRenderDevice;
+
+ Texture* m_aTextures;
+ uint32_t m_count;
+ uint32_t m_capacity;
+ FreeList m_freeList;
+
+ void destroyTexture( Texture& tex );
+
+ Texture& fetchTextureUnchecked( TextureID rid );
+
+public:
+ static uint32_t calculateRequiredMipLevels( uint32_t w, uint32_t h, uint32_t d );
+
+ [[nodiscard]] bool isValidID( TextureID rid ) const;
+ [[nodiscard]] std::optional createTexture( VkExtent3D extent );
+ void freeTexture( TextureID rid );
+
+ DEPRECATE_JULY_2025
+ std::optional fetchImage( TextureID rid );
+ std::optional fetchImageView( TextureID rid );
+
+ //
+ TextureManager( RenderDevice* pRenderDevice, Texture* aTextures, uint32_t capacity );
+ void destroy();
+
+ TextureManager( TextureManager const& other ) = delete;
+ TextureManager( TextureManager&& other ) noexcept = delete;
+ TextureManager& operator=( TextureManager const& other ) = delete;
+ TextureManager& operator=( TextureManager&& other ) noexcept = delete;
+ ~TextureManager();
+};
+
+TextureManager* TextureManager_Create( GlobalMemory* mem, RenderDevice* renderDevice, uint32_t maxCount );