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 );