2026-02-03 13:33:31 -08:00
|
|
|
#include "rendering/loading_screen.hpp"
|
2026-02-21 19:41:21 -08:00
|
|
|
#include "rendering/vk_context.hpp"
|
2026-02-03 13:33:31 -08:00
|
|
|
#include "core/logger.hpp"
|
2026-02-06 14:43:18 -08:00
|
|
|
#include <imgui.h>
|
2026-02-06 14:49:53 -08:00
|
|
|
#include <imgui_internal.h>
|
2026-02-21 19:41:21 -08:00
|
|
|
#include <imgui_impl_vulkan.h>
|
2026-02-06 14:43:18 -08:00
|
|
|
#include <imgui_impl_sdl2.h>
|
2026-02-25 03:45:13 -08:00
|
|
|
#include <SDL2/SDL.h>
|
2026-02-03 13:33:31 -08:00
|
|
|
#include <random>
|
|
|
|
|
#include <chrono>
|
2026-02-06 14:43:18 -08:00
|
|
|
#include <cstdio>
|
2026-02-03 13:33:31 -08:00
|
|
|
|
|
|
|
|
#define STB_IMAGE_IMPLEMENTATION
|
|
|
|
|
#include "stb_image.h"
|
|
|
|
|
|
|
|
|
|
namespace wowee {
|
|
|
|
|
namespace rendering {
|
|
|
|
|
|
|
|
|
|
LoadingScreen::LoadingScreen() {
|
2026-02-22 02:59:24 -08:00
|
|
|
imagePaths.push_back("assets/krayonload.png");
|
2026-02-03 13:33:31 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LoadingScreen::~LoadingScreen() {
|
|
|
|
|
shutdown();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool LoadingScreen::initialize() {
|
2026-02-21 19:41:21 -08:00
|
|
|
LOG_INFO("Initializing loading screen (Vulkan/ImGui)");
|
2026-02-03 13:33:31 -08:00
|
|
|
selectRandomImage();
|
|
|
|
|
LOG_INFO("Loading screen initialized");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadingScreen::shutdown() {
|
2026-02-21 19:41:21 -08:00
|
|
|
if (vkCtx && bgImage) {
|
|
|
|
|
VkDevice device = vkCtx->getDevice();
|
|
|
|
|
vkDeviceWaitIdle(device);
|
|
|
|
|
|
|
|
|
|
if (bgDescriptorSet) {
|
|
|
|
|
// ImGui manages descriptor set lifetime
|
|
|
|
|
bgDescriptorSet = VK_NULL_HANDLE;
|
|
|
|
|
}
|
|
|
|
|
if (bgSampler) {
|
|
|
|
|
vkDestroySampler(device, bgSampler, nullptr);
|
|
|
|
|
bgSampler = VK_NULL_HANDLE;
|
|
|
|
|
}
|
|
|
|
|
if (bgImageView) {
|
|
|
|
|
vkDestroyImageView(device, bgImageView, nullptr);
|
|
|
|
|
bgImageView = VK_NULL_HANDLE;
|
|
|
|
|
}
|
|
|
|
|
if (bgImage) {
|
|
|
|
|
vkDestroyImage(device, bgImage, nullptr);
|
|
|
|
|
bgImage = VK_NULL_HANDLE;
|
|
|
|
|
}
|
|
|
|
|
if (bgMemory) {
|
|
|
|
|
vkFreeMemory(device, bgMemory, nullptr);
|
|
|
|
|
bgMemory = VK_NULL_HANDLE;
|
|
|
|
|
}
|
2026-02-06 14:43:18 -08:00
|
|
|
}
|
2026-02-03 13:33:31 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadingScreen::selectRandomImage() {
|
|
|
|
|
if (imagePaths.empty()) return;
|
|
|
|
|
|
|
|
|
|
unsigned seed = static_cast<unsigned>(
|
|
|
|
|
std::chrono::system_clock::now().time_since_epoch().count());
|
|
|
|
|
std::default_random_engine generator(seed);
|
|
|
|
|
std::uniform_int_distribution<int> distribution(0, imagePaths.size() - 1);
|
|
|
|
|
|
|
|
|
|
currentImageIndex = distribution(generator);
|
|
|
|
|
LOG_INFO("Selected loading screen: ", imagePaths[currentImageIndex]);
|
|
|
|
|
|
|
|
|
|
loadImage(imagePaths[currentImageIndex]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
static uint32_t findMemoryType(VkPhysicalDevice physDevice, uint32_t typeFilter, VkMemoryPropertyFlags properties) {
|
|
|
|
|
VkPhysicalDeviceMemoryProperties memProperties;
|
|
|
|
|
vkGetPhysicalDeviceMemoryProperties(physDevice, &memProperties);
|
|
|
|
|
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
|
|
|
|
|
if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-03 13:33:31 -08:00
|
|
|
bool LoadingScreen::loadImage(const std::string& path) {
|
2026-02-21 19:41:21 -08:00
|
|
|
if (!vkCtx) {
|
|
|
|
|
LOG_WARNING("No VkContext for loading screen image");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clean up old image
|
|
|
|
|
if (bgImage) {
|
|
|
|
|
VkDevice device = vkCtx->getDevice();
|
|
|
|
|
vkDeviceWaitIdle(device);
|
|
|
|
|
if (bgSampler) { vkDestroySampler(device, bgSampler, nullptr); bgSampler = VK_NULL_HANDLE; }
|
|
|
|
|
if (bgImageView) { vkDestroyImageView(device, bgImageView, nullptr); bgImageView = VK_NULL_HANDLE; }
|
|
|
|
|
if (bgImage) { vkDestroyImage(device, bgImage, nullptr); bgImage = VK_NULL_HANDLE; }
|
|
|
|
|
if (bgMemory) { vkFreeMemory(device, bgMemory, nullptr); bgMemory = VK_NULL_HANDLE; }
|
|
|
|
|
bgDescriptorSet = VK_NULL_HANDLE;
|
2026-02-03 13:33:31 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int channels;
|
2026-02-21 19:41:21 -08:00
|
|
|
stbi_set_flip_vertically_on_load(false); // ImGui expects top-down
|
2026-02-03 13:33:31 -08:00
|
|
|
unsigned char* data = stbi_load(path.c_str(), &imageWidth, &imageHeight, &channels, 4);
|
|
|
|
|
|
|
|
|
|
if (!data) {
|
|
|
|
|
LOG_ERROR("Failed to load loading screen image: ", path);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_INFO("Loaded loading screen image: ", imageWidth, "x", imageHeight);
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
VkDevice device = vkCtx->getDevice();
|
|
|
|
|
VkPhysicalDevice physDevice = vkCtx->getPhysicalDevice();
|
|
|
|
|
VkDeviceSize imageSize = static_cast<VkDeviceSize>(imageWidth) * imageHeight * 4;
|
2026-02-03 13:33:31 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Create 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;
|
|
|
|
|
vkCreateBuffer(device, &bufInfo, nullptr, &stagingBuffer);
|
|
|
|
|
|
|
|
|
|
VkMemoryRequirements memReqs;
|
|
|
|
|
vkGetBufferMemoryRequirements(device, stagingBuffer, &memReqs);
|
|
|
|
|
|
|
|
|
|
VkMemoryAllocateInfo allocInfo{};
|
|
|
|
|
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
|
|
|
|
allocInfo.allocationSize = memReqs.size;
|
|
|
|
|
allocInfo.memoryTypeIndex = findMemoryType(physDevice, memReqs.memoryTypeBits,
|
|
|
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
|
|
|
|
vkAllocateMemory(device, &allocInfo, nullptr, &stagingMemory);
|
|
|
|
|
vkBindBufferMemory(device, stagingBuffer, stagingMemory, 0);
|
|
|
|
|
|
|
|
|
|
void* mapped;
|
|
|
|
|
vkMapMemory(device, stagingMemory, 0, imageSize, 0, &mapped);
|
|
|
|
|
memcpy(mapped, data, imageSize);
|
|
|
|
|
vkUnmapMemory(device, stagingMemory);
|
|
|
|
|
}
|
2026-02-03 13:33:31 -08:00
|
|
|
|
|
|
|
|
stbi_image_free(data);
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Create image
|
|
|
|
|
{
|
|
|
|
|
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<uint32_t>(imageWidth), static_cast<uint32_t>(imageHeight), 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;
|
|
|
|
|
vkCreateImage(device, &imgInfo, nullptr, &bgImage);
|
|
|
|
|
|
|
|
|
|
VkMemoryRequirements memReqs;
|
|
|
|
|
vkGetImageMemoryRequirements(device, bgImage, &memReqs);
|
|
|
|
|
|
|
|
|
|
VkMemoryAllocateInfo allocInfo{};
|
|
|
|
|
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
|
|
|
|
allocInfo.allocationSize = memReqs.size;
|
|
|
|
|
allocInfo.memoryTypeIndex = findMemoryType(physDevice, memReqs.memoryTypeBits,
|
|
|
|
|
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
|
|
|
|
vkAllocateMemory(device, &allocInfo, nullptr, &bgMemory);
|
|
|
|
|
vkBindImageMemory(device, bgImage, bgMemory, 0);
|
|
|
|
|
}
|
2026-02-03 13:33:31 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Transfer: transition, copy, transition
|
|
|
|
|
vkCtx->immediateSubmit([&](VkCommandBuffer cmd) {
|
|
|
|
|
// Transition to transfer dst
|
|
|
|
|
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 = bgImage;
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
// Copy buffer to image
|
|
|
|
|
VkBufferImageCopy region{};
|
|
|
|
|
region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
|
|
|
|
region.imageExtent = {static_cast<uint32_t>(imageWidth), static_cast<uint32_t>(imageHeight), 1};
|
|
|
|
|
vkCmdCopyBufferToImage(cmd, stagingBuffer, bgImage,
|
|
|
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
|
|
|
|
|
|
|
|
|
// Transition to shader read
|
|
|
|
|
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
|
|
|
|
|
{
|
|
|
|
|
VkImageViewCreateInfo viewInfo{};
|
|
|
|
|
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
|
|
|
|
viewInfo.image = bgImage;
|
|
|
|
|
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
|
|
|
viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
|
|
|
|
|
viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
|
|
|
|
vkCreateImageView(device, &viewInfo, nullptr, &bgImageView);
|
|
|
|
|
}
|
2026-02-06 14:43:18 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Create sampler
|
|
|
|
|
{
|
|
|
|
|
VkSamplerCreateInfo samplerInfo{};
|
|
|
|
|
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
|
|
|
|
samplerInfo.magFilter = VK_FILTER_LINEAR;
|
|
|
|
|
samplerInfo.minFilter = VK_FILTER_LINEAR;
|
|
|
|
|
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
|
|
|
|
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
|
|
|
|
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
|
|
|
|
vkCreateSampler(device, &samplerInfo, nullptr, &bgSampler);
|
|
|
|
|
}
|
2026-02-06 14:43:18 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Register with ImGui as a texture
|
|
|
|
|
bgDescriptorSet = ImGui_ImplVulkan_AddTexture(bgSampler, bgImageView,
|
|
|
|
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
2026-02-06 14:43:18 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
return true;
|
2026-02-06 14:43:18 -08:00
|
|
|
}
|
|
|
|
|
|
2026-03-07 18:40:24 -08:00
|
|
|
void LoadingScreen::renderOverlay() {
|
|
|
|
|
// Draw loading screen content as ImGui overlay within an existing ImGui frame.
|
|
|
|
|
// Caller is responsible for ImGui NewFrame/Render and Vulkan frame management.
|
|
|
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
|
|
|
float screenW = io.DisplaySize.x;
|
|
|
|
|
float screenH = io.DisplaySize.y;
|
|
|
|
|
|
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
|
|
|
|
ImGui::SetNextWindowSize(ImVec2(screenW, screenH));
|
|
|
|
|
ImGui::Begin("##LoadingScreenOverlay", nullptr,
|
|
|
|
|
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
|
|
|
|
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar |
|
|
|
|
|
ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoBackground |
|
|
|
|
|
ImGuiWindowFlags_NoBringToFrontOnFocus);
|
|
|
|
|
|
|
|
|
|
if (bgDescriptorSet) {
|
|
|
|
|
ImGui::GetWindowDrawList()->AddImage(
|
|
|
|
|
reinterpret_cast<ImTextureID>(bgDescriptorSet),
|
|
|
|
|
ImVec2(0, 0), ImVec2(screenW, screenH));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Progress bar
|
|
|
|
|
{
|
|
|
|
|
const float barWidthFrac = 0.6f;
|
|
|
|
|
const float barHeight = 6.0f;
|
|
|
|
|
const float barY = screenH * 0.06f;
|
|
|
|
|
float barX = screenW * (0.5f - barWidthFrac * 0.5f);
|
|
|
|
|
float barW = screenW * barWidthFrac;
|
|
|
|
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
|
|
|
|
drawList->AddRectFilled(ImVec2(barX, barY), ImVec2(barX + barW, barY + barHeight),
|
|
|
|
|
IM_COL32(25, 25, 25, 200), 2.0f);
|
|
|
|
|
if (loadProgress > 0.001f) {
|
|
|
|
|
drawList->AddRectFilled(ImVec2(barX, barY), ImVec2(barX + barW * loadProgress, barY + barHeight),
|
|
|
|
|
IM_COL32(199, 156, 33, 255), 2.0f);
|
|
|
|
|
}
|
|
|
|
|
drawList->AddRect(ImVec2(barX - 1, barY - 1), ImVec2(barX + barW + 1, barY + barHeight + 1),
|
|
|
|
|
IM_COL32(140, 110, 25, 255), 2.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Percentage text
|
|
|
|
|
{
|
|
|
|
|
char pctBuf[32];
|
|
|
|
|
snprintf(pctBuf, sizeof(pctBuf), "%d%%", static_cast<int>(loadProgress * 100.0f));
|
|
|
|
|
float textY = screenH * 0.06f - 20.0f;
|
|
|
|
|
ImVec2 pctSize = ImGui::CalcTextSize(pctBuf);
|
|
|
|
|
ImGui::SetCursorPos(ImVec2((screenW - pctSize.x) * 0.5f, textY));
|
|
|
|
|
ImGui::TextColored(ImVec4(0.0f, 0.0f, 0.0f, 1.0f), "%s", pctBuf);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Status text
|
|
|
|
|
{
|
|
|
|
|
float statusY = screenH * 0.06f + 14.0f;
|
|
|
|
|
ImVec2 statusSize = ImGui::CalcTextSize(statusText.c_str());
|
|
|
|
|
ImGui::SetCursorPos(ImVec2((screenW - statusSize.x) * 0.5f, statusY));
|
|
|
|
|
ImGui::TextColored(ImVec4(0.0f, 0.0f, 0.0f, 1.0f), "%s", statusText.c_str());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui::End();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-03 13:33:31 -08:00
|
|
|
void LoadingScreen::render() {
|
2026-02-21 19:41:21 -08:00
|
|
|
// If a frame is already in progress (e.g. called from a UI callback),
|
|
|
|
|
// end it before starting our own
|
|
|
|
|
ImGuiContext* ctx = ImGui::GetCurrentContext();
|
|
|
|
|
if (ctx && ctx->FrameCount >= 0 && ctx->WithinFrameScope) {
|
|
|
|
|
ImGui::EndFrame();
|
|
|
|
|
}
|
2026-02-03 13:33:31 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
|
|
|
float screenW = io.DisplaySize.x;
|
|
|
|
|
float screenH = io.DisplaySize.y;
|
2026-02-03 13:33:31 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
ImGui_ImplVulkan_NewFrame();
|
|
|
|
|
ImGui_ImplSDL2_NewFrame();
|
|
|
|
|
ImGui::NewFrame();
|
|
|
|
|
|
|
|
|
|
// Invisible fullscreen window
|
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
|
|
|
|
ImGui::SetNextWindowSize(ImVec2(screenW, screenH));
|
|
|
|
|
ImGui::Begin("##LoadingScreen", nullptr,
|
|
|
|
|
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
|
|
|
|
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar |
|
|
|
|
|
ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoBackground |
|
|
|
|
|
ImGuiWindowFlags_NoBringToFrontOnFocus);
|
2026-02-03 13:33:31 -08:00
|
|
|
|
2026-02-06 14:43:18 -08:00
|
|
|
// Draw background image
|
2026-02-21 19:41:21 -08:00
|
|
|
if (bgDescriptorSet) {
|
|
|
|
|
ImGui::GetWindowDrawList()->AddImage(
|
|
|
|
|
reinterpret_cast<ImTextureID>(bgDescriptorSet),
|
|
|
|
|
ImVec2(0, 0), ImVec2(screenW, screenH));
|
2026-02-06 14:43:18 -08:00
|
|
|
}
|
2026-02-03 13:33:31 -08:00
|
|
|
|
2026-02-23 06:22:30 -08:00
|
|
|
// Progress bar (top of screen)
|
2026-02-21 19:41:21 -08:00
|
|
|
{
|
|
|
|
|
const float barWidthFrac = 0.6f;
|
|
|
|
|
const float barHeight = 6.0f;
|
2026-02-23 06:22:30 -08:00
|
|
|
const float barY = screenH * 0.06f;
|
2026-02-21 19:41:21 -08:00
|
|
|
float barX = screenW * (0.5f - barWidthFrac * 0.5f);
|
|
|
|
|
float barW = screenW * barWidthFrac;
|
2026-02-06 14:43:18 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
2026-02-06 14:43:18 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Background
|
|
|
|
|
drawList->AddRectFilled(
|
|
|
|
|
ImVec2(barX, barY),
|
|
|
|
|
ImVec2(barX + barW, barY + barHeight),
|
|
|
|
|
IM_COL32(25, 25, 25, 200), 2.0f);
|
|
|
|
|
|
|
|
|
|
// Fill (gold)
|
|
|
|
|
if (loadProgress > 0.001f) {
|
|
|
|
|
drawList->AddRectFilled(
|
|
|
|
|
ImVec2(barX, barY),
|
|
|
|
|
ImVec2(barX + barW * loadProgress, barY + barHeight),
|
|
|
|
|
IM_COL32(199, 156, 33, 255), 2.0f);
|
2026-02-06 14:43:18 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Border
|
|
|
|
|
drawList->AddRect(
|
|
|
|
|
ImVec2(barX - 1, barY - 1),
|
|
|
|
|
ImVec2(barX + barW + 1, barY + barHeight + 1),
|
|
|
|
|
IM_COL32(140, 110, 25, 255), 2.0f);
|
2026-02-06 14:43:18 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Percentage text above bar
|
2026-02-06 14:43:18 -08:00
|
|
|
{
|
|
|
|
|
char pctBuf[32];
|
|
|
|
|
snprintf(pctBuf, sizeof(pctBuf), "%d%%", static_cast<int>(loadProgress * 100.0f));
|
2026-02-23 06:22:30 -08:00
|
|
|
float barCenterY = screenH * 0.06f;
|
2026-02-21 19:41:21 -08:00
|
|
|
float textY = barCenterY - 20.0f;
|
2026-02-06 14:43:18 -08:00
|
|
|
|
|
|
|
|
ImVec2 pctSize = ImGui::CalcTextSize(pctBuf);
|
|
|
|
|
ImGui::SetCursorPos(ImVec2((screenW - pctSize.x) * 0.5f, textY));
|
2026-02-23 06:22:30 -08:00
|
|
|
ImGui::TextColored(ImVec4(0.0f, 0.0f, 0.0f, 1.0f), "%s", pctBuf);
|
2026-02-21 19:41:21 -08:00
|
|
|
}
|
2026-02-06 14:43:18 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Status text below bar
|
|
|
|
|
{
|
2026-02-23 06:22:30 -08:00
|
|
|
float statusY = screenH * 0.06f + 14.0f;
|
2026-02-06 14:43:18 -08:00
|
|
|
ImVec2 statusSize = ImGui::CalcTextSize(statusText.c_str());
|
|
|
|
|
ImGui::SetCursorPos(ImVec2((screenW - statusSize.x) * 0.5f, statusY));
|
2026-02-23 06:22:30 -08:00
|
|
|
ImGui::TextColored(ImVec4(0.0f, 0.0f, 0.0f, 1.0f), "%s", statusText.c_str());
|
2026-02-06 14:43:18 -08:00
|
|
|
}
|
2026-02-03 13:33:31 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
ImGui::End();
|
|
|
|
|
ImGui::Render();
|
|
|
|
|
|
|
|
|
|
// Submit the frame to Vulkan (loading screen runs outside the main render loop)
|
|
|
|
|
if (vkCtx) {
|
2026-02-25 03:45:13 -08:00
|
|
|
// Handle window resize: recreate swapchain before acquiring an image
|
|
|
|
|
if (vkCtx->isSwapchainDirty() && sdlWindow) {
|
|
|
|
|
int w = 0, h = 0;
|
|
|
|
|
SDL_GetWindowSize(sdlWindow, &w, &h);
|
|
|
|
|
if (w > 0 && h > 0) {
|
|
|
|
|
vkCtx->recreateSwapchain(w, h);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
uint32_t imageIndex = 0;
|
|
|
|
|
VkCommandBuffer cmd = vkCtx->beginFrame(imageIndex);
|
|
|
|
|
if (cmd != VK_NULL_HANDLE) {
|
|
|
|
|
// Begin render pass
|
|
|
|
|
VkRenderPassBeginInfo rpInfo{};
|
|
|
|
|
rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
|
|
|
|
rpInfo.renderPass = vkCtx->getImGuiRenderPass();
|
|
|
|
|
rpInfo.framebuffer = vkCtx->getSwapchainFramebuffers()[imageIndex];
|
|
|
|
|
rpInfo.renderArea.offset = {0, 0};
|
|
|
|
|
rpInfo.renderArea.extent = vkCtx->getSwapchainExtent();
|
|
|
|
|
|
2026-02-22 03:32:08 -08:00
|
|
|
// 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;
|
2026-02-21 19:41:21 -08:00
|
|
|
|
|
|
|
|
vkCmdBeginRenderPass(cmd, &rpInfo, VK_SUBPASS_CONTENTS_INLINE);
|
|
|
|
|
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), cmd);
|
|
|
|
|
vkCmdEndRenderPass(cmd);
|
|
|
|
|
|
|
|
|
|
vkCtx->endFrame(cmd, imageIndex);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-03 13:33:31 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace rendering
|
|
|
|
|
} // namespace wowee
|