Initial procedural 3D engine setup
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

- 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>
This commit is contained in:
Claude Code 2025-08-17 18:56:17 +02:00
parent ca9be0c589
commit 09ba229353
2429 changed files with 7751 additions and 112835 deletions

View file

@ -0,0 +1,523 @@
/*
* UI overlay class using ImGui
*
* Copyright (C) 2017-2024 by Sascha Willems - www.saschawillems.de
*
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
*/
#include "VulkanUIOverlay.h"
namespace vks
{
UIOverlay::UIOverlay()
{
#if defined(__ANDROID__)
if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_XXHIGH) {
scale = 3.5f;
}
else if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_XHIGH) {
scale = 2.5f;
}
else if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_HIGH) {
scale = 2.0f;
};
#endif
// Init ImGui
ImGui::CreateContext();
// Color scheme
ImGuiStyle& style = ImGui::GetStyle();
style.Colors[ImGuiCol_TitleBg] = ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
style.Colors[ImGuiCol_TitleBgActive] = ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(1.0f, 0.0f, 0.0f, 0.1f);
style.Colors[ImGuiCol_MenuBarBg] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
style.Colors[ImGuiCol_Header] = ImVec4(0.8f, 0.0f, 0.0f, 0.4f);
style.Colors[ImGuiCol_HeaderActive] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
style.Colors[ImGuiCol_HeaderHovered] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
style.Colors[ImGuiCol_FrameBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.8f);
style.Colors[ImGuiCol_CheckMark] = ImVec4(1.0f, 0.0f, 0.0f, 0.8f);
style.Colors[ImGuiCol_SliderGrab] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(1.0f, 0.0f, 0.0f, 0.8f);
style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(1.0f, 1.0f, 1.0f, 0.1f);
style.Colors[ImGuiCol_FrameBgActive] = ImVec4(1.0f, 1.0f, 1.0f, 0.2f);
style.Colors[ImGuiCol_Button] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
style.Colors[ImGuiCol_ButtonHovered] = ImVec4(1.0f, 0.0f, 0.0f, 0.6f);
style.Colors[ImGuiCol_ButtonActive] = ImVec4(1.0f, 0.0f, 0.0f, 0.8f);
// Dimensions
ImGuiIO& io = ImGui::GetIO();
io.FontGlobalScale = scale;
}
UIOverlay::~UIOverlay() {
if (ImGui::GetCurrentContext()) {
ImGui::DestroyContext();
}
}
/** Prepare all vulkan resources required to render the UI overlay */
void UIOverlay::prepareResources()
{
ImGuiIO& io = ImGui::GetIO();
// Create font texture
unsigned char* fontData;
int texWidth, texHeight;
#if defined(__ANDROID__)
float scale = (float)vks::android::screenDensity / (float)ACONFIGURATION_DENSITY_MEDIUM;
AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, "Roboto-Medium.ttf", AASSET_MODE_STREAMING);
if (asset) {
size_t size = AAsset_getLength(asset);
assert(size > 0);
char *fontAsset = new char[size];
AAsset_read(asset, fontAsset, size);
AAsset_close(asset);
io.Fonts->AddFontFromMemoryTTF(fontAsset, size, 14.0f * scale);
delete[] fontAsset;
}
#else
const std::string filename = getAssetPath() + "Roboto-Medium.ttf";
io.Fonts->AddFontFromFileTTF(filename.c_str(), 16.0f * scale);
#endif
io.Fonts->GetTexDataAsRGBA32(&fontData, &texWidth, &texHeight);
VkDeviceSize uploadSize = texWidth*texHeight * 4 * sizeof(char);
//SRS - Set ImGui style scale factor to handle retina and other HiDPI displays (same as font scaling above)
ImGuiStyle& style = ImGui::GetStyle();
style.ScaleAllSizes(scale);
// 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, queue, 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);
}
/** Prepare a separate pipeline for the UI overlay rendering decoupled from the main application */
void UIOverlay::preparePipeline(const VkPipelineCache pipelineCache, const VkRenderPass renderPass, const VkFormat colorFormat, const VkFormat depthFormat)
{
// 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_ALWAYS);
VkPipelineViewportStateCreateInfo viewportState =
vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
VkPipelineMultisampleStateCreateInfo multisampleState =
vks::initializers::pipelineMultisampleStateCreateInfo(rasterizationSamples);
std::vector<VkDynamicState> dynamicStateEnables = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
VkPipelineDynamicStateCreateInfo dynamicState =
vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
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>(shaders.size());
pipelineCreateInfo.pStages = shaders.data();
pipelineCreateInfo.subpass = subpass;
#if defined(VK_KHR_dynamic_rendering)
// SRS - if we are using dynamic rendering (i.e. renderPass null), must define color, depth and stencil attachments at pipeline create time
VkPipelineRenderingCreateInfo pipelineRenderingCreateInfo = {};
if (renderPass == VK_NULL_HANDLE) {
pipelineRenderingCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
pipelineRenderingCreateInfo.colorAttachmentCount = 1;
pipelineRenderingCreateInfo.pColorAttachmentFormats = &colorFormat;
pipelineRenderingCreateInfo.depthAttachmentFormat = depthFormat;
pipelineRenderingCreateInfo.stencilAttachmentFormat = depthFormat;
pipelineCreateInfo.pNext = &pipelineRenderingCreateInfo;
}
#endif
// 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;
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device->logicalDevice, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline));
}
/** Update vertex and index buffer containing the imGui elements when required */
bool UIOverlay::update()
{
ImDrawData* imDrawData = ImGui::GetDrawData();
bool updateCmdBuffers = false;
if (!imDrawData) { return false; };
// Note: Alignment is done inside buffer creation
VkDeviceSize vertexBufferSize = imDrawData->TotalVtxCount * sizeof(ImDrawVert);
VkDeviceSize indexBufferSize = imDrawData->TotalIdxCount * sizeof(ImDrawIdx);
// Update buffers only if vertex or index count has been changed compared to current buffer size
if ((vertexBufferSize == 0) || (indexBufferSize == 0)) {
return false;
}
// 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.unmap();
vertexBuffer.map();
updateCmdBuffers = true;
}
// 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();
updateCmdBuffers = true;
}
// 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();
return updateCmdBuffers;
}
void UIOverlay::draw(const VkCommandBuffer commandBuffer)
{
ImDrawData* imDrawData = ImGui::GetDrawData();
int32_t vertexOffset = 0;
int32_t indexOffset = 0;
if ((!imDrawData) || (imDrawData->CmdListsCount == 0)) {
return;
}
ImGuiIO& io = ImGui::GetIO();
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
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);
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
}
}
void UIOverlay::resize(uint32_t width, uint32_t height)
{
ImGuiIO& io = ImGui::GetIO();
io.DisplaySize = ImVec2((float)(width), (float)(height));
}
void UIOverlay::freeResources()
{
vertexBuffer.destroy();
indexBuffer.destroy();
vkDestroyImageView(device->logicalDevice, fontView, nullptr);
vkDestroyImage(device->logicalDevice, fontImage, nullptr);
vkFreeMemory(device->logicalDevice, fontMemory, nullptr);
vkDestroySampler(device->logicalDevice, sampler, nullptr);
vkDestroyDescriptorSetLayout(device->logicalDevice, descriptorSetLayout, nullptr);
vkDestroyDescriptorPool(device->logicalDevice, descriptorPool, nullptr);
vkDestroyPipelineLayout(device->logicalDevice, pipelineLayout, nullptr);
vkDestroyPipeline(device->logicalDevice, pipeline, nullptr);
}
bool UIOverlay::header(const char *caption)
{
return ImGui::CollapsingHeader(caption, ImGuiTreeNodeFlags_DefaultOpen);
}
bool UIOverlay::checkBox(const char *caption, bool *value)
{
bool res = ImGui::Checkbox(caption, value);
if (res) { updated = true; };
return res;
}
bool UIOverlay::checkBox(const char *caption, int32_t *value)
{
bool val = (*value == 1);
bool res = ImGui::Checkbox(caption, &val);
*value = val;
if (res) { updated = true; };
return res;
}
bool UIOverlay::radioButton(const char* caption, bool value)
{
bool res = ImGui::RadioButton(caption, value);
if (res) { updated = true; };
return res;
}
bool UIOverlay::inputFloat(const char *caption, float *value, float step, uint32_t precision)
{
bool res = ImGui::InputFloat(caption, value, step, step * 10.0f, precision);
if (res) { updated = true; };
return res;
}
bool UIOverlay::sliderFloat(const char* caption, float* value, float min, float max)
{
bool res = ImGui::SliderFloat(caption, value, min, max);
if (res) { updated = true; };
return res;
}
bool UIOverlay::sliderInt(const char* caption, int32_t* value, int32_t min, int32_t max)
{
bool res = ImGui::SliderInt(caption, value, min, max);
if (res) { updated = true; };
return res;
}
bool UIOverlay::comboBox(const char *caption, int32_t *itemindex, std::vector<std::string> items)
{
if (items.empty()) {
return false;
}
std::vector<const char*> charitems;
charitems.reserve(items.size());
for (size_t i = 0; i < items.size(); i++) {
charitems.push_back(items[i].c_str());
}
uint32_t itemCount = static_cast<uint32_t>(charitems.size());
bool res = ImGui::Combo(caption, itemindex, &charitems[0], itemCount, itemCount);
if (res) { updated = true; };
return res;
}
bool UIOverlay::button(const char *caption)
{
bool res = ImGui::Button(caption);
if (res) { updated = true; };
return res;
}
bool UIOverlay::colorPicker(const char* caption, float* color) {
bool res = ImGui::ColorEdit4(caption, color, ImGuiColorEditFlags_NoInputs);
if (res) { updated = true; };
return res;
}
void UIOverlay::text(const char *formatstr, ...)
{
va_list args;
va_start(args, formatstr);
ImGui::TextV(formatstr, args);
va_end(args);
}
}

