procedural-3d-engine/examples/imgui/main_backup.cpp
Claude Code 09ba229353
Some checks failed
Build Project / Build Ubuntu (push) Has been cancelled
Build Project / Build Windows (push) Has been cancelled
Build Project / Build macOS (push) Has been cancelled
Initial procedural 3D engine setup
- Updated README.md with modern project structure and features
- Cleaned up Android build files (not needed for desktop engine)
- Restructured as procedural 3D engine with ImGui integration
- Based on Sascha Willems Vulkan framework with dynamic rendering
- Added comprehensive build instructions and camera system docs

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-17 18:56:17 +02:00

1805 lines
64 KiB
C++

/*
* 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 <imgui.h>
#include "vulkanexamplebase.h"
#include "VulkanglTFModel.h"
#include <cmath>
#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<ProceduralVertex> vertices;
std::vector<uint32_t> 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<SceneNode*> 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<SceneNode*> 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<float, 50> 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<VkDescriptorPoolSize> 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<VkDescriptorSetLayoutBinding> 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<VkWriteDescriptorSet> writeDescriptorSets = {
vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &fontDescriptor)
};
vkUpdateDescriptorSets(device->logicalDevice, static_cast<uint32_t>(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<VkDynamicState> dynamicStateEnables = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
VkPipelineDynamicStateCreateInfo dynamicState =
vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
std::array<VkPipelineShaderStageCreateInfo, 2> 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<uint32_t>(shaderStages.size());
pipelineCreateInfo.pStages = shaderStages.data();
// Vertex bindings an attributes based on ImGui vertex definition
std::vector<VkVertexInputBindingDescription> vertexInputBindings = {
vks::initializers::vertexInputBindingDescription(0, sizeof(ImDrawVert), VK_VERTEX_INPUT_RATE_VERTEX),
};
std::vector<VkVertexInputAttributeDescription> 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<uint32_t>(vertexInputBindings.size());
vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data();
vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(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<VulkanExample*>(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<VulkanExample*>(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<VulkanExample*>(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<VulkanExample*>(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<VulkanExample*>(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<VkDescriptorPoolSize> 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<VkDescriptorSetLayoutBinding> 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<VkWriteDescriptorSet> writeDescriptorSets = {
vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBufferVS.descriptor),
};
vkUpdateDescriptorSets(device, static_cast<uint32_t>(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<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
std::array<VkPipelineShaderStageCreateInfo,2> 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<uint32_t>(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()