diff --git a/include/rendering/vk_context.hpp b/include/rendering/vk_context.hpp index 625c244a..1b3e66ec 100644 --- a/include/rendering/vk_context.hpp +++ b/include/rendering/vk_context.hpp @@ -76,6 +76,12 @@ public: void setMsaaSamples(VkSampleCountFlagBits samples); VkSampleCountFlagBits getMaxUsableSampleCount() const; + // UI texture upload: creates a Vulkan texture from RGBA data and returns + // a VkDescriptorSet suitable for use as ImTextureID. + // The caller does NOT need to free the result — resources are tracked and + // cleaned up when the VkContext is destroyed. + VkDescriptorSet uploadImGuiTexture(const uint8_t* rgba, int width, int height); + private: bool createInstance(SDL_Window* window); bool createSurface(SDL_Window* window); @@ -144,6 +150,17 @@ private: VkRenderPass imguiRenderPass = VK_NULL_HANDLE; VkDescriptorPool imguiDescriptorPool = VK_NULL_HANDLE; + // Shared sampler for UI textures (created on first uploadImGuiTexture call) + VkSampler uiTextureSampler_ = VK_NULL_HANDLE; + + // Tracked UI textures for cleanup + struct UiTexture { + VkImage image; + VkDeviceMemory memory; + VkImageView view; + }; + std::vector uiTextures_; + #ifndef NDEBUG bool enableValidation = true; #else diff --git a/include/ui/game_screen.hpp b/include/ui/game_screen.hpp index 19eeeb43..57b8be0b 100644 --- a/include/ui/game_screen.hpp +++ b/include/ui/game_screen.hpp @@ -8,7 +8,7 @@ #include "ui/quest_log_screen.hpp" #include "ui/spellbook_screen.hpp" #include "ui/talent_screen.hpp" -#include +#include #include #include #include @@ -217,21 +217,21 @@ private: // WorldMap is now owned by Renderer (accessed via renderer->getWorldMap()) // Spell icon cache: spellId -> GL texture ID - std::unordered_map spellIconCache_; + std::unordered_map spellIconCache_; // SpellIconID -> icon path (from SpellIcon.dbc) std::unordered_map spellIconPaths_; // SpellID -> SpellIconID (from Spell.dbc field 133) std::unordered_map spellIconIds_; bool spellIconDbLoaded_ = false; - GLuint getSpellIcon(uint32_t spellId, pipeline::AssetManager* am); + VkDescriptorSet getSpellIcon(uint32_t spellId, pipeline::AssetManager* am); // Action bar drag state (-1 = not dragging) int actionBarDragSlot_ = -1; - GLuint actionBarDragIcon_ = 0; + VkDescriptorSet actionBarDragIcon_ = VK_NULL_HANDLE; // Bag bar state - GLuint backpackIconTexture_ = 0; - GLuint emptyBagSlotTexture_ = 0; + VkDescriptorSet backpackIconTexture_ = VK_NULL_HANDLE; + VkDescriptorSet emptyBagSlotTexture_ = VK_NULL_HANDLE; int bagBarPickedSlot_ = -1; // Visual drag in progress (-1 = none) int bagBarDragSource_ = -1; // Mouse pressed on this slot, waiting for drag or click (-1 = none) diff --git a/include/ui/inventory_screen.hpp b/include/ui/inventory_screen.hpp index 237b10e0..baffe8de 100644 --- a/include/ui/inventory_screen.hpp +++ b/include/ui/inventory_screen.hpp @@ -3,7 +3,7 @@ #include "game/inventory.hpp" #include "game/character.hpp" #include "game/world_packets.hpp" -#include +#include #include #include #include @@ -93,9 +93,9 @@ private: pipeline::AssetManager* assetManager_ = nullptr; // Item icon cache: displayInfoId -> GL texture - std::unordered_map iconCache_; + std::unordered_map iconCache_; public: - GLuint getItemIcon(uint32_t displayInfoId); + VkDescriptorSet getItemIcon(uint32_t displayInfoId); private: // Character model preview diff --git a/include/ui/spellbook_screen.hpp b/include/ui/spellbook_screen.hpp index ae3ec39e..b77a2ece 100644 --- a/include/ui/spellbook_screen.hpp +++ b/include/ui/spellbook_screen.hpp @@ -1,7 +1,7 @@ #pragma once #include "game/game_handler.hpp" -#include +#include #include #include #include @@ -41,7 +41,7 @@ public: // Drag-and-drop state for action bar assignment bool isDraggingSpell() const { return draggingSpell_; } uint32_t getDragSpellId() const { return dragSpellId_; } - void consumeDragSpell() { draggingSpell_ = false; dragSpellId_ = 0; dragSpellIconTex_ = 0; } + void consumeDragSpell() { draggingSpell_ = false; dragSpellId_ = 0; dragSpellIconTex_ = VK_NULL_HANDLE; } private: bool open = false; @@ -55,7 +55,7 @@ private: // Icon data (loaded from SpellIcon.dbc) bool iconDbLoaded = false; std::unordered_map spellIconPaths; // SpellIconID -> path - std::unordered_map spellIconCache; // SpellIconID -> GL texture + std::unordered_map spellIconCache; // SpellIconID -> GL texture // Skill line data (loaded from SkillLine.dbc + SkillLineAbility.dbc) bool skillLineDbLoaded = false; @@ -71,13 +71,13 @@ private: // Drag-and-drop from spellbook to action bar bool draggingSpell_ = false; uint32_t dragSpellId_ = 0; - GLuint dragSpellIconTex_ = 0; + VkDescriptorSet dragSpellIconTex_ = VK_NULL_HANDLE; void loadSpellDBC(pipeline::AssetManager* assetManager); void loadSpellIconDBC(pipeline::AssetManager* assetManager); void loadSkillLineDBCs(pipeline::AssetManager* assetManager); void categorizeSpells(const std::unordered_set& knownSpells); - GLuint getSpellIcon(uint32_t iconId, pipeline::AssetManager* assetManager); + VkDescriptorSet getSpellIcon(uint32_t iconId, pipeline::AssetManager* assetManager); const SpellInfo* getSpellInfo(uint32_t spellId) const; }; diff --git a/include/ui/talent_screen.hpp b/include/ui/talent_screen.hpp index 55dd429b..792e7706 100644 --- a/include/ui/talent_screen.hpp +++ b/include/ui/talent_screen.hpp @@ -2,7 +2,7 @@ #include "game/game_handler.hpp" #include -#include +#include #include #include @@ -25,7 +25,7 @@ private: void loadSpellDBC(pipeline::AssetManager* assetManager); void loadSpellIconDBC(pipeline::AssetManager* assetManager); - GLuint getSpellIcon(uint32_t iconId, pipeline::AssetManager* assetManager); + VkDescriptorSet getSpellIcon(uint32_t iconId, pipeline::AssetManager* assetManager); bool open = false; bool nKeyWasDown = false; @@ -35,7 +35,7 @@ private: bool iconDbcLoaded = false; std::unordered_map spellIconIds; // spellId -> iconId std::unordered_map spellIconPaths; // iconId -> path - std::unordered_map spellIconCache; // iconId -> texture + std::unordered_map spellIconCache; // iconId -> texture std::unordered_map spellTooltips; // spellId -> description }; diff --git a/src/rendering/loading_screen.cpp b/src/rendering/loading_screen.cpp index f0795cbe..b60673e0 100644 --- a/src/rendering/loading_screen.cpp +++ b/src/rendering/loading_screen.cpp @@ -338,9 +338,14 @@ void LoadingScreen::render() { rpInfo.renderArea.offset = {0, 0}; rpInfo.renderArea.extent = vkCtx->getSwapchainExtent(); - VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; - rpInfo.clearValueCount = 1; - rpInfo.pClearValues = &clearColor; + // Render pass has 2 attachments (color + depth) or 3 with MSAA + VkClearValue clearValues[3]{}; + clearValues[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}}; + clearValues[1].depthStencil = {1.0f, 0}; + clearValues[2].color = {{0.0f, 0.0f, 0.0f, 1.0f}}; + bool msaaOn = vkCtx->getMsaaSamples() > VK_SAMPLE_COUNT_1_BIT; + rpInfo.clearValueCount = msaaOn ? 3 : 2; + rpInfo.pClearValues = clearValues; vkCmdBeginRenderPass(cmd, &rpInfo, VK_SUBPASS_CONTENTS_INLINE); ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), cmd); diff --git a/src/rendering/vk_context.cpp b/src/rendering/vk_context.cpp index 701ffe8c..8264ced2 100644 --- a/src/rendering/vk_context.cpp +++ b/src/rendering/vk_context.cpp @@ -3,6 +3,7 @@ #include "core/logger.hpp" #include #include +#include #include #include @@ -639,6 +640,18 @@ bool VkContext::createImGuiResources() { } void VkContext::destroyImGuiResources() { + // Destroy uploaded UI textures + for (auto& tex : uiTextures_) { + if (tex.view) vkDestroyImageView(device, tex.view, nullptr); + if (tex.image) vkDestroyImage(device, tex.image, nullptr); + if (tex.memory) vkFreeMemory(device, tex.memory, nullptr); + } + uiTextures_.clear(); + if (uiTextureSampler_) { + vkDestroySampler(device, uiTextureSampler_, nullptr); + uiTextureSampler_ = VK_NULL_HANDLE; + } + if (imguiDescriptorPool) { vkDestroyDescriptorPool(device, imguiDescriptorPool, nullptr); imguiDescriptorPool = VK_NULL_HANDLE; @@ -652,6 +665,167 @@ void VkContext::destroyImGuiResources() { } } +static uint32_t findMemType(VkPhysicalDevice physDev, uint32_t typeFilter, VkMemoryPropertyFlags props) { + VkPhysicalDeviceMemoryProperties memProps; + vkGetPhysicalDeviceMemoryProperties(physDev, &memProps); + for (uint32_t i = 0; i < memProps.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProps.memoryTypes[i].propertyFlags & props) == props) + return i; + } + return 0; +} + +VkDescriptorSet VkContext::uploadImGuiTexture(const uint8_t* rgba, int width, int height) { + if (!device || !physicalDevice || width <= 0 || height <= 0 || !rgba) + return VK_NULL_HANDLE; + + VkDeviceSize imageSize = static_cast(width) * height * 4; + + // Create shared sampler on first call + if (!uiTextureSampler_) { + VkSamplerCreateInfo si{}; + si.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + si.magFilter = VK_FILTER_LINEAR; + si.minFilter = VK_FILTER_LINEAR; + si.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + si.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + si.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + if (vkCreateSampler(device, &si, nullptr, &uiTextureSampler_) != VK_SUCCESS) { + LOG_ERROR("Failed to create UI texture sampler"); + return VK_NULL_HANDLE; + } + } + + // Staging buffer + VkBuffer stagingBuffer; + VkDeviceMemory stagingMemory; + { + VkBufferCreateInfo bufInfo{}; + bufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufInfo.size = imageSize; + bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + if (vkCreateBuffer(device, &bufInfo, nullptr, &stagingBuffer) != VK_SUCCESS) + return VK_NULL_HANDLE; + + VkMemoryRequirements memReqs; + vkGetBufferMemoryRequirements(device, stagingBuffer, &memReqs); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memReqs.size; + allocInfo.memoryTypeIndex = findMemType(physicalDevice, memReqs.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + if (vkAllocateMemory(device, &allocInfo, nullptr, &stagingMemory) != VK_SUCCESS) { + vkDestroyBuffer(device, stagingBuffer, nullptr); + return VK_NULL_HANDLE; + } + vkBindBufferMemory(device, stagingBuffer, stagingMemory, 0); + + void* mapped; + vkMapMemory(device, stagingMemory, 0, imageSize, 0, &mapped); + memcpy(mapped, rgba, imageSize); + vkUnmapMemory(device, stagingMemory); + } + + // Create image + VkImage image; + VkDeviceMemory imageMemory; + { + VkImageCreateInfo imgInfo{}; + imgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imgInfo.imageType = VK_IMAGE_TYPE_2D; + imgInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + imgInfo.extent = {static_cast(width), static_cast(height), 1}; + imgInfo.mipLevels = 1; + imgInfo.arrayLayers = 1; + imgInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imgInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + imgInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imgInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + if (vkCreateImage(device, &imgInfo, nullptr, &image) != VK_SUCCESS) { + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingMemory, nullptr); + return VK_NULL_HANDLE; + } + + VkMemoryRequirements memReqs; + vkGetImageMemoryRequirements(device, image, &memReqs); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memReqs.size; + allocInfo.memoryTypeIndex = findMemType(physicalDevice, memReqs.memoryTypeBits, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { + vkDestroyImage(device, image, nullptr); + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingMemory, nullptr); + return VK_NULL_HANDLE; + } + vkBindImageMemory(device, image, imageMemory, 0); + } + + // Upload via immediate submit + immediateSubmit([&](VkCommandBuffer cmd) { + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + + VkBufferImageCopy region{}; + region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; + region.imageExtent = {static_cast(width), static_cast(height), 1}; + vkCmdCopyBufferToImage(cmd, stagingBuffer, image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + }); + + // Cleanup staging + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingMemory, nullptr); + + // Create image view + VkImageView imageView; + { + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { + vkDestroyImage(device, image, nullptr); + vkFreeMemory(device, imageMemory, nullptr); + return VK_NULL_HANDLE; + } + } + + // Register with ImGui + VkDescriptorSet ds = ImGui_ImplVulkan_AddTexture(uiTextureSampler_, imageView, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + // Track for cleanup + uiTextures_.push_back({image, imageMemory, imageView}); + + return ds; +} + bool VkContext::recreateSwapchain(int width, int height) { vkDeviceWaitIdle(device); diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index c9fbd59a..65c722fa 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -1,5 +1,6 @@ #include "ui/game_screen.hpp" #include "rendering/character_preview.hpp" +#include "rendering/vk_context.hpp" #include "core/application.hpp" #include "core/coordinates.hpp" #include "core/spawn_presets.hpp" @@ -854,7 +855,7 @@ void GameScreen::renderChatWindow(game::GameHandler& gameHandler) { if (const auto* eq = findComparableEquipped(static_cast(info->inventoryType))) { ImGui::Separator(); ImGui::TextDisabled("Equipped:"); - GLuint eqIcon = inventoryScreen.getItemIcon(eq->item.displayInfoId); + VkDescriptorSet eqIcon = inventoryScreen.getItemIcon(eq->item.displayInfoId); if (eqIcon) { ImGui::Image((ImTextureID)(uintptr_t)eqIcon, ImVec2(18.0f, 18.0f)); ImGui::SameLine(); @@ -3217,8 +3218,8 @@ void GameScreen::renderWorldMap(game::GameHandler& gameHandler) { // Action Bar (Phase 3) // ============================================================ -GLuint GameScreen::getSpellIcon(uint32_t spellId, pipeline::AssetManager* am) { - if (spellId == 0 || !am) return 0; +VkDescriptorSet GameScreen::getSpellIcon(uint32_t spellId, pipeline::AssetManager* am) { + if (spellId == 0 || !am) return VK_NULL_HANDLE; // Check cache first auto cit = spellIconCache_.find(spellId); @@ -3276,43 +3277,41 @@ GLuint GameScreen::getSpellIcon(uint32_t spellId, pipeline::AssetManager* am) { // Look up spellId -> SpellIconID -> icon path auto iit = spellIconIds_.find(spellId); if (iit == spellIconIds_.end()) { - spellIconCache_[spellId] = 0; - return 0; + spellIconCache_[spellId] = VK_NULL_HANDLE; + return VK_NULL_HANDLE; } auto pit = spellIconPaths_.find(iit->second); if (pit == spellIconPaths_.end()) { - spellIconCache_[spellId] = 0; - return 0; + spellIconCache_[spellId] = VK_NULL_HANDLE; + return VK_NULL_HANDLE; } // Path from DBC has no extension — append .blp std::string iconPath = pit->second + ".blp"; auto blpData = am->readFile(iconPath); if (blpData.empty()) { - spellIconCache_[spellId] = 0; - return 0; + spellIconCache_[spellId] = VK_NULL_HANDLE; + return VK_NULL_HANDLE; } auto image = pipeline::BLPLoader::load(blpData); if (!image.isValid()) { - spellIconCache_[spellId] = 0; - return 0; + spellIconCache_[spellId] = VK_NULL_HANDLE; + return VK_NULL_HANDLE; } - GLuint texId = 0; - glGenTextures(1, &texId); - glBindTexture(GL_TEXTURE_2D, texId); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image.width, image.height, 0, - GL_RGBA, GL_UNSIGNED_BYTE, image.data.data()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glBindTexture(GL_TEXTURE_2D, 0); + // Upload to Vulkan via VkContext + auto* window = core::Application::getInstance().getWindow(); + auto* vkCtx = window ? window->getVkContext() : nullptr; + if (!vkCtx) { + spellIconCache_[spellId] = VK_NULL_HANDLE; + return VK_NULL_HANDLE; + } - spellIconCache_[spellId] = texId; - return texId; + VkDescriptorSet ds = vkCtx->uploadImGuiTexture(image.data.data(), image.width, image.height); + spellIconCache_[spellId] = ds; + return ds; } void GameScreen::renderActionBar(game::GameHandler& gameHandler) { @@ -3362,7 +3361,7 @@ void GameScreen::renderActionBar(game::GameHandler& gameHandler) { }; // Try to get icon texture for this slot - GLuint iconTex = 0; + VkDescriptorSet iconTex = VK_NULL_HANDLE; const game::ItemDef* barItemDef = nullptr; uint32_t itemDisplayInfoId = 0; std::string itemNameFromQuery; @@ -3655,22 +3654,17 @@ void GameScreen::renderBagBar(game::GameHandler& gameHandler) { if (!blpData.empty()) { auto image = pipeline::BLPLoader::load(blpData); if (image.isValid()) { - glGenTextures(1, &backpackIconTexture_); - glBindTexture(GL_TEXTURE_2D, backpackIconTexture_); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image.width, image.height, 0, - GL_RGBA, GL_UNSIGNED_BYTE, image.data.data()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glBindTexture(GL_TEXTURE_2D, 0); + auto* w = core::Application::getInstance().getWindow(); + auto* vkCtx = w ? w->getVkContext() : nullptr; + if (vkCtx) + backpackIconTexture_ = vkCtx->uploadImGuiTexture(image.data.data(), image.width, image.height); } } } // Track bag slot screen rects for drop detection ImVec2 bagSlotMins[4], bagSlotMaxs[4]; - GLuint bagIcons[4] = {}; + VkDescriptorSet bagIcons[4] = {}; // Slots 1-4: Bag slots (leftmost) for (int i = 0; i < 4; ++i) { @@ -3680,7 +3674,7 @@ void GameScreen::renderBagBar(game::GameHandler& gameHandler) { game::EquipSlot bagSlot = static_cast(static_cast(game::EquipSlot::BAG1) + i); const auto& bagItem = inv.getEquipSlot(bagSlot); - GLuint bagIcon = 0; + VkDescriptorSet bagIcon = VK_NULL_HANDLE; if (!bagItem.empty() && bagItem.item.displayInfoId != 0) { bagIcon = inventoryScreen.getItemIcon(bagItem.item.displayInfoId); } @@ -3844,7 +3838,7 @@ void GameScreen::renderBagBar(game::GameHandler& gameHandler) { auto pickedEquip = static_cast( static_cast(game::EquipSlot::BAG1) + bagBarPickedSlot_); const auto& pickedItem = inv2.getEquipSlot(pickedEquip); - GLuint pickedIcon = 0; + VkDescriptorSet pickedIcon = VK_NULL_HANDLE; if (!pickedItem.empty() && pickedItem.item.displayInfoId != 0) { pickedIcon = inventoryScreen.getItemIcon(pickedItem.item.displayInfoId); } @@ -4429,7 +4423,7 @@ void GameScreen::renderBuffBar(game::GameHandler& gameHandler) { ImVec4 borderColor = isBuff ? ImVec4(0.2f, 0.8f, 0.2f, 0.9f) : ImVec4(0.8f, 0.2f, 0.2f, 0.9f); // Try to get spell icon - GLuint iconTex = 0; + VkDescriptorSet iconTex = VK_NULL_HANDLE; if (assetMgr) { iconTex = getSpellIcon(aura.spellId, assetMgr); } @@ -4534,7 +4528,7 @@ void GameScreen::renderLootWindow(game::GameHandler& gameHandler) { // Get item icon uint32_t displayId = item.displayInfoId; if (displayId == 0 && info) displayId = info->displayInfoId; - GLuint iconTex = inventoryScreen.getItemIcon(displayId); + VkDescriptorSet iconTex = inventoryScreen.getItemIcon(displayId); ImVec2 cursor = ImGui::GetCursorScreenPos(); float rowH = std::max(iconSize, ImGui::GetTextLineHeight() * 2.0f); @@ -4963,7 +4957,7 @@ void GameScreen::renderQuestOfferRewardWindow(game::GameHandler& gameHandler) { bool selected = (selectedChoice == static_cast(i)); // Get item icon if we have displayInfoId - uint32_t iconTex = 0; + VkDescriptorSet iconTex = VK_NULL_HANDLE; if (info && info->valid && info->displayInfoId != 0) { iconTex = inventoryScreen.getItemIcon(info->displayInfoId); } diff --git a/src/ui/inventory_screen.cpp b/src/ui/inventory_screen.cpp index 4879f89d..56bdc3ec 100644 --- a/src/ui/inventory_screen.cpp +++ b/src/ui/inventory_screen.cpp @@ -1,6 +1,7 @@ #include "ui/inventory_screen.hpp" #include "game/game_handler.hpp" #include "core/application.hpp" +#include "rendering/vk_context.hpp" #include "core/input.hpp" #include "rendering/character_preview.hpp" #include "rendering/character_renderer.hpp" @@ -72,10 +73,7 @@ const game::ItemSlot* findComparableEquipped(const game::Inventory& inventory, u } // namespace InventoryScreen::~InventoryScreen() { - // Clean up icon textures - for (auto& [id, tex] : iconCache_) { - if (tex) glDeleteTextures(1, &tex); - } + // Vulkan textures are owned by VkContext and cleaned up on shutdown iconCache_.clear(); } @@ -95,8 +93,8 @@ ImVec4 InventoryScreen::getQualityColor(game::ItemQuality quality) { // Item Icon Loading // ============================================================ -GLuint InventoryScreen::getItemIcon(uint32_t displayInfoId) { - if (displayInfoId == 0 || !assetManager_) return 0; +VkDescriptorSet InventoryScreen::getItemIcon(uint32_t displayInfoId) { + if (displayInfoId == 0 || !assetManager_) return VK_NULL_HANDLE; auto it = iconCache_.find(displayInfoId); if (it != iconCache_.end()) return it->second; @@ -104,50 +102,48 @@ GLuint InventoryScreen::getItemIcon(uint32_t displayInfoId) { // Load ItemDisplayInfo.dbc auto displayInfoDbc = assetManager_->loadDBC("ItemDisplayInfo.dbc"); if (!displayInfoDbc) { - iconCache_[displayInfoId] = 0; - return 0; + iconCache_[displayInfoId] = VK_NULL_HANDLE; + return VK_NULL_HANDLE; } int32_t recIdx = displayInfoDbc->findRecordById(displayInfoId); if (recIdx < 0) { - iconCache_[displayInfoId] = 0; - return 0; + iconCache_[displayInfoId] = VK_NULL_HANDLE; + return VK_NULL_HANDLE; } // Field 5 = inventoryIcon_1 const auto* dispL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("ItemDisplayInfo") : nullptr; std::string iconName = displayInfoDbc->getString(static_cast(recIdx), dispL ? (*dispL)["InventoryIcon"] : 5); if (iconName.empty()) { - iconCache_[displayInfoId] = 0; - return 0; + iconCache_[displayInfoId] = VK_NULL_HANDLE; + return VK_NULL_HANDLE; } std::string iconPath = "Interface\\Icons\\" + iconName + ".blp"; auto blpData = assetManager_->readFile(iconPath); if (blpData.empty()) { - iconCache_[displayInfoId] = 0; - return 0; + iconCache_[displayInfoId] = VK_NULL_HANDLE; + return VK_NULL_HANDLE; } auto image = pipeline::BLPLoader::load(blpData); if (!image.isValid()) { - iconCache_[displayInfoId] = 0; - return 0; + iconCache_[displayInfoId] = VK_NULL_HANDLE; + return VK_NULL_HANDLE; } - GLuint texId = 0; - glGenTextures(1, &texId); - glBindTexture(GL_TEXTURE_2D, texId); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image.width, image.height, 0, - GL_RGBA, GL_UNSIGNED_BYTE, image.data.data()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glBindTexture(GL_TEXTURE_2D, 0); + // Upload to Vulkan via VkContext + auto* window = core::Application::getInstance().getWindow(); + auto* vkCtx = window ? window->getVkContext() : nullptr; + if (!vkCtx) { + iconCache_[displayInfoId] = VK_NULL_HANDLE; + return VK_NULL_HANDLE; + } - iconCache_[displayInfoId] = texId; - return texId; + VkDescriptorSet ds = vkCtx->uploadImGuiTexture(image.data.data(), image.width, image.height); + iconCache_[displayInfoId] = ds; + return ds; } // ============================================================ @@ -507,7 +503,7 @@ void InventoryScreen::renderHeldItem() { ImU32 borderCol = ImGui::ColorConvertFloat4ToU32(qColor); // Try to show icon - GLuint iconTex = getItemIcon(heldItem.displayInfoId); + VkDescriptorSet iconTex = getItemIcon(heldItem.displayInfoId); if (iconTex) { drawList->AddImage((ImTextureID)(uintptr_t)iconTex, pos, ImVec2(pos.x + size, pos.y + size)); @@ -1351,7 +1347,7 @@ void InventoryScreen::renderItemSlot(game::Inventory& inventory, const game::Ite } // Try to show icon - GLuint iconTex = getItemIcon(item.displayInfoId); + VkDescriptorSet iconTex = getItemIcon(item.displayInfoId); if (iconTex) { drawList->AddImage((ImTextureID)(uintptr_t)iconTex, pos, ImVec2(pos.x + size, pos.y + size)); @@ -1559,7 +1555,7 @@ void InventoryScreen::renderItemTooltip(const game::ItemDef& item, const game::I if (const game::ItemSlot* eq = findComparableEquipped(*inventory, item.inventoryType)) { ImGui::Separator(); ImGui::TextDisabled("Equipped:"); - GLuint eqIcon = getItemIcon(eq->item.displayInfoId); + VkDescriptorSet eqIcon = getItemIcon(eq->item.displayInfoId); if (eqIcon) { ImGui::Image((ImTextureID)(uintptr_t)eqIcon, ImVec2(18.0f, 18.0f)); ImGui::SameLine(); diff --git a/src/ui/spellbook_screen.cpp b/src/ui/spellbook_screen.cpp index 0cccac44..9fec4524 100644 --- a/src/ui/spellbook_screen.cpp +++ b/src/ui/spellbook_screen.cpp @@ -1,6 +1,7 @@ #include "ui/spellbook_screen.hpp" #include "core/input.hpp" #include "core/application.hpp" +#include "rendering/vk_context.hpp" #include "pipeline/asset_manager.hpp" #include "pipeline/dbc_loader.hpp" #include "pipeline/blp_loader.hpp" @@ -201,44 +202,41 @@ void SpellbookScreen::categorizeSpells(const std::unordered_set& known lastKnownSpellCount = knownSpells.size(); } -GLuint SpellbookScreen::getSpellIcon(uint32_t iconId, pipeline::AssetManager* assetManager) { - if (iconId == 0 || !assetManager) return 0; +VkDescriptorSet SpellbookScreen::getSpellIcon(uint32_t iconId, pipeline::AssetManager* assetManager) { + if (iconId == 0 || !assetManager) return VK_NULL_HANDLE; auto cit = spellIconCache.find(iconId); if (cit != spellIconCache.end()) return cit->second; auto pit = spellIconPaths.find(iconId); if (pit == spellIconPaths.end()) { - spellIconCache[iconId] = 0; - return 0; + spellIconCache[iconId] = VK_NULL_HANDLE; + return VK_NULL_HANDLE; } std::string iconPath = pit->second + ".blp"; auto blpData = assetManager->readFile(iconPath); if (blpData.empty()) { - spellIconCache[iconId] = 0; - return 0; + spellIconCache[iconId] = VK_NULL_HANDLE; + return VK_NULL_HANDLE; } auto image = pipeline::BLPLoader::load(blpData); if (!image.isValid()) { - spellIconCache[iconId] = 0; - return 0; + spellIconCache[iconId] = VK_NULL_HANDLE; + return VK_NULL_HANDLE; } - GLuint texId = 0; - glGenTextures(1, &texId); - glBindTexture(GL_TEXTURE_2D, texId); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image.width, image.height, 0, - GL_RGBA, GL_UNSIGNED_BYTE, image.data.data()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glBindTexture(GL_TEXTURE_2D, 0); + auto* window = core::Application::getInstance().getWindow(); + auto* vkCtx = window ? window->getVkContext() : nullptr; + if (!vkCtx) { + spellIconCache[iconId] = VK_NULL_HANDLE; + return VK_NULL_HANDLE; + } - spellIconCache[iconId] = texId; - return texId; + VkDescriptorSet ds = vkCtx->uploadImGuiTexture(image.data.data(), image.width, image.height); + spellIconCache[iconId] = ds; + return ds; } const SpellInfo* SpellbookScreen::getSpellInfo(uint32_t spellId) const { @@ -320,7 +318,7 @@ void SpellbookScreen::render(game::GameHandler& gameHandler, pipeline::AssetMana bool isPassive = info->isPassive(); bool isDim = isPassive || onCooldown; - GLuint iconTex = getSpellIcon(info->iconId, assetManager); + VkDescriptorSet iconTex = getSpellIcon(info->iconId, assetManager); // Selectable consumes clicks properly (prevents window drag) ImGui::Selectable("##row", false, diff --git a/src/ui/talent_screen.cpp b/src/ui/talent_screen.cpp index 0275433d..a2906695 100644 --- a/src/ui/talent_screen.cpp +++ b/src/ui/talent_screen.cpp @@ -2,11 +2,11 @@ #include "core/input.hpp" #include "core/application.hpp" #include "core/logger.hpp" +#include "rendering/vk_context.hpp" #include "pipeline/asset_manager.hpp" #include "pipeline/blp_loader.hpp" #include "pipeline/dbc_layout.hpp" #include -#include namespace wowee { namespace ui { @@ -260,7 +260,7 @@ void TalentScreen::renderTalent(game::GameHandler& gameHandler, // Get spell icon uint32_t spellId = talent.rankSpells[0]; - GLuint iconTex = 0; + VkDescriptorSet iconTex = VK_NULL_HANDLE; if (spellId != 0) { auto it = spellIconIds.find(spellId); if (it != spellIconIds.end()) { @@ -491,47 +491,41 @@ void TalentScreen::loadSpellIconDBC(pipeline::AssetManager* assetManager) { LOG_INFO("Talent screen: Loaded ", spellIconPaths.size(), " spell icon paths from SpellIcon.dbc"); } -GLuint TalentScreen::getSpellIcon(uint32_t iconId, pipeline::AssetManager* assetManager) { - if (iconId == 0 || !assetManager) return 0; +VkDescriptorSet TalentScreen::getSpellIcon(uint32_t iconId, pipeline::AssetManager* assetManager) { + if (iconId == 0 || !assetManager) return VK_NULL_HANDLE; - // Check cache auto cit = spellIconCache.find(iconId); if (cit != spellIconCache.end()) return cit->second; - // Look up icon path auto pit = spellIconPaths.find(iconId); if (pit == spellIconPaths.end()) { - spellIconCache[iconId] = 0; - return 0; + spellIconCache[iconId] = VK_NULL_HANDLE; + return VK_NULL_HANDLE; } - // Load BLP file std::string iconPath = pit->second + ".blp"; auto blpData = assetManager->readFile(iconPath); if (blpData.empty()) { - spellIconCache[iconId] = 0; - return 0; + spellIconCache[iconId] = VK_NULL_HANDLE; + return VK_NULL_HANDLE; } - // Decode BLP auto image = pipeline::BLPLoader::load(blpData); if (!image.isValid()) { - spellIconCache[iconId] = 0; - return 0; + spellIconCache[iconId] = VK_NULL_HANDLE; + return VK_NULL_HANDLE; } - // Create OpenGL texture - GLuint texId = 0; - glGenTextures(1, &texId); - glBindTexture(GL_TEXTURE_2D, texId); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image.width, image.height, 0, - GL_RGBA, GL_UNSIGNED_BYTE, image.data.data()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glBindTexture(GL_TEXTURE_2D, 0); + auto* window = core::Application::getInstance().getWindow(); + auto* vkCtx = window ? window->getVkContext() : nullptr; + if (!vkCtx) { + spellIconCache[iconId] = VK_NULL_HANDLE; + return VK_NULL_HANDLE; + } - spellIconCache[iconId] = texId; - return texId; + VkDescriptorSet ds = vkCtx->uploadImGuiTexture(image.data.data(), image.width, image.height); + spellIconCache[iconId] = ds; + return ds; } }} // namespace wowee::ui