View file

@ -0,0 +1,98 @@
/*
* UI overlay class using ImGui
*
* Copyright (C) 2017-2024 by Sascha Willems - www.saschawillems.de
*
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
*/
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <vector>
#include <sstream>
#include <iomanip>
#include <vulkan/vulkan.h>
#include "../core/VulkanTools.h"
#include "../core/VulkanDebug.h"
#include "../memory/VulkanBuffer.h"
#include "../device/VulkanDevice.h"
#include "../external/imgui/imgui.h"
#if defined(__ANDROID__)
#include "VulkanAndroid.h"
#endif
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
namespace vks
{
class UIOverlay
{
public:
vks::VulkanDevice* device{ nullptr };
VkQueue queue{ VK_NULL_HANDLE };
VkSampleCountFlagBits rasterizationSamples{ VK_SAMPLE_COUNT_1_BIT };
uint32_t subpass{ 0 };
vks::Buffer vertexBuffer;
vks::Buffer indexBuffer;
int32_t vertexCount{ 0 };
int32_t indexCount{ 0 };
std::vector<VkPipelineShaderStageCreateInfo> shaders;
VkDescriptorPool descriptorPool{ VK_NULL_HANDLE };
VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE };
VkDescriptorSet descriptorSet{ VK_NULL_HANDLE };
VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE };
VkPipeline pipeline{ VK_NULL_HANDLE };
VkDeviceMemory fontMemory{ VK_NULL_HANDLE };
VkImage fontImage{ VK_NULL_HANDLE };
VkImageView fontView{ VK_NULL_HANDLE };
VkSampler sampler{ VK_NULL_HANDLE };
struct PushConstBlock {
glm::vec2 scale;
glm::vec2 translate;
} pushConstBlock;
bool visible{ true };
bool updated{ false };
float scale{ 1.0f };
float updateTimer{ 0.0f };
UIOverlay();
~UIOverlay();
void preparePipeline(const VkPipelineCache pipelineCache, const VkRenderPass renderPass, const VkFormat colorFormat, const VkFormat depthFormat);
void prepareResources();
bool update();
void draw(const VkCommandBuffer commandBuffer);
void resize(uint32_t width, uint32_t height);
void freeResources();
bool header(const char* caption);
bool checkBox(const char* caption, bool* value);
bool checkBox(const char* caption, int32_t* value);
bool radioButton(const char* caption, bool value);
bool inputFloat(const char* caption, float* value, float step, uint32_t precision);
bool sliderFloat(const char* caption, float* value, float min, float max);
bool sliderInt(const char* caption, int32_t* value, int32_t min, int32_t max);
bool comboBox(const char* caption, int32_t* itemindex, std::vector<std::string> items);
bool button(const char* caption);
bool colorPicker(const char* caption, float* color);
void text(const char* formatstr, ...);
};
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,423 @@
/*
* Vulkan Example base class
*
* Copyright (C) 2016-2025 by Sascha Willems - www.saschawillems.de
*
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
*/
#pragma once
#ifdef _WIN32
// Note: Removed /subsystem:windows pragma to allow console applications
// #pragma comment(linker, "/subsystem:windows")
#include <windows.h>
#include <fcntl.h>
#include <io.h>
#include <ShellScalingAPI.h>
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
#include <android/native_activity.h>
#include <android/asset_manager.h>
#include <android_native_app_glue.h>
#include <sys/system_properties.h>
#include "VulkanAndroid.h"
#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
#include <directfb.h>
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
#include <wayland-client.h>
#include "xdg-shell-client-protocol.h"
#elif defined(_DIRECT2DISPLAY)
//
#elif defined(VK_USE_PLATFORM_XCB_KHR)
#include <xcb/xcb.h>
#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT))
#include <TargetConditionals.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <vector>
#include <array>
#include <unordered_map>
#include <numeric>
#include <ctime>
#include <iostream>
#include <chrono>
#include <random>
#include <algorithm>
#include <sys/stat.h>
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/matrix_inverse.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <string>
#include <numeric>
#include <array>
#include "vulkan/vulkan.h"
#include "CommandLineParser.hpp"
#include "keycodes.hpp"
#include "../core/VulkanTools.h"
#include "../core/VulkanDebug.h"
#include "VulkanUIOverlay.h"
#include "../device/VulkanSwapChain.h"
#include "../memory/VulkanBuffer.h"
#include "../device/VulkanDevice.h"
#include "../memory/VulkanTexture.h"
#include "../core/VulkanInitializers.hpp"
#include "../camera.hpp"
#include "../benchmark.hpp"
class VulkanExampleBase
{
private:
std::string getWindowTitle() const;
uint32_t destWidth{};
uint32_t destHeight{};
bool resizing = false;
void handleMouseMove(int32_t x, int32_t y);
void nextFrame();
void updateOverlay();
void createPipelineCache();
void createCommandPool();
void createSynchronizationPrimitives();
void createSurface();
void createSwapChain();
void createCommandBuffers();
void destroyCommandBuffers();
std::string shaderDir = "glsl";
protected:
// Returns the path to the root of the glsl, hlsl or slang shader directory.
std::string getShadersPath() const;
// Frame counter to display fps
uint32_t frameCounter = 0;
uint32_t lastFPS = 0;
std::chrono::time_point<std::chrono::high_resolution_clock> lastTimestamp, tPrevEnd;
// Vulkan instance, stores all per-application states
VkInstance instance{ VK_NULL_HANDLE };
std::vector<std::string> supportedInstanceExtensions;
// Physical device (GPU) that Vulkan will use
VkPhysicalDevice physicalDevice{ VK_NULL_HANDLE };
// Stores physical device properties (for e.g. checking device limits)
VkPhysicalDeviceProperties deviceProperties{};
// Stores the features available on the selected physical device (for e.g. checking if a feature is available)
VkPhysicalDeviceFeatures deviceFeatures{};
// Stores all available memory (type) properties for the physical device
VkPhysicalDeviceMemoryProperties deviceMemoryProperties{};
/** @brief Set of physical device features to be enabled for this example (must be set in the derived constructor) */
VkPhysicalDeviceFeatures enabledFeatures{};
/** @brief Set of device extensions to be enabled for this example (must be set in the derived constructor) */
std::vector<const char*> enabledDeviceExtensions;
/** @brief Set of instance extensions to be enabled for this example (must be set in the derived constructor) */
std::vector<const char*> enabledInstanceExtensions;
/** @brief Set of layer settings to be enabled for this example (must be set in the derived constructor) */
std::vector<VkLayerSettingEXT> enabledLayerSettings;
/** @brief Optional pNext structure for passing extension structures to device creation */
void* deviceCreatepNextChain = nullptr;
/** @brief Logical device, application's view of the physical device (GPU) */
VkDevice device{ VK_NULL_HANDLE };
// Handle to the device graphics queue that command buffers are submitted to
VkQueue queue{ VK_NULL_HANDLE };
// Depth buffer format (selected during Vulkan initialization)
VkFormat depthFormat{VK_FORMAT_UNDEFINED};
// Command buffer pool
VkCommandPool cmdPool{ VK_NULL_HANDLE };
/** @brief Pipeline stages used to wait at for graphics queue submissions */
VkPipelineStageFlags submitPipelineStages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
// Contains command buffers and semaphores to be presented to the queue
VkSubmitInfo submitInfo{};
// Command buffers used for rendering
std::vector<VkCommandBuffer> drawCmdBuffers;
// Global render pass for frame buffer writes
VkRenderPass renderPass{ VK_NULL_HANDLE };
// List of available frame buffers (same as number of swap chain images)
std::vector<VkFramebuffer>frameBuffers;
// Active frame buffer index
uint32_t currentBuffer = 0;
// Descriptor set pool
VkDescriptorPool descriptorPool{ VK_NULL_HANDLE };
// List of shader modules created (stored for cleanup)
std::vector<VkShaderModule> shaderModules;
// Pipeline cache object
VkPipelineCache pipelineCache{ VK_NULL_HANDLE };
// Wraps the swap chain to present images (framebuffers) to the windowing system
VulkanSwapChain swapChain;
// Synchronization semaphores
struct {
// Swap chain image presentation
VkSemaphore presentComplete;
// Command buffer submission and execution
VkSemaphore renderComplete;
} semaphores{};
std::vector<VkFence> waitFences;
bool requiresStencil{ false };
public:
bool prepared = false;
bool resized = false;
bool viewUpdated = false;
uint32_t width = 1920;
uint32_t height = 1080;
vks::UIOverlay ui;
CommandLineParser commandLineParser;
/** @brief Last frame time measured using a high performance timer (if available) */
float frameTimer = 1.0f;
vks::Benchmark benchmark;
/** @brief Encapsulated physical and logical vulkan device */
vks::VulkanDevice *vulkanDevice{};
/** @brief Example settings that can be changed e.g. by command line arguments */
struct Settings {
/** @brief Activates validation layers (and message output) when set to true */
bool validation = false;
/** @brief Set to true if fullscreen mode has been requested via command line */
bool fullscreen = false;
/** @brief Set to true if v-sync will be forced for the swapchain */
bool vsync = false;
/** @brief Enable UI overlay */
bool overlay = true;
} settings;
/** @brief State of gamepad input (only used on Android) */
struct {
glm::vec2 axisLeft = glm::vec2(0.0f);
glm::vec2 axisRight = glm::vec2(0.0f);
} gamePadState;
/** @brief State of mouse/touch input */
struct {
struct {
bool left = false;
bool right = false;
bool middle = false;
} buttons;
glm::vec2 position;
} mouseState;
VkClearColorValue defaultClearColor = { { 0.025f, 0.025f, 0.025f, 1.0f } };
static std::vector<const char*> args;
// Defines a frame rate independent timer value clamped from -1.0...1.0
// For use in animations, rotations, etc.
float timer = 0.0f;
// Multiplier for speeding up (or slowing down) the global timer
float timerSpeed = 0.25f;
bool paused = false;
Camera camera;
std::string title = "Vulkan Example";
std::string name = "vulkanExample";
uint32_t apiVersion = VK_API_VERSION_1_0;
/** @brief Default depth stencil attachment used by the default render pass */
struct {
VkImage image;
VkDeviceMemory memory;
VkImageView view;
} depthStencil{};
// OS specific
#if defined(_WIN32)
HWND window;
HINSTANCE windowInstance;
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
// true if application has focused, false if moved to background
bool focused = false;
struct TouchPos {
int32_t x;
int32_t y;
} touchPos{};
bool touchDown = false;
double touchTimer = 0.0;
int64_t lastTapTime = 0;
#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT))
void* view;
#if defined(VK_USE_PLATFORM_METAL_EXT)
CAMetalLayer* metalLayer;
#endif
#if defined(VK_EXAMPLE_XCODE_GENERATED)
bool quit = false;
#endif
#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
bool quit = false;
IDirectFB *dfb = nullptr;
IDirectFBDisplayLayer *layer = nullptr;
IDirectFBWindow *window = nullptr;
IDirectFBSurface *surface = nullptr;
IDirectFBEventBuffer *event_buffer = nullptr;
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
wl_display *display = nullptr;
wl_registry *registry = nullptr;
wl_compositor *compositor = nullptr;
struct xdg_wm_base *shell = nullptr;
wl_seat *seat = nullptr;
wl_pointer *pointer = nullptr;
wl_keyboard *keyboard = nullptr;
wl_surface *surface = nullptr;
struct xdg_surface *xdg_surface;
struct xdg_toplevel *xdg_toplevel;
bool quit = false;
bool configured = false;
#elif defined(_DIRECT2DISPLAY)
bool quit = false;
#elif defined(VK_USE_PLATFORM_XCB_KHR)
bool quit = false;
xcb_connection_t *connection;
xcb_screen_t *screen;
xcb_window_t window;
xcb_intern_atom_reply_t *atom_wm_delete_window;
#elif defined(VK_USE_PLATFORM_HEADLESS_EXT)
bool quit = false;
#elif defined(VK_USE_PLATFORM_SCREEN_QNX)
screen_context_t screen_context = nullptr;
screen_window_t screen_window = nullptr;
screen_event_t screen_event = nullptr;
bool quit = false;
#endif
/** @brief Default base class constructor */
VulkanExampleBase();
virtual ~VulkanExampleBase();
/** @brief Setup the vulkan instance, enable required extensions and connect to the physical device (GPU) */
bool initVulkan();
#if defined(_WIN32)
void setupConsole(std::string title);
void setupDPIAwareness();
HWND setupWindow(HINSTANCE hinstance, WNDPROC wndproc);
void handleMessages(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
static int32_t handleAppInput(struct android_app* app, AInputEvent* event);
static void handleAppCommand(android_app* app, int32_t cmd);
#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT))
void* setupWindow(void* view);
void displayLinkOutputCb();
void mouseDragged(float x, float y);
void windowWillResize(float x, float y);
void windowDidResize();
#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
IDirectFBSurface *setupWindow();
void handleEvent(const DFBWindowEvent *event);
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
struct xdg_surface *setupWindow();
void initWaylandConnection();
void setSize(int width, int height);
static void registryGlobalCb(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version);
void registryGlobal(struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version);
static void registryGlobalRemoveCb(void *data, struct wl_registry *registry,
uint32_t name);
static void seatCapabilitiesCb(void *data, wl_seat *seat, uint32_t caps);
void seatCapabilities(wl_seat *seat, uint32_t caps);
static void pointerEnterCb(void *data, struct wl_pointer *pointer,
uint32_t serial, struct wl_surface *surface, wl_fixed_t sx,
wl_fixed_t sy);
static void pointerLeaveCb(void *data, struct wl_pointer *pointer,
uint32_t serial, struct wl_surface *surface);
static void pointerMotionCb(void *data, struct wl_pointer *pointer,
uint32_t time, wl_fixed_t sx, wl_fixed_t sy);
void pointerMotion(struct wl_pointer *pointer,
uint32_t time, wl_fixed_t sx, wl_fixed_t sy);
static void pointerButtonCb(void *data, struct wl_pointer *wl_pointer,
uint32_t serial, uint32_t time, uint32_t button, uint32_t state);
void pointerButton(struct wl_pointer *wl_pointer,
uint32_t serial, uint32_t time, uint32_t button, uint32_t state);
static void pointerAxisCb(void *data, struct wl_pointer *wl_pointer,
uint32_t time, uint32_t axis, wl_fixed_t value);
void pointerAxis(struct wl_pointer *wl_pointer,
uint32_t time, uint32_t axis, wl_fixed_t value);
static void keyboardKeymapCb(void *data, struct wl_keyboard *keyboard,
uint32_t format, int fd, uint32_t size);
static void keyboardEnterCb(void *data, struct wl_keyboard *keyboard,
uint32_t serial, struct wl_surface *surface, struct wl_array *keys);
static void keyboardLeaveCb(void *data, struct wl_keyboard *keyboard,
uint32_t serial, struct wl_surface *surface);
static void keyboardKeyCb(void *data, struct wl_keyboard *keyboard,
uint32_t serial, uint32_t time, uint32_t key, uint32_t state);
void keyboardKey(struct wl_keyboard *keyboard,
uint32_t serial, uint32_t time, uint32_t key, uint32_t state);
static void keyboardModifiersCb(void *data, struct wl_keyboard *keyboard,
uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched,
uint32_t mods_locked, uint32_t group);
#elif defined(_DIRECT2DISPLAY)
//
#elif defined(VK_USE_PLATFORM_XCB_KHR)
xcb_window_t setupWindow();
void initxcbConnection();
void handleEvent(const xcb_generic_event_t *event);
#elif defined(VK_USE_PLATFORM_SCREEN_QNX)
void setupWindow();
void handleEvent();
#else
void setupWindow();
#endif
/** @brief (Virtual) Creates the application wide Vulkan instance */
virtual VkResult createInstance();
/** @brief (Pure virtual) Render function to be implemented by the sample application */
virtual void render() = 0;
/** @brief (Virtual) Called after a key was pressed, can be used to do custom key handling */
virtual void keyPressed(uint32_t);
/** @brief (Virtual) Called after the mouse cursor moved and before internal events (like camera rotation) is handled */
virtual void mouseMoved(double x, double y, bool &handled);
/** @brief (Virtual) Called when the window has been resized, can be used by the sample application to recreate resources */
virtual void windowResized();
/** @brief (Virtual) Called when resources have been recreated that require a rebuild of the command buffers (e.g. frame buffer), to be implemented by the sample application */
virtual void buildCommandBuffers();
/** @brief (Virtual) Setup default depth and stencil views */
virtual void setupDepthStencil();
/** @brief (Virtual) Setup default framebuffers for all requested swapchain images */
virtual void setupFrameBuffer();
/** @brief (Virtual) Setup a default renderpass */
virtual void setupRenderPass();
/** @brief (Virtual) Called after the physical device features have been read, can be used to set features to enable on the device */
virtual void getEnabledFeatures();
/** @brief (Virtual) Called after the physical device extensions have been read, can be used to enable extensions based on the supported extension listing*/
virtual void getEnabledExtensions();
/** @brief Prepares all Vulkan resources and functions required to run the sample */
virtual void prepare();
/** @brief Loads a SPIR-V shader file for the given shader stage */
VkPipelineShaderStageCreateInfo loadShader(std::string fileName, VkShaderStageFlagBits stage);
void windowResize();
/** @brief Entry point for the main render loop */
void renderLoop();
/** @brief Adds the drawing commands for the ImGui overlay to the given command buffer */
void drawUI(const VkCommandBuffer commandBuffer);
/** Prepare the next frame for workload submission by acquiring the next swap chain image */
void prepareFrame();
/** @brief Presents the current image to the swap chain */
void submitFrame();
/** @brief (Virtual) Default image acquire + submission and command buffer submission function */
virtual void renderFrame();
/** @brief (Virtual) Called when the UI overlay is updating, can be used to add custom elements to the overlay */
virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay);
#if defined(_WIN32)
virtual void OnHandleMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
#endif
};
#include "Entrypoints.h"