/* * Vulkan Example - imGui (https://github.com/ocornut/imgui) * * Copyright (C) 2017-2025 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ #include #include "vulkanexamplebase.h" #include "VulkanglTFModel.h" #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif // Forward declaration class VulkanExample; // Procedural Geometry Generation struct ProceduralVertex { glm::vec3 position; glm::vec3 normal; glm::vec2 texCoord; }; struct ProceduralShape { std::vector vertices; std::vector indices; std::string name; int type; // 0=cube, 1=sphere, 2=cylinder, 3=plane, 4=cone, 5=torus // Shape parameters struct { float width = 2.0f, height = 2.0f, depth = 2.0f; int subdivisions = 1; float radius = 1.0f; int segments = 16; float majorRadius = 1.0f, minorRadius = 0.3f; } params; }; class ProceduralGeometry { public: static ProceduralShape generateCube(float width = 4.0f, float height = 4.0f, float depth = 4.0f) { ProceduralShape shape; shape.name = "Cube"; shape.type = 0; shape.params.width = width; shape.params.height = height; shape.params.depth = depth; float w = width * 0.5f; float h = height * 0.5f; float d = depth * 0.5f; // Define 24 vertices (4 per face, 6 faces) shape.vertices = { // Front face {{-w, -h, d}, {0, 0, 1}, {0, 0}}, {{ w, -h, d}, {0, 0, 1}, {1, 0}}, {{ w, h, d}, {0, 0, 1}, {1, 1}}, {{-w, h, d}, {0, 0, 1}, {0, 1}}, // Back face {{ w, -h, -d}, {0, 0, -1}, {0, 0}}, {{-w, -h, -d}, {0, 0, -1}, {1, 0}}, {{-w, h, -d}, {0, 0, -1}, {1, 1}}, {{ w, h, -d}, {0, 0, -1}, {0, 1}}, // Left face {{-w, -h, -d}, {-1, 0, 0}, {0, 0}}, {{-w, -h, d}, {-1, 0, 0}, {1, 0}}, {{-w, h, d}, {-1, 0, 0}, {1, 1}}, {{-w, h, -d}, {-1, 0, 0}, {0, 1}}, // Right face {{ w, -h, d}, {1, 0, 0}, {0, 0}}, {{ w, -h, -d}, {1, 0, 0}, {1, 0}}, {{ w, h, -d}, {1, 0, 0}, {1, 1}}, {{ w, h, d}, {1, 0, 0}, {0, 1}}, // Top face {{-w, h, d}, {0, 1, 0}, {0, 0}}, {{ w, h, d}, {0, 1, 0}, {1, 0}}, {{ w, h, -d}, {0, 1, 0}, {1, 1}}, {{-w, h, -d}, {0, 1, 0}, {0, 1}}, // Bottom face {{-w, -h, -d}, {0, -1, 0}, {0, 0}}, {{ w, -h, -d}, {0, -1, 0}, {1, 0}}, {{ w, -h, d}, {0, -1, 0}, {1, 1}}, {{-w, -h, d}, {0, -1, 0}, {0, 1}} }; // Define indices for 12 triangles (2 per face) shape.indices = { 0,1,2, 0,2,3, // Front 4,5,6, 4,6,7, // Back 8,9,10, 8,10,11, // Left 12,13,14, 12,14,15, // Right 16,17,18, 16,18,19, // Top 20,21,22, 20,22,23 // Bottom }; return shape; } static ProceduralShape generateSphere(float radius = 1.0f, int segments = 16) { ProceduralShape shape; shape.name = "Sphere"; shape.type = 1; shape.params.radius = radius; shape.params.segments = segments; // Generate sphere vertices using spherical coordinates for (int lat = 0; lat <= segments; ++lat) { float theta = lat * M_PI / segments; float sinTheta = sin(theta); float cosTheta = cos(theta); for (int lon = 0; lon <= segments; ++lon) { float phi = lon * 2 * M_PI / segments; float sinPhi = sin(phi); float cosPhi = cos(phi); glm::vec3 pos(radius * sinTheta * cosPhi, radius * cosTheta, radius * sinTheta * sinPhi); glm::vec3 normal = glm::normalize(pos); glm::vec2 texCoord((float)lon / segments, (float)lat / segments); shape.vertices.push_back({pos, normal, texCoord}); } } // Generate indices for (int lat = 0; lat < segments; ++lat) { for (int lon = 0; lon < segments; ++lon) { int first = lat * (segments + 1) + lon; int second = first + segments + 1; shape.indices.push_back(first); shape.indices.push_back(second); shape.indices.push_back(first + 1); shape.indices.push_back(second); shape.indices.push_back(second + 1); shape.indices.push_back(first + 1); } } return shape; } static ProceduralShape generatePlane(float width = 2.0f, float height = 2.0f, int subdivisions = 1) { ProceduralShape shape; shape.name = "Plane"; shape.type = 3; shape.params.width = width; shape.params.height = height; shape.params.subdivisions = subdivisions; float w = width * 0.5f; float h = height * 0.5f; // Simple quad for now shape.vertices = { {{-w, 0, -h}, {0, 1, 0}, {0, 0}}, {{ w, 0, -h}, {0, 1, 0}, {1, 0}}, {{ w, 0, h}, {0, 1, 0}, {1, 1}}, {{-w, 0, h}, {0, 1, 0}, {0, 1}} }; shape.indices = {0, 1, 2, 0, 2, 3}; return shape; } static ProceduralShape generateGrid(float size = 10.0f, int divisions = 10) { ProceduralShape shape; shape.name = "Grid"; shape.type = 6; // Grid type shape.params.width = size; shape.params.subdivisions = divisions; float step = size / divisions; float halfSize = size * 0.5f; // Create grid lines for (int i = 0; i <= divisions; ++i) { float pos = -halfSize + i * step; // Horizontal lines shape.vertices.push_back({{-halfSize, 0, pos}, {0, 1, 0}, {0, 0}}); shape.vertices.push_back({{ halfSize, 0, pos}, {0, 1, 0}, {1, 0}}); // Vertical lines shape.vertices.push_back({{pos, 0, -halfSize}, {0, 1, 0}, {0, 0}}); shape.vertices.push_back({{pos, 0, halfSize}, {0, 1, 0}, {1, 0}}); } // Generate indices for lines for (int i = 0; i < (divisions + 1) * 4; i += 2) { shape.indices.push_back(i); shape.indices.push_back(i + 1); } return shape; } static ProceduralShape generateCone(float radius = 1.0f, float height = 2.0f, int segments = 16) { ProceduralShape shape; shape.name = "Cone"; shape.type = 4; // Cone type shape.params.radius = radius; shape.params.height = height; shape.params.segments = segments; // Add tip vertex shape.vertices.push_back({{0, height * 0.5f, 0}, {0, 1, 0}, {0.5f, 1.0f}}); // Add center vertex for base shape.vertices.push_back({{0, -height * 0.5f, 0}, {0, -1, 0}, {0.5f, 0.0f}}); // Generate base vertices for (int i = 0; i <= segments; ++i) { float angle = (float)i / segments * 2.0f * M_PI; float x = cos(angle) * radius; float z = sin(angle) * radius; float u = (cos(angle) + 1.0f) * 0.5f; float v = (sin(angle) + 1.0f) * 0.5f; // Base vertex shape.vertices.push_back({{x, -height * 0.5f, z}, {0, -1, 0}, {u, v}}); // Side vertex (for side triangles) glm::vec3 sideNormal = glm::normalize(glm::vec3(x, radius / height, z)); shape.vertices.push_back({{x, -height * 0.5f, z}, sideNormal, {(float)i / segments, 0.0f}}); } // Generate indices // Side triangles (tip to base edge) for (int i = 0; i < segments; ++i) { int baseStart = 2 + segments + 1; shape.indices.push_back(0); // tip shape.indices.push_back(baseStart + (i + 1) * 2); shape.indices.push_back(baseStart + i * 2); } // Base triangles for (int i = 0; i < segments; ++i) { shape.indices.push_back(1); // center shape.indices.push_back(2 + i); shape.indices.push_back(2 + ((i + 1) % (segments + 1))); } return shape; } static ProceduralShape generateCylinder(float radius = 1.0f, float height = 2.0f, int segments = 16) { ProceduralShape shape; shape.name = "Cylinder"; shape.type = 5; // Cylinder type shape.params.radius = radius; shape.params.height = height; shape.params.segments = segments; float halfHeight = height * 0.5f; // Add center vertices for caps shape.vertices.push_back({{0, halfHeight, 0}, {0, 1, 0}, {0.5f, 0.5f}}); // top center shape.vertices.push_back({{0, -halfHeight, 0}, {0, -1, 0}, {0.5f, 0.5f}}); // bottom center // Generate side vertices (double for proper normals) for (int i = 0; i <= segments; ++i) { float angle = (float)i / segments * 2.0f * M_PI; float x = cos(angle) * radius; float z = sin(angle) * radius; glm::vec3 normal = glm::normalize(glm::vec3(x, 0, z)); float u = (float)i / segments; // Top vertices shape.vertices.push_back({{x, halfHeight, z}, {0, 1, 0}, {(cos(angle) + 1) * 0.5f, (sin(angle) + 1) * 0.5f}}); // top cap shape.vertices.push_back({{x, halfHeight, z}, normal, {u, 1.0f}}); // top side // Bottom vertices shape.vertices.push_back({{x, -halfHeight, z}, {0, -1, 0}, {(cos(angle) + 1) * 0.5f, (sin(angle) + 1) * 0.5f}}); // bottom cap shape.vertices.push_back({{x, -halfHeight, z}, normal, {u, 0.0f}}); // bottom side } // Generate indices for (int i = 0; i < segments; ++i) { int topCapStart = 2; int bottomCapStart = 4; // Top cap triangles shape.indices.push_back(0); // top center shape.indices.push_back(topCapStart + ((i + 1) % (segments + 1)) * 4); shape.indices.push_back(topCapStart + i * 4); // Bottom cap triangles shape.indices.push_back(1); // bottom center shape.indices.push_back(bottomCapStart + i * 4); shape.indices.push_back(bottomCapStart + ((i + 1) % (segments + 1)) * 4); // Side quads (as two triangles) int topSide1 = topCapStart + 1 + i * 4; int topSide2 = topCapStart + 1 + ((i + 1) % (segments + 1)) * 4; int bottomSide1 = bottomCapStart + 1 + i * 4; int bottomSide2 = bottomCapStart + 1 + ((i + 1) % (segments + 1)) * 4; // First triangle shape.indices.push_back(topSide1); shape.indices.push_back(bottomSide1); shape.indices.push_back(topSide2); // Second triangle shape.indices.push_back(topSide2); shape.indices.push_back(bottomSide1); shape.indices.push_back(bottomSide2); } return shape; } static ProceduralShape generateTorus(float majorRadius = 1.0f, float minorRadius = 0.3f, int majorSegments = 16, int minorSegments = 8) { ProceduralShape shape; shape.name = "Torus"; shape.type = 6; // Torus type shape.params.majorRadius = majorRadius; shape.params.minorRadius = minorRadius; shape.params.segments = majorSegments; shape.params.subdivisions = minorSegments; // Generate vertices for (int i = 0; i <= majorSegments; ++i) { float majorAngle = (float)i / majorSegments * 2.0f * M_PI; float cosMajor = cos(majorAngle); float sinMajor = sin(majorAngle); for (int j = 0; j <= minorSegments; ++j) { float minorAngle = (float)j / minorSegments * 2.0f * M_PI; float cosMinor = cos(minorAngle); float sinMinor = sin(minorAngle); // Calculate position float x = (majorRadius + minorRadius * cosMinor) * cosMajor; float y = minorRadius * sinMinor; float z = (majorRadius + minorRadius * cosMinor) * sinMajor; // Calculate normal glm::vec3 center(majorRadius * cosMajor, 0, majorRadius * sinMajor); glm::vec3 position(x, y, z); glm::vec3 normal = glm::normalize(position - center); // Calculate UV coordinates float u = (float)i / majorSegments; float v = (float)j / minorSegments; shape.vertices.push_back({{x, y, z}, normal, {u, v}}); } } // Generate indices for (int i = 0; i < majorSegments; ++i) { for (int j = 0; j < minorSegments; ++j) { int current = i * (minorSegments + 1) + j; int next = ((i + 1) % (majorSegments + 1)) * (minorSegments + 1) + j; // First triangle shape.indices.push_back(current); shape.indices.push_back(next); shape.indices.push_back(current + 1); // Second triangle shape.indices.push_back(next); shape.indices.push_back(next + 1); shape.indices.push_back(current + 1); } } return shape; } }; // Hierarchical Scene Node structure (based on Sascha's gltfscenerendering) struct SceneNode { SceneNode* parent = nullptr; std::vector children; std::string name; glm::vec3 position = {0.0f, 0.0f, 0.0f}; glm::vec3 rotation = {0.0f, 0.0f, 0.0f}; glm::vec3 scale = {1.0f, 1.0f, 1.0f}; glm::mat4 matrix = glm::mat4(1.0f); bool visible = true; // Procedural shape data ProceduralShape* proceduralShape = nullptr; bool isProceduralShape = false; // Object type for icons/identification enum ObjectType { SCENE_ROOT, PROCEDURAL_OBJECT, MODEL_OBJECT, LIGHT_OBJECT, CAMERA_OBJECT } type = PROCEDURAL_OBJECT; // Constructor SceneNode(const std::string& nodeName = "Object", ObjectType nodeType = PROCEDURAL_OBJECT) : name(nodeName), type(nodeType) {} // Destructor - clean up children ~SceneNode() { for (auto child : children) { delete child; } if (proceduralShape) { delete proceduralShape; } } // Add child to this node void addChild(SceneNode* child) { if (child && child->parent != this) { if (child->parent) { child->parent->removeChild(child); } child->parent = this; children.push_back(child); } } // Remove child from this node void removeChild(SceneNode* child) { auto it = std::find(children.begin(), children.end(), child); if (it != children.end()) { (*it)->parent = nullptr; children.erase(it); } } // Get world transform matrix glm::mat4 getWorldMatrix() const { glm::mat4 nodeMatrix = matrix; SceneNode* currentParent = parent; while (currentParent) { nodeMatrix = currentParent->matrix * nodeMatrix; currentParent = currentParent->parent; } return nodeMatrix; } // Update transform matrix from position, rotation, scale void updateMatrix() { matrix = glm::mat4(1.0f); matrix = glm::translate(matrix, position); matrix = glm::rotate(matrix, glm::radians(rotation.x), glm::vec3(1, 0, 0)); matrix = glm::rotate(matrix, glm::radians(rotation.y), glm::vec3(0, 1, 0)); matrix = glm::rotate(matrix, glm::radians(rotation.z), glm::vec3(0, 0, 1)); matrix = glm::scale(matrix, scale); } }; // Scene management with hierarchical structure struct SceneManager { std::vector rootNodes; SceneNode* selectedNode = nullptr; SceneNode* sceneRoot = nullptr; SceneManager() { // Create clean scene root without default categories sceneRoot = new SceneNode("Scene", SceneNode::SCENE_ROOT); rootNodes.push_back(sceneRoot); } ~SceneManager() { for (auto node : rootNodes) { delete node; } } void addProceduralShape(const ProceduralShape& shape, SceneNode* parent = nullptr) { if (!parent) { // Add directly to scene root for flat hierarchy parent = sceneRoot; } SceneNode* newNode = new SceneNode(shape.name + " " + std::to_string(getObjectCount() + 1), SceneNode::PROCEDURAL_OBJECT); newNode->isProceduralShape = true; newNode->proceduralShape = new ProceduralShape(shape); newNode->updateMatrix(); parent->addChild(newNode); selectedNode = newNode; } SceneNode* findNodeByName(const std::string& name) { for (auto root : rootNodes) { SceneNode* found = findNodeByNameRecursive(root, name); if (found) return found; } return nullptr; } SceneNode* findNodeByNameRecursive(SceneNode* node, const std::string& name) { if (node->name == name) return node; for (auto child : node->children) { SceneNode* found = findNodeByNameRecursive(child, name); if (found) return found; } return nullptr; } int getObjectCount() const { int count = 0; for (auto root : rootNodes) { count += getObjectCountRecursive(root); } return count; } int getObjectCountRecursive(SceneNode* node) const { int count = (node->type == SceneNode::PROCEDURAL_OBJECT || node->type == SceneNode::MODEL_OBJECT) ? 1 : 0; for (auto child : node->children) { count += getObjectCountRecursive(child); } return count; } void deleteNode(SceneNode* node) { if (!node || node == sceneRoot) return; if (selectedNode == node) { selectedNode = nullptr; } if (node->parent) { node->parent->removeChild(node); } else { auto it = std::find(rootNodes.begin(), rootNodes.end(), node); if (it != rootNodes.end()) { rootNodes.erase(it); } } delete node; } void clearScene() { selectedNode = nullptr; // Clear all objects from scene root if (sceneRoot) { for (auto child : sceneRoot->children) { delete child; } sceneRoot->children.clear(); } } } sceneManager; // Options and values to display/toggle from the UI struct UISettings { bool displayModels = false; bool displayBackground = false; bool animateLight = false; float lightSpeed = 0.25f; std::array frameTimes{}; float frameTimeMin = 9999.0f, frameTimeMax = 0.0f; float lightTimer = 0.0f; bool showGrid = true; float gridSize = 10.0f; int gridDivisions = 10; } uiSettings; // ---------------------------------------------------------------------------- // ImGUI class // ---------------------------------------------------------------------------- class ImGUI { private: // Vulkan resources for rendering the UI VkSampler sampler; vks::Buffer vertexBuffer; vks::Buffer indexBuffer; int32_t vertexCount = 0; int32_t indexCount = 0; VkDeviceMemory fontMemory = VK_NULL_HANDLE; VkImage fontImage = VK_NULL_HANDLE; VkImageView fontView = VK_NULL_HANDLE; VkPipelineCache pipelineCache; VkPipelineLayout pipelineLayout; VkPipeline pipeline; VkDescriptorPool descriptorPool; VkDescriptorSetLayout descriptorSetLayout; VkDescriptorSet descriptorSet; vks::VulkanDevice *device; VkPhysicalDeviceDriverProperties driverProperties = {}; VulkanExampleBase *example; ImGuiStyle vulkanStyle; int selectedStyle = 0; public: // UI params are set via push constants struct PushConstBlock { glm::vec2 scale; glm::vec2 translate; } pushConstBlock; ImGUI(VulkanExampleBase *example) : example(example) { device = example->vulkanDevice; ImGui::CreateContext(); //SRS - Set ImGui font and style scale factors to handle retina and other HiDPI displays ImGuiIO& io = ImGui::GetIO(); io.FontGlobalScale = example->ui.scale; ImGuiStyle& style = ImGui::GetStyle(); style.ScaleAllSizes(example->ui.scale); }; ~ImGUI() { ImGui::DestroyContext(); // Release all Vulkan resources required for rendering imGui vertexBuffer.destroy(); indexBuffer.destroy(); vkDestroyImage(device->logicalDevice, fontImage, nullptr); vkDestroyImageView(device->logicalDevice, fontView, nullptr); vkFreeMemory(device->logicalDevice, fontMemory, nullptr); vkDestroySampler(device->logicalDevice, sampler, nullptr); vkDestroyPipelineCache(device->logicalDevice, pipelineCache, nullptr); vkDestroyPipeline(device->logicalDevice, pipeline, nullptr); vkDestroyPipelineLayout(device->logicalDevice, pipelineLayout, nullptr); vkDestroyDescriptorPool(device->logicalDevice, descriptorPool, nullptr); vkDestroyDescriptorSetLayout(device->logicalDevice, descriptorSetLayout, nullptr); } // Initialize styles, keys, etc. void init(float width, float height) { // Color scheme vulkanStyle = ImGui::GetStyle(); vulkanStyle.Colors[ImGuiCol_TitleBg] = ImVec4(1.0f, 0.0f, 0.0f, 0.6f); vulkanStyle.Colors[ImGuiCol_TitleBgActive] = ImVec4(1.0f, 0.0f, 0.0f, 0.8f); vulkanStyle.Colors[ImGuiCol_MenuBarBg] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f); vulkanStyle.Colors[ImGuiCol_Header] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f); vulkanStyle.Colors[ImGuiCol_CheckMark] = ImVec4(0.0f, 1.0f, 0.0f, 1.0f); setStyle(0); // Dimensions ImGuiIO& io = ImGui::GetIO(); io.DisplaySize = ImVec2(width, height); io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); #if defined(_WIN32) // If we directly work with os specific key codes, we need to map special key types like tab io.KeyMap[ImGuiKey_Tab] = VK_TAB; io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT; io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT; io.KeyMap[ImGuiKey_UpArrow] = VK_UP; io.KeyMap[ImGuiKey_DownArrow] = VK_DOWN; io.KeyMap[ImGuiKey_Backspace] = VK_BACK; io.KeyMap[ImGuiKey_Enter] = VK_RETURN; io.KeyMap[ImGuiKey_Space] = VK_SPACE; io.KeyMap[ImGuiKey_Delete] = VK_DELETE; #endif } void setStyle(uint32_t index) { switch (index) { case 0: { ImGuiStyle& style = ImGui::GetStyle(); style = vulkanStyle; break; } case 1: ImGui::StyleColorsClassic(); break; case 2: ImGui::StyleColorsDark(); break; case 3: ImGui::StyleColorsLight(); break; case 4: // Blue theme { ImGuiStyle& style = ImGui::GetStyle(); style = ImGui::GetStyle(); style.Colors[ImGuiCol_TitleBg] = ImVec4(0.0f, 0.3f, 0.8f, 0.6f); style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.0f, 0.4f, 1.0f, 0.8f); style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.0f, 0.2f, 0.6f, 0.4f); style.Colors[ImGuiCol_Header] = ImVec4(0.0f, 0.3f, 0.7f, 0.4f); style.Colors[ImGuiCol_CheckMark] = ImVec4(0.0f, 1.0f, 1.0f, 1.0f); style.Colors[ImGuiCol_WindowBg] = ImVec4(0.05f, 0.05f, 0.15f, 0.9f); break; } case 5: // Green theme { ImGuiStyle& style = ImGui::GetStyle(); style = ImGui::GetStyle(); style.Colors[ImGuiCol_TitleBg] = ImVec4(0.0f, 0.6f, 0.2f, 0.6f); style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.0f, 0.8f, 0.3f, 0.8f); style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.0f, 0.4f, 0.1f, 0.4f); style.Colors[ImGuiCol_Header] = ImVec4(0.0f, 0.5f, 0.2f, 0.4f); style.Colors[ImGuiCol_CheckMark] = ImVec4(0.0f, 1.0f, 0.0f, 1.0f); style.Colors[ImGuiCol_WindowBg] = ImVec4(0.05f, 0.15f, 0.05f, 0.9f); break; } case 6: // Purple theme { ImGuiStyle& style = ImGui::GetStyle(); style = ImGui::GetStyle(); style.Colors[ImGuiCol_TitleBg] = ImVec4(0.5f, 0.0f, 0.8f, 0.6f); style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.7f, 0.0f, 1.0f, 0.8f); style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.3f, 0.0f, 0.6f, 0.4f); style.Colors[ImGuiCol_Header] = ImVec4(0.4f, 0.0f, 0.7f, 0.4f); style.Colors[ImGuiCol_CheckMark] = ImVec4(1.0f, 0.0f, 1.0f, 1.0f); style.Colors[ImGuiCol_WindowBg] = ImVec4(0.1f, 0.05f, 0.15f, 0.9f); break; } } } // Initialize all Vulkan resources used by the ui void initResources(VkRenderPass renderPass, VkQueue copyQueue, const std::string& shadersPath) { ImGuiIO& io = ImGui::GetIO(); // Create font texture unsigned char* fontData; int texWidth, texHeight; io.Fonts->GetTexDataAsRGBA32(&fontData, &texWidth, &texHeight); VkDeviceSize uploadSize = texWidth*texHeight * 4 * sizeof(char); //SRS - Get Vulkan device driver information if available, use later for display if (device->extensionSupported(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME)) { VkPhysicalDeviceProperties2 deviceProperties2 = {}; deviceProperties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; deviceProperties2.pNext = &driverProperties; driverProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES; vkGetPhysicalDeviceProperties2(device->physicalDevice, &deviceProperties2); } // Create target image for copy VkImageCreateInfo imageInfo = vks::initializers::imageCreateInfo(); imageInfo.imageType = VK_IMAGE_TYPE_2D; imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; imageInfo.extent.width = texWidth; imageInfo.extent.height = texHeight; imageInfo.extent.depth = 1; imageInfo.mipLevels = 1; imageInfo.arrayLayers = 1; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageInfo, nullptr, &fontImage)); VkMemoryRequirements memReqs; vkGetImageMemoryRequirements(device->logicalDevice, fontImage, &memReqs); VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo(); memAllocInfo.allocationSize = memReqs.size; memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &fontMemory)); VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, fontImage, fontMemory, 0)); // Image view VkImageViewCreateInfo viewInfo = vks::initializers::imageViewCreateInfo(); viewInfo.image = fontImage; viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM; viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; viewInfo.subresourceRange.levelCount = 1; viewInfo.subresourceRange.layerCount = 1; VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewInfo, nullptr, &fontView)); // Staging buffers for font data upload vks::Buffer stagingBuffer; VK_CHECK_RESULT(device->createBuffer( VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffer, uploadSize)); stagingBuffer.map(); memcpy(stagingBuffer.mapped, fontData, uploadSize); stagingBuffer.unmap(); // Copy buffer data to font image VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); // Prepare for transfer vks::tools::setImageLayout( copyCmd, fontImage, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); // Copy VkBufferImageCopy bufferCopyRegion = {}; bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; bufferCopyRegion.imageSubresource.layerCount = 1; bufferCopyRegion.imageExtent.width = texWidth; bufferCopyRegion.imageExtent.height = texHeight; bufferCopyRegion.imageExtent.depth = 1; vkCmdCopyBufferToImage( copyCmd, stagingBuffer.buffer, fontImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bufferCopyRegion ); // Prepare for shader read vks::tools::setImageLayout( copyCmd, fontImage, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); device->flushCommandBuffer(copyCmd, copyQueue, true); stagingBuffer.destroy(); // Font texture Sampler VkSamplerCreateInfo samplerInfo = vks::initializers::samplerCreateInfo(); samplerInfo.magFilter = VK_FILTER_LINEAR; samplerInfo.minFilter = VK_FILTER_LINEAR; samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_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; samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerInfo, nullptr, &sampler)); // Descriptor pool std::vector poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1) }; VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2); VK_CHECK_RESULT(vkCreateDescriptorPool(device->logicalDevice, &descriptorPoolInfo, nullptr, &descriptorPool)); // Descriptor set layout std::vector setLayoutBindings = { vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0), }; VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device->logicalDevice, &descriptorLayout, nullptr, &descriptorSetLayout)); // Descriptor set VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); VK_CHECK_RESULT(vkAllocateDescriptorSets(device->logicalDevice, &allocInfo, &descriptorSet)); VkDescriptorImageInfo fontDescriptor = vks::initializers::descriptorImageInfo( sampler, fontView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ); std::vector writeDescriptorSets = { vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &fontDescriptor) }; vkUpdateDescriptorSets(device->logicalDevice, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); // Pipeline cache VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {}; pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; VK_CHECK_RESULT(vkCreatePipelineCache(device->logicalDevice, &pipelineCacheCreateInfo, nullptr, &pipelineCache)); // Pipeline layout // Push constants for UI rendering parameters VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(PushConstBlock), 0); VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); pipelineLayoutCreateInfo.pushConstantRangeCount = 1; pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange; VK_CHECK_RESULT(vkCreatePipelineLayout(device->logicalDevice, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); // Setup graphics pipeline for UI rendering VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE); VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE); // Enable blending VkPipelineColorBlendAttachmentState blendAttachmentState{}; blendAttachmentState.blendEnable = VK_TRUE; blendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD; blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD; VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState); VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL); VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0); VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT); std::vector dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); std::array shaderStages{}; VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass); pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; pipelineCreateInfo.pRasterizationState = &rasterizationState; pipelineCreateInfo.pColorBlendState = &colorBlendState; pipelineCreateInfo.pMultisampleState = &multisampleState; pipelineCreateInfo.pViewportState = &viewportState; pipelineCreateInfo.pDepthStencilState = &depthStencilState; pipelineCreateInfo.pDynamicState = &dynamicState; pipelineCreateInfo.stageCount = static_cast(shaderStages.size()); pipelineCreateInfo.pStages = shaderStages.data(); // Vertex bindings an attributes based on ImGui vertex definition std::vector vertexInputBindings = { vks::initializers::vertexInputBindingDescription(0, sizeof(ImDrawVert), VK_VERTEX_INPUT_RATE_VERTEX), }; std::vector vertexInputAttributes = { vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(ImDrawVert, pos)), // Location 0: Position vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(ImDrawVert, uv)), // Location 1: UV vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R8G8B8A8_UNORM, offsetof(ImDrawVert, col)), // Location 0: Color }; VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); vertexInputState.vertexBindingDescriptionCount = static_cast(vertexInputBindings.size()); vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data(); vertexInputState.vertexAttributeDescriptionCount = static_cast(vertexInputAttributes.size()); vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data(); pipelineCreateInfo.pVertexInputState = &vertexInputState; shaderStages[0] = example->loadShader(shadersPath + "imgui/ui.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); shaderStages[1] = example->loadShader(shadersPath + "imgui/ui.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); VK_CHECK_RESULT(vkCreateGraphicsPipelines(device->logicalDevice, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline)); } // Recursive function to render Maya-style outliner nodes void renderSceneNodeInOutliner(SceneNode* node, int depth = 0) { if (!node) return; // Get type-specific icon const char* icon = "📁"; // Default folder switch (node->type) { case SceneNode::PROCEDURAL_OBJECT: switch (node->proceduralShape ? node->proceduralShape->type : -1) { case 0: icon = "📦"; break; // Cube case 1: icon = "🔵"; break; // Sphere case 2: icon = "🔴"; break; // Cylinder case 3: icon = "📏"; break; // Plane case 4: icon = "🔺"; break; // Cone case 6: icon = "🍩"; break; // Torus default: icon = "🔷"; break; // Generic shape } break; case SceneNode::MODEL_OBJECT: icon = "🗿"; break; case SceneNode::LIGHT_OBJECT: icon = "💡"; break; case SceneNode::CAMERA_OBJECT: icon = "📷"; break; case SceneNode::SCENE_ROOT: icon = "📁"; break; } // Create unique ID for ImGui ImGui::PushID(node); ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; if (sceneManager.selectedNode == node) { flags |= ImGuiTreeNodeFlags_Selected; } if (node->children.empty()) { flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; } // Visibility toggle if (node->type != SceneNode::SCENE_ROOT) { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); if (ImGui::SmallButton(node->visible ? "👁" : "🚫")) { node->visible = !node->visible; } ImGui::PopStyleColor(); ImGui::SameLine(); } // Node name with icon bool nodeOpen = false; if (node->children.empty()) { // Leaf node - use Selectable flags &= ~ImGuiTreeNodeFlags_OpenOnArrow; // Remove arrow for leaf nodes bool isSelected = (sceneManager.selectedNode == node); if (ImGui::Selectable((std::string(icon) + " " + node->name).c_str(), isSelected)) { sceneManager.selectedNode = node; } } else { // Branch node - use TreeNode nodeOpen = ImGui::TreeNodeEx((std::string(icon) + " " + node->name).c_str(), flags); if (ImGui::IsItemClicked()) { sceneManager.selectedNode = node; } } // Context menu if (ImGui::BeginPopupContextItem()) { if (node->type != SceneNode::SCENE_ROOT) { if (ImGui::MenuItem("Delete", "Del")) { sceneManager.deleteNode(node); ImGui::PopID(); ImGui::EndPopup(); return; // Node deleted, exit } if (ImGui::MenuItem("Duplicate", "Ctrl+D")) { // TODO: Implement duplication } ImGui::Separator(); } if (ImGui::MenuItem("Create Child")) { // TODO: Show submenu for object types } ImGui::EndPopup(); } // Render children if node is open if (nodeOpen && !node->children.empty()) { for (auto child : node->children) { renderSceneNodeInOutliner(child, depth + 1); } ImGui::TreePop(); } ImGui::PopID(); } // Starts a new imGui frame and sets up windows and ui elements void newFrame(VulkanExampleBase *example, bool updateFrameGraph) { ImGui::NewFrame(); // Menu Bar if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("File")) { if (ImGui::MenuItem("New Scene", "Ctrl+N")) { // Clear scene sceneManager.clearScene(); } if (ImGui::MenuItem("Open Scene", "Ctrl+O")) { // TODO: Implement scene loading } if (ImGui::MenuItem("Save Scene", "Ctrl+S")) { // TODO: Implement scene saving } ImGui::Separator(); if (ImGui::MenuItem("Exit", "Alt+F4")) { example->prepared = false; } ImGui::EndMenu(); } if (ImGui::BeginMenu("Preferences")) { if (ImGui::BeginMenu("UI Theme")) { if (ImGui::MenuItem("Vulkan Red", nullptr, selectedStyle == 0)) { setStyle(0); selectedStyle = 0; } if (ImGui::MenuItem("Classic", nullptr, selectedStyle == 1)) { setStyle(1); selectedStyle = 1; } if (ImGui::MenuItem("Dark", nullptr, selectedStyle == 2)) { setStyle(2); selectedStyle = 2; } if (ImGui::MenuItem("Light", nullptr, selectedStyle == 3)) { setStyle(3); selectedStyle = 3; } if (ImGui::MenuItem("Blue", nullptr, selectedStyle == 4)) { setStyle(4); selectedStyle = 4; } if (ImGui::MenuItem("Green", nullptr, selectedStyle == 5)) { setStyle(5); selectedStyle = 5; } if (ImGui::MenuItem("Purple", nullptr, selectedStyle == 6)) { setStyle(6); selectedStyle = 6; } ImGui::EndMenu(); } ImGui::Separator(); if (ImGui::BeginMenu("Viewport")) { ImGui::MenuItem("Show Grid", nullptr, &uiSettings.showGrid); ImGui::EndMenu(); } ImGui::EndMenu(); } if (ImGui::BeginMenu("Help")) { if (ImGui::MenuItem("About")) { // TODO: Show about dialog } ImGui::EndMenu(); } ImGui::EndMainMenuBar(); } // Init imGui windows and elements // Debug window ImGui::SetWindowPos(ImVec2(20 * example->ui.scale, 20 * example->ui.scale), ImGuiSetCond_FirstUseEver); ImGui::SetWindowSize(ImVec2(300 * example->ui.scale, 300 * example->ui.scale), ImGuiSetCond_Always); ImGui::TextUnformatted(example->title.c_str()); ImGui::TextUnformatted(device->properties.deviceName); //SRS - Display Vulkan API version and device driver information if available (otherwise blank) ImGui::Text("Vulkan API %i.%i.%i", VK_API_VERSION_MAJOR(device->properties.apiVersion), VK_API_VERSION_MINOR(device->properties.apiVersion), VK_API_VERSION_PATCH(device->properties.apiVersion)); ImGui::Text("%s %s", driverProperties.driverName, driverProperties.driverInfo); // Update frame time display if (updateFrameGraph) { std::rotate(uiSettings.frameTimes.begin(), uiSettings.frameTimes.begin() + 1, uiSettings.frameTimes.end()); float frameTime = 1000.0f / (example->frameTimer * 1000.0f); uiSettings.frameTimes.back() = frameTime; if (frameTime < uiSettings.frameTimeMin) { uiSettings.frameTimeMin = frameTime; } if (frameTime > uiSettings.frameTimeMax) { uiSettings.frameTimeMax = frameTime; } } ImGui::PlotLines("Frame Times", &uiSettings.frameTimes[0], 50, 0, "", uiSettings.frameTimeMin, uiSettings.frameTimeMax, ImVec2(0, 80)); ImGui::Text("Camera"); ImGui::InputFloat3("position", &example->camera.position.x, 2); ImGui::InputFloat3("rotation", &example->camera.rotation.x, 2); // Maya-Style Scene Hierarchy Panel ImGui::SetNextWindowPos(ImVec2(20 * example->ui.scale, 360 * example->ui.scale), ImGuiSetCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(300 * example->ui.scale, 400 * example->ui.scale), ImGuiSetCond_FirstUseEver); ImGui::Begin("Scene Hierarchy"); // Header with object count ImGui::Text("Scene Objects: %d", sceneManager.getObjectCount()); ImGui::Separator(); // Render the hierarchical scene tree if (sceneManager.sceneRoot) { for (auto rootNode : sceneManager.rootNodes) { renderSceneNodeInOutliner(rootNode); } } // Show message if scene is empty if (sceneManager.getObjectCount() == 0) { ImGui::Spacing(); ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Scene is empty"); ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Add objects from Asset Browser"); } ImGui::End(); // Inspector Panel ImGui::SetNextWindowPos(ImVec2(1180 * example->ui.scale, 20 * example->ui.scale), ImGuiSetCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(300 * example->ui.scale, 700 * example->ui.scale), ImGuiSetCond_FirstUseEver); ImGui::Begin("Inspector"); ImGui::Text("Object Properties:"); ImGui::Separator(); if (sceneManager.selectedNode && sceneManager.selectedNode->type != SceneNode::SCENE_ROOT) { SceneNode* selectedNode = sceneManager.selectedNode; ImGui::Text("Selected: %s", selectedNode->name.c_str()); ImGui::Separator(); // Object type info const char* typeStr = "Unknown"; switch (selectedNode->type) { case SceneNode::PROCEDURAL_OBJECT: typeStr = "Procedural Object"; break; case SceneNode::MODEL_OBJECT: typeStr = "3D Model"; break; case SceneNode::LIGHT_OBJECT: typeStr = "Light"; break; case SceneNode::CAMERA_OBJECT: typeStr = "Camera"; break; } ImGui::Text("Type: %s", typeStr); ImGui::Separator(); // Transform controls ImGui::Text("Transform:"); bool transformChanged = false; transformChanged |= ImGui::DragFloat3("Position", &selectedNode->position.x, 0.1f); transformChanged |= ImGui::DragFloat3("Rotation", &selectedNode->rotation.x, 1.0f); transformChanged |= ImGui::DragFloat3("Scale", &selectedNode->scale.x, 0.01f); if (transformChanged) { selectedNode->updateMatrix(); } ImGui::Separator(); // Procedural shape parameters if (selectedNode->isProceduralShape && selectedNode->proceduralShape) { ImGui::Text("Shape Parameters:"); ProceduralShape* shape = selectedNode->proceduralShape; bool regenerate = false; switch (shape->type) { case 0: // Cube regenerate |= ImGui::DragFloat("Width", &shape->params.width, 0.1f, 0.1f, 10.0f); regenerate |= ImGui::DragFloat("Height", &shape->params.height, 0.1f, 0.1f, 10.0f); regenerate |= ImGui::DragFloat("Depth", &shape->params.depth, 0.1f, 0.1f, 10.0f); if (regenerate) { *shape = ProceduralGeometry::generateCube(shape->params.width, shape->params.height, shape->params.depth); } break; case 1: // Sphere regenerate |= ImGui::DragFloat("Radius", &shape->params.radius, 0.1f, 0.1f, 5.0f); regenerate |= ImGui::DragInt("Segments", &shape->params.segments, 1, 4, 64); if (regenerate) { *shape = ProceduralGeometry::generateSphere(shape->params.radius, shape->params.segments); } break; case 3: // Plane regenerate |= ImGui::DragFloat("Width", &shape->params.width, 0.1f, 0.1f, 10.0f); regenerate |= ImGui::DragFloat("Height", &shape->params.height, 0.1f, 0.1f, 10.0f); regenerate |= ImGui::DragInt("Subdivisions", &shape->params.subdivisions, 1, 1, 32); if (regenerate) { *shape = ProceduralGeometry::generatePlane(shape->params.width, shape->params.height, shape->params.subdivisions); } break; case 4: // Cone regenerate |= ImGui::DragFloat("Radius", &shape->params.radius, 0.1f, 0.1f, 5.0f); regenerate |= ImGui::DragFloat("Height", &shape->params.height, 0.1f, 0.1f, 10.0f); regenerate |= ImGui::DragInt("Segments", &shape->params.segments, 1, 3, 64); if (regenerate) { *shape = ProceduralGeometry::generateCone(shape->params.radius, shape->params.height, shape->params.segments); } break; case 5: // Cylinder regenerate |= ImGui::DragFloat("Radius", &shape->params.radius, 0.1f, 0.1f, 5.0f); regenerate |= ImGui::DragFloat("Height", &shape->params.height, 0.1f, 0.1f, 10.0f); regenerate |= ImGui::DragInt("Segments", &shape->params.segments, 1, 3, 64); if (regenerate) { *shape = ProceduralGeometry::generateCylinder(shape->params.radius, shape->params.height, shape->params.segments); } break; case 6: // Torus regenerate |= ImGui::DragFloat("Major Radius", &shape->params.majorRadius, 0.1f, 0.1f, 5.0f); regenerate |= ImGui::DragFloat("Minor Radius", &shape->params.minorRadius, 0.1f, 0.05f, 2.0f); regenerate |= ImGui::DragInt("Major Segments", &shape->params.segments, 1, 3, 64); regenerate |= ImGui::DragInt("Minor Segments", &shape->params.subdivisions, 1, 3, 32); if (regenerate) { *shape = ProceduralGeometry::generateTorus(shape->params.majorRadius, shape->params.minorRadius, shape->params.segments, shape->params.subdivisions); } break; } ImGui::Separator(); } } else { ImGui::Text("Selected: None"); ImGui::Text("Select an object in the Scene Hierarchy"); ImGui::Separator(); } // Lighting Settings ImGui::Text("Lighting:"); ImGui::Checkbox("Animate light", &uiSettings.animateLight); ImGui::SliderFloat("Light speed", &uiSettings.lightSpeed, 0.1f, 1.0f); ImGui::Separator(); // Grid Settings ImGui::Text("Viewport Grid:"); ImGui::Checkbox("Show Grid", &uiSettings.showGrid); if (uiSettings.showGrid) { ImGui::DragFloat("Grid Size", &uiSettings.gridSize, 0.5f, 1.0f, 50.0f); ImGui::DragInt("Grid Divisions", &uiSettings.gridDivisions, 1, 2, 50); } ImGui::Separator(); // Procedural generation settings ImGui::Text("Procedural Settings:"); ImGui::Separator(); //ImGui::ShowStyleSelector("UI style"); if (ImGui::Combo("UI style", &selectedStyle, "Vulkan Red\0Classic\0Dark\0Light\0Blue\0Green\0Purple\0")) { setStyle(selectedStyle); } ImGui::End(); // Asset Browser Panel ImGui::SetNextWindowPos(ImVec2(20 * example->ui.scale, 780 * example->ui.scale), ImGuiSetCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(600 * example->ui.scale, 220 * example->ui.scale), ImGuiSetCond_FirstUseEver); ImGui::Begin("Asset Browser"); ImGui::Text("Project Assets:"); ImGui::Separator(); if (ImGui::CollapsingHeader("Procedural Shapes", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Text("Basic Geometric Shapes:"); ImGui::Separator(); // Create buttons for each shape type - First row if (ImGui::Button("📦 Cube")) { ProceduralShape cube = ProceduralGeometry::generateCube(); sceneManager.addProceduralShape(cube); ((VulkanExample*)example)->addShapeToRenderer(cube); } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Add a procedural cube to the scene"); ImGui::SameLine(); if (ImGui::Button("🔵 Sphere")) { ProceduralShape sphere = ProceduralGeometry::generateSphere(); sceneManager.addProceduralShape(sphere); static_cast(example)->addShapeToRenderer(sphere); } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Add a procedural sphere to the scene"); ImGui::SameLine(); if (ImGui::Button("📏 Plane")) { ProceduralShape plane = ProceduralGeometry::generatePlane(); sceneManager.addProceduralShape(plane); static_cast(example)->addShapeToRenderer(plane); } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Add a procedural plane to the scene"); // Second row if (ImGui::Button("🔺 Cone")) { ProceduralShape cone = ProceduralGeometry::generateCone(); sceneManager.addProceduralShape(cone); static_cast(example)->addShapeToRenderer(cone); } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Add a procedural cone to the scene"); ImGui::SameLine(); if (ImGui::Button("🔴 Cylinder")) { ProceduralShape cylinder = ProceduralGeometry::generateCylinder(); sceneManager.addProceduralShape(cylinder); static_cast(example)->addShapeToRenderer(cylinder); } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Add a procedural cylinder to the scene"); ImGui::SameLine(); if (ImGui::Button("🍩 Torus")) { ProceduralShape torus = ProceduralGeometry::generateTorus(); sceneManager.addProceduralShape(torus); static_cast(example)->addShapeToRenderer(torus); } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Add a procedural torus to the scene"); } if (ImGui::CollapsingHeader("Models")) { ImGui::Text("• MobulaBirostris.gltf"); ImGui::Text("• PolarBear.gltf"); } if (ImGui::CollapsingHeader("Textures")) { ImGui::Text("• Loading textures from glTF files..."); } if (ImGui::CollapsingHeader("Materials")) { ImGui::Text("• Default Vulkan Materials"); } ImGui::End(); // Console Panel ImGui::SetNextWindowPos(ImVec2(640 * example->ui.scale, 780 * example->ui.scale), ImGuiSetCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(840 * example->ui.scale, 200 * example->ui.scale), ImGuiSetCond_FirstUseEver); ImGui::Begin("Console"); ImGui::Text("System Console:"); ImGui::Separator(); ImGui::Text("[INFO] ProceduralEngine - Vulkan Renderer initialized"); ImGui::Text("[INFO] Scene loaded successfully - Ready for procedural generation"); ImGui::Separator(); static char inputBuf[256] = ""; if (ImGui::InputText("Command", inputBuf, sizeof(inputBuf), ImGuiInputTextFlags_EnterReturnsTrue)) { // Process command inputBuf[0] = '\0'; } ImGui::End(); //SRS - ShowDemoWindow() sets its own initial position and size, cannot override here // ImGui::ShowDemoWindow(); // Render to generate draw buffers ImGui::Render(); } // Update vertex and index buffer containing the imGui elements when required void updateBuffers() { ImDrawData* imDrawData = ImGui::GetDrawData(); // Note: Alignment is done inside buffer creation VkDeviceSize vertexBufferSize = imDrawData->TotalVtxCount * sizeof(ImDrawVert); VkDeviceSize indexBufferSize = imDrawData->TotalIdxCount * sizeof(ImDrawIdx); if ((vertexBufferSize == 0) || (indexBufferSize == 0)) { return; } // Update buffers only if vertex or index count has been changed compared to current buffer size // Vertex buffer if ((vertexBuffer.buffer == VK_NULL_HANDLE) || (vertexCount != imDrawData->TotalVtxCount)) { vertexBuffer.unmap(); vertexBuffer.destroy(); VK_CHECK_RESULT(device->createBuffer(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &vertexBuffer, vertexBufferSize)); vertexCount = imDrawData->TotalVtxCount; vertexBuffer.map(); } // Index buffer if ((indexBuffer.buffer == VK_NULL_HANDLE) || (indexCount < imDrawData->TotalIdxCount)) { indexBuffer.unmap(); indexBuffer.destroy(); VK_CHECK_RESULT(device->createBuffer(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &indexBuffer, indexBufferSize)); indexCount = imDrawData->TotalIdxCount; indexBuffer.map(); } // Upload data ImDrawVert* vtxDst = (ImDrawVert*)vertexBuffer.mapped; ImDrawIdx* idxDst = (ImDrawIdx*)indexBuffer.mapped; for (int n = 0; n < imDrawData->CmdListsCount; n++) { const ImDrawList* cmd_list = imDrawData->CmdLists[n]; memcpy(vtxDst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); memcpy(idxDst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); vtxDst += cmd_list->VtxBuffer.Size; idxDst += cmd_list->IdxBuffer.Size; } // Flush to make writes visible to GPU vertexBuffer.flush(); indexBuffer.flush(); } // Draw current imGui frame into a command buffer void drawFrame(VkCommandBuffer commandBuffer) { ImGuiIO& io = ImGui::GetIO(); vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); VkViewport viewport = vks::initializers::viewport(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y, 0.0f, 1.0f); vkCmdSetViewport(commandBuffer, 0, 1, &viewport); // UI scale and translate via push constants pushConstBlock.scale = glm::vec2(2.0f / io.DisplaySize.x, 2.0f / io.DisplaySize.y); pushConstBlock.translate = glm::vec2(-1.0f); vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstBlock), &pushConstBlock); // Render commands ImDrawData* imDrawData = ImGui::GetDrawData(); int32_t vertexOffset = 0; int32_t indexOffset = 0; if (imDrawData->CmdListsCount > 0) { VkDeviceSize offsets[1] = { 0 }; vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexBuffer.buffer, offsets); vkCmdBindIndexBuffer(commandBuffer, indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT16); for (int32_t i = 0; i < imDrawData->CmdListsCount; i++) { const ImDrawList* cmd_list = imDrawData->CmdLists[i]; for (int32_t j = 0; j < cmd_list->CmdBuffer.Size; j++) { const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[j]; VkRect2D scissorRect; scissorRect.offset.x = std::max((int32_t)(pcmd->ClipRect.x), 0); scissorRect.offset.y = std::max((int32_t)(pcmd->ClipRect.y), 0); scissorRect.extent.width = (uint32_t)(pcmd->ClipRect.z - pcmd->ClipRect.x); scissorRect.extent.height = (uint32_t)(pcmd->ClipRect.w - pcmd->ClipRect.y); vkCmdSetScissor(commandBuffer, 0, 1, &scissorRect); vkCmdDrawIndexed(commandBuffer, pcmd->ElemCount, 1, indexOffset, vertexOffset, 0); indexOffset += pcmd->ElemCount; } #if (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) && TARGET_OS_SIMULATOR // Apple Device Simulator does not support vkCmdDrawIndexed() with vertexOffset > 0, so rebind vertex buffer instead offsets[0] += cmd_list->VtxBuffer.Size * sizeof(ImDrawVert); vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexBuffer.buffer, offsets); #else vertexOffset += cmd_list->VtxBuffer.Size; #endif } } } }; // ---------------------------------------------------------------------------- // VulkanExample // ---------------------------------------------------------------------------- class VulkanExample : public VulkanExampleBase { public: ImGUI *imGui = nullptr; struct Models { vkglTF::Model models; vkglTF::Model logos; vkglTF::Model background; } models; vks::Buffer uniformBufferVS; struct UBOVS { glm::mat4 projection; glm::mat4 modelview; glm::vec4 lightPos; } uboVS; VkPipelineLayout pipelineLayout; VkPipeline pipeline; VkDescriptorSetLayout descriptorSetLayout; VkDescriptorSet descriptorSet; VulkanExample() : VulkanExampleBase() { title = "ProceduralEngine - Vulkan 3D Viewport"; camera.type = Camera::CameraType::lookat; camera.setPosition(glm::vec3(0.0f, 0.0f, -8.0f)); camera.setRotation(glm::vec3(4.5f, -380.0f, 0.0f)); camera.setPerspective(45.0f, (float)width / (float)height, 0.1f, 256.0f); //SRS - Enable VK_KHR_get_physical_device_properties2 to retrieve device driver information for display enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); // Don't use the ImGui overlay of the base framework in this sample settings.overlay = false; } ~VulkanExample() { vkDestroyPipeline(device, pipeline, nullptr); vkDestroyPipelineLayout(device, pipelineLayout, nullptr); vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); uniformBufferVS.destroy(); proceduralRenderer.cleanup(device); delete imGui; } void addShapeToRenderer(const ProceduralShape& shape) { proceduralRenderer.addShape(shape, vulkanDevice, queue); } void renderProceduralShapes(VkCommandBuffer commandBuffer) { // Render all procedural shapes in the scene uint32_t shapeIndex = 0; renderSceneNodeShapes(sceneManager.sceneRoot, commandBuffer, shapeIndex); } void renderSceneNodeShapes(SceneNode* node, VkCommandBuffer commandBuffer, uint32_t& shapeIndex) { if (!node) return; // Render this node if it's a procedural shape and visible if (node->type == SceneNode::PROCEDURAL_OBJECT && node->visible && node->isProceduralShape && node->proceduralShape) { proceduralRenderer.draw(commandBuffer, shapeIndex); shapeIndex++; } // Recursively render children for (auto child : node->children) { renderSceneNodeShapes(child, commandBuffer, shapeIndex); } } void buildCommandBuffers() { VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); VkClearValue clearValues[2]; clearValues[0].color = { { 0.2f, 0.2f, 0.2f, 1.0f} }; clearValues[1].depthStencil = { 1.0f, 0 }; VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo(); renderPassBeginInfo.renderPass = renderPass; renderPassBeginInfo.renderArea.offset.x = 0; renderPassBeginInfo.renderArea.offset.y = 0; renderPassBeginInfo.renderArea.extent.width = width; renderPassBeginInfo.renderArea.extent.height = height; renderPassBeginInfo.clearValueCount = 2; renderPassBeginInfo.pClearValues = clearValues; imGui->newFrame(this, (frameCounter == 0)); imGui->updateBuffers(); for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) { // Set target frame buffer renderPassBeginInfo.framebuffer = frameBuffers[i]; VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo)); vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f); vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0); vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor); // Render scene vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); VkDeviceSize offsets[1] = { 0 }; if (uiSettings.displayBackground) { models.background.draw(drawCmdBuffers[i]); } if (uiSettings.displayModels) { models.models.draw(drawCmdBuffers[i]); } // Render procedural shapes renderProceduralShapes(drawCmdBuffers[i]); // Render imGui if (ui.visible) { imGui->drawFrame(drawCmdBuffers[i]); } vkCmdEndRenderPass(drawCmdBuffers[i]); VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); } } void setupLayoutsAndDescriptors() { // descriptor pool std::vector poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2), vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1) }; VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); // Set layout std::vector setLayoutBindings = { vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0), }; VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); // Pipeline layout VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); // Descriptor set VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); std::vector writeDescriptorSets = { vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBufferVS.descriptor), }; vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); } void preparePipelines() { // Rendering VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE); VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE); VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE); VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState); VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL); VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0); VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT); std::vector dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); std::array shaderStages; VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass); pipelineCI.pInputAssemblyState = &inputAssemblyState; pipelineCI.pRasterizationState = &rasterizationState; pipelineCI.pColorBlendState = &colorBlendState; pipelineCI.pMultisampleState = &multisampleState; pipelineCI.pViewportState = &viewportState; pipelineCI.pDepthStencilState = &depthStencilState; pipelineCI.pDynamicState = &dynamicState; pipelineCI.stageCount = static_cast(shaderStages.size()); pipelineCI.pStages = shaderStages.data(); pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Color });; shaderStages[0] = loadShader(getShadersPath() + "imgui/scene.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); shaderStages[1] = loadShader(getShadersPath() + "imgui/scene.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline)); } // Prepare and initialize uniform buffer containing shader uniforms void prepareUniformBuffers() { // Vertex shader uniform buffer block VK_CHECK_RESULT(vulkanDevice->createBuffer( VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &uniformBufferVS, sizeof(uboVS), &uboVS)); updateUniformBuffers(); } void updateUniformBuffers() { // Vertex shader uboVS.projection = camera.matrices.perspective; uboVS.modelview = camera.matrices.view * glm::mat4(1.0f); // Light source if (uiSettings.animateLight) { uiSettings.lightTimer += frameTimer * uiSettings.lightSpeed; uboVS.lightPos.x = sin(glm::radians(uiSettings.lightTimer * 360.0f)) * 15.0f; uboVS.lightPos.z = cos(glm::radians(uiSettings.lightTimer * 360.0f)) * 15.0f; }; VK_CHECK_RESULT(uniformBufferVS.map()); memcpy(uniformBufferVS.mapped, &uboVS, sizeof(uboVS)); uniformBufferVS.unmap(); } void draw() { VulkanExampleBase::prepareFrame(); buildCommandBuffers(); submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); VulkanExampleBase::submitFrame(); } void loadAssets() { const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY; // Models available in assets but not auto-loaded // models.models.loadFromFile(getAssetPath() + "models/MobulaBirostris.gltf", vulkanDevice, queue, glTFLoadingFlags); // models.background.loadFromFile(getAssetPath() + "models/PolarBear.gltf", vulkanDevice, queue, glTFLoadingFlags); } void prepareImGui() { imGui = new ImGUI(this); imGui->init((float)width, (float)height); imGui->initResources(renderPass, queue, getShadersPath()); } void prepare() { VulkanExampleBase::prepare(); loadAssets(); prepareUniformBuffers(); setupLayoutsAndDescriptors(); preparePipelines(); prepareImGui(); buildCommandBuffers(); prepared = true; } virtual void render() { if (!prepared) return; updateUniformBuffers(); // Update imGui ImGuiIO& io = ImGui::GetIO(); io.DisplaySize = ImVec2((float)width, (float)height); io.DeltaTime = frameTimer; io.MousePos = ImVec2(mouseState.position.x, mouseState.position.y); io.MouseDown[0] = mouseState.buttons.left && ui.visible; io.MouseDown[1] = mouseState.buttons.right && ui.visible; io.MouseDown[2] = mouseState.buttons.middle && ui.visible; draw(); } virtual void mouseMoved(double x, double y, bool &handled) { ImGuiIO& io = ImGui::GetIO(); handled = io.WantCaptureMouse && ui.visible; } // Input handling is platform specific, to show how it's basically done this sample implements it for Windows #if defined(_WIN32) virtual void OnHandleMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { ImGuiIO& io = ImGui::GetIO(); // Only react to keyboard input if ImGui is active if (io.WantCaptureKeyboard) { // Character input if (uMsg == WM_CHAR) { if (wParam > 0 && wParam < 0x10000) { io.AddInputCharacter((unsigned short)wParam); } } // Special keys (tab, cursor, etc.) if ((wParam < 256) && (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN)) { io.KeysDown[wParam] = true; } if ((wParam < 256) && (uMsg == WM_KEYUP || uMsg == WM_SYSKEYUP)) { io.KeysDown[wParam] = false; } } } #endif }; VULKAN_EXAMPLE_MAIN